Skip to main content

Centralized model validation both for MVC/WebApi and SPA client-side validation using FluentValidation

Validation is one of the crucial parts of any application. It has to validate on both client side and server side requests.

What are target features or implementation from this article?

  • Model validation for any given model.
  • Centralized/One code for validation on both server-side and client-side.
  • Automatic validation of model without writing any extra codes on/under actions for validation.
  •  NO EXTRA/ANY codes on client-side to validate any form.
  • Compatible with SPA.
  • Can be compatible with any client-side validation framework/library. Like Angular Reactive form validation or any jquery validation libraries.

Tools used in the implementation?

  • FluentValidation: I feel DataAnnotation validation are excellent and simple to use, but in case of complex validation or writing any custom validations are always tricker and need to write a lot of codes to achieve whereas FluentValidations are simple even in case of complex validation. Generally, we need to validate incoming input against database values, which are straight-forward in FluentValidation. Also, it is not bound to be used only with MVC it can implement at any layers or any front-end, and on the entry point, it just need initialization. Also, the Fluent approach is similar to Entity Framework Fluent mapping so an identical pattern.
  • jquery-validation: In case of MVC and as long as cshtml/Views are used it is effortless to implement client-side validation since it is automatically taken care by auto-generated unobtrusive validation under rendered HTML. No work at all there unless some remote validations are required. In case of SPA, it gets hugely affected since rendering engines are on client-side and Microsoft Unobtrusive validation won't play a role.

    jquery-validation works based on JSON data, we would be getting rules and messages from server side and applying validation on the form through centralized code implementation. So, one code would take care of everything.
  • MVC and ActionFilterAttribute: The filter to generate validation rules based on JS validation library, validate model when posted on the server and remote validation for given field if it needs any check against database/server side codes.
Now, it is time to look into coding I have already pushed changes in GitHub repository https://github.com/viku85/CustomSpaApp. Also, this article could be treated as a continuation of SPA architecture that I had done in my last blog article https://vikutech.blogspot.in/2017/10/custom-spa-engine-similar-to-mvc-pattern-by-using-typescript.html.

Composing Fluent validation for the view model

Let's first directly begin with composing fluent validation and later look into Filter implementation. The validation is straightforward but a bit of enhancement through RuleSet and implementation of a little wrapper on base class BaseValidation.

 /// <summary>  
 /// Fluent validation for <see cref="ContactViewModel"/> view model.  
 /// </summary>  
 /// <seealso cref="BaseValidation{ContactViewModel}" />  
 public class ContactViewModelValidation  
   : BaseValidation<ContactViewModel>  
 {  
   /// <summary>  
   /// Initializes a new instance of the <see cref="ContactViewModelValidation"/> class.  
   /// </summary>  
   public ContactViewModelValidation()  
   {  
     RuleFor(model => model.Name)  
       .NotEmpty()  
       .Length(3, 100);  
     RuleFor(model => model.Email)  
       .NotEmpty()  
       .EmailAddress();  
     RuleFor(model => model.Message)  
       .NotEmpty()  
       .Length(5, 2000);  
     RuleFor(model => model.Subject)  
       .NotEmpty();  
     RuleSet(OperationRequest.Insert, () =>  
     {  
       RuleFor(model => model.Message)  
               .Must((model, field, token) =>  
             {  
               // TODO: Check against database if Email and message already exists.  
               return false;  
             }).WithMessage("Your information is already submitted.")  
             ;//.When(model => !String.IsNullOrEmpty(model.Message) && String.IsNullOrEmpty(model.Email));  
     });  
   }  
 }  

The above looks pretty simplified, and names say it all. The little twist is, it can hold validation rules for any addition, update or deletion of a model. An example can be seen from above through RuleSet of Insert through OperationRequest.Insert enum.

The above code includes Common rules for any CRUD operation which would be shared in all operations.

Here is code for OperationRequest enum and Base class from above.

Enum
   /// <summary>  
   /// Operation request  
   /// </summary>  
   public enum OperationRequest  
   {  
     /// <summary>  
     /// The insert request  
     /// </summary>  
     Insert,  
     /// <summary>  
     /// The update request  
     /// </summary>  
     Update,  
     /// <summary>  
     /// The delete request  
     /// </summary>  
     Delete  
   }  


Base Class
 /// <summary>  
 /// Base validation for all Fluent validations.  
 /// </summary>  
 /// <typeparam name="TModel">The type of the model.</typeparam>  
 /// <seealso cref="AbstractValidator{TModel}" />  
 public class BaseValidation<TModel>  
   : AbstractValidator<TModel>  
 {  
   /// <summary>  
   /// Defines a RuleSet that can be used to group together several validators.  
   /// </summary>  
   /// <param name="ruleSetName">The name of the ruleset.</param>  
   /// <param name="action">Action that encapsulates the rules in the ruleset.</param>  
   protected void RuleSet(OperationRequest ruleSetName, Action action)  
   {  
     RuleSet(ruleSetName.ToString(), action);  
   }  
 }  

Above two items are simple enough, after this, we just need to register it in MVC service collection for dependency injection.

We are not going to register in-built fluent validation on assembly level instead register on DI and use it on our custom smart filter for validation of actions and generation of JSON for client-side validation.

 /// <summary>  
 /// Extension to Register all Fluent validations.  
 /// </summary>  
 public static class ExtensionRegisterValidation  
 {  
   /// <summary>  
   /// Adds FluentValidation for the application.  
   /// </summary>  
   /// <param name="services">The services.</param>  
   /// <returns>The Service collection.</returns>  
   public static IServiceCollection AddFluentValidation(this IServiceCollection services)  
   {  
     services.AddScoped<IValidator<ContactViewModel>, ContactViewModelValidation>();  
     return services;  
   }  
 }  

Startup.cs
 services.AddFluentValidation();  

That completes FluentValidation part.

Filter to auto-trigger FluentValidation and generation of client-side validation rules along with support of remote validation

I have already explained three responsibilities of same, let's look in detail:
  • Model validation for action. We discussed four modes of  FluentValidation including Common for all kind of CRUD operation using OperationRequest seen earlier. If you see GetValidationRule function it retrieves RuleSet for FluentValidation. Common RuleSet is named as Default under FluentValidation. While creating Fluent Validator, we can specify an array of RuleSets. The function generates RuleSet based on Action name in MVC/WebApi so that we do not have to put Validate filter by specifying RuleSets manually. Do modify based on your need as of current implementation it pulls from action name containing add, create, insert, delete, update. So, action name like CreateCustomer, AddCustomer, UpdateCustomerAsync would be automatically resolved.
  • Remote validation: In this, an AJAX call would be made for a field to validate on the server side. The trick is, it would request the same URL with query string validate with property name to validate. From code level, you can see some condition with validate checks are done for the same reason. A JsonResult is sent from this. An example:
    Remote Validation
    AJAX/Remote validation for Message field.
  • Validation Rules and messages: This is a static data served when a form is rendered, this would contain necessary information about validations for fields used in the form. This, JSON generation is achieved through JqueryValidatorProvider class implementation, and the result is this:
Validation Rules for the entire form generated from JqueryValidatorProvider 

Codes of Action filter attribute

 /// <summary>  
 /// Validation filter for model state validation.  
 /// </summary>  
 /// <seealso cref="ActionFilterAttribute" />  
 public sealed partial class ValidationResponseFilterAttribute  
   : ActionFilterAttribute  
 {  
   /// <summary>  
   /// Action execution on any controller.  
   /// </summary>  
   /// <param name="context">Action context.</param>  
   public override void OnActionExecuting(ActionExecutingContext context)  
   {  
     if (context == null)  
     {  
       return;  
     }  
     var model = GetTheActionArgument(context);  
     if (model == null && context.HttpContext.Request.Query.TryGetValue("validate", out var _))  
     {  
       context.Result = new JsonResult(string.Empty);  
       return;  
     }  
     if (model == null)  
     {  
       return;  
     }  
     var validator = context.HttpContext.RequestServices  
       .GetService(typeof(IValidator<>)  
       .MakeGenericType(model.ModelType)) as IValidator;  
     // In case of no validator and URL requested for validation rule  
     if (validator == null && context.HttpContext.Request.Query.Keys.Any(val => val == "validation"))  
     {  
       context.Result = new EmptyResult();  
       return;  
     }  
     if (validator == null)  
     {  
       return;  
     }  
     if (context.HttpContext.Request.Query.TryGetValue("validation", out var clientRuleRequest))  
     {  
       switch (clientRuleRequest)  
       {  
         default:  
           context.Result = new JqueryValidatorProvider(validator).GetValidationRules();  
           break;  
       }  
       return;  
     }  
     model.ExecuteOnModel((modelValue) =>  
     {  
       var validationResult =  
         validator.Validate(new ValidationContext(  
           modelValue,  
           new PropertyChain(),  
           new RulesetValidatorSelector(GetValidationRule(context))));  
       validationResult.AddToModelState(context.ModelState, null);  
     });  
     // If requested for Ajax validation.  
     if (context.HttpContext.Request.Query.TryGetValue("validate", out var modelProperty))  
     {  
       context.Result = new JsonResult(context.ModelState.ToDictionary(  
               kvp => kvp.Key,  
               kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).Humanize()));  
       return;  
     }  
     if (!context.ModelState.IsValid)  
     {  
       context.Result = new BadRequestObjectResult(context.ModelState);  
     }  
   }  
   /// <summary>  
   /// Gets the action argument.  
   /// </summary>  
   /// <param name="actionContext">The action context.</param>  
   /// <returns>Object type and object.</returns>  
   private static ModelDefination GetTheActionArgument(ActionExecutingContext actionContext)  
   {  
     return (from arg in actionContext.ActionArguments.Values  
         let typ = arg?.GetType()  
         where typ != null && !typ.IsPrimitive && !typ.IsValueType && typ != typeof(string)  
         //&& typ != typeof(Kendo.Mvc.UI.DataSourceRequest) TODO: Note could exclude any other types  
         select new ModelDefination(arg)).FirstOrDefault();  
   }  
   /// <summary>  
   /// Gets the validation rule set names for Fluent validation.  
   /// </summary>  
   /// <param name="actionContext">The action context.</param>  
   /// <returns>Validation rules for Fluent mapping</returns>  
   private static string[] GetValidationRule(ActionExecutingContext actionContext)  
   {  
     var ruleSetNames = new List<string>(2) { "default" };  
     if (actionContext.ActionDescriptor is ControllerActionDescriptor descriptor)  
     {  
       if (descriptor.ActionName.Contains(nameof(OperationRequest.Delete)))  
       {  
         ruleSetNames.Add(OperationRequest.Delete.ToString());  
       }  
       else if (descriptor.ActionName.Contains("Create") ||  
         descriptor.ActionName.Contains("Add") ||  
         descriptor.ActionName.Contains(nameof(OperationRequest.Insert)))  
       {  
         ruleSetNames.Add(OperationRequest.Insert.ToString());  
       }  
       else if (descriptor.ActionName.Contains(nameof(OperationRequest.Update)))  
       {  
         ruleSetNames.Add(OperationRequest.Update.ToString());  
       }  
     }  
     return ruleSetNames.ToArray();  
   }  
 }  

JqueryValidatorProvider

This class is responsible for generating JSON for general form validation. This is done by looking into FluentValidation models meta information and ultimately creating JSON which helps in form validation.
The implementation could be changed based on your choice of the client-side validator, and server-side codes would accordingly change.
 /// <summary>  
 /// jQuery.Validator rules provider.  
 /// </summary>  
 public class JqueryValidatorProvider  
 {  
   /// <summary>  
   /// The validator  
   /// </summary>  
   private readonly IValidator Validator;  
   /// <summary>  
   /// Initializes a new instance of the <see cref="JqueryValidatorProvider"/> class.  
   /// </summary>  
   /// <param name="validator">The validator.</param>  
   public JqueryValidatorProvider(IValidator validator)  
   {  
     Validator = validator;  
   }  
   /// <summary>  
   /// Gets the validation rules based on jQuery.validator.  
   /// </summary>  
   /// <returns>Result along with rules.</returns>  
   public IActionResult GetValidationRules()  
   {  
     if (Validator == null)  
     {  
       return new EmptyResult();  
     }  
     var validationObject = new Dictionary<string, object>();  
     var errorObject = new Dictionary<string, Dictionary<string, string>>();  
     var validatorDescriptor = Validator.CreateDescriptor();  
     foreach (var valida in validatorDescriptor.GetMembersWithValidators())  
     {  
       var validations = new Dictionary<string, object>();  
       var errors = new Dictionary<string, string>();  
       foreach (var propertyValidator in valida)  
       {  
         var validatorAdded = false;  
         Action<string, object, Action<MessageFormatter, Action<Func<string, string>>>> add = (validationType, value, format) =>  
         {  
           validatorAdded = true;  
           var formatter = new MessageFormatter();  
           formatter.AppendPropertyName(valida.Key.Humanize());  
           var template = propertyValidator.ErrorMessageSource.GetString(null);  
           format?.Invoke(formatter, (d) => { template = d?.Invoke(template); });  
           validations.SafeAdd(validationType, value);  
           errors.SafeAdd(validationType, formatter.BuildMessage(template));  
         };  
         if (propertyValidator is NotNullValidator  
           || propertyValidator is NotEmptyValidator)  
         {  
           add("required", true, null);  
         }  
         if (propertyValidator is EmailValidator)  
         {  
           add("email", true, null);  
         }  
         if (propertyValidator is LengthValidator lengthValidator)  
         {  
           if (lengthValidator.Max > 0)  
           {  
             add(  
               "maxlength",  
               lengthValidator.Max,  
             (formatter, template) =>  
             {  
               formatter.AppendArgument("MinLength", lengthValidator.Min);  
               formatter.AppendArgument("MaxLength", lengthValidator.Max);  
               template?.Invoke((tem) => !tem.Contains("TotalLength") ? tem :  
                   tem.Split(".")?.FirstOrDefault());  
             });  
           }  
           add(  
             "minlength",  
             lengthValidator.Min,  
             (formatter, template) =>  
             {  
               formatter.AppendArgument("MinLength", lengthValidator.Min);  
               formatter.AppendArgument("MaxLength", lengthValidator.Max);  
               template?.Invoke((tem) => !tem.Contains("TotalLength") ? tem :  
               tem.Split(".")?.FirstOrDefault());  
             });  
         }  
         if (propertyValidator is RegularExpressionValidator expressionValidator)  
         {  
           add("regex", expressionValidator.Expression, null);  
         }  
         if (!validatorAdded)  
         {  
           add("remote", valida.Key, null);  
         }  
       }  
       validations.IfNotEmpty(() => validationObject.Add(valida.Key, validations));  
       errors.IfNotEmpty(() => errorObject.Add(valida.Key, errors));  
     }  
     return new JsonResult(new  
     {  
       rules = validationObject,  
       messages = errorObject  
     });  
   }  
 }  

From codes, remote validation would only enable when it did not fall in any category of validation.
That's an end of server-side codes. Client-side codes are relatively simple.

Client-side codes for validation

The client-side validation is entirely dependent on jquery-validation plugin since it is our choice for client-side validation.

FormValidation.ts
 import { injectable } from "inversify";  
 import { HttpRequestResponse } from './../RequestResponse/HttpRequestResponse';  
 @injectable()  
 class FormValidation {  
   private AppendError(form: JQuery<HTMLFormElement> | JQuery<HTMLElement>,  
     error: JQuery<HTMLElement>, element: JQuery<HTMLElement>) {  
     let errorSelector = $(`#${$(element).attr("id")}Error`);  
     errorSelector = errorSelector.length ? errorSelector : $(`#${$(element).attr("id")}-error`);  
     if (errorSelector.length) {  
       errorSelector.removeClass('field-validation-valid').addClass('field-validation-error');  
       errorSelector.text(error.text());  
     }  
     else {  
       $(element)  
         .parent().after(  
         `<div id='${$(element).attr('name')}Holder' class="field-validation-error"></div>`);  
       $(`#${(element).attr('name')}Holder`).append(error);  
     }  
   }  
   ParseModelStateError(data: JQuery.jqXHR<any>, eachErrorCallback: (message: string, propName: string) => void) {  
     if (data == undefined || data.responseJSON == undefined) {  
       return;  
     }  
     var message = '';  
     var propStrings = Object.keys(data.responseJSON);  
     $.each(propStrings, (errIndex, propString) => {  
       var propErrors = data.responseJSON[propString];  
       $.each(propErrors, (errMsgIndex, propError) => {  
         message += propError;  
       });  
       message += '\n';  
       eachErrorCallback(message, propString);  
       message = '';  
     });  
   }  
   private RemoteValidation(elementName: string,  
     form: JQuery<HTMLFormElement> | JQuery<HTMLElement>) {  
     let getData = () => {  
       let serializedData = {};  
       $(form).serializeArray()  
         .map((key) => { serializedData[key.name] = key.value; });  
       return JSON.stringify(serializedData);  
     }  
     let remote: JQueryAjaxSettings = {  
       url: `${form.attr('action')}?validate=${elementName}`,  
       type: "post",  
       async: true,  
       contentType: 'application/json',  
       beforeSend: (xhr, setting) => {  
         setting.data = getData();  
       },  
       dataFilter: (response) => {  
         if ($.parseJSON(response) == true) {  
           return response;  
         }  
         var data = $.parseJSON(response);  
         var validator: any = $(form).validate();  
         validator.invalid[elementName] = data[elementName] != undefined;  
         return JSON.stringify(data[elementName] != undefined ? data[elementName] : true);  
       }  
     };  
     return remote;  
   }  
   RegisterJqueryValidation(formSelector: string) {  
     let form = $(formSelector);  
     $.ajax({  
       url: form.attr('action') + '?validation=jquery',  
       data: '{}',  
       contentType: 'application/json',  
       method: 'post',  
       success: (validationRule: JQueryValidation.ValidationOptions) => {  
         if (validationRule != undefined && validationRule.rules != undefined) {  
           validationRule.errorPlacement = (error, element) =>  
             this.AppendError(form, error, element);  
           validationRule.success = (label, input) =>  
             label.empty();  
           $.each(validationRule.rules, (prop: any) => {  
             if (validationRule.rules[prop].hasOwnProperty('remote')) {  
               validationRule.rules[prop].remote = () => this.RemoteValidation(prop, form);  
             }  
           });  
           $.validator.setDefaults({ ignore: '' });  
           var validator = form.validate(validationRule);  
         }  
       }  
     });  
   }  
 }  
 export { FormValidation }  

Usage

Form.ts was explained in the earlier blog post, but please refer to Form.ts in case of any confusion. The function RegisterJqueryValidation would take care of everything.

 this.Validation.RegisterJqueryValidation(this.ContactViewOption.FormSelector);  
 this.EventHelper.RegisterClickEvent(  
   this.ContactViewOption.SubmitButtonSelector,  
   (evt, selector) => {  
     this.FormHelper.SubmitForm({  
       Source: {  
         ButtonEvent: evt  
       },  
       OnPostSuccessResult: (data) => {  
         console.log('Submitted successfully.');  
       }  
     });  
   });  




Comments

Post a Comment

Popular posts from this blog

Elegantly dealing with TimeZones in MVC Core / WebApi

In any new application handling TimeZone/DateTime is mostly least priority and generally, if someone is concerned then it would be handled by using DateTime.UtcNow on codes while creating current dates and converting incoming Date to UTC to save on servers. Basically, the process is followed by saving DateTime to UTC format in a database and keep converting data to native format based on user region or single region in the application's presentation layer. The above is tedious work and have to be followed religiously. If any developer misses out the manual conversion, then that area of code/view would not work. With newer frameworks, there are flexible ways to deal/intercept incoming or outgoing calls to simplify conversion of TimeZones. These are steps/process to achieve it. 1. Central code for storing user's state about TimeZone. Also, central code for conversion logic based on TimeZones. 2. Dependency injection for the above class to be able to use global

Handling JSON DateTime format on Asp.Net Core

This is a very simple trick to handle JSON date format on AspNet Core by global settings. This can be applicable for the older version as well. In a newer version by default, .Net depends upon Newtonsoft to process any JSON data. Newtonsoft depends upon Newtonsoft.Json.Converters.IsoDateTimeConverter class for processing date which in turns adds timezone for JSON data format. There is a global setting available for same that can be adjusted according to requirement. So, for example, we want to set default formatting to US format, we just need this code. services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.DateTimeZoneHandling = "MM/dd/yyyy HH:mm:ss"; });

Making FluentValidation compatible with Swagger including Enum or fixed List support

FluentValidation is not directly compatible with Swagger API to validate models. But they do provide an interface through which we can compose Swagger validation manually. That means we look under FluentValidation validators and compose Swagger validator properties to make it compatible. More of all mapping by reading information from FluentValidation and setting it to Swagger Model Schema. These can be done on any custom validation from FluentValidation too just that proper schema property has to be available from Swagger. Custom validation from Enum/List values on FluentValidation using FluentValidation.Validators; using System.Collections.Generic; using System.Linq; using static System.String; /// <summary> /// Validator as per list of items. /// </summary> /// <seealso cref="PropertyValidator" /> public class FixedListValidator : PropertyValidator { /// <summary> /// Gets the valid items /// <

Kendo MVC Grid DataSourceRequest with AutoMapper

Kendo Grid does not work directly with AutoMapper but could be managed by simple trick using mapping through ToDataSourceResult. The solution works fine until different filters are applied. The problems occurs because passed filters refer to view model properties where as database model properties are required after AutoMapper is implemented. So, the plan is to intercept DataSourceRequest  and modify names based on database model. To do that we are going to create implementation of  CustomModelBinderAttribute to catch calls and have our own implementation of DataSourceRequestAttribute from Kendo MVC. I will be using same source code from Kendo but will replace column names for different criteria for sort, filters, group etc. Let's first look into how that will be implemented. public ActionResult GetRoles([MyDataSourceRequest(GridId.RolesUserGrid)] DataSourceRequest request) { if (request == null) { throw new ArgumentNullExce

LDAP with ASP.Net Identity Core in MVC with project.json

Lightweight Directory Access Protocol (LDAP), the name itself explain it. An application protocol used over an IP network to access the distributed directory information service. The first and foremost thing is to add references for consuming LDAP. This has to be done by adding reference from Global Assembly Cache (GAC) into project.json "frameworks": { "net461": { "frameworkAssemblies": { "System.DirectoryServices": "4.0.0.0", "System.DirectoryServices.AccountManagement": "4.0.0.0" } } }, These  System.DirectoryServices  and  System.DirectoryServices.AccountManagement  references are used to consume LDAP functionality. It is always better to have an abstraction for irrelevant items in consuming part. For an example, the application does not need to know about PrincipalContext or any other dependent items from those two references to make it extensible. So, we can begin wi

Using Redis distributed cache in dotnet core with helper extension methods

Redis cache is out process cache provider for a distributed environment. It is popular in Azure Cloud solution, but it also has a standalone application to operate upon in case of small enterprises application. How to install Redis Cache on a local machine? Redis can be used as a local cache server too on our local machines. At first install, Chocolatey https://chocolatey.org/ , to make installation of Redis easy. Also, the version under Chocolatey supports more commands and compatible with Official Cache package from Microsoft. After Chocolatey installation hit choco install redis-64 . Once the installation is done, we can start the server by running redis-server . Distributed Cache package and registration dotnet core provides IDistributedCache interface which can be overrided with our own implementation. That is one of the beauties of dotnet core, having DI implementation at heart of framework. There is already nuget package available to override IDistributedCache i

Data seed for the application with EF, MongoDB or any other ORM.

Most of ORMs has moved to Code first approach where everything is derived/initialized from codes rather than DB side. In this situation, it is better to set data through codes only. We would be looking through simple technique where we would be Seeding data through Codes. I would be using UnitOfWork and Repository pattern for implementing Data Seeding technique. This can be applied to any data source MongoDB, EF, or any other ORM or DB. Things we would be doing. - Creating a base class for easy usage. - Interface for Seed function for any future enhancements. - Individual seed classes. - Configuration to call all seeds. - AspNet core configuration to Seed data through Seed configuration. Creating a base class for easy usage public abstract class BaseSeed<TModel> where TModel : class { protected readonly IMyProjectUnitOfWork MyProjectUnitOfWork; public BaseSeed(IMyProjectUnitOfWork MyProjectUnitOfWork) { MyProject

Trim text in MVC Core through Model Binder

Trimming text can be done on client side codes, but I believe it is most suitable on MVC Model Binder since it would be at one place on infrastructure level which would be free from any manual intervention of developer. This would allow every post request to be processed and converted to a trimmed string. Let us start by creating Model binder using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Threading.Tasks; public class TrimmingModelBinder : IModelBinder { private readonly IModelBinder FallbackBinder; public TrimmingModelBinder(IModelBinder fallbackBinder) { FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder)); } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var valueProviderResult = bindingContext.ValueProvider.GetValue(bin

Storing and restoring Kendo Grid state from Database

There is no any built in way to store entire grid state into database and restore back again with all filters, groups, aggregates, page and page size. At first, I was trying to restore only filters by looking through DataSourceRequest. DataSourceRequest is kind of communication medium between client and server for the operation we do on grid. All the request comes via DataSourceRequest. In previous approach, I was trying to store IFileDescriptor interface which come with class FileDescriptor by looping through filters and serializing into string for saving into database but this IFileDescriptor can also contain CompositeFilterDescriptor which can be nested in nested object which are very tricky to handle. So, I had decompiled entire Kendo.MVC library and found out that all Kendo MVC controls are derived from “JsonObject”. It is there own implementation with ”Serialize” abstract function and “ToJson” function. In controls they are overriding “Serialize” method which depicts t

A wrapper implementation for Kendo Grid usage

A wrapper implementation for any heavily used item is always a good practice. Whatever is not written by us and used at a lot of places should be wrapped within specific functionality to keep it future proof and easily changeable. This also encourages DRY principle to keep our common setting at a central place. Kendo UI items are enormous in configuration, one of an issue I find people keep repeating codes for Kendo Grid configuration. They have built very flexible system to have any configuration, but in most of the cases, we do not need all of those complicated configuration. We would try to see a simpler configuration of same. The actual core implementation is bit complex, but we do not have to bother about it once done since the focus is just on usage only. I recommend doing this practice for as simple as jQuery events, form handling or as simple as any notification system. This just won't make things simple but makes codes much more manageable, easy understand, read or open f