KendoUI MVC Json - do they work together? - asp.net-mvc

I am client-server, SQL programmer (RAD guy) and the company decided that we must move to .NET environment. We are examining the platform now.
I studied the MVC4 and the Entities Framework this week and I have read a lot about KendoUI. I was skeptical because most of the examples come with KendoGrid+WebApi. I know awfully little about WebApi but I really liked the Entities thing a lot so I do not think I should give it a chance.
I want to ask some question which may seem naive but the answers will help me a lot
Once I create entities from an existing database, can I have the results in Json format and with this to feed a KendoGrid?
If yes, how? I mean:
How I can convert the results in Json inside the Controller?
In the transport property of the KendoGrid I should put the URL of the Controller/Action?
and the most naive one
Does Telerik have any thoughts of providing a visual tool to create-configure the kendoGrid? To make it more RAD because now need TOO MUCH coding. Maybe a wizard that you can connect entities with Grid columns, datasource, transport selectors etc..

I hope you choose the Kendo Entities path, there will be a learning curve though. No, on question 4. But let me give you a jump start using the Razor view engine and answer 1, 2, and 3.
First, EF is creating business objects. You 'should' convert these to Models in MVC. In this example Person is from EF. I think of this as flattening because it removes the depth from the object, though it i still available so you could reference something like Person.Titles.Name if your database was setup like that. You can also drop in DataAnnotations, which just rock.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Project.Business;
namespace Project.Web.Models
{
public class PersonModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Last Name is required.")]
public string LastName { get; set; }
[Required(ErrorMessage = "First Name is required.")]
public string FirstName { get; set; }
[Display(Name = "Created")]
public System.DateTime StampCreated { get; set; }
[Display(Name = "Updated")]
public System.DateTime StampUpdated { get; set; }
[Display(Name = "Enabled")]
public bool IsActive { get; set; }
public PersonModel()
{}
public PersonModel(Person person)
{
Id = person.Id;
FirstName = person.FirstName;
LastName = person.LastName;
StampCreated = person.StampCreated;
StampUpdated = person.StampUpdated;
IsActive = person.IsActive;
}
public static IList<PersonModel> FlattenToThis(IList<Person> people)
{
return people.Select(person => new PersonModel(person)).ToList();
}
}
}
Moving along...
#(Html.Kendo().Grid<PersonModel>()
.Name("PersonGrid")
.Columns(columns => {
columns.Bound(b => b.Id).Hidden();
columns.Bound(b => b.LastName).EditorTemplateName("_TextBox50");
columns.Bound(b => b.FirstName).EditorTemplateName("_TextBox50");
columns.Bound(b => b.StampUpdated);
columns.Bound(b => b.StampCreated);
columns.Bound(b => b.IsActive).ClientTemplate("<input type='checkbox' ${ IsActive == true ? checked='checked' : ''} disabled />").Width(60);
columns.Command(cmd => { cmd.Edit(); cmd.Destroy(); }).Width(180);
})
.ToolBar(toolbar => toolbar.Create())
.Pageable()
.Filterable()
.Sortable()
.Selectable()
.Editable(editable => editable.Mode(GridEditMode.InLine))
.DataSource(dataSource => dataSource
.Ajax()
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(a => a.Id);
model.Field(a => a.StampCreated).Editable(false);
model.Field(a => a.StampUpdated).Editable(false);
model.Field(a => a.IsActive).DefaultValue(true);
})
.Create(create => create.Action("CreatePerson", "People"))
.Read(read => read.Action("ReadPeople", "People"))
.Update(update => update.Action("UpdatePerson", "People"))
.Destroy(destroy => destroy.Action("DestroyPerson", "People"))
.PageSize(10)
)
)
Those _TextBox50 are EditorTemplates named _TextBox50.cshtml that MUST go either in a subfolder relative to your view or relative to your Shared folder - the folder must be called EditorTemplates. This one looks like this...
#Html.TextBox(string.Empty, string.Empty, new { #class = "k-textbox", #maxlength = "50" })
Yes, thats all. This is a simple example, they can get much more complicated. Or you don't have to use them initially.
And finally, what I think you are really looking for...
public partial class PeopleController : Controller
{
private readonly IPersonDataProvider _personDataProvider;
public PeopleController() : this(new PersonDataProvider())
{}
public PeopleController(IPersonDataProvider personDataProvider)
{
_personDataProvider = personDataProvider;
}
public ActionResult Manage()
{
>>> Left in as teaser, good to apply a special Model to a View to pass goodies ;)
var model = new PeopleViewModel();
model.AllQualifications = QualificationModel.FlattenToThis(_qualificationDataProvider.Read());
return View(model);
}
[HttpPost]
public JsonResult CreatePerson([DataSourceRequest]DataSourceRequest request, Person person)
{
if (ModelState.IsValid)
{
try
{
person = _personDataProvider.Create(person);
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.InnerException.Message);
}
}
var persons = new List<Person> {person};
DataSourceResult result = PersonModel.FlattenToThis(persons).ToDataSourceResult(request, ModelState);
return Json(result, JsonRequestBehavior.AllowGet);
}
public JsonResult ReadPeople([DataSourceRequest]DataSourceRequest request)
{
var persons = _personDataProvider.Read(false);
DataSourceResult result = PersonModel.FlattenToThis(persons).ToDataSourceResult(request);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult UpdatePerson([DataSourceRequest]DataSourceRequest request, Person person)
{
if (ModelState.IsValid)
{
try
{
person = _personDataProvider.Update(person);
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.InnerException.Message);
}
}
var persons = new List<Person>() {person};
DataSourceResult result = PersonModel.FlattenToThis(persons).ToDataSourceResult(request, ModelState);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult DestroyPerson([DataSourceRequest]DataSourceRequest request, Person person)
{
if (ModelState.IsValid)
{
try
{
person = _personDataProvider.Destroy(person);
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, "There was an error deleting this record, it may still be in use.");
}
}
var persons = new List<Person>() {person};
DataSourceResult result = PersonModel.FlattenToThis(persons).ToDataSourceResult(request, ModelState);
return Json(result, JsonRequestBehavior.AllowGet);
}
}
Notice that, in this case, each method takes the EF Person as a parameter, it would be better to use PersonModel but then I would have to show the opposite of the Flatten. This works becuase they are practically identical. If the model was different or you were using a class factory it gets a bit more tricky.
I intentionally showed you all the CRUD operations. If you don't pass the result back to the grid it will act funny and give you dupicates or not show updates correctly on CREATE and UPDATE. It is passed back on DELETE to pass the ModelState which would have any errors.
And finally the data provider, so as to leave nothing to the imagination...
(Namespace declaration omited.)
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using Project.Business;
public class PersonDataProvider : ProviderBase, IPersonDataProvider
{
public Person Create(Person person)
{
try
{
person.StampCreated = DateTime.Now;
person.StampUpdated = DateTime.Now;
Context.People.Attach(person);
Context.Entry(person).State = EntityState.Added;
Context.SaveChanges();
return person;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
}
public IList<Person> Read(bool showAll)
{
try
{
return (from q in Context.People
orderby q.LastName, q.FirstName, q.StampCreated
where (q.IsActive == true || showAll)
select q).ToList();
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
}
...
}
Note the Interface and ProviderBase inheritance, you have to make those. Should be simple enough to find examples.
This may seem like a lot of coding, but once you get it down, just copy paste.
Happy coding.

yes you can send the JSON back kendo UI using the JsonResult or using the JSON with allow get specified. you can set the url in the transport and specify the json as the type.
About your last question there is no visual tools right now but there are helper methods available for kendo UI

Related

Kendo UI not loading data

I am using Kendo UI (Telerik UI for ASP.NET MVC R3 2018) and I attempt to load data into it but the grid appears only showing header columns without the data. I have also tried debugging but I can't figure what the problem is. I also tried this thread but it doesn't solve my problem.
Below is what I have done and I am expecting the grid to show the data.
Model
public partial class PlacementType
{
public byte Id { get; set; }
[Display(Name = "Placement Name")]
public string PlacementName { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Date Created")]
public DateTime? DateCreated { get; set; }
[Display(Name = "Created By")]
public string CreatedBy { get; set; }
public string Description{get; set;}
}
View
#(Html.Kendo().Grid<PlacementType>()
.Name("Grid")
.Columns(columns =>
{
columns.Bound(c => c.PlacementName);
columns.Bound(c => c.DateCreated);
columns.Bound(c => c.CreatedBy);
columns.Bound(c => c.Description);
}
)
.HtmlAttributes(new { style = "height: 550px;" })
.Scrollable()
.Groupable()
.Sortable()
.Pageable(pageable => pageable.Refresh(true).PageSizes(true))
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("Index", "PlacementType"))
.PageSize(20)
)
)
Controller Action
public ActionResult Index()
{
List<PlacementType> types = db.PlacementTypes.ToList();
return View(types);
}
Your controller should have a method which returns a Json result.
E.g.
public JsonResult GetPlacementTypes()
{
var types = db.PlacementTypes.ToList();
return Json(types, JsonRequestBehavior.AllowGet);
}
And update your view to use this method. (Assume your controller is called "PlacementTypeController")
.Read(read => read.Action("GetPlacementTypes", "PlacementType"))
You define the following Read Action:
.Read(read => read.Action("Index", "PlacementType"))
Therefore make sure your Controller is named PlacementTypeController (with standard convention). The method needs to be named Index. That`s how you have configured it above.
If Index is already used by your view, you need to change read.Action Index to SomethingElse. Make sure, you Controller Action is also called SomethingElse Then.
Then the following Code should work:
public ActionResult Index([DataSourceRequest] DataSourceRequest request)
{
// var breakPoint = db.PlacementTypes.ToList(); // uncomment and set a breakpoint to see if you have data
return Json(db.PlacementTypes.ToDataSourceResult(request));
}
Note the use of the DataSourceRequest Attribute and the ToDataSourceResult method.
If it`s still not working, uncomment the comment, and set a breakpoint. Is your database actualy returning data?
Also take a look in the Browser Console CTRL+F12 and show if there are any errors. Also take a look at the Network tab.

Client Side Validation Using Custom Validator FluentValidation MVC

I have seen many examples of using FluentValidation here but none seem to fit my need. I have an existing server side implementation and based on answers here, I am convinced I have to change my implementation to get it work client side. One reason is because I can't set a ValidationType which seems required for the client side. I am having trouble converting the code so I can use it client side as well. I am submitting a list of File objects and want client side validation that the file extensions are either .pdf or .doc.
Global - Many examples here show a much more complicated Configure
protected void Application_Start()
{
FluentValidationModelValidatorProvider.Configure();
}
Model - I've simplified my model to show that I have at least one property and a collection
[Validator(typeof(MyCustomValidator))]
public class MyCustomModel
{
public DateTime SubmitDate { get; set; }
public List<HttpPostedFileBase> MyFiles { get; set; }
}
Model Validator - I have a separate validator for the collection
public class MyCustomModelValidator : AbstractValidator<MyCustomModel>
{
public MyCustomModelValidator()
{
RuleFor(x => x.SubmitDate)
.NotEmpty()
.WithMessage("Date Required");
RuleFor(x => x.MyFiles)
.SetCollectionValidator(new MyFileValidator())
.Where(x => x != null);
}
}
Collection Validator - This should check a file for a valid extension
public class MyFileValidator : AbstractValidator<HttpPostedFileBase>
{
public MyFileValidator()
{
RuleFor(x => x)
.Must(x => x.IsValidFileType())
.WithMessage("Invalid File Type")
}
}
public static bool IsValidFileType(this HttpPostedFileBase file)
{
var extensions = { ".pdf", ".doc" };
return extensions.Contains(Path.GetExtension(file.FileName.ToLower()));
}
Controller - Just showing the basics
[HttpGet]
public ActionResult Index(DefaultParameters parameters)
{
var model = new MyCustomModel();
return this.View(model);
}
[HttpPost]
public ActionResult Submit(MyCustomModel model)
{
if (!ModelState.IsValid)
{
return this.View("Index", model);
}
}
View - I am allowing 5 uploads per submission
#Html.TextBoxFor(m => m.SubmitDate)
#Html.ValidationMessageFor(m => m.TextBoxFor
#for (int i = 0; i < 5; i++)
{
#Html.TextBoxFor(m => m.FileSubmissions[i], new { type = "file" })
#Html.ValidationMessageFor(m => m.FileSubmissions[i])
}
Im not sure why you would change it to clientside? Not all validations should run clientside. Maybe you can get it working using the regex validation method instead of the Must method which is performed clientside according to the docs.

Fluent Validation in MVC: specify RuleSet for Client-Side validation

In my ASP.NET MVC 4 project I have validator for one of my view models, that contain rules definition for RuleSets. Edit ruleset used in Post action, when all client validation passed. Url and Email rule sets rules used in Edit ruleset (you can see it below) and in special ajax actions that validate only Email and only Url accordingly.
My problem is that view doesn't know that it should use Edit rule set for client html attributes generation, and use default rule set, which is empty. How can I tell view to use Edit rule set for input attributes generation?
Model:
public class ShopInfoViewModel
{
public long ShopId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
public string Email { get; set; }
}
Validator:
public class ShopInfoViewModelValidator : AbstractValidator<ShopInfoViewModel>
{
public ShopInfoViewModelValidator()
{
var shopManagementService = ServiceLocator.Instance.GetService<IShopService>();
RuleSet("Edit", () =>
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Enter name.")
.Length(0, 255).WithMessage("Name length should not exceed 255 chars.");
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Enter name.")
.Length(0, 10000).WithMessage("Name length should not exceed 10000 chars.");
ApplyUrlRule(shopManagementService);
ApplyEmailRule(shopManagementService);
});
RuleSet("Url", () => ApplyUrlRule(shopManagementService));
RuleSet("Email", () => ApplyEmailRule(shopManagementService));
}
private void ApplyUrlRule(IShopService shopService)
{
RuleFor(x => x.Url)
.NotEmpty().WithMessage("Enter url.")
.Length(4, 30).WithMessage("Length between 4 and 30 chars.")
.Matches(#"[a-z\-\d]").WithMessage("Incorrect format.")
.Must((model, url) => shopService.Available(url, model.ShopId)).WithMessage("Shop with this url already exists.");
}
private void ApplyEmailRule(IShopService shopService)
{
// similar to url rule: not empty, length, regex and must check for unique
}
}
Validation action example:
public ActionResult ValidateShopInfoUrl([CustomizeValidator(RuleSet = "Url")]
ShopInfoViewModel infoViewModel)
{
return Validation(ModelState);
}
Get and Post actions for ShopInfoViewModel:
[HttpGet]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
[HttpPost]
public ActionResult ShopInfo(CustomizeValidator(RuleSet = "Edit")]ShopInfoViewModel infoViewModel)
{
var success = false;
if (ModelState.IsValid)
{
// save logic goes here
}
}
View contains next code:
#{
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
}
<form class="master-form" action="#Url.RouteUrl(ManagementRoutes.ShopInfo)" method="POST" id="masterforminfo">
#Html.TextBoxFor(x => x.Name)
#Html.TextBoxFor(x => x.Url, new { validationUrl = Url.RouteUrl(ManagementRoutes.ValidateShopInfoUrl) })
#Html.TextAreaFor(x => x.Description)
#Html.TextBoxFor(x => x.Email, new { validationUrl = Url.RouteUrl(ManagementRoutes.ValidateShopInfoEmail) })
<input type="submit" name="asdfasfd" value="Сохранить" style="display: none">
</form>
Result html input (without any client validation attributes):
<input name="Name" type="text" value="Super Shop"/>
After digging in FluentValidation sources I found solution. To tell view that you want to use specific ruleset, decorate your action, that returns view, with RuleSetForClientSideMessagesAttribute:
[HttpGet]
[RuleSetForClientSideMessages("Edit")]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
If you need to specify more than one ruleset — use another constructor overload and separate rulesets with commas:
[RuleSetForClientSideMessages("Edit", "Email", "Url")]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
If you need to decide about which ruleset would be used directly in action — you can hack FluentValidation by putting array in HttpContext next way (RuleSetForClientSideMessagesAttribute currently is not designed to be overriden):
public ActionResult ShopInfo(validateOnlyEmail)
{
var emailRuleSet = new[]{"Email"};
var allRuleSet = new[]{"Edit", "Url", "Email"};
var actualRuleSet = validateOnlyEmail ? emailRuleSet : allRuleSet;
HttpContext.Items["_FV_ClientSideRuleSet"] = actualRuleSet;
return PartialView("_ShopInfo", viewModel);
}
Unfortunately, there are no info about this attribute in official documentation.
UPDATE
In newest version we have special extension method for dynamic ruleset setting, that you should use inside your action method or inside OnActionExecuting/OnActionExecuted/OnResultExecuting override methods of controller:
ControllerContext.SetRulesetForClientsideMessages("Edit", "Email");
Or inside custom ActionFilter/ResultFilter:
public class MyFilter: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
((Controller)context.Controller).ControllerContext.SetRulesetForClientsideMessages("Edit", "Email");
//same syntax for OnActionExecuted/OnResultExecuting
}
}
Adding to this as the library has been updated to account for this situation...
As of 7.4.0, it's possible to dynamically select one or multiple rule sets based on your specific conditions;
ControllerContext.SetRulesetForClientsideMessages("ruleset1", "ruleset2" /*...etc*);
Documentation on this can be found in the latest FluentValidation site:
https://fluentvalidation.net/aspnet#asp-net-mvc-5
Adding the CustomizeValidator attribute to the action will apply the ruleset within the pipeline when the validator is being initialized and the model is being automatically validated.
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
// ...
}

Kendo UI DropDownList ForeignKey in Grid

I'm trying to set up a Dropdown list in Kendo UI Grid using HTML helpers.
When I click to edit the grid, the dropdown appears and I can select a value. However, this does not save in the database when I click update (though the simple string WordName field does).
I would also like the CatId value from the WordViewModel to also be displayed as a word/dropdown when you're not editing the fields.
As far as I can tell, I have nothing which links the int CatId to the GetCategories list. How do I go about connecting those two? I've read a little about column.ForeignKey, but I don't understand it. Below is all my relevant code.
My WordViewModel (which loads from a similar, slightly more complex database model)
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public Nullable<int> CatId { get; set; }
}
My Category model (generated by the database)
public partial class Category
{
public Category()
{
this.Words = new HashSet<Word>();
}
public int CatId { get; set; }
public string CategoryName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Word> Words { get; set; }
}
Here's my razor code for the grid in Index.cshtml
#(Html.Kendo().Grid<WordViewModel>
()
.Name("wordGrid")
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(word => word.WordId); // Specify the property which is the unique identifier of the model
model.Field(word => word.WordId).Editable(false); // Make the ID property not editable
})
.Read(read => read.Action("Read", "Words")) //Populate the grid with Words
.Update(update => update.Action("Update", "Words")) // Action invoked when the user saves an updated data item
)
.Editable(editable => editable.Mode(GridEditMode.InLine)) // Use inline editing mode
.Columns(columns =>
{
columns.Bound(c => c.WordName);
columns.Bound(c => c.CatId).EditorTemplateName("_CategoryDropdown"); //link to EditorTemplate with the same name
columns.Command(commands =>
{
commands.Edit(); // The "edit" command will edit and update data items
}).Title("Commands").Width(200);
})
.Filterable()
)
Editor Template _CategoryDropdown.cshtml
#(
Html.Kendo().DropDownList()
.Name("Category") //is this Name important?
.DataTextField("CategoryName")
.DataValueField("CategoryId")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
My function to get the drop down list from the database.
public JsonResult GetCategories()
{
var items = db.Categories.ToList().Select(c => new Category
{
CatId = c.CatId,
CategoryName = c.CategoryName
});
return Json(items, JsonRequestBehavior.AllowGet);
}
Here is a working solution. Rather than using column.ForeignKey, I ended up manually connecting the CatId with the CategoryName and including both in the WordViewModel.
My final files:
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public string CategoryName { get; set; } //I added this field which is actually displayed on the grid
public Nullable<int> CatId { get; set; } //only used temporarily to transfer data
}
I did not end up referencing the Category model almost at all.
In my Grid I changed the binding on CategoryId to bind on CategoryName instead. Essentially with my solution, I only referenced Category Name in the view, and just matched up CategoryName with CategoryId in the Read/Update functions in the controller.
//The Title string below needs to be the same as the Name field in the EditorTemplate and possibly the same as the name in the model
columns.Bound(c => c.CategoryName).Title("CategoryName").EditorTemplateName("_CategoryDropdown");
The location of this file is important.
Views/Shared/EditorTemplates/_CategoryDropdown.cshtml:
#(
Html.Kendo().DropDownList()
.Name("CategoryName") //This name has to be the same as the Title on the main grid page
.DataTextField("CategoryName")
.DataValueField("CategoryName")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
The Words/GetCategories function was correct.
I had to do some work in Words/Read to get the category name from the category ID
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Select(w => new WordViewModel
{
WordId = w.WordId,
CatId = w.CatId,
CategoryName = "",
WordName = w.WordName
}).ToList(); //need .ToList to be able to iterate through it
//finish building the word
foreach(var item in items)
{
if(item.CatId!=null)
{
//add CategoryName corresponding to each CatId
//In my database I have a table for Categories which matches up CatId to CategoryName
Category cat = db.Categories.Select(c => c).Where(c => c.CatId == item.CatId).FirstOrDefault();
item.CategoryName = cat.CategoryName;
}
}
return Json(items.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
and some stuff in Words/Update to do the reverse Name->Id:
public ActionResult Update([DataSourceRequest]DataSourceRequest request, [Bind(Prefix = "models")] WordViewModel word)
{
if (ModelState.IsValid)
{
// Create a new Product entity and set its properties from the posted ProductViewModel
var entity = new Word
{
WordId = word.WordId,
CategoryName = word.CategoryName,
WordName = word.WordName
};
if (word.CategoryName != "")
{
//match CategoryWord to CatID
Category cat = db.Categories.Select(c => c).Where(c => c.CategoryName == word.CategoryName).FirstOrDefault();
entity.CatId = cat.CatId;
}
// Attach the entity
db.Words.Attach(entity);
// Change its state to Modified so Entity Framework can update the existing product instead of creating a new one
db.Entry(entity).State = EntityState.Modified;
// Update the entity in the database
db.SaveChanges();
}
// Return the updated product. Also return any validation errors.
return Json(new [] { word }.ToDataSourceResult(request, ModelState));
}
There might be some minor errors since this is a little simplified from my real code, but all the important pieces are there. Figuring out all the linkages and what I could depend on Kendo for vs what I had to manually was pretty difficult to figure out. Good luck to anyone else trying to use Kendo Grid, and I hope this example helps!
Nullable CatId is the problem. Check out the fix here Kendo MVC dropdown lists inside inline Kendo MVC grids. Second option is following but this one only works with InLine.
function onSave(e) {
// kendo nullable dropdown bug workaround
$("#wordGrid tbody [data-role=dropdownlist]").each(function () {
var kd = $(this).data("kendoDropDownList");
if (kd) {
var v = kd.value();
var p = kd.list.attr('id').replace('-list', '');
if(p) e.model.set(p, v);
}
})
}
There are also a suggestion to use default value but it never worked for me. see here

Kendo Grid and ASP.NET MVC4 wrapper and Conditional .ToClientTemplate()

I would like to put my grids in partials and have a strongly typed model for each grid view that passes the data and specifies if the grid should be rendered to a client template.
For example:
--MODEL
class ProductGridModel
{
public List<Products> Products{get;set;}
public bool LoadAsChildGrid{get;set;}
public string ParentGrid {get;set;}
}
--VIEW
#(Html.Kendo().Grid<Models.ProductGridModel>()
{
.Ajax()
.Read(read => read.Action("GetProducts", "Products", new
{ orderID=(#Model.LoadAsChildGrid)?"#=OrderID":#Model.OrderID }))
...
.ToClientTemplate(#Model.LoadAsChildGrid)//!!!<-- This can't be done
.Events(e => e.DataBound((#Model.LoadAsChildGrid)?"BaseGridOnDataBound('grdProducts_#=OrderID#')":""))
}
--CONTROLLER
public ActionResult GetProducts(int orderID, [DataSourceRequest] DataSourceRequest request)
{
try
{
base.RequireAuthorization(xxxx.StockAdmin, orderID);
List<Products> products= new ProductManagement().GetProductsByOrderID(orderID);
return Json(products.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
ModelState.AddModelError("", e.ToString());
throw e;
}
}
Is there a way to optionally render ToClientTemplate???. If there is no work around then the only alternative I have is to implement a custom HTmlHelper KendoGridBuilder:
public virtual GridBuilder<T> Grid<T>() where T : class;
, which I would rather not do at this time. In case I have to extend and implement a grid I have been looking for a step by step guide on how it should be done. Any help would be appreciated.
Try this:
#{
var grid = (Html.Kendo().Grid<Models.ProductGridModel>()
...
);
}
#if(#Model.LoadAsChildGrid) {
#grid.ToClientTemplate()
} else {
#grid
}

Resources