Search code examples
asp.net-mvc-3timestamptelerik-gridasp.net-mvc-ajax

MVC 3 Telerik Grid Won't Return Timestamp to View Model


Update: I haven't figured anything out yet. It sure seems like it ought to work the way it is.

I have a telerik grid that I have bound to a View Model. The grid basically displays name and type of a Vehicle object. In the Datakeys, I have added both VehicleID and Timestamp fields. When the grid posts for an ajax update, the Timestamp field is null.

I have verified that the Timestamp field in the view model has a value when it is being passed to the View.

Here is the relevant code:

View:

...
@(Html.Telerik().Grid(Model)
.Name("Grid")
.Columns(columns =>
{
    columns.Bound(o => o.VehicleID).Visible(false);        
    columns.Bound(o => o.VehicleName);
    columns.Bound(o => o.VehicleType);
})
.DataBinding(dataBinding =>
    {
        dataBinding.Server().Select("Index", "Vehicle");
        dataBinding.Ajax().Select("_Index", "Vehicle").Enabled(true);
        dataBinding.Ajax().Update("_SaveVehicle", "Vehicle");
        dataBinding.Ajax().Delete("_DeleteVehicle", "Vehicle");
    })
    .Scrollable(scrolling => scrolling.Enabled(true))
    .Sortable(sorting => sorting.Enabled(true))
    .Pageable(paging => 
        paging.Enabled(true)
        .PageSize(20)
        .Position(GridPagerPosition.Bottom))
    .Filterable(filtering => filtering.Enabled(true))
    .Groupable(grouping => grouping.Enabled(true))
    .Footer(true)
    .ToolBar(toolbar => toolbar.Template(
        @<text>
            @using (Html.BeginForm("ExportCsv", "Vehicle", FormMethod.Post, new {id = "export"}))
            {
                <text>
                    <input type="submit" value="Export to CSV" id="export" />
                </text>
            }

            <label class="error">@ViewBag.AjaxErrorMessage</label>

        </text>
        ))            

    .Columns(columns =>
        {
            columns.Command(command =>
                {
                    command.Custom("details").Text("Details").Action("Edit", "Vehicle").HtmlAttributes(new { style = "text-align: center" }) ;
                    command.Edit().ButtonType(GridButtonType.Image);
                    command.Delete().ButtonType(GridButtonType.Image);
                }).Width(165);
        })
    .DataKeys(dataKeys => 
        {
            dataKeys.Add(key => key.Timestamp).RouteKey("Timestamp");
            dataKeys.Add(key => key.VehicleID).RouteKey("VehicleID"); 

        })
    .ClientEvents(events => events.OnEdit("onEdit"))
    .ColumnContextMenu()
    .Resizable(config =>
        {
            config.Columns(true);
        })
    .Reorderable(config =>
        {
            config.Columns(true);
        })

)

<script type = "text/javascript">
function onEdit(e) {
    $(e.form).find('#VehicleType').data('tDropDownList').select(function (dataItem) {
        return dataItem.Text == e.dataItem['vehicleType'];
    });
}
</script>

View Model:

using System;
using System.ComponentModel.DataAnnotations;

namespace ShopLog.ViewModels
{
    public class VehicleIndexViewModel
    {

    //public IEnumerable<Vehicle> Vehicles { get; set; }
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid VehicleID { get; set; }

    [Timestamp]
    public Byte[] Timestamp { get; set; }

    [Required]
    [Display(Name = "Vehicle Name")]
    [MaxLength(30, ErrorMessage = "The {0} must be no more than {2} characters long.")]
    public string VehicleName { get; set; }

    [Display(Name = "Fuel Type")]
    [StringLength(30, ErrorMessage = "\"{0}\" must be no more than {1} characters long.")]
    public string FuelType { get; set; }

    [Display(Name = "Notes")]
    [DataType(DataType.MultilineText)]
    public string Notes { get; set; }

    [UIHint("VehicleType")]
    [Display(Name = "Type")]
    public string VehicleType { get; set; }

    [Display(Name = "Transmission Type")]
    public String TransmissionType { get; set; }
    }
}

Controller Post Code for Update Putting a breakpoint at the first line shows that the vehicleIndexViewModel's timestamp property is null. I tried to get the Timestamp from the formcollection, but it's null there as well.

[AcceptVerbs(HttpVerbs.Post)]
    [GridAction]
    public ActionResult _SaveVehicle(VehicleIndexViewModel vehicleIndexViewModel, FormCollection formcollection)
    {          

        if (TryUpdateModel(vehicle))
        {
            try
            {
                //Delete the record
                db.Entry(vehicle).State = EntityState.Modified;
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                ViewBag.AjaxErrorMessage = "That record has been edited by someone else since you started editing it.";
            }
            catch (DataException)
            {
                ViewBag.AjaxErrorMessage = "Error saving data, please try again.";
            }
        }
        //Rebind the grid
        PopulateVehicleTypes();
        return View(new GridModel(GetIndexViewData()));
    }

Thanks!


Solution

  • Here is what I have figured out:

    byte[] does not pass back from the ViewModel well. Why? I don't know.

    I ended up making the "Timestamp" field in my ViewModel a string instead of a byte[]. When I pass data from the Model to the ViewModel, I call "Convert.ToBase64String(value)". When I pass data from the ViewModel back to the Model, call "Convert.FromBase64String(value)".

    Model:

    [Timestamp]
    public Byte[] Timestamp { get; set; }
    

    ViewModel:

    [Timestamp]
    public string Timestamp {get; set;}
    

    Controller:

        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult _SaveVehicle(VehicleIndexViewModel vehicleIndexViewModel)
        {
            Vehicle vehicle = Mapper.Map<VehicleIndexViewModel, Vehicle>(vehicleIndexViewModel);
    
            if (TryUpdateModel(vehicle))
            {
                try
                {
                    //Delete the record
                    db.Entry(vehicle).State = EntityState.Modified;
                    db.SaveChanges();
                }
                catch (DbUpdateConcurrencyException)
                {
                    ViewBag.AjaxErrorMessage = "That record has been edited by someone else since you started editing it.";
                }
                catch (DataException)
                {
                    ViewBag.AjaxErrorMessage = "Error saving data, please try again.";
                }
            }
            //Rebind the grid
            PopulateVehicleTypes();
            return View(new GridModel(GetIndexViewData()));
        }
    

    Automapper mappings in Global.ascx

    AutoMapper.Mapper.CreateMap<ShopLog.Models.Vehicle, ShopLog.ViewModels.VehicleIndexViewModel>().ForMember(m => m.Timestamp, opt => opt.ResolveUsing<VehicleTimestampResolver>());
    AutoMapper.Mapper.CreateMap<ShopLog.ViewModels.VehicleIndexViewModel, ShopLog.Models.Vehicle>().ForMember(m => m.Timestamp, opt => opt.ResolveUsing<VehicleIndexViewModelTimestampResolver>());
    
        public class VehicleTimestampResolver : AutoMapper.ValueResolver<Vehicle, string>
        {
            protected override string ResolveCore(Vehicle source)
            {
                return Convert.ToBase64String(source.Timestamp);
            }
        }
    
        public class VehicleIndexViewModelTimestampResolver : AutoMapper.ValueResolver<ShopLog.ViewModels.VehicleIndexViewModel, byte[]>
        {
            protected override byte[] ResolveCore(ShopLog.ViewModels.VehicleIndexViewModel source)
            {
                return Convert.FromBase64String(source.Timestamp);
            }
        }
    

    View: (note datakeys)

    @(Html.Telerik().Grid(Model)
    .Name("Grid")
    .Columns(columns =>
    {
        columns.Bound(o => o.VehicleID).Visible(false);        
        columns.Bound(o => o.VehicleName);
        columns.Bound(o => o.VehicleType);
    })    
    .DataBinding(dataBinding =>
        {
            dataBinding.Server().Select("Index", "Vehicle");
            dataBinding.Ajax().Select("_Index", "Vehicle").Enabled(true);
            dataBinding.Ajax().Update("_SaveVehicle", "Vehicle");
            dataBinding.Ajax().Delete("_DeleteVehicle", "Vehicle");
        })
        .Scrollable(scrolling => scrolling.Enabled(true))
        .Sortable(sorting => sorting 
            .Enabled(true)
            .OrderBy(sortOrder => sortOrder.Add(o => o.VehicleName).Ascending())
            .SortMode(GridSortMode.MultipleColumn))
        .Pageable(paging => 
            paging.Enabled(true)
            .PageSize(20)
            .Position(GridPagerPosition.Bottom))
        .Filterable(filtering => filtering.Enabled(true))
        .Groupable(grouping => grouping.Enabled(true))
        .Footer(true)
        .ToolBar(commands => commands
            .Custom()
                .HtmlAttributes(new {id = "export"})
                .Text("Export to CSV")
                    .Action("ExportCsv", "Vehicle", new { page = 1, orderBy = "~", filter = "~" }))                                                     
        .Columns(columns =>
            {
                columns.Command(command =>
                    {
                        command.Custom("details").Text("Details").Action("Edit", "Vehicle").HtmlAttributes(new { style = "text-align: center" }) ;
                        command.Edit().ButtonType(GridButtonType.Image);
                        command.Delete().ButtonType(GridButtonType.Image);
                    }).Width(165);
            })
        .DataKeys(dataKeys => 
            {
                dataKeys.Add(key => key.Timestamp).RouteKey("Timestamp");
                dataKeys.Add(key => key.VehicleID).RouteKey("VehicleID"); 
    
            })
        .ClientEvents(events => events 
            .OnEdit("onEdit")
            .OnDataBound("onDataBound"))
        .ColumnContextMenu()
        .Resizable(config =>
            {
                config.Columns(true);
            })
        .Reorderable(config =>
            {
                config.Columns(true);
            })        
    )
    

    The only thing left from this is to figure out where to display the "ViewBag.AjaxErrorMessage" when it is returned to the view, but that shouldn't bee too hard. :)