Modern Web Application Layered High Level Architecture with SPA, MVC, Web API, EF, Kendo UI, OData

This will be part one of a six part series of blog posts.

  1. Modern Web Application Layered High Level Architecture with SPA, MVC, Web API, EF, Kendo UI, OData
  2. Generically Implementing the Unit of Work & Repository Pattern with Entity Framework in MVC & Simplifying Entity Graphs
  3. MVC 4, Kendo UI, SPA with Layout, View, Router & MVVM
  4. MVC 4, Web API, OData, EF, Kendo UI, Grid, Datasource (CRUD) with MVVM
  5. MVC 4, Web API, OData, EF, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM
  6. Upgrading to Async with Entity Framework, MVC, OData AsyncEntitySetController, Kendo UI, Glimpse & Generic Unit of Work Repository Framework v2.0

Update: 07/22/2013 – Added blog series on actual implementation steps, for this architecture with patterns towards the end of this blog post.

Search and searched and seems difficult to locate any comprehensive top down, full stack architecture or high level design diagrams for modern (SPA) web apps. It’s probably important you have at least a high level picture what this architecture looks like now that there is quite a bit more design work involved on the client side especially with more and more implementations are around SPA and patterns like MVVM; so hence this post. Obviously there is no such thing as one size fits all especially when it comes to architecture, so feel free to omit or add to the architecture based on your specific needs.

Modern Web Application Logical and Physical Architecture High Level Design with SPA

Client Layer (HTML5 Browser)
Model View ViewModel (MVVM) is a design pattern which helps developers separate the Model (the data) from the View (the UI). The View-Model part of MVVM is responsible for exposing the data objects from the Model in such a way that those objects are easily consumed in the View. Kendo MVVM is an implementation of the MVVM pattern which seamlessly integrates with the rest of the Kendo framework (widgets and DataSource).

Web Layer (Server)
Almost the entire ASP.NET MVC Web Layer can leverage the DI & IoC Pattern, you can read up on what the benefits are and how to do this download both a sample MVC app that uses MEF or Unity 3 from one of my previous post.

  • Presentation Layer
    For modern MVC web applications, the presentation layer (server-side) consists of a Controllers who’s only tasks are to render an HTML page, css, Javascript, HTML templates, images, etc. Very little server-side code, if any, is responsible for any UI rendering responsibilities. Once the page is rendered in the browser client-side components (the browser or user agent that executes scripts and displays the HTML). With client-side techniques such as AJAX and with rich client-side frameworks such as Kendo UI Web, it is possible to execute logic on the client, for nice fluid user experiences. Implementing a SPA, can greatly increase the user experience by, reducing or eliminating post backs and refreshes.

  • Business Layer
    When designing the business layer for your Web application, consider how to implement the business logic and long-running workflows. Using a separate business layer that implements the business logic and workflows can improve the maintainability and testability of your application, and allow you to centralize and reuse common business logic functions.

  • Data Layer
    Consider designing a data layer for your Web application that abstracts the logic necessary to access the database. This can be achieved with implementing the Repository pattern, the Repository pattern is often implemented with the Unit of Work pattern. Entity Framework already implements the Unit of Work Pattern with the DbContext, however you should always work an abstraction of this, you can read up on one of previous post on how to do this. Using a separate data layer makes the application easier to configure and maintain, and hides the details of the database from other layers of the application.

    Your business entities, usually shared between the layers of your application e.g. Business and Data Layer should be POCO entities. Entity Framework enables you to use custom data classes together with your data model without making any modifications to the data classes themselves. This means that you can use “plain-old” CLR objects (POCO), such as existing domain objects, with your data model. These POCO data classes (also known as persistence-ignorant objects), which are mapped to entities that are defined in a data model, support most of the same query, insert, update, and delete behaviors as entity types that are generated by the Entity Data Model tools.

Services Layer
Consider designing a separate service layer if you plan to deploy your business layer on a remote tier, or if you plan to expose your business logic using a Web service. Design the services to achieve maximum reusability by not assuming the specific details of clients that will use them, and avoid changes over time that might break the service interface for existing clients. Instead, implement versions of the interface to allow clients to connect to the appropriate version.

Download PNG Version
Download PDF Version

I’ve posted a three part blog series, that covers the actual implementation of most of this architecture and patterns used:

Part 1 – MVC 4, Kendo UI, SPA with Layout, View, Router & MVVM
Part 2 – MVC 4, Web API, OData, EF, Kendo UI, Grid, Datasource (CRUD) with MVVM
Part 3 – MVC 4, Web API, OData, EF, Kendo UI, Binding a Form to Datasource (CRUD) with MVVM

Happy Architecting…!

Learning Kendo UI Web Development with MVC Book Published…!

Follow up on one of my previous blog post in regards to Kendo UI Web with MVC book project I had the opportunity to be involved with as a Technical Reviewer, the book is complete and has been published..! Kudos to John Adams (author) from RBA for all the late nights, weekends and hard work I know he put into the project.

For those of you looking for a good starting point, incorporating Kendo UI Web into your MVC project or solution the book has some good real world practical implementations with ASP.NET MVC.

Personally and professionally Kendo UI Web is my client side framework of choice. It’s one solution that offers it all, so that you don’t have into incorporate and include several different frameworks to gain some of the fundamental features of Kendo UI Web, especially when working with MVC.

My personal top favorite features of Kendo UI Web Framework

  • Widgets (client-side controls)
  • MVVM Framework (a must have for modern web apps)
  • Validation Framework
  • Templates
  • Built on top of jQuery
  • Custom UI/UX Themes
  • MVC 4 Sever Side Wrappers
  • SPA (Single Page App) Support (a must have for modern web apps)
  • Globalization
  • DataSource
  • List goes on…

http://www.amazon.com/dp/1849694346/?tag=packtpubli-20
http://www.packtpub.com/learning-kendo-ui-web-development/book
http://packtlib.packtpub.com/library/9781849694346

An easy-to-follow practical tutorial to add exciting features to your web pages without being a JavaScript expert with this book and ebook

Overview

Learn from clear and specific examples on how to utilize the full range of the Kendo UI tool set for the web
Add powerful tools to your website supported by a familiar and trusted name in innovative technology
Learn how to add amazing features with clear examples and make your website more interactive without being a JavaScript expert

In Detail

Creating useful and attractive web sites for today’s audiences requires more JavaScript programming than ever before. JavaScript, however, isn’t easy and staying up to date with recent trends makes it even harder. You need a way to integrate the latest technology into your web site without having to start from the beginning.

“Learning Kendo UI Web Development” shows you how to add the latest web features to your site without doing all of the work yourself. The experts at Telerik have created a fully supported code library that lets you focus on what makes your sites special while letting them focus on how that translates into cutting edge JavaScript code.

This book will take you on a survey through Kendo UI for the web from the basic widgets that wrap input fields to the full page scaffolding widgets for setting up your site.

Kendo UI for the web is a rich framework of JavaScript widgets and tools that will add immediate value to your web sites. Learn the full spectrum of what Kendo UI has to offer in specialized widgets that gather input from users by displaying calendars, sliders, or date pickers all the way up to widgets that structure your web pages through data-driven models in accordions, tabs, list views, and tree views.

“Learning Kendo UI Web Development” is the perfect companion for navigating the broad features offered by Telerik in JavaScript web development.

What you will learn from this book

  • Leverage data source objects and JavaScript templates with the TabStrip and Grid widgets
  • Guide users in date selection with the Calendar widget
  • Create fast and fluid word wheels with the AutoComplete widget and date selection with the Calendar widget
  • Take advantage of the powerful MVVM JavaScript architectural pattern
  • Take full HTML input from users with a graphical editor
  • Structure your site with the Menu and ListView widgets
  • Build interactive accordions with the PanelBar widget and a fun way to select numbers with the Slider widget
  • Organize your data with the Splitter and TreeView widgets and Customize pop-up windows and file uploads with the Window and Upload widgets

Approach

A practical tutorial with step-by-step example based approach.

Who this book is written for

This book is for web developers who want to take advantage of cutting edge JavaScript and HTML 5 web site features, but who don’t have the time or the knowledge to write all of that code by hand. The reader should be familiar with basic HTML 5 and JavaScript but does not need to be an expert.

Author:

John Adams currently works as an application development consultant in the Dallas/Fort Worth area for a fantastic company called RBA. He has been developing custom business applications with the Microsoft .NET platform for 6 years and has specialized in development with ASP.NET MVC. He loves writing code and creating solutions. Above all, he loves his wife and children and the lord Jesus Christ.

This book is dedicated to Michell, Samuel, and Sophie whose patience with my late nights made this project possible.

I would also like to thank RBA, especially my manager Will, who introduced me to the project and kicked everything off.

Finally, I would like to thank Kartikey Pandey, Anugya Khurana, Mayur Hule, Ricardo Covo, and Long Le for their oversight and editing skills. Their work has been exceptional and valuable throughout.

Reviewers:

Ricardo Covo has more than a decade of international experience in the Software Development field, with experience in Latin America, California, and Canada. He has a wealth of experience in delivering data-driven enterprise solutions across various industries.

With a Bachelor’s degree in Systems Engineering, complemented with a certification in Advanced Project Management, he has the right combination of technical and leadership skills to build development teams and set them up for efficient execution.

In 2007 he founded (and is the principal of) Web Nodes – Software Development (http://webnodes.ca); a custom software development company, with clients big and small in Canada, United States, and South America.

Prior to Web Nodes, Ricardo spent some years in the corporate world both in Canada and in the U.S., being part of companies such as Loblaws Inc., Trader Corporation, UNX (http://www.unix.com) and Auctiva (http://www.auctiva.com).

Ricardo’s passion for technology goes beyond work; he normally works on personal projects in an effort to always remain on top of the changes in technology. These projects include: http://ytnext.com, http://serversok.com, and http://toystrunk.com.

Long Le is a Principle .NET Architect and ALM Practitioner at CBRE. He also serves as consultant for Thinklabs and spends most of his time developing frameworks and application blocks, providing guidance for best practices and patterns, and standardizing the enterprise technology stack. He has been working with Microsoft technologies for over 10 years.

Le has focused on a wide spectrum of server-side and web technologies, such as ASP.NET Web Forms, ASP.NET MVC, Windows Workflow, LINQ and Entity Framework, DevExpress, and Kendo UI. In his spare time, he enjoys blogging (http://blog.longle.net) and playing Call of Duty on his XBOX. He’s recently became a proud father of his new born daughter Khloe Le. You can reach and follow him on Twitter @LeLong37.

Special thanks to my significant other Tina Le for all your love and support throughout this project and to my wonderful newborn daughter Khloe Le. I love you.

https://twitter.com/LeLong37/status/344402714240897024

Received my Kendo UI T-Shirt and new Nokia Lumia 920 Windows Phone 8 Today!

What an awesome Thursday, received my new Kendo UI t-shirt and my new AT&T Nokia Lumia 920 Windows Phone 8 package all in the same day…! Is this the best Thursday or what…?! 🙂

Time to retire my HTC Titan I Windows 7 Phone, sayonara… If time permits I may due a blog review on the phone, so far it’s the sexiest thing ever, and I must say first impression the screen display does look better than my colleague’s iPhone 5.

Thanks Kendo UI for dev appreciation and shirt, and to AT&T for shipping the phone overnight and getting it to me a day before the official release.

Image

 

Telerik’s HTML5 Kendo UI Web Validator Framework

I recently wrote a blog on Telerik’s Kendo UI Web MVVM Framework. This blog post was intended to illustrate the power of MVVM binding on the client side (declaritive binding).

In this blog post I wanted to demonstrate the ability to do some nice validation with the Kendo UI Web Validator Framework which also let’ you do a quite bit of validtion declaritively. With that beind said let’s get right into it using the MVVM form we created from the previous post.

Let’s take a quick at where we left off at on our last form.


    <div id="formContainer">
        <h1>
            My First MVVM Web View!</h1>
        <form id="myForm" action="">
        <label>
            First Name</label>
        <input type="text" data-bind="value: firstName, disabled:  isDisabled" />
        <label>
            Last Name</label>
        <input type="text" data-bind="value: lastName, disabled:  isDisabled" />
        <label>
            Email</label>
        <input type="text" data-bind="value: email, disabled:  isDisabled" />
        <label>
            Twitter</label>
        <input type="text" data-bind="value: twitter, disabled:  isDisabled" />
        <label>
            Site</label>
        <input type="text" data-bind="value: site, disabled:  isDisabled" />
        <label>
            Address</label>
        <input type="text" data-bind="value: address, disabled:  isDisabled" />
        <label>
            City</label>
        <input type="text" data-bind="value: city, disabled:  isDisabled" />
        <label>
            State</label>
        <input type="text" data-bind="value: state, disabled:  isDisabled" />
        <label>
            Zip</label>
        <input type="text" data-bind="value: zip, disabled:  isDisabled" />
        <label>
            Occupation</label>
        <select data-bind="source: occupations, value: occupation, disabled:  isDisabled">
        </select>
        <br />
        <br />
        <input type="button" value="Load" data-bind="click: load" />
        <input type="button" value="Edit" data-bind="click: edit" />
        <input type="button" value="Cancel" data-bind="click: cancel" />
        <input type="button" value="Reset" data-bind="click: reset" />
        </form>
    </div>

Now let’s add a couple of extra fields and declaritively add some validation to our form. While doing this let’s go ahead and change the types to our input controls with the appropriate HTML5 (http://www.w3schools.com/html5/html5_form_attributes.asp) types e.g. date, number, email, etc.. Quick note, notice that we are using the HTML5 [pattern] attribute for the input field named “Phone”.


    <div id="formContainer">
        <h1>
            My First MVVM Web View <br />
            now with Validation!</h1>
        <form id="myForm" action="">
        <label>
            First Name</label>
        <input type="text" name="firstName" data-bind="value: firstName, disabled:  isDisabled" required validationMessage="please enter a first name" />
        <label>
            Last Name</label>
        <input type="text" name="lastName" data-bind="value: lastName, disabled:  isDisabled" required validationMessage="please enter a last name" />
        <label>
            Email</label>
        <input type="email" name="email" data-bind="value: email, disabled:  isDisabled" required />
        <label>
            Twitter</label>
        <input type="url" name="twitter" data-bind="value: twitter, disabled:  isDisabled" required data-required-msg="please enter a {0}" data-url-msg="please enter a valid url" />
        <label>
            Site</label>
        <input type="url" name="site" data-bind="value: site, disabled:  isDisabled" required validationMessage="please enter a {0}" data-url-msg="please enter a valid url"/>
        <label>
            Address</label>
        <input type="text" name="address" data-bind="value: address, disabled:  isDisabled" required validationMessage="please enter a {0}" />
        <label>
            City</label>
        <input type="text" name="city" data-bind="value: city, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            State</label>
        <input type="text" name="state" data-bind="value: state, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            Zip</label>
        <input type="text" name="zip" data-bind="value: zip, disabled:  isDisabled" required validationMessage="please enter a {0}"/>
        <label>
            Phone</label>
        <input type="tel" name="phone" pattern="\d{10}"  data-bind="value: phone, disabled:  isDisabled" required data-required-msg="please enter a {0} number" data-pattern-msg="please enter a 10 digit phone number"/>
        <label>
            Lucky Number</label>
        <input type="number" name="luckynumber" data-bind="value: luckyNumber, disabled:  isDisabled" required data-required-msg="please enter a lucky number" data-max-msg="please enter a number less than 100" min="1" max="100" />
        <label>
            Birth Date</label>
        <input type="date" name="birthdate" data-bind="value: birthDate, disabled:  isDisabled" required validationMessage="please enter a {0}" />
        <label>
            Occupation</label>
        <select name="occupation" data-bind="source: occupations, value: occupation, disabled:  isDisabled" required valdationMessage="please select a {0}">
        </select>
        <br />
        <br />
        <input type="button" value="Load" data-bind="click: load" />
        <input type="button" value="Edit" data-bind="click: edit" />
        <input type="button" value="Cancel" data-bind="click: cancel" />
        <input type="button" value="Reset" data-bind="click: reset" />
        <button type="submit" value="Submit">Save</button>
        </form>
    </div>

Notice that all we are doing here is really providing the Validator Framework with is validation messages for the type of validation. There is a convention that we need to follow which is marking the control with the attribute: data-[rule]-msg, [rule] just needs to be replaced with actual rule name so for example data-required-msg=”please enter a luckly number” will be the message that is displayed when there is no number in the input field and data-max-message=”please enter a number less than 100″ will be the validation message that is shown if a user enters a number that is greater than 100. Now that all of our validation messages are setup let’s add our one line of script to get this all of our validations wired up (line 45).


    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: "Long",
            lastName: "Le",
            email: "lelong37@gmail.com",
            twitter: "twitter.com/lelong37",
            site: "blog.longle.net",
            address: "3737 Galatic Avenue",
            city: "Cloud City",
            state: "Texas",
            occupations: ["", "Hacker", "Jedi", "Ninja"],
            occupation: "Jedi",
            phone: "1111111111",
            luckyNumber: 34,
            birthDate: null,
            isSaved: false,
            isDisabled: true,
            edit: function (e) {
                this.set("isDisabled", false);
            },
            cancel: function (e) {
                this.set("isDisabled", true);
            },
            reset: function (e) {
                this.set("firstName", null);
                this.set("lastName", null);
                this.set("email", null);
                this.set("twitter", null);
                this.set("site", null);
                this.set("address", null);
                this.set("city", null);
                this.set("state", null);
                this.set("zip", null);
                this.set("occupation", "");
                this.set("phone", null);
                this.set("luckyNumber", null);
            },
            load: function (e) {
                LoadJohnDoesInfo();
            }
        });

        kendo.bind($("#myForm"), myViewModel);
        var validator = $("#myForm").kendoValidator().data("kendoValidator");
    });

Now with our declaritive validation and one line of Javascript to wire everything up let’s give our form a spin. Let’s clear out our form by and try saving by clicking [Edit], [Reset] and [Save].

Let’s test out our messsages that are custom to a specific validation type by typing in an invalid Url for our Twitter, Site and invalid email address when filling out our form and clicking [Save].

Notice how are validation messages are not the displaying the standard required messages but now they are validation messages specific to the type of validation. For example with the field “Lucky Number” the standard validation message was “please enter a lucky number” now that we actually typed in a value that is greater than the max attribute that was set to 100 we are getting the declartive validation message “please enter a number less than 100”. Also for the field [email] we also have typed in something however because we changed the input type to email (new with HTML5) out of the box the framework is displaying a standard message for us “email is not a valid email” even though we did not explicitly declare a validation message for email.

Now let’s fix all the fields with the correct values email, twitter, site, phone and lucky number and notice as we tab out of each field our validation messages dissapear and we are able to post the form.

Last but not least we can manually invoke the Validator by invoking the validate() method, let’s go ahead and rewire our click button to go ahead and validate our form as well as display a message to the user.


        $("button").click(function (e) {
            e.preventDefault();
            if (validator.validate()) {
                alert("Your form is golden!");
            } else {
                alert("Your form has errors!");
            }
        });

Let’s give this a try.

Happy Coding! 🙂

Download:

Telerik’s Kendo UI Web MVVM Framework Rocks!

For those of us that fell in love with the awesome binding power that WPF and Silverlight brought to the masses with MVVM (Model-ViewModel-Model), well I have good news for you folks. Telerik has their Kendo UI Web MVVM Framework so that you can leverage MVVM in your (MVC) web apps!

Quick synopsis on MVVM for those of us that are new to it (especially MVC devs) and in the interest of time I’ll try to do this in a 60 second nutshell. So when working with a view whether it be in WPF, Silverlight, and now MVC, you can declaritively set which controls bind to which properties to your ViewModel (simply a JSON object with properties). So for example I can set a DropDownList (select) control on my view to bind directly to a property of my ViewModel that is a collection.

So let’s get right to it with a few simple examples.

First add a script references for jQuery and Kendo UI, here are some links for Microsoft’s and Teleriks’ CDN’s for these scripts.


&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Kendo UI Web MVVM&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;script src=&quot;http://code.jquery.com/jquery-1.7.1.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;http://cdn.kendostatic.com/2012.2.710/js/kendo.all.min.js&quot;&gt;&lt;/script&gt;
&lt;/html&gt;

Now let’s create a simple form.


&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Kendo UI Web MVVM&lt;/title&gt;
    &lt;script src=&quot;http://code.jquery.com/jquery-1.7.1.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;http://cdn.kendostatic.com/2012.2.710/js/kendo.all.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;style type=&quot;text/css&quot;&gt;
        body
        {
            font-family: Arial;
        }
        label
        {
            display: block;
            margin-top: 10px;
        }
        #formContainer
        {
            background-color: #F2F7F9;
            width: 400px;
            padding: 20px;
            margin: 50px auto;
            border: 6px solid #8FB5C1;
            border-radius: 15px;
            position: relative;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;formContainer&quot;&gt;
        &lt;h1&gt;My First MVVM Web View!&lt;/h1&gt;
        &lt;form id=&quot;myForm&quot; action=&quot;&quot;&gt;
        &lt;label&gt;
            First Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Last Name&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Email&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Twitter&lt;/label&gt;
        &lt;input type=&quot;text&quot;  /&gt;
        &lt;label&gt;
            Site&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Address&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            City&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            State&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Zip&lt;/label&gt;
        &lt;input type=&quot;text&quot; /&gt;
        &lt;label&gt;
            Occupation&lt;/label&gt;
        &lt;select&gt;
        &lt;/select&gt;
        &lt;br /&gt;
        &lt;br /&gt;
        &lt;input type=&quot;button&quot; /&gt;
        &lt;input type=&quot;button&quot; /&gt;
        &lt;input type=&quot;button&quot; /&gt;
         &lt;input type=&quot;button&quot; /&gt;
        &lt;/form&gt;
    &lt;/div&gt;
&lt;/body&gt;

&lt;/html&gt;

Now let’s create a ViewModel (JavaScript JSON object or class if you will) with some default values for our View (in this case our form) to bind to. We will also add some methods to our ViewModel so that we can bind our buttons that are on our view too.


  &lt;script language=&quot;javascript&quot; type=&quot;text/javascript&quot;&gt;

    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: &quot;Long&quot;,
            lastName: &quot;Le&quot;,
            email: &quot;lelong37@gmail.com&quot;,
            twitter: &quot;twitter.com/lelong37&quot;,
            site: &quot;blog.longle.net&quot;,
            address: &quot;3737 Galatic Avenue&quot;,
            city: &quot;Cloud City&quot;,
            state: &quot;Texas&quot;,
            occupations: [&quot;Please Select&quot;, &quot;Hacker&quot;, &quot;Jedi&quot;, &quot;Ninja&quot;],
            occupation: &quot;Jedi&quot;,
        });

        kendo.bind($(&quot;#myForm&quot;), myViewModel);
    });

&lt;/script&gt;

Note: Line 18 is what will bind our form to our ViewModel, if your wondering why nothing is happening yet it’s because we are missing once more step which is to provide the binding (mapping) information, which will map our controls to our ViewModel. The beauty here is we will do this declaritively with without code.

Our View will have two modes: read-only and edit, so we will bind our controls for where each control will get and set it’s values to and another binding for enabling and disabling them.


    &lt;div id=&quot;formContainer&quot;&gt;
        &lt;h1&gt;My First MVVM Web View!&lt;/h1&gt;
        &lt;form id=&quot;myForm&quot; action=&quot;&quot;&gt;
        &lt;label&gt;
            First Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: firstName, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Last Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: lastName, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Email&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: email, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Twitter&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: twitter, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Site&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: site, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Address&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: address, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            City&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: city, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            State&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: state, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Zip&lt;/label&gt;
        &lt;input type=&quot;text&quot; data-bind=&quot;value: zip, disabled:  isDisabled&quot; /&gt;
        &lt;label&gt;
            Occupation&lt;/label&gt;
        &lt;select data-bind=&quot;source: occupations, value: occupation, disabled:  isDisabled&quot;&gt;
        &lt;/select&gt;
        &lt;br /&gt;
        &lt;br /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Load&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Edit&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Cancel&quot; /&gt;
         &lt;input type=&quot;button&quot; value=&quot;Reset&quot; /&gt;
        &lt;/form&gt;
    &lt;/div&gt;

Now if we refresh the page we that our View is not bound to our ViewModel…!

Note: Notice that our form is in read-only mode, all of our controls are disabled for editing because the disabled attribute is bound to the isDisabled property in our ViewModel whose default value is set to true.

For those of us that are ASP.NET MVC developers, the answer is yes you can get and post your ViewModel back as JSON using jQuery from and to an actions on your controller!

Now let’s demonstrate binding our buttons to methods on our ViewModel, first let’s add a couple of methods to our ViewModel (edit, cancel, reset, and load)

  • edit, will enabled our View for editing
  • cancel, will set our View back to read-only mode
  • reset, will clear out our View
  • load, will load John Doe’s information into our View (obviously here you could load this from using a “GET” to an action off of your controller)

    var myViewModel;
    $(document).ready(function () {
        myViewModel = kendo.observable({
            firstName: &quot;Long&quot;,
            lastName: &quot;Le&quot;,
            email: &quot;lelong37@gmail.com&quot;,
            twitter: &quot;twitter.com/lelong37&quot;,
            site: &quot;blog.longle.net&quot;,
            address: &quot;3737 Galatic Avenue&quot;,
            city: &quot;Cloud City&quot;,
            state: &quot;Texas&quot;,
            occupations: [&quot;Please Select&quot;, &quot;Hacker&quot;, &quot;Jedi&quot;, &quot;Ninja&quot;],
            occupation: &quot;Jedi&quot;,
            isSaved: false,
            isDisabled: true,
            edit: function (e) {
                this.set(&quot;isDisabled&quot;, false);
            },
            cancel: function (e) {
                this.set(&quot;isDisabled&quot;, true);
            },
            reset: function (e) {
                this.set(&quot;firstName&quot;, null);
                this.set(&quot;lastName&quot;, null);
                this.set(&quot;email&quot;, null);
                this.set(&quot;twitter&quot;, null);
                this.set(&quot;site&quot;, null);
                this.set(&quot;address&quot;, null);
                this.set(&quot;city&quot;, null);
                this.set(&quot;state&quot;, null);
                this.set(&quot;zip&quot;, null);
                this.set(&quot;occupation&quot;, &quot;Please Select&quot;);
            },
            load: function (e) {
                LoadJohnDoesInfo();
            }
        });

        kendo.bind($(&quot;#myForm&quot;), myViewModel);
    });

    function LoadJohnDoesInfo() {
        myViewModel.set(&quot;firstName&quot;, &quot;John&quot;);
        myViewModel.set(&quot;lastName&quot;, &quot;Doe&quot;);
        myViewModel.set(&quot;email&quot;, &quot;jdoe@skyranch.com&quot;);
        myViewModel.set(&quot;twitter&quot;, &quot;twitter.com/jedi&quot;);
        myViewModel.set(&quot;site&quot;, &quot;starwars.com&quot;);
        myViewModel.set(&quot;address&quot;,  &quot;1212 SkyRanch&quot;);
        myViewModel.set(&quot;state&quot;, &quot;California&quot;);
        myViewModel.set(&quot;zip&quot;, &quot;98000&quot;);
        myViewModel.set(&quot;occupation&quot;, &quot;Jedi&quot;);
    }

Add our binding meta-data to our buttons declaritively.


        &lt;input type=&quot;button&quot; value=&quot;Load&quot; data-bind=&quot;click: load&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Edit&quot; data-bind=&quot;click: edit&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Cancel&quot; data-bind=&quot;click: cancel&quot; /&gt;
        &lt;input type=&quot;button&quot; value=&quot;Reset&quot; data-bind=&quot;click: reset&quot; /&gt;


So let’s invoke some our methods on our ViewModel and give our button’s a run for their money.

Now let’s click on the [Load] button and here I just wanted to demonstrating that all we need to do here to update the View is interact with the ViewModel, any changes to the ViewModel and the View automatically updates because they are bound together, which is the magic and essense of the MVVM pattern!

Our load method on our ViewModel.


            load: function (e) {
                LoadJohnDoesInfo();
            }

Which turns around and invokes our LoadJohnDoesInfo method and set’s his data on our ViewModel.


    function LoadJohnDoesInfo() {
        myViewModel.set(&quot;firstName&quot;, &quot;John&quot;);
        myViewModel.set(&quot;lastName&quot;, &quot;Doe&quot;);
        myViewModel.set(&quot;email&quot;, &quot;jdoe@skyranch.com&quot;);
        myViewModel.set(&quot;twitter&quot;, &quot;twitter.com/jedi&quot;);
        myViewModel.set(&quot;site&quot;, &quot;starwars.com&quot;);
        myViewModel.set(&quot;address&quot;, &quot;1212 SkyRanch&quot;);
        myViewModel.set(&quot;state&quot;, &quot;California&quot;);
        myViewModel.set(&quot;zip&quot;, &quot;98000&quot;);
        myViewModel.set(&quot;occupation&quot;, &quot;Jedi&quot;);
    }

Notice when the View first loads our entire form is disabled for our read-only mode and when we click [Edit] all of our controls that had the binding for disabled that was bound to the isDisabled property on our ViewModel which be default is set to true. Once we click on the Edit button the isDisabled property is set to true enabling all of our controls for edit mode.

Our declaritive binding on one of our controls.


&lt;input type=&quot;text&quot; data-bind=&quot;value: firstName, disabled:  isDisabled&quot; /&gt;

The Edit method that is invoked when clicking the [Edit] button.


            edit: function (e) {
                this.set(&quot;isDisabled&quot;, false);
            },

Hitting the [Reset] button will simply invoke the reset method on our ViewModel which clears out all our properties by setting them null values and again our View is automagically updated because it is bound to our ViewModel.


            reset: function (e) {
                this.set(&quot;firstName&quot;, null);
                this.set(&quot;lastName&quot;, null);
                this.set(&quot;email&quot;, null);
                this.set(&quot;twitter&quot;, null);
                this.set(&quot;site&quot;, null);
                this.set(&quot;address&quot;, null);
                this.set(&quot;city&quot;, null);
                this.set(&quot;state&quot;, null);
                this.set(&quot;zip&quot;, null);
                this.set(&quot;occupation&quot;, &quot;Please Select&quot;);
            },

Well great, I know we aren’t doing anything ground breaking here, however this post is really just to illustrate MVVM on the client-side using Telerik’s Kendo UI Web MVVM Framework and ellaborate on how we can really just work with the ViewModel to update the View vice- versa any updates to the View will update our ViewModel because they are “bound” together using MVVM.

Last but not least, even though the Kendo UI Framework was written with jQuery, notice how we didn’t have to explicitly use any jQuery or Javascript to set or get any values from our controls. I’m not at all saying jQuery is a bad thing, however when developing you probably want to be coding something that is immediately adding business value rather than coding jQuery selectors to get and and set values in your View right..? :p

Happy coding…! 🙂

Download sample code: https://skydrive.live.com/redir?resid=949A1C97C2A17906!1951

Telerik’s HTML5 Kendo UI Grid with Server Side Paging, Sorting & Filtering with MVC3, EF4 & Dynamic LINQ

Update: 06/18/2013 – It is recommended that you follow this post for Kendo UI Grid, Datasource filtering http://blog.longle.net/2013/06/18/mvc-4-web-api-odata-entity-framework-kendo-ui-grid-datasource-with-mvvm/

Update: 05/11/2012 – Added support for querying objects with child properties

Update: 04/24/2012 – Added recursion to the filters processing to support multiple search criterias on the same field while at the same time supporting searches across multiple fields.

I recently did a post Telerik’s HTML5 Kendo UI (Grid, Detail Template, TabStrip) which illustrated how to wire up their HTML5 Grid and handle server side paging. After doing so I quickly found myself needing to wire up the rest of server side bells and whistles e.g. sorting, filtering, etc.. Did some relentless googling and didn’t find any good resources on how to do this with MVC3 and EF4 so hence this blog post for the those of us that are doing just that. Rather than starting from scratch I’ll go ahead and continue where the my last blog left off.

So this first thing we need to do is configure our Kendo UI Grid for to do server side sorting and filtering so that we decompose what the requests pay loads look like coming from the Grid when performing these types of actions on it.

Configuring the Kendo UI Grid:


    $(document).ready(function () {
        var grid = $(&quot;#grid&quot;).kendoGrid({
            dataSource: {
                type: &quot;json&quot;,
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                pageSize: 5,
                transport: {
                    read: {
                        url: &quot;Products/GetAll&quot;,
                        dataType: &quot;json&quot;,
                        type: &quot;POST&quot;,
                        contentType: &quot;application/json; charset=utf-8&quot;,
                        data: {}
                    },
                    parameterMap: function (options) {
                        return JSON.stringify(options);
                    }
                },
                schema: {
                    model: {
                        fields: {
                            ProductId: { type: &quot;number&quot; },
                            Name: { type: &quot;string&quot; },
                            Status: { type: &quot;string&quot; },
                            Created: { type: &quot;date&quot; }
                        }
                    },
                    data: &quot;Products&quot;,
                    total: &quot;TotalCount&quot;
                }
            },
            height: 700,
            sortable: true,
            groupable: true,
            pageable: true,
            filterable: true,
            columns: [
                    { field: &quot;ProductId&quot;, title: &quot;ProductId&quot; },
                    { field: &quot;ProductType&quot;, title: &quot;ProductType&quot; },
                    { field: &quot;Name&quot;, title: &quot;Name&quot; },
                    { field: &quot;Created&quot;, title: &quot;Created&quot;, format: &quot;{0:MM/dd/yyyy}&quot; }
                ],
            detailTemplate: kendo.template($(&quot;#template&quot;).html()),
            toolbar: kendo.template($(&quot;#toolBarTemplate&quot;).html()),
            detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find(&quot;tr.k-master-row&quot;).first());
            }
        });

        var dropDown = grid.find(&quot;#requestType&quot;).kendoDropDownList({
            dataTextField: &quot;text&quot;,
            dataValueField: &quot;value&quot;,
            autoBind: false,
            optionLabel: &quot;All&quot;,
            dataSource: [
                    { text: &quot;Electronics&quot;, value: &quot;2&quot; },
                    { text: &quot;Machinery&quot;, value: &quot;1&quot; }
                ],
            change: function () {
                var value = this.value();
                if (value) {
                    grid.data(&quot;kendoGrid&quot;).dataSource.filter(
                        { field: &quot;ProductType&quot;, operator: &quot;eq&quot;, value: parseInt(value) });
                } else {
                    grid.data(&quot;kendoGrid&quot;).dataSource.filter({});
                }
            }
        });
    });

I’ve highlighted some of the major changes we made to our configuration which include setting up the Grid for server side actions: paging, sorting, filter, unsort and surfacing the filteration capabilities to the UI. Lines 54-72 is for setting up a Grid Toolbar which will contain a Kendo UI DrownDownList so that we can filter the Grid on ProductTypes which we will come back around to later on.

Now that we have the Grid configured for server side processing let’s take a quick look at what’s going down the wire in terms of pay loads for each of these actions so that we can mock up our models for these requests. When loading up IE Developer Tools (hit F12 or Tools > Developer Tools) and clicking on the Network Tab to start capturing network traffic we can see the actual pay load request for each of these actions.

So we can see that the pay load that is coming down the wire when a user performs a filter and sort on the grid is:


{&quot;take&quot;:5,&quot;skip&quot;:0,&quot;page&quot;:1,&quot;pageSize&quot;:5,&quot;group&quot;:[],&quot;filter&quot;:{&quot;filters&quot;:[{&quot;field&quot;:&quot;ProductType&quot;,&quot;operator&quot;:&quot;eq&quot;,&quot;value&quot;:&quot;3&quot;}],&quot;logic&quot;:&quot;and&quot;},&quot;sort&quot;:[{&quot;field&quot;:&quot;Name&quot;,&quot;dir&quot;:&quot;desc&quot;}]}

From this we can start mocking up our models needed for these types of Grid Actions for our Controller.


namespace MvcApplication3.Models
{
    public class GridFilter
    {
        public string Operator { get; set; }
        public string Field { get; set; }
        public string Value { get; set; }
    }

    public class GridFilters
    {
        public List&lt;GridFilter&gt; Filters { get; set; }
        public string Logic { get; set; }
    }

    public class GridSort
    {
        public string Field { get; set; }
        public string Dir { get; set; }
    }
}

Making changes to our Controller Action

We need to make changes to our existing Action on our Controller to support these new Grid objects that is being posted from our Grid when a user does a server side sort, filter, etc..


    public class ProductsController : Controller
    {
        [HttpPost]
        public JsonResult GetAll(int skip, int take, int page, int pageSize, 
            List&lt;GridSort&gt; sort = null, GridFilters filter = null)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var products = myDatabaseContext.Products.AsQueryable();
            var totalCount = myDatabaseContext.Products.AsQueryable();

            if (filter != null &amp;&amp; (filter.Filters != null &amp;&amp; filter.Filters.Count &gt; 0))
            {
                string whereClause = null;
                var parameters = new List&lt;object&gt;();
                var filters = filter.Filters;

                for (var i = 0; i &lt; filters.Count; i++)
                {
                    if (i == 0)
                        whereClause += string.Format(&quot; {0}&quot;, 
                            BuildWhereClause&lt;Product&gt;(i, filter.Logic, filters[i], 
                            parameters));
                    else
                        whereClause += string.Format(&quot; {0} {1}&quot;, 
                            ToLinqOperator(filter.Logic), 
                            BuildWhereClause&lt;Product&gt;(i, filter.Logic, filters[i], 
                            parameters));
                }

                products = products.Where(whereClause, parameters.ToArray());
                totalCount = products.Where(whereClause, parameters.ToArray());
            }

            if (sort != null &amp;&amp; sort.Count &gt; 0)
                foreach (var s in sort)
                {
                    s.Field = (s.Field == &quot;ProductType&quot;) ? &quot;ProductTypeId&quot; : s.Field;
                    products = products.OrderBy(s.Field + &quot; &quot; + s.Dir);
                }

            products = products.Skip(skip).Take(take);

            List&lt;Product&gt; productList = products.ToList();

            var productViewModels = new List&lt;ProductViewModel.Product&gt;();

            foreach (var p in productList)
            {
                productViewModels.Add(new ProductViewModel.Product
                                            {
                                                Completed = p.Completed.Date,
                                                CompletedBy = p.CompletedBy,
                                                Created = p.Created.Date,
                                                CreatedBy = p.CreatedBy,
                                                Name = p.Name,
                                                ProductId = p.ProductId,
                                                ProductType = p.ProductType.Name,
                                                ProductDetails = p.ProductDetails,
                                                Status = p.Status,
                                                Updated = p.Updated.Date,
                                                UpdatedBy = p.UpdatedBy
                                            });
            }

            return Json(
                new ProductViewModel
                    {
                        Products = productViewModels,
                        TotalCount = totalCount.Count()
                    });
        }

        public static string BuildWhereClause&lt;T&gt;(int index, string logic, 
            GridFilter filter, List&lt;object&gt; parameters)
        {
            var entityType = (typeof(T));
            var property = entityType.GetProperty(filter.Field);

            switch (filter.Operator.ToLower())
            {
                case &quot;eq&quot;:
                case &quot;neq&quot;:
                case &quot;gte&quot;:
                case &quot;gt&quot;:
                case &quot;lte&quot;:
                case &quot;lt&quot;:
                    if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(DateTime.Parse(filter.Value).Date);
                        return string.Format(&quot;EntityFunctions.TruncateTime({0}){1}@{2}&quot;, 
                            filter.Field, 
                            ToLinqOperator(filter.Operator), 
                            index);
                    }
                    if (typeof(int).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(int.Parse(filter.Value));
                        return string.Format(&quot;{0}{1}@{2}&quot;, 
                            filter.Field, 
                            ToLinqOperator(filter.Operator), 
                            index);
                    }
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}{1}@{2}&quot;, 
                        filter.Field, 
                        ToLinqOperator(filter.Operator), 
                        index);
                case &quot;startswith&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.StartsWith(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                case &quot;endswith&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.EndsWith(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                case &quot;contains&quot;:
                    parameters.Add(filter.Value);
                    return string.Format(&quot;{0}.Contains(&quot; + &quot;@{1})&quot;, 
                        filter.Field, 
                        index);
                default:
                    throw new ArgumentException(
                        &quot;This operator is not yet supported for this Grid&quot;, 
                        filter.Operator);
            }
        }

        public static string ToLinqOperator(string @operator)
        {
            switch (@operator.ToLower())
            {
                case &quot;eq&quot;: return &quot; == &quot;;
                case &quot;neq&quot;: return &quot; != &quot;;
                case &quot;gte&quot;: return &quot; &gt;= &quot;;
                case &quot;gt&quot;: return &quot; &gt; &quot;;
                case &quot;lte&quot;: return &quot; &lt;= &quot;;
                case &quot;lt&quot;: return &quot; &lt; &quot;;
                case &quot;or&quot;: return &quot; || &quot;;
                case &quot;and&quot;: return &quot; &amp;&amp; &quot;;
                default: return null;
            }
        }

        public JsonResult GetProductDetails(int skip, int take, int page, 
            int pageSize, string group)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var productDetails = myDatabaseContext.ProductDetails
                .OrderBy(p =&gt; p.ProducDetailtId);

            return Json(
                new ProductDetailsViewModel
                    {
                        ProductDetails = productDetails.Skip(skip).Take(take),
                        TotalCount = productDetails.Count()
                    },
                JsonRequestBehavior.AllowGet);
        }
    }

Note: Instead of downloading the LINQ Dynamic Query Library, you may want to actually download the sample application for this post because the DynamicQueryable.cs class from the Linq Dynamic Libray has been slightly modified to handle EntityFunctions to support string search actions from our Grid such as Contains, StartsWidth and EndsWith string searches.

A few quick notes in regards to our changes to our Action on our Controller to now support complete server side processing of paging, sorting and filtering.

  • BuildWhereClause<T>(int index, string logic, GridFilter filter, List parameters)
    This helper method will build our our where clauses and predicates so tha we can chain them up and pass them into Dynamic LINQ.

  • ToLinqOperator(string @operator)

    This helper method will convert operators that are sent from our Grid to C# operators that Dynamic LINQ will understand and convert them for us

  • Lines 48-64, here we are iterating through the results to trim off the timestamp off of any properties that are of type datetime, so that when we do any grouping or filtering from the grid the timestamp of these fields are ignored.

                foreach (var p in productList)
                {
                    productViewModels.Add(new ProductViewModel.Product
                                                {
                                                    Completed = p.Completed.Date,
                                                    CompletedBy = p.CompletedBy,
                                                    Created = p.Created.Date,
                                                    CreatedBy = p.CreatedBy,
                                                    Name = p.Name,
                                                    ProductId = p.ProductId,
                                                    ProductType = p.ProductType.Name,
                                                    ProductDetails = p.ProductDetails,
                                                    Status = p.Status,
                                                    Updated = p.Updated.Date,
                                                    UpdatedBy = p.UpdatedBy
                                                });
                }
    
  • Lines 88-103, here we are checking against the type of the column (property) that we are searching against so that we can convert the search criteria to the appropriate type. Currently we are supporting searches against types of string, datetime and int. If you need to add more types simply enhance this section of the implementation.

                        if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                        {
                            parameters.Add(DateTime.Parse(filter.Value).Date);
                            return string.Format(&quot;EntityFunctions.TruncateTime({0}){1}@{2}&quot;, 
                                filter.Field, 
                                ToLinqOperator(filter.Operator), 
                                index);
                        }
                        if (typeof(int).IsAssignableFrom(property.PropertyType))
                        {
                            parameters.Add(int.Parse(filter.Value));
                            return string.Format(&quot;{0}{1}@{2}&quot;, 
                                filter.Field, 
                                ToLinqOperator(filter.Operator), 
                                index);
                        }
                        parameters.Add(filter.Value);
    
    
  • Lines 109-123, here we are just framing up the different queries for string searches from the Grid. The Grid supports StartsWith, Contains, and EndsWith.

                    case &quot;startswith&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.StartsWith(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
                    case &quot;endswith&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.EndsWith(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
                    case &quot;contains&quot;:
                        parameters.Add(filter.Value);
                        return string.Format(&quot;{0}.Contains(&quot; + &quot;@{1})&quot;, 
                            filter.Field, 
                            index);
    

    As you can see in the screenshot right below these are the current string search capabilites that the Grid has.

Great, let’s run a few searches from the Grid now.

  • Search on ProductId: 3

  • Search on Created Date: 04/20/2013

  • Search on Created Date >= 04/15/2012 and Name containing “sample product 3”

    Voila! Our controller only returns 3 records which assert our test case and are all of Created date >= 04/20/2012 and all contain the string “sample product 3” in the name.

Update: 04/24/2012

Added recursion to the filters processing to support multiple search criterias on the same field while at the same time supporting searches across multiple fields.

 

        [HttpPost]
        public JsonResult GetAll(int skip, int take, int page, int pageSize, 
            List&lt;GridSort&gt; sort = null, GridFilter filter = null)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var products = myDatabaseContext.Products.AsQueryable();
            var totalCount = myDatabaseContext.Products.AsQueryable();

            if (filter != null &amp;&amp; (filter.Filters != null &amp;&amp; filter.Filters.Count &gt; 0))
            {
                ProcessFilters(filter, ref products);
                totalCount = products;
            }

            if (sort != null &amp;&amp; sort.Count &gt; 0)
                foreach (var s in sort)
                {
                    s.Field = (s.Field == &quot;ProductType&quot;) ? &quot;ProductTypeId&quot; : s.Field;
                    products = products.OrderBy(s.Field + &quot; &quot; + s.Dir);
                }

            products = products.Skip(skip).Take(take);

            List&lt;Product&gt; productList = products.ToList();

            var productViewModels = new List&lt;ProductViewModel.Product&gt;();

            foreach (var p in productList)
            {
                productViewModels.Add(new ProductViewModel.Product
                                            {
                                                Completed = p.Completed.Date,
                                                CompletedBy = p.CompletedBy,
                                                Created = p.Created.Date,
                                                CreatedBy = p.CreatedBy,
                                                Name = p.Name,
                                                ProductId = p.ProductId,
                                                ProductType = p.ProductType.Name,
                                                ProductDetails = p.ProductDetails,
                                                Status = p.Status,
                                                Updated = p.Updated.Date,
                                                UpdatedBy = p.UpdatedBy
                                            });
            }

            return Json(
                new ProductViewModel
                    {
                        Products = productViewModels,
                        TotalCount = totalCount.Count()
                    });
        }

        public static void ProcessFilters(GridFilter filter, ref IQueryable&lt;Product&gt; queryable)
        {
            var whereClause = string.Empty;
            var filters = filter.Filters;
            var parameters = new List&lt;object&gt;();
            for (int i = 0; i &lt; filters.Count; i++)
            {
                var f = filters[i];

                if (f.Filters == null)
                {
                    if (i == 0)
                        whereClause += BuildWhereClause&lt;Product&gt;(f, i, parameters) + &quot; &quot;;
                    if (i != 0)
                        whereClause += ToLinqOperator(filter.Logic) + 
                            BuildWhereClause&lt;Product&gt;(f, i, parameters) + &quot; &quot;;
                    if (i == (filters.Count - 1))
                    {
                        CleanUp(ref whereClause);
                        queryable = queryable.Where(whereClause, parameters.ToArray());
                    }
                }
                else
                    ProcessFilters(f, ref queryable);
            }
        }

Looks like our server side paging, sorting and filteration is golden!

Update: 05/11/2012- Added support for querying objects with child properties


    public static class GridHelper
    {
        public static void ProcessFilters&lt;T&gt;(GridFilter filter, ref IQueryable&lt;T&gt; queryable)
        {
            var whereClause = string.Empty;
            var filters = filter.Filters;
            var parameters = new List&lt;object&gt;();
            for (int i = 0; i &lt; filters.Count; i++)
            {
                var f = filters[i];

                if (f.Filters == null)
                {
                    if (i == 0)
                        whereClause += BuildWherePredicate&lt;T&gt;(f, i, parameters) + &quot; &quot;;
                    if (i != 0)
                        whereClause += ToLinqOperator(filter.Logic) + BuildWherePredicate&lt;T&gt;(f, i, parameters) + &quot; &quot;;
                    if (i == (filters.Count - 1))
                    {
                        TrimWherePredicate(ref whereClause);
                        queryable = queryable.Where(whereClause, parameters.ToArray());
                    }
                }
                else
                    ProcessFilters(f, ref queryable);
            }
        }

        public static string TrimWherePredicate(ref string whereClause)
        {
            switch (whereClause.Trim().Substring(0, 2).ToLower())
            {
                case &quot;&amp;&amp;&quot;:
                    whereClause = whereClause.Trim().Remove(0, 2);
                    break;
                case &quot;||&quot;:
                    whereClause = whereClause.Trim().Remove(0, 2);
                    break;
            }

            return whereClause;
        }

        public static string BuildWherePredicate&lt;T&gt;(GridFilter filter, int index, List&lt;object&gt; parameters)
        {
            var entityType = (typeof(T));
            PropertyInfo property;
            
            if(filter.Field.Contains(&quot;.&quot;))
                property = GetNestedProp&lt;T&gt;(filter.Field);
            else 
                property = entityType.GetProperty(filter.Field);
            
            var parameterIndex = parameters.Count;

            switch (filter.Operator.ToLower())
            {
                case &quot;eq&quot;:
                case &quot;neq&quot;:
                case &quot;gte&quot;:
                case &quot;gt&quot;:
                case &quot;lte&quot;:
                case &quot;lt&quot;:
                    if (typeof(DateTime).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(DateTime.Parse(filter.Value).Date);
                        return string.Format(&quot;EntityFunctions.TruncateTime(&quot; + filter.Field + &quot;)&quot; + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                    }
                    if (typeof(int).IsAssignableFrom(property.PropertyType))
                    {
                        parameters.Add(int.Parse(filter.Value));
                        return string.Format(filter.Field + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                    }
                    parameters.Add(filter.Value);
                    return string.Format(filter.Field + ToLinqOperator(filter.Operator) + &quot;@&quot; + parameterIndex);
                case &quot;startswith&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.StartsWith(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                case &quot;endswith&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.EndsWith(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                case &quot;contains&quot;:
                    parameters.Add(filter.Value);
                    return filter.Field + &quot;.Contains(&quot; + &quot;@&quot; + parameterIndex + &quot;)&quot;;
                default:
                    throw new ArgumentException(&quot;This operator is not yet supported for this Grid&quot;, filter.Operator);
            }
        }

        public static string ToLinqOperator(string @operator)
        {
            switch (@operator.ToLower())
            {
                case &quot;eq&quot;:
                    return &quot; == &quot;;
                case &quot;neq&quot;:
                    return &quot; != &quot;;
                case &quot;gte&quot;:
                    return &quot; &gt;= &quot;;
                case &quot;gt&quot;:
                    return &quot; &gt; &quot;;
                case &quot;lte&quot;:
                    return &quot; &lt;= &quot;;
                case &quot;lt&quot;:
                    return &quot; &lt; &quot;;
                case &quot;or&quot;:
                    return &quot; || &quot;;
                case &quot;and&quot;:
                    return &quot; &amp;&amp; &quot;;
                default:
                    return null;
            }
        }

        public static PropertyInfo GetNestedProp&lt;T&gt;(String name)
        {
            PropertyInfo info = null;
            var type = (typeof(T));
            foreach(var prop in name.Split('.'))
            {
                info = type.GetProperty(prop);
                type = info.PropertyType;
            }
            return info;
        }
    }

Happy Coding…! 🙂

Download sample application: https://skydrive.live.com/redir.aspx?cid=949a1c97c2a17906&resid=949A1C97C2A17906!465&parid=949A1C97C2A17906!361

https://drive.google.com/file/d/0B91gwLeUpEWBU2tnN0dNeVhSTzA/view?usp=sharing