Friday, August 28, 2009

Getting Started with Telerik Extensions for ASP.NET MVC

I initially decided to try out the Telerik Extensions for ASP.NET MVC because I was looking to create a EditorTemplate (ASP.NET MVC v2 Preview 1 feature) for Date and I wanted to use the jQuery UI DatePicker wrapper that Telerik provides.

Not only was I pleased with the Fluent Interface that they used to wrap the jQuery UI Plugins, but I was also surprised and impressed by their Script and Style management system.

Web.config Changes

The first thing you’ll need to do is to make a couple of web.config changes to the Handler sections before starting to use the Telerik Extensions.

<?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>

Including jQuery & jQuery UI

Before you can start using the jQuery UI wrappers, you’ll need to first include jQuery & jQuery UI into your project. For my simple project, I just did the following…

Site.Master MasterPage

<%@ Import Namespace="Telerik.Web.Mvc.UI"%>
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <% Html.Telerik().StyleSheetRegistrar()
        .DefaultGroup(group => group
            .Add("Site.css")
            .Add("jquery-ui-1.7.2.custom.css").Render(); %>              
</head>        
<body>
    <!-- Misc -->
    <%  Html.Telerik().ScriptRegistrar().Render(); %>            
</body>
</html>

The code will automatically insert jQuery by default and if you happened to use one of the Telerik jQuery UI Extensions then it will insert jQuery UI as well.

Note: It is important to know that the ScriptRegistrar must be directly before the end body tag in order to work.

Date EditorTemplate

Before creating the EditorTemplate, I annotated the DateOfBirth property with DataType.Date so the Template framework has additional metadata to work with.

Employee.cs View Model

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NestedTemplates.Helpers;

namespace TelerikExtensions.Models
{
    public class Employee
    {
        [UIHint("Hidden")]
        public long Id { get; set; }

        [Required(ErrorMessage = "First Name Required")]
        [StringLength(50, ErrorMessage = "Must be less than 50 characters")]
        [DisplayName("First Name")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name Required")]
        [StringLength(50, ErrorMessage = "Must be less than 50 characters")]
        [DisplayName("Last Name")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Social Security Required")]
        [StringLength(50, ErrorMessage = "Must be less than 50 characters")]
        [RegularExpression(@"^[0-9]{3}-?[0-9]{2}-?[0-9]{4}$", ErrorMessage = "Invalid Social Security Number Format")]
        [DisplayName("Social Security Number")]
        public string SocialSecurity { get; set; }

        [Required(ErrorMessage = "Date of Birth Required")]
        [StringLength(50, ErrorMessage = "Must be less than 50 characters")]
        [DataType(DataType.Date, ErrorMessage = "Date of Birth must be expressed as a date")]
        [DisplayName("Date of Birth")]
        public DateTime DateOfBirth { get; set; }
    }
}

Then I went ahead and made a EditorTemplate called Date.ascx (which matches the DataType.Date name) using the Telerik DatePicker Extension

Date.ascx EditorTemplate

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<%@ Import Namespace="Mvc.UI.jQuery"%>

<% Html.jQuery().DatePicker()
    .Name("dateField")
    .Value((DateTime) Model).Render(); %>

Since we annotated the DateOfBirth property in our View Model with DataType.Date there isn't anything really special you need to do for ASP.NET MVC v2 Preview 1 to use it other than calling Html.EditorFor(m => m.DateOfBirth). Behind the scenes it matches up the types and uses the above EditorTemplate.

Edit.aspx View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TelerikExtensions.Models.Employee>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Edit
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Edit</h2>
    
    <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
    
    <% using (Html.BeginForm()) {%>
        <p>
            <%= Html.LabelFor(m => m.FirstName) %>
            <%= Html.EditorFor(m => m.FirstName) %>
            <%= Html.ValidationMessage("FirstName", "*") %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.LastName) %>
            <%= Html.EditorFor(m => m.LastName) %>
            <%= Html.ValidationMessage("LastName", "*") %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.SocialSecurity) %>
            <%= Html.EditorFor(m => m.SocialSecurity) %>
            <%= Html.ValidationMessage("SocialSecurity", "*") %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.DateOfBirth) %>
            <%= Html.EditorFor(m => m.DateOfBirth) %>
            <%= Html.ValidationMessage("DateOfBirth", "*") %>
        </p>   
        <p>
            <input type="submit" value="Save" />
        </p>
    <% } %>
    
    <div>
        <%=Html.ActionLink("Back to List", "Index") %>
    </div>
</asp:Content>

jQuery UI Goodness

The result is what you would expect if you had manually wired up the jQuery UI DatePicker into your project, but this time with strong type checking and a nice Fluent Interface.

jQueryUiDatePickerGoodness

Compress, Combine, & Cache

Now, what I thought was even cooler than that was that I could specify to combine, compress, and cache the scripts & styles… how cool is that!?!

<%@ Import Namespace="Telerik.Web.Mvc.UI"%>
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <% Html.Telerik().StyleSheetRegistrar()
        .DefaultGroup(group => group
            .Add("Site.css")
            .Add("jquery-ui-1.7.2.custom.css")
            .Combined(true)
            .Compress(true)).Render(); %>              
</head>        
<body>
    <!-- Misc -->
    <%  Html.Telerik().ScriptRegistrar().
            DefaultGroup(group => group
                .Combined(true)
                .Compress(true)
                .CacheDurationInDays(365)).Render(); %>            
</body>
</html>

Instead of compressing, combining, & caching everything together you could also choose to separate like scripts in groups like the following…

<% Html.Telerik().ScriptRegistrar().Scripts(script => script
        .AddGroup("Validation", group => group
            .Add("~/Content/Scripts/jquery.validate.min.js")
            .Add("~/Content/Scripts/xVal.jquery.validate.js")
            .Combined(true)
            .Compress(true)
            .CacheDurationInDays(365))
        .AddGroup("TableSorter", group => group
            .Add("~/Content/Scripts/metadata.js")
            .Add("~/Content/Scripts/jquery.tablesorter.js")
            .Add("~/Content/Scripts/jquery.tablesorter.pager.js")
            .Combined(true)
            .Compress(true)
            .CacheDurationInDays(365))                
    ).Render(); %>   

Gotchas

The next step was to integrate the test code into my main project. I had some initial issues resolving the jQuery & jQuery UI scripts. Initially, the Telerik Extensions look in the standard Scripts folder that the ASP.NET MVC project creates. However, I moved my Scripts folder under the Content folder alongside the Styles folder. Thanks to Todd Anglin, Kazi Manzur Rashid, & some research into their codebase I was able to put the following two lines of code in my Global.asax to override the default folder for the scripts.

using System.Web.Mvc;
using System.Web.Routing;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.UI;

namespace TelerikExtensions
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);

            ScriptRegistrar.FrameworkScriptPath = "~/Content/Scripts";
            WebAssetDefaultSettings.ScriptFilesPath = "~/Content/Scripts";
        }
    }
}

Summary

All in all, this is a very cool open source library that I think would be beneficial for most any ASP.NET MVC application.

Feel free to download the sample project I put together using most of the above code.

No comments:

Post a Comment