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!
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. :)