Kendo Grid and ASP.NET MVC4 wrapper and Conditional .ToClientTemplate() - asp.net-mvc

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
}

Related

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) {
// ...
}

.NET MVC Custom validation (without data annotation)

use .NET MVC and code-first EF to implement of requested functionality. Business objects are relatively complex and I use System.ComponentModel.DataAnnotations.IValidatableObject to validate business object.
Now I'm trying to find the way, how to show validation result from business object, using MVC ValidationSummary without using data annotations. For example (very simplified):
Business Object:
public class MyBusinessObject : BaseEntity, IValidatableObject
{
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Validate();
}
public IEnumerable<ValidationResult> Validate()
{
List<ValidationResult> results = new List<ValidationResult>();
if (DealType == DealTypes.NotSet)
{
results.Add(new ValidationResult("BO.DealType.NotSet", new[] { "DealType" }));
}
return results.Count > 0 ? results.AsEnumerable() : null;
}
}
Now in my MVC controller I have something like this:
public class MyController : Controller
{
[HttpPost]
public ActionResult New(MyModel myModel)
{
MyBusinessObject bo = GetBoFromModel(myModel);
IEnumerable<ValidationResult> result = bo.Validate();
if(result == null)
{
//Save bo, using my services layer
//return RedirectResult to success page
}
return View(myModel);
}
}
In view, I have Html.ValidationSummary();.
How I can pass IEnumerable<ValidationResult> to the ValidationSummary?
I tried to find an answer by googling, but all examples I found describes how to show validation summary using data annotations in Model and not in Business object.
Thanks
Add property, say BusinessError, in the model
in the View do the following
#Html.ValidationMessageFor(model => model.BusinessError)
Then in your controller whenever you have error do the following
ModelState.AddModelError("BussinessError", your error)
I would have a look at FluentValidation. It's a framework for validation without data annoations. I've used it with great success in some projects for complex validation, and it is also usable outside of the MVC-project.
Here is the sample code from their page:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Company).NotNull();
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
Entity Framework should throw a DbEntityValidationException if there are validation errors. You can then use the exception to add the errors to the ModelState.
try
{
SaveChanges();
}
catch (DbEntityValidationException ex)
{
AddDbErrorsToModelState(ex);
}
return View(myModel);
protected void AddDbErrorsToModelState(DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
ModelState.AddModelError(validationError.PropertyName, validationError.ErrorMessage);
}
}
}
One of the ways to pass the contents of IEnumerate and keep taking advantage of Html.ValidationSummary is by updating ModelState.
You can find a good discussion on how to update the ModelState here.

KendoUI MVC Json - do they work together?

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

How to render a model property of string type as checkbox in ASP.NET MVC

I want to display a string type as checkbox on MVC view, but returns it as string type on HTTP post. The problem is that it returns false on HTTP Post. Below is my code:
View:
#model List<Car>
foreach(var car in Model){
bool isFourWheel = false;
if(bool.TryParse(car.IsFourWheel, out isFourWheel){
#Html.CheckBox("IsFourWheel", isFourWheel); //need to be rendered as checkbox, but returns string type on HTTP POST
}
}
Model:
public class Car
{
public string IsFourWheel { get; set; } //bad naming, but it can contain any type, include boolean
}
Controller:
public ActionResult Index()
{
var cars = new List<Car>(){ new Car(){IsFourWheel = "true"},new Car(){IsFourWheel = "false"} };
return View(cars);
}
[HttpPost]
public ActionResult Index(List<Car> cars) **Problem IsFourWheel is false when true is selected **
{
return View(cars);
}
Any ideal would be very much appreciated.
You can try specifying a template name in your helper:
#Html.EditorFor(car => car.IsFourWheel, "CheckBox")
And defining the template to render the data the way you want, in either ~/Views/{YourControllerName}/EditorTemplates/CheckBox.cshtml or ~/Views/Shared/EditorTemplates/CheckBox.cshtml.
You can find a whole series of post by Brad Wilson on MVC templates here:
Brad Wilson: ASP.NET MVC 2 Templates, Part 1: Introduction
It is for MVC 2, but most concepts still apply to MVC 3 as well (save for the Razor syntax).
Update:
Actually you probably don't need a custom template for this. Try using #Html.CheckBoxFor(car => car.IsFourWheel) instead.
Update 2:
Drop the following template in ~/Views/Shared/EditorTemplates:
IsFourWheel.cshtml
#functions {
private bool IsChecked() {
if (ViewData.Model == null) return false;
return Convert.ToBoolean(ViewData.Model, System.Globalization.CultureInfo.InvariantCulture);
}
}
#Html.CheckBox("", IsChecked(), new { #class = "check-box" })
Then from your view, call it like so:
#Html.EditorFor(model => model.IsFourWheel, "IsFourWheel")
I tested it and binding works in both GET and POST scenarios.
You could alter your viewmodel like this:
public class Car
{
public string IsFourWheel { get; set; }
public bool IsFourWheelBool { get { return bool.Parse(IsFourWheel); } }
}
Your view would look like this:
#Html.EditFor(x => x.IsFourWheelBool);
I think it will be easier, if you add an Id to your model. Just like this
Model:
public class Car
{
public int CarID { get; set; }
public string IsFourWheel { get; set; }
}
View:
#model IEnumerable<Car>
foreach (var car in Model)
{
if(car.IsFourWheel == "true"){
<input type="checkbox" name="carID" value="#car.CarID" checked="checked" />
}
else
{
<input type="checkbox" name="carID" value="#car.CarID" />
}
}
Controller:
[HttpPost]
public ActionResult Index(List<int> carID)
{
//handle selected cars here
return View();
}

MVC 3 Layout Page, Razor Template, and DropdownList

I want to include a drop down list of years across all the pages in my website. I assumed a good place to put this logic was in the layout page (_layout.cshtml). If a user changes the year I want to change my year session (ModelBinder) to change as well. This was so easy to do with ASP.NET web forms, but seems near impossible to do in MVC. I tried a partial view with no luck. Anybody have any ideas?
As usual you could start by defining a view model:
public class YearsViewModel
{
public string Year { get; set; }
public IEnumerable<SelectListItem> Years
{
get
{
return new SelectList(
Enumerable.Range(1900, 112)
.OrderByDescending(year => year)
.Select(year => new SelectListItem
{
Value = year.ToString(),
Text = year.ToString()
}
), "Value", "Text");
}
}
}
Then a controller:
public class YearsController : Controller
{
public ActionResult Index()
{
return View(new YearsViewModel());
}
[HttpPost]
public ActionResult Index(int year)
{
// TODO: do something with the selected year
return new EmptyResult();
}
}
and a corresponding view for the index action:
#model SomeAppName.Models.YearsViewModel
#{
Layout = null;
}
#Html.DropDownListFor(x => x.Year, Model.Years)
And finally inside your _Layout.cshtml you could use this controller:
<div id="selectyear">#Html.Action("index", "years")</div>
and attach a corresponding script which would send an AJAX request when the value changes:
$(function () {
$('#selectyear select').change(function () {
$.post('#Url.Action("index", "years")', { year: $(this).val() }, function (result) {
});
});
});

Resources