November 2009 - Posts

In the last post, I have shown you how you can extend the ASP.NET MVC2 templates and some of the  issues of the current beta. In this post, I will show you how we are taking advantages of the new DisplayFor/EditorFor in our MVC Grid. One of the benefit of using those statements in our grid is, you can host any kind of component in an individual cell, obviously the first two components will be the DateTimePicker and NumericTextBox that we are going to include in our next release.

One of the most requested feature since our last release is the inline editing support in the grid. In this, In this post, I will show you the inline editing of the product of my last post, but this time with our Grid. The Grid should support both server and ajax editing, but in this example we will only discuss the server side mode.

So lets see the grid first:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<GridModel<Product>>" %>

<% Html.Telerik()
       .Grid(Model)
       .Name("products")
       .Columns(columns =>
        {
           columns.Add((p, commands) =>
           {
               commands.Edit()
                       .Action<ProductController>(c => c.Edit(p.Id));

               commands.Delete()
                       .Action<ProductController>(c => c.Delete(p.Id));

           });

           columns.Add(p => p.Name);
           columns.Add(p => p.Category);
           columns.Add(p => p.Price);
        })
       .Filterable()
       .Sortable()
       .Scrollable()
       .Pageable()
       .Render(); %>

As you can see that we are introducing a new kind of column - GridCommandColumn(line 6-16). This column type can contain multiple actions, for example in the above code snippet we have both Edit and Delete action, you can also add custom actions, in that case you have to specify the name as well.

Now, let us take a look at the controller code:

[HandleError, PopulateCategories]
public class ProductController : Controller
{
    private readonly IDatabase database;

    public ProductController() : this(Database.Create())
    {
    }

    public ProductController(IDatabase database)
    {
        this.database = database;
    }

    [GridAction(GridName = "products")]
    public ActionResult Index(GridCommand command)
    {
        return View(database.Products.ToGridModel(command));
    }

    [HttpPost, GridAction(GridName = "products")]
    public ActionResult Edit(int id)
    {
        Product product = database.Products.FirstOrDefault(p => p.Id == id);

        // Since we are working with in memory version, we are clonnening it,
        // in real life this would not be necessary.

        Product clonned = new Product
                              {
                                  Id = product.Id,
                                  Name = product.Name,
                                  Category = new Category { Id = product.Category.Id, Name = product.Category.Name},
                                  Price = product.Price
                              };

        if (TryUpdateModel(clonned, null, null, new[] { "Id" }))
        {
            product = clonned;
            return RedirectToAction("Index");
        }

        return View("Index", database.Products.ToGridModel(command));
    }

    [HttpPost, GridAction(GridName = "products")]
    public ActionResult Delete(int id)
    {
        Product product = database.Products.FirstOrDefault(p => p.Id == id);

        database.Products.Remove(product);

        return RedirectToAction("Index");
    }
}

When your run the above code and navigate to product/index the following view will appear:

InlineGridEditing

When clicking the Edit Button, it will enter into the edit mode, the edit button will be replaced with two new buttons Update and Cancel and the Delete button will be become hidden. The text of these button are completely configurable, you can set those when setting up the Grid. The following is the screenshot when the Grid enters into edit mode.

InlineGridEditingMode

 

When the validation fails:

InlineGridValidation

The Grid also have the out of the box support of PRG (Post/Redirect/Get) pattern, to activate it you have set few properties of the GridAction filter. The following is the complete code of the Product Controller when PRG is enabled.

[HandleError, PopulateCategories]
public class ProductController : Controller
{
    private readonly IDatabase database;

    public ProductController() : this(Database.Create())
    {
    }

    public ProductController(IDatabase database)
    {
        this.database = database;
    }

    [GridAction(GridName = "products", ImportViewDataFromTempData = true)]
    public ActionResult Index(GridCommand command)
    {
        return View(database.Products.ToGridModel(command));
    }

    [HttpPost, GridAction(GridName = "products", ExportViewDataToTempData = true)]
    public ActionResult Edit(int id)
    {
        Product product = database.Products.FirstOrDefault(p => p.Id == id);

        // Since we are working with in memory version, we are clonnening it,
        // in real life this would not be necessary.

        Product clonned = new Product
                              {
                                  Id = product.Id,
                                  Name = product.Name,
                                  Category = new Category { Id = product.Category.Id, Name = product.Category.Name},
                                  Price = product.Price
                              };

        if (TryUpdateModel(clonned, null, null, new[] { "Id" }))
        {
            product = clonned;
        }

        return this.RedirectToPreviousOr(() => RedirectToAction("Index"));
    }

    [HttpPost, GridAction(GridName = "products", ExportViewDataToTempData = true)]
    public ActionResult Delete(int id)
    {
        Product product = database.Products.FirstOrDefault(p => p.Id == id);

        database.Products.Remove(product);

        return this.RedirectToPreviousOr(() => RedirectToAction("Index"));
    }
}

As you can see that the Edit and Delete action are copying the ViewData to TempData and the Index action is copying the ViewData back from the TempData so that validation messages get intact between the redirects. There one more thing in the above code that we are using RedirectToPreviousOr, this is just a extension method which checks whether the UrlReferrer is set, if set it redirect to that url or execute the provided action.

public static ActionResult RedirectToPreviousOr(this ControllerBase controller, Func<ActionResult> action)
{
    HttpContextBase httpContext = controller.ControllerContext.HttpContext;

    string previousUrl = (httpContext.Request.UrlReferrer != null) ? httpContext.Request.UrlReferrer.ToString() : null;

    return !string.IsNullOrEmpty(previousUrl) ? new RedirectResult(previousUrl) : action();
}

Now, lets check two other screenshots which contains multiple data types:

multiv

multie

What do you think, comments and suggestions are greatly appreciated.

Shout it

One of the new features of ASP.NET MVC 2 is Templates (DisplayFor/EditorFor), Brad Wilson did a series of post which explains how the templates works, Please read it before you continue this post.

Although he did an excellent job explaining the inner-details, but one thing you will notice that the object model in those examples are traversed from top to bottom or parent to child which is good for explaining the internal, but in our real application we need a bit more support. Lets take a trivial example, you are creating a create/edit screen for your Product, where each product has a associated Category and you want to show this category in a DropDownList, say we have a Product class like the following:

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public Category Category { get; set; }

    public decimal Price { get; set; }
}

And we want to show a screen like the following:

taim

How can we create the above screen with the new EditorFor statement like the following:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ExtendedTemplating.Product>" %>
<% using (Html.BeginForm()) {%>
    <fieldset>
        <legend>Fields</legend>
        <%= Html.EditorForModel() %>
        <div>
            <input type="submit" value="Save" />
            <%= Html.ActionLink("Cancel", "Index") %>
        </div>
    </fieldset>
<% } %>

If you run the above with default configuration, you will find instead of DropDownList, the Category is appearing in a text box with the category id and if scan the ASP.NET MVC source code you will find there is no out of the box support to show a DropDownList.

There are quite a few way to solve the above. First, let us see the easy one:

In our ProductController we will put the categories in the ViewData and set the current product as Model like the following:

public ActionResult Edit(int id)
{
    ViewData["categories"] = database.Categories;
    return View(database.Products.SingleOrDefault(p => p.Id == id));
}

Then, we will create new Editor Template for the Category Type and put into the EditorTemplates folder. The editor template will use the following code:

<%@ Import Namespace="ExtendedTemplating"%>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Category>" %>
<%= Html.DropDownList(string.Empty, new SelectList(ViewData["categories"] as IEnumerable<Category>, "Id", "Name", Model.Id), "[Select category]")%>

Now, when you run the above, the DropDownList will be shown instead of the TextBox.

This is okay if we are only showing one or two DropDownList in our application. But how can we create a generic solution which should work for all kinds of DropDownList.

Lets extend the ASP.NET MVC ModelMetaData, but before that let me show you the final result of this extension and then I will explain the internal details and some of issues that I found in the current implementation.

The Category Property of Product class will have a new attribute DropDownList.

[DropDownList("categories", "Id", "Name", "[Select category]")]
public Category Category { get; set; }

As you can see, we have moved the hard coded strings from the View into this new attribute, then in the view (DropDownList.asax) we have the following code:

<%@ Import Namespace="ExtendedTemplating"%>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">
    DropDownListAttribute GetDropDownListAttribute()
    {
        FieldTemplateMetadata metaData = ViewData.ModelMetadata as FieldTemplateMetadata;

        return (metaData != null) ? metaData.Attributes.OfType<DropDownListAttribute>().SingleOrDefault() : null;
    }
</script>
<% DropDownListAttribute attribute = GetDropDownListAttribute();%>
<% if (attribute != null) {%>
    <%= Html.DropDownList(string.Empty, new SelectList(ViewData[attribute.ViewDataKey] as IEnumerable, attribute.DataValueField, attribute.DataTextField, attribute.GetSelectedValue(Model)), attribute.OptionLabel, attribute.HtmlAttributes) %>
<% }%>
<% else {%>
    <%= Html.DisplayForModel()%>
<% }%>

I expect, except the Html.DropDownList you are not familiar with the other parts of the above code. So let me explain this bit by bit, first in the GetDropDownList method you can see we are casting the View.ModelMetaData to a new type FieldTemplateMetaData, then we are retrieving the DropDownListAttribute from its Attributes collection. Next, we are checking whether DropDownListAttribute is present (this is somewhat unnecessary as this template is only used for DropDownList, but to be in safe side we are adding this check), if present we are showing the DropDownList otherwise we are using the MVC framework to handle it.

In MVC2, there are quite a few new things that are introduced to manage the meta data of model, behind the scene it uses the Provider Pattern that we are already familiar with. Currently the MVC2 framework uses the DataAnnotationsModelMetadata and DataAnnotationsModelMetadataProvider to collect the Data Annotation Attributes, the DataAnnotationsModelMetadata is inherited from the ModelMetadata and DataAnnotationsModelMetadataProvider from ModelMetadataProvider.

The first issue that I found is that when the provider is requested to create ModelMetaData (check the CreateMetadata method), it does not store the attributes of the model in ModelMetaData, instead it only uses the attributes to setup the ModelMetaData and forgets the attributes completely. The reason it is wrong that like above example we can use these attributes in building this view. The class also contains another property AdditionalValues which can hold arbitrary data against a key but I am not sure how to utilize that without writing a custom meta model provider.

The next issue is the DataAnnotationsModelMetadataProvider is not correctly handling the DataTyeAttribute, the impact is even you set the DataType like Currency/Date/Time etc  in your model, you have to explicitly use the DisplayFormatAttribute to supply the format. But the correct behavior should be if DisplayFormatAttribute is used it will use the attribute, otherwise it will use the associated DisplayFormatAttribute of the DataTypeAttribute.

The following line of CreateMetadata method in DataAnnotationsModelMetadataProvider:

DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault();

Should become:

DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault() ??
                                                (dataTypeAttribute != null ? dataTypeAttribute.DisplayFormat : null);

The third and the last issue that I found is, it is not possible to create template with code like the default. Well, it is not a big issue for those who are creating only applications, but for component developer like us, it adds some overheads in deployment, documentation as well as support tickets .

Now lets get back to the implementation, first we will create an interface ITemplateField which we will use to store the template name, Please check the above DropDownListAttribute as you can see that we are not specifying any template with built-in UIHintAttribute, the DropDownListAttribute itself maintains the template name internally.

ITemplateField:

public interface ITemplateField
{
    string TemplateName
    {
        get;
    }
}

DropDownListAttribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class DropDownListAttribute : Attribute, ITemplateField
{
    private static string defaultTemplateName;

    public DropDownListAttribute(string viewDataKey, string dataValueField) : this(viewDataKey, dataValueField, null)
    {
    }

    public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField) : this(viewDataKey, dataValueField, dataTextField, null)
    {
    }

    public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField, string optionLabel) : this(DefaultTemplateName, viewDataKey, dataValueField, dataTextField, optionLabel, null)
    {
    }

    public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField, string optionLabel, object htmlAttributes) : this(DefaultTemplateName, viewDataKey, dataValueField, dataTextField, optionLabel, htmlAttributes)
    {
    }

    public DropDownListAttribute(string templateName, string viewDataKey, string dataValueField, string dataTextField, string optionLabel, object htmlAttributes)
    {
        if (string.IsNullOrEmpty(templateName))
        {
            throw new ArgumentException("Template name cannot be empty.");
        }

        if (string.IsNullOrEmpty(viewDataKey))
        {
            throw new ArgumentException("View data key cannot be empty.");
        }

        if (string.IsNullOrEmpty(dataValueField))
        {
            throw new ArgumentException("Data value field cannot be empty.");
        }

        TemplateName = templateName;
        ViewDataKey = viewDataKey;
        DataValueField = dataValueField;
        DataTextField = dataTextField;
        OptionLabel = optionLabel;
        HtmlAttributes = new RouteValueDictionary(htmlAttributes);
    }

    public static string DefaultTemplateName
    {
        get
        {
            if (string.IsNullOrEmpty(defaultTemplateName))
            {
                defaultTemplateName = "DropDownList";
            }

            return defaultTemplateName;
        }
        set
        {
            defaultTemplateName = value;
        }
    }

    public string TemplateName { get; private set; }

    public string ViewDataKey { get; private set; }

    public string DataValueField { get; private set; }

    public string DataTextField { get; private set; }

    public string OptionLabel { get; private set; }

    public IDictionary<string, object> HtmlAttributes { get; private set; }

    public object GetSelectedValue(object model)
    {
        return GetPropertyValue(model, DataValueField);
    }

    public object GetSelectedText(object model)
    {
        return GetPropertyValue(model, !string.IsNullOrEmpty(DataTextField) ? DataTextField : DataValueField);
    }

    private static object GetPropertyValue(object model, string propertyName)
    {
        if (model != null)
        {
            PropertyDescriptor property = GetTypeDescriptor(model.GetType()).GetProperties()
                                                                            .Cast<PropertyDescriptor>()
                                                                            .SingleOrDefault(p => string.Compare(p.Name, propertyName, StringComparison.OrdinalIgnoreCase) == 0);

            if (property != null)
            {
                return property.GetValue(model);
            }
        }

        return null;
    }

    private static ICustomTypeDescriptor GetTypeDescriptor(Type type)
    {
        return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
    }
}

Next, we will create the FieldTemplateMetadata class which inherits from DataAnnotationsModelMetadata to store the attribute collection of the model.

public class FieldTemplateMetadata : DataAnnotationsModelMetadata
{
    public FieldTemplateMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName, DisplayColumnAttribute displayColumnAttribute, IEnumerable<Attribute> attributes) : base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
    {
        Attributes = new List<Attribute>(attributes);
    }

    public IList<Attribute> Attributes
    {
        get;
        private set;
    }
}

Next, we will create a provider that inherits from DataAnnotationsModelMetadataProvider to pass the attribute collection to ModelMetaData as well as setting correct template.

public class FieldTemplateMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        DataAnnotationsModelMetadata result = (DataAnnotationsModelMetadata) base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        string templateName = attributes.OfType<ITemplateField>()
                                        .Select(field => field.TemplateName)
                                        .LastOrDefault();

        return new FieldTemplateMetadata(this, containerType, modelAccessor, modelType, propertyName, attributes.OfType<DisplayColumnAttribute>().FirstOrDefault(), attributes)
                   {
                       TemplateHint = !string.IsNullOrEmpty(templateName) ? templateName : result.TemplateHint,
                       HideSurroundingHtml = result.HideSurroundingHtml,
                       DataTypeName = result.DataTypeName,
                       IsReadOnly = result.IsReadOnly,
                       NullDisplayText = result.NullDisplayText,
                       DisplayFormatString = result.DisplayFormatString,
                       ConvertEmptyStringToNull = result.ConvertEmptyStringToNull,
                       EditFormatString = result.EditFormatString,
                       ShowForDisplay = result.ShowForDisplay,
                       ShowForEdit = result.ShowForEdit,
                       DisplayName = result.DisplayName
                   };
    }
}

There are one more thing that we have to do before completing this post, we have register the custom mode meta data provider in the provider collection, just put the following line in the global.asax.

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ModelMetadataProviders.Current = new FieldTemplateMetadataProvider();
}

And that's it.

At the end, templating is an nice edition in ASP.NET MVC2 and it provides us a great opportunity to take ASP.NET MVC to the next level (I am already having few idea in my mind).

You will find the complete source code at the bottom of this post.

Download: ExtendedTemplating.zip

Shout it

Dear readers, as you know that we have released our beta few weeks back, we are currently looking for your feedback on the existing features as well as the features that you would like to see in our next release. This is a very short survey only 4/5 screens to complete, click here to submit your valuable feedback.

Thanks in advance for your precious time.

Shout it

Scott showed how to render the Grid in a Transaction. Certainly it does the job but in my opinion view component should not be responsible for this kind of cross cutting concerns, instead we can use the Action Filters. Lets see how we can utilize the Action Filter in this scenario instead of modifying the Grid code. What Scott is trying to do is encapsulate the data access operation in a transaction, the Action Filter has several methods which the ASP.NET MVC framework executes in different stages of a request. In this case, we will use the OnActionExecuting which fires before the code enters into the controller method to start a transaction and OnResultExecuted which fires when the view is processed, we will commit/rollback based upon the status, here is the code that would process the action result in a transaction:

namespace AltNorthwind
{
    using System.Web.Mvc;
    using System.Transactions;

    public class TransactionAttribute : ActionFilterAttribute
    {
        private CommittableTransaction transaction;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            transaction = new CommittableTransaction();

            base.OnActionExecuting(filterContext);
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);

            try
            {
                if ((filterContext.Exception != null) && (!filterContext.ExceptionHandled))
                {
                    transaction.Rollback();
                }
                else
                {
                    transaction.Commit();
                }
            }
            finally
            {
                transaction.Dispose();
            }
        }
    }
}

Next, we will decorate the Controller Action method with this attribute like the following:

[Transaction]
public ActionResult Index()
{
    return View(repository.All());
}

Hope, you would find it useful.

Shout it

[Updated: Source code attached]

In the recent release, there has been few enhancements in the Web Asset Management. One of the new thing that we introduced which was actually requested by the community is Shared Web Asset. In this post, I will show you, how to use it in your ASP.NET MVC Application.

In the previous version, you can only define the web assets either in the ScriptRegistrar or StyleSheetRegistrar like the following:

<% Html.Telerik()
       .ScriptRegistrar()
       .Scripts(scripts =>
                scripts.AddGroup("myScripts",
                                  group => group.Add("script1.js")
                                                .Add("script2.js")
                                                .Add("script3.js")
                                                .Combined(true)
                                                .Compress(true)
                                )
               )
       .Render();%>

if you want to reuse it in another page, you have to copy the exact same thing. Also the url that it generates is bit cryptic and very long, for example for the above the following html snippet is generated:

<script type="text/javascript" src="http://weblogs.asp.net/asset.axd?id=rgAAAB-LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee--997o7nU4n99__P1xmZAFs9s5K2smeIYCqyB8_fnwfPyJ-8UfT9qNHH2WrVVlMs7aolnffbf90dpk107pYtR-NPrqkr_d2dh6O7413d3fujXfos-lHj9p6ndMvs48e3fv0_ugj-vm9X_zRitq-5hcbanXOn1UfPdoZfbSkbwTk7vinm49-yYi_2PW_2PO-2PO_uMdffP-XfP-X_D-iwlMArgAAAA%3d%3d"></script>

After evaluating quite a different scenarios, we found that we have to include the complete details of the web assets in the script/stylesheet url and this is the reason the url becomes long and cryptic. Behind the scene, we are serializing and compressing the web asset group and then writing it as a base64 encoded string in the page.

With our new Shared Web Asset, you will not only be able to share the same web assets across the different pages, you will also be able to control the generated urls. There are two ways you can define the shared web assets, through the configuration file or with the fluent syntax. First, lets see, how you can define it the in the configuration file.

For example, for the above web assets we can declare it in the web.config file like the following:

<configuration>
    <configSections>
        <sectionGroup name="telerik">
            <section name="webAssets" type="Telerik.Web.Mvc.Configuration.WebAssetConfigurationSection, Telerik.Web.Mvc"/>
        </sectionGroup>
    </configSections>
    <telerik>
        <webAssets>
            <scripts>
                <add name="myScripts" combined="true" compress="true" enabled="true">
                    <items>
                        <add source="script1.js"/>
                        <add source="script2.js"/>
                        <add source="script3.js"/>
                    </items>
                </add>
            </scripts>
        </webAssets>
    </telerik>
</configuration>

Or you can place it the global.asax like the following:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    SharedWebAssets.Scripts(scripts =>
                            scripts.AddGroup("myScripts",
                                              group => group.Add("Script1.js")
                                                            .Add("Script2.js")
                                                            .Add("Script3.js")
                                                            .Combined(true)
                                                            .Compress(true)
                                            )
                           );
}

Now, use the AddSharedGroup of the ScriptRegistrar like the following:

<% Html.Telerik()
       .ScriptRegistrar()
       .Scripts(scripts => scripts.AddSharedGroup("myScripts"))
       .Render(); %>

And it would render the following:

<script type="text/javascript" src="http://weblogs.asp.net/asset.axd?id=myScripts"></script>

You can also mix the configuration file and fluent syntax, in that case, the configuration file assets will be registered first and then the fluent syntax.

Before completing this post, I would like to mention one more feature, though it was available from the CTP release. If you have downloaded the release zip file, in the Example Project, you have seen, we have placed the css and js files under the “2009.3.1103.0” folder in there corresponding assets folders. The “2009.3.1103.0” is actually the version number of our component. When locating any asset the version number folder is first scanned for the requested asset name, if it does not exist, it search its parent folder which is Content for css and Scripts for the js files. The next rule is whether the application is running in debug or release mode (compilation debug="true" in web.config file), when the application is running in debug mode, and lets say the assets is script1.js, then it will be searched in the following order:

1. script1.debug.js
2. script1.js
3. script1.min.js

and in the release mode:

1. script1.min.js
2. script1.js
3. script1.min.js

And this is the reason our examples, we did not mention the actual file name, instead we depend on the framework to resolve it based upon the runtime environment and don’t worry about the performance, in the release mode the resolve logic gets only executed when the asset is accessed for the first time and then we cache the resolve path for the consequent request.

That’s it for today.

Download: SharedWebAsset.zip

Shout it

I am proud to inform you that yesterday we released our Q3 2009 version of Telerik Extensions for ASP.NET MVC. As promised this release includes:

  • Grid
  • Menu
  • PanelBar
  • TabStrip

You can find the live version and source codes in the following locations:

Also checkout the product home page and part-II of Tod’s unofficial faq.

In this post, I will show you how to create a basic CRUD(Create/Read/Update/Delete) application with our new MVC Grid. I will be using both Entity Framework v1.0 with the default web form view engine and NHibernate with Spark to create the CRUD screens for the Customer table of Northwind database.

Lets start with the Entity Framework and Default View Engine.

First, lets create a new ASP.NET MVC application and name it as Northwind, when Visual Studio prompts you for the Unit Test project, just skip it. Now, right click the Models folder and add an Entity Data Model and name it as Database. Next, drag the Customers table of the Northwind database from Server Explorer in the VS design surface.

Now, we will create a generic repository to access the database, lets define the interface first.

public interface IRepository<TEntity, TId>
{
    void Add(TEntity entity);

    void Delete(TId id);

    TEntity Get(TId id);

    IEnumerable<TEntity> All();
}

And the implementation:

public class Repository<TEntity, TId> : IRepository<TEntity, TId>
{
    private readonly Database database;

    public Repository() : this(EntityFrameworkObjectContextPerRequest.CurrentDatabase)
    {
    }

    public Repository(Database database)
    {
        this.database = database;
    }

    public void Add(TEntity entity)
    {
        database.AddObject(TypeName(), entity);
    }

    public void Delete(TId id)
    {
        database.DeleteObject(Get(id));
    }

    public TEntity Get(TId id)
    {
        var typeName = TypeName();

        var keyName = database.MetadataWorkspace
                              .GetItems<EntityType>(DataSpace.CSpace)
                              .Single(meta => meta.Name == typeName)
                              .KeyMembers[0].Name;

        var param = Expression.Parameter(typeof(TEntity), "x");
        var left = Expression.Property(param, keyName);
        var right = Expression.Constant(id);
        var equal = Expression.Equal(left, right);

        var predicate = Expression.Lambda<Func<TEntity, bool>>(equal, param).Compile();

        return All().SingleOrDefault(predicate);
    }

    public IEnumerable<TEntity> All()
    {
        return database.CreateQuery<TEntity>("[" + TypeName() + "]");
    }

    private static string TypeName()
    {
        return typeof(TEntity).Name;
    }
}

Very basic stuff,except the Get() method, since we are creating a generic repository which entity’s identifier is not known, we are using the Entity Framework meta data for building the identifier lambda expression. Please note that the stuffs that I have shown in the above has nothing to do with our Grid, we can directly use the Entity Framework ObjectContext in our Controller instead of this repository.

Now, right click the Controllers folder and a new Controller named CustomerController with the following code:

[HandleError]
public class CustomerController : Controller
{
    private readonly IRepository<Customers, string> repository;

    public CustomerController() : this(new Repository<Customers, string>())
    {
    }

    public CustomerController(IRepository<Customers, string> repository)
    {
        this.repository = repository;
    }

    public ActionResult Index()
    {
        return View(repository.All());
    }

    public ActionResult Details(string id)
    {
        return View(repository.Get(id));
    }

    public ActionResult Create()
    {
        return View();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create([Bind(Exclude = "CustomerId")]Customers customer)
    {
        customer.CustomerID = CreateNewId();
        repository.Add(customer);

        return RedirectToAction("Index");
    }

    public ActionResult Edit(string id)
    {
        return View(repository.Get(id));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(string id, FormCollection collection)
    {
        var customer = repository.Get(id);

        UpdateModel(customer, collection.ToValueProvider());

        return RedirectToAction("Index");
    }

    public ActionResult Delete(string id)
    {
        return View(repository.Get(id));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Delete(string id, string confirm)
    {
        repository.Delete(id);

        return RedirectToAction("Index");
    }

    // Not a bullet proof method, but it should work for the demo
    private string CreateNewId()
    {
        Func<int, string> generateId = length =>
                                       {
                                           string generatingId = string.Empty;
                                           Random rnd = new Random();

                                           for (int i = 1; i <= length; i++)
                                           {
                                               int characterCode = rnd.Next(65, 90); // Only uppercase;
                                               generatingId += Convert.ToChar(characterCode).ToString();
                                           }

                                           return generatingId;
                                       };

        string id = generateId(5);

        while (repository.Get(id) != null)
        {
            id = generateId(5);
        }

        return id;
    }
}

There are one more thing we have to do before we start working on the Views, as you can see in the above the Repository uses a special class EntityFrameworkObjectContextPerRequest in the constructor to get the Database reference. EntityFrameworkObjectContextPerRequest is a HttpModule which creates a new instance of the Database in the BeginRequest and stores it in the HttpContext.Items to reuse it in the same request and in the EndRequest it commit the changes and Dispose the Database instance. Here is the code:

public class EntityFrameworkObjectContextPerRequest : IHttpModule
{
    private static readonly string key = typeof(Database).FullName;

    public static Database CurrentDatabase
    {
        get
        {
            return HttpContext.Current.Items[key] as Database;
        }
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += OnBeginRequest;
        context.EndRequest += OnEndRequest;
    }

    public void Dispose()
    {
    }

    private static void OnBeginRequest(object sender, EventArgs e)
    {
        var database = HttpContext.Current.Items[key] as Database;

        if (database == null)
        {
            database = new Database();
            HttpContext.Current.Items[key] = database;
        }
    }

    private static void OnEndRequest(object sender, EventArgs e)
    {
        var database = HttpContext.Current.Items[key] as Database;

        if (database != null)
        {
            database.SaveChanges();
            database.Dispose();
        }
    }
}

Now, add the Telerik.Web.Mvc.dll from the binary folder that you have downloaded previously.

Once you include the dll, the first thing you should do is register the HttpHandler for the Web Assets, lets put these lines in the web.config file (I am only showing the relevant section of the web.config):

<?xml version="1.0"?>
<configuration>
    <system.web>
        <httpHandlers>
            <add verb="GET,HEAD" path="asset.axd" validate="false" type="Telerik.Web.Mvc.WebAssetHttpHandler, Telerik.Web.Mvc" />
        </httpHandlers>
    </system.web>
    <system.webServer>
        <handlers>
            <remove name="AssetHandler" />
            <add name="AssetHandler" preCondition="integratedMode" verb="GET,HEAD" path="asset.axd" type="Telerik.Web.Mvc.WebAssetHttpHandler, Telerik.Web.Mvc" />
        </handlers>
    </system.webServer>
</configuration>

Now, Copy the telerik.common.min.css, telerik.vista.min.css and Vista from the download Content folder to your project Content folder. We also have to copy few javascripts files, please copy the telerik.common.min.js, telerik.grid.min.js and telerik.grid.filtering.min.js from the downloaded Scripts folder to your project Scripts folder. Once you are done, it will look very similar to the following:

SE

Now open the Site.Master from Views/Shared directory and put the following lines in the head section:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <%= Html.Telerik().StyleSheetRegistrar()
                      .DefaultGroup(group => group.Add("Site.css")
                                                  .Add("telerik.common.css")
                                                  .Add("telerik.vista.css")
                                                  .Combined(true)
                                   ) %>
</head>

This will ensure the stylesheets files that we have added in the previous steps will be included when the page renders. We have do the same for javascript files, but this time we will only add a ScriptRegistrar at the bottom of the page, but we will not add the javascript files like we did for the stylesheet files. Lets put the following code at the bottom of the page:

    <% Html.Telerik().ScriptRegistrar()
                     .DefaultGroup(group => group.Combined(true).Compress(true))
                     .Render(); %>
</body>
</html>

Now, go back to the Index method of the CustomerController, right click and select Add View, this will show the Add View Dialog. When the dialog appears, turn on the strongly typed view and select the Customers class as the view data class and finally select the List in View content drop down.

AV

Once the view is generated replace the generated code with the following:

<% Html.Telerik()
       .Grid(Model)
       .Name("customers")
       .PrefixUrlParameters(false)
       .Columns(columns =>
                {
                    columns.Add(c =>
                                {
                                    %>
                                        <%= Html.ActionLink("Edit", "Edit", new { id = c.CustomerID })%>
                                        <%= Html.ActionLink("Delete", "Delete", new { id = c.CustomerID })%>
                                    <%
                                }).Title("Action");

                    columns.Add(c => c.CompanyName).Width(200);
                    columns.Add(c => c.ContactName);
                    columns.Add(c => c.Address);
                    columns.Add(c => c.City);
                    columns.Add(c => c.PostalCode);
                    columns.Add(c => c.Country);
                    columns.Add(c => c.Phone);
                    columns.Add(c => c.Fax);
                })
        .Filterable()
        .Sortable(sort => sort.SortMode(GridSortMode.MultipleColumn))
        .Pageable()
        .Scrollable(scrolling => scrolling.Height(250))
        .Render(); %>

Now press F5 and navigate to /Customers, you will find a nice looking Grid like the following:

GRID

As you can see the Grid has out of the box support for data sorting/filtering/paging, all you have to do is set an IEnumerable<T> as its DataSource, the rest is taken care by itself. If the IEnumerable<T> has a Linq Provider the data will be processed at the database level. If you run the SQL Profiler for the above example and navigate to page 2 you will find the following SQL is generated:

SELECT TOP (10) 
[Project1].[CustomerID] AS [CustomerID], 
[Project1].[CompanyName] AS [CompanyName], 
[Project1].[ContactName] AS [ContactName], 
[Project1].[ContactTitle] AS [ContactTitle], 
[Project1].[Address] AS [Address], 
[Project1].[City] AS [City], 
[Project1].[Region] AS [Region], 
[Project1].[PostalCode] AS [PostalCode], 
[Project1].[Country] AS [Country], 
[Project1].[Phone] AS [Phone], 
[Project1].[Fax] AS [Fax]
FROM ( SELECT [Project1].[C1] AS [C1], [Project1].[CustomerID] AS [CustomerID], [Project1].[CompanyName] AS [CompanyName], [Project1].[ContactName] AS [ContactName], [Project1].[ContactTitle] AS [ContactTitle], [Project1].[Address] AS [Address], [Project1].[City] AS [City], [Project1].[Region] AS [Region], [Project1].[PostalCode] AS [PostalCode], [Project1].[Country] AS [Country], [Project1].[Phone] AS [Phone], [Project1].[Fax] AS [Fax], row_number() OVER (ORDER BY [Project1].[C1] ASC) AS [row_number]
	FROM ( SELECT 
		CASE WHEN ([Extent1].[CustomerID] IS NOT NULL) THEN [Extent1].[CustomerID] END AS [C1], 
		[Extent1].[CustomerID] AS [CustomerID], 
		[Extent1].[CompanyName] AS [CompanyName], 
		[Extent1].[ContactName] AS [ContactName], 
		[Extent1].[ContactTitle] AS [ContactTitle], 
		[Extent1].[Address] AS [Address], 
		[Extent1].[City] AS [City], 
		[Extent1].[Region] AS [Region], 
		[Extent1].[PostalCode] AS [PostalCode], 
		[Extent1].[Country] AS [Country], 
		[Extent1].[Phone] AS [Phone], 
		[Extent1].[Fax] AS [Fax]
		FROM [dbo].[Customers] AS [Extent1]
	)  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 10
ORDER BY [Project1].[C1] ASC

The rest of the screens are pretty simple, just use the ASP.NET MVC Add View dialog to implement them and that is it for the Entity Framework and Web form view engine.

Now, lets do the same for NHibernate and Spark.

First, Create a new ASP.NET MVC Project named AltNorthwind and skip the Unit Test Project. Now, the add following the references:

  • NHibernate
  • NHibernate.Linq
  • FluentNHibernate
  • Spark.

Now, right click the Models folder add a new class named Customers and create simple getter/setter properties for the Customers table fields, also ensure the properties are declared as virtual.Next, Add a new class named CustomerMap which will be used by the NHibernate to map the Customers class with the Customer table.

public class CustomerMap : ClassMap<Customers>
{
    public CustomerMap()
    {
        Id(c => c.CustomerID).Length(5).Not.Nullable();
        Map(c => c.CompanyName).Length(40).Not.Nullable();
        Map(c => c.ContactName).Length(30);
        Map(c => c.ContactTitle).Length(30);
        Map(c => c.Address).Length(60);
        Map(c => c.City).Length(15);
        Map(c => c.Region).Length(15);
        Map(c => c.PostalCode).Length(10);
        Map(c => c.Country).Length(15);
        Map(c => c.Phone).Length(24);
        Map(c => c.Fax).Length(24);
    }
}

Lets create the Repository, we will be using the same IRepository interface that we used in the Entity Framework section.

public class Repository<TEntity, TId> : IRepository<TEntity, TId>
{
    private readonly ISession session;

    public Repository() : this(NHibernateSessionPerRequest.CurrentSession)
    {
    }

    public Repository(ISession session)
    {
        this.session = session;
    }

    public void Add(TEntity entity)
    {
        session.SaveOrUpdate(entity);
    }

    public void Delete(TId id)
    {
        session.Delete(Get(id));
    }

    public TEntity Get(TId id)
    {
        return session.Get<TEntity>(id);
    }

    public IEnumerable<TEntity> All()
    {
        return session.Linq<TEntity>();
    }
}

As we can see that we are using the NHibernate ISession which is passed in the constructor similar to the Entity Framework version, we are also using an HttpModule for managing NHibernate session.

public class NHibernateSessionPerRequest : IHttpModule
{
    private static readonly ISessionFactory sessionFactory = CreateSessionFactory();

    public static ISession CurrentSession
    {
        get
        {
            return sessionFactory.GetCurrentSession();
        }
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += OnBeginRequest;
        context.EndRequest += OnEndRequest;
    }

    public void Dispose()
    {
    }

    private static void OnBeginRequest(object sender, EventArgs e)
    {
        var session = sessionFactory.OpenSession();

        session.BeginTransaction();

        CurrentSessionContext.Bind(session);
    }

    private static void OnEndRequest(object sender, EventArgs e)
    {
        var session = CurrentSessionContext.Unbind(sessionFactory);

        if (session != null)
        {
            try
            {
                session.Transaction.Commit();
            }
            catch
            {
                session.Transaction.Rollback();
            }
            finally
            {
                session.Close();
                session.Dispose();
            }
        }
    }

    private static ISessionFactory CreateSessionFactory()
    {
        const string ConnectionStringName = "NorthwindConnectionString";

        var configuration = Fluently.Configure()
                                    .Database(MsSqlConfiguration.MsSql2005.ConnectionString(x => x.FromConnectionStringWithKey(ConnectionStringName)))
                                    .ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
                                    .Mappings(m => m.FluentMappings.AddFromAssemblyOf<NHibernateSessionPerRequest>());

        return configuration.BuildSessionFactory();
    }
}

The Controller is same as the Entity Framework version. Now, lets configure the Spark view engine, first put the following lines in the web.config file.

<configSections>
    <section name="spark" type="Spark.Configuration.SparkSectionHandler, Spark"/>
</configSections>
<spark>
    <compilation debug="true">
        <assemblies>
            <add assembly="AltNorthwind"/>
            <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        </assemblies>
    </compilation>
    <pages automaticEncoding="true">
        <namespaces>
            <add namespace="System.Collections.Generic"/>
            <add namespace="System.Linq"/>
            <add namespace="System.Web.Mvc"/>
            <add namespace="System.Web.Mvc.Html"/>
            <add namespace="System.Web.Routing"/>
            <add namespace="Telerik.Web.Mvc.UI"/>
        </namespaces>
    </pages>
</spark>

And register the view engine in the global.asax, like the following:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    SparkEngineStarter.RegisterViewEngine();
}

Now, Copy the telerik.common.min.css, telerik.vista.min.css and Vista from the download Content folder to your project Content folder and  telerik.common.min.js, telerik.grid.min.js and telerik.grid.filtering.min.js from the downloaded Scripts folder to your project Scripts folder, same as we did in the Entity Framework version.

Now, lets add an application.spark file in Views/Shared for the stylesheets and javascripts and add the following code in the head section for the stylesheets registration:

<% Html.Telerik().StyleSheetRegistrar()
                  .DefaultGroup(group => group.Add("Site.css")
                                              .Add("telerik.common.css")
                                              .Add("telerik.vista.css")
                                              .Combined(true)
                               ) 
                   .Render(); %>

And at the bottom for the javascript files:

<% Html.Telerik().ScriptRegistrar()
                 .DefaultGroup(group => group.Combined(true).Compress(true))
                 .Render(); %>

Now, create a new folder named Customer under the Views folder and add new spark file named Index.spark and put the following code:

<content name="TitleContent">
    Customers
</content>
<content name="MainContent">
    <h2>Customers</h2>
    <viewdata model="IEnumerable[[Customers]]"/>
    <% Html.Telerik()
           .Grid(Model)
           .Name("customers")
           .PrefixUrlParameters(false)
           .Columns(columns =>
                    {
                        columns.Add(c =>
                                    {
                                        %>
                                            !{ Html.ActionLink("Edit", "Edit", new { id = c.CustomerID })}
                                            !{ Html.ActionLink("Delete", "Delete", new { id = c.CustomerID })}
                                        <%
                                    }).Title("Action");

                        columns.Add(c => c.CompanyName).Width(200);
                        columns.Add(c => c.ContactName);
                        columns.Add(c => c.Address);
                        columns.Add(c => c.City);
                        columns.Add(c => c.PostalCode);
                        columns.Add(c => c.Country);
                        columns.Add(c => c.Phone);
                        columns.Add(c => c.Fax);
                    })
            .Sortable(sort => sort.SortMode(GridSortMode.MultipleColumn))
            .Pageable()
            .Scrollable(scrolling => scrolling.Height(250))
            .Render(); %>
    <p>!{ Html.ActionLink("Create New", "Create") }</p>
</content>

Once you are done, press F5 and navigate to /Customer, you will find a nice looking Grid. I am skipping the rest of the views as they are pretty easy to implement, but you will find the complete code in the following link.

That’s it for today, I hope I will be posting more on these components, so stay tuned.

Source Codes: TelerikMVCGridCRUDDemo.zip (Sorry for the earlier inconvenience, just made it public, it is accessible now.)

Shout it
More Posts