[Update: I have updated the images with a recent version which more clarifies the whole picture, the result shows the parallel version is 2x fast comparing to the plain.]

In this post, I will give you a sneak preview of the ScriptRegistrar major enhancement. Those who have checked our ScriptRegistrar or followed my blog for last few weeks already know that it has out of box support for Grouping. Combining, Caching, Compressing, Synchronizing statements between Master/Content and as far as I know, no other component including the other commercial vendors has all these features that we are providing for free and as well as open sourced. Currently we are working on parallel script downloading support that we are going to include in our next release, if you do not know about parallel script downloading let me first show you a regular example, lets say you have a page which contains five script tags:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Plain Script Loading</title>
</head>
<body>
    <h1>Plain Script Loading example</h1>
    <p>Here goes the sample body..</p>
    <div id="status" style="font-family:Consolas;background-color:#ccc"></div>
    <% string ticks = DateTime.Now.Ticks.ToString(); %>
    <script type="text/javascript" src="Scripts/plainScript1.js?t=<%= ticks %>"></script>
    <script type="text/javascript" src="Scripts/plainScript2.js?t=<%= ticks %>"></script>
    <script type="text/javascript" src="Scripts/plainScript3.js?t=<%= ticks %>"></script>
    <script type="text/javascript" src="Scripts/plainScript4.js?t=<%= ticks %>"></script>
    <script type="text/javascript" src="Scripts/plainScript5.js?t=<%= ticks %>"></script>
    <script type="text/javascript">
        document.getElementById('status').innerHTML += 'You must have seen all the messages.<br>';
    </script>
</body>
</html>

If you run the above page with Firebug, you will find, it loads the page similar to the following:

plain

Check the above red marked area which shows as steps of a stairway. It indicates when a browser encounters a script tag it stops its rendering until it downloads the script file. Since we have included five script files there are five steps (total six, the first one is for the page), each for a script. Now lets add the same number of script files with our ScriptRegistrar:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Next ScriptRegistrar</title>
</head>
<body>
    <h1>ScriptRegistrar : Parallel script loading example</h1>
    <p>Here goes the sample body..</p>
    <div id="status" style="font-family:Consolas;background-color:#ccc"></div>
    <% string ticks = DateTime.Now.Ticks.ToString(); %>
    <% Html.Telerik().ScriptRegistrar()
                     .Scripts(script =>
                              script.Add("~/Scripts/parallelScript1.js?t=" + ticks)
                                    .Add("~/Scripts/parallelScript2.js?t=" + ticks)
                                    .Add("~/Scripts/parallelScript3.js?t=" + ticks)
                                    .Add("~/Scripts/parallelScript4.js?t=" + ticks)
                                    .Add("~/Scripts/parallelScript5.js?t=" + ticks)
                             )
                     .OnDocumentReady(() =>
                                      {%>
                                        document.getElementById('status').innerHTML += 'You must have seen all the messages.<br>';
                                      <%}
                                     )
                     .Render(); %>
</body>
</html>

(I have intentionally removed the default jQuery script form the ScriptRegistrar and did not merge it as a group to show the parallelization).

When you run the ScriptRegistrar version with the Firebug, you will get the following picture:

parallel

Now check the red marked area again, as you can see there are no steps which means download of this scripts starts almost at the same time, we are initially loading our tiny script loader(Telerik.ScriptRegistrar.js) which then loads these scripts without blocking each other. We are still working on different cases and how you can more easily define the dependencies between the groups/script files.

So to those folks who thinks that a msbuild/nant task that minifies and combines script files of a predefined directory on post build and a ScriptInclude helper method is all the optimization that can be done when it comes to script management, maybe it is okay for a small application where the number of javascript files are same or less than your hand’s finger, but for today’s highly ajax application, there are a lot of cool things that you can do to enhance its performance and we are fully committed for doing that.

Shout it

Convention over Configuration is getting more and more popular in the .NET space, especially after the release of ASP.NET MVC. In our extensions, there are quite a few places where you will find this concept, lets consider the following snippet of ScriptRegistrar:

<% Html.Telerik().ScriptRegistrar()
                 .Scripts(script => script.AddGroup(
                                                        "validation",
                                                        group => group.Add("jquery.validate.js")
                                                                      .Add("xVal.jquery.validate.js")
                                                                      .Combined(true)
                                                   )
						 )
				 .Render(); %>

Check that we did not mention any path for the script files, by default it assumes that all the script files are located in the default “Scripts” folder that is created when you create a ASP.NET MVC Application in Visual Studio. Now what if the script files resides in a different folder.

<% Html.Telerik().ScriptRegistrar()
                 .Scripts(script => script.AddGroup(
                                                        "validation",
                                                        group => group
                                                                      .DefaultPath("~/Scripts/Validation") // Setting the default path of a Group.
                                                                      .Add("jquery.validate.js")
                                                                      .Add("xVal.jquery.validate.js")
                                                                      .Combined(true)
                                                   )
						 )
				 .Render(); %>

Now what if one of the file in a group resides in a different folder:

<% Html.Telerik().ScriptRegistrar()
                 .Scripts(script => script.AddGroup(
                                                        "validation",
                                                        group => group
                                                                      .DefaultPath("~/Scripts/Validation")
                                                                      .Add("jquery.validate.js")
                                                                      .Add("~/SomeOtherPath/xVal.jquery.validate.js") // Check that we are setting the complete path.
                                                                      .Combined(true)
                                                   )
						 )
				 .Render(); %>

Okay, now what if you want to change the default location of both scripts and stylesheet files, Just add the following lines in your application start (global.asax maybe):

WebAssetDefaultSettings.StyleSheetFilesPath = "~/assets/stylesheets";
WebAssetDefaultSettings.ScriptFilesPath = "~/assets/scripts";

So by convention we are assuming the default locations (for script “~/Scripts” and Stylesheet “~/Content”) but with configuration (the above 2, 3, 4 code snippets) you can completely override it. Let me show one more case before completing this post.

We all know we should minify our script and stylesheet files with tools like Yahoo compressor before making the application live. Lets say we have minified the above two files with a minifying tool and saved in the Scripts directory with .min.js extension. Now, if you run the application in release mode debug = “false” in web.config, you will find that the ScriptRegistrar is picking up the .min.js instead of .js or debug.js and the vice versa when debug=”true” and the same holds true for StyleSheetRegistrar.

Shout it

Recently there has been a talk of Actionless Controller in ASP.NET MVC space. Basically we are doing a lot of things in the controller and that is the reason we often finds our controllers with number of dependencies in the constructor. Jeffrey Palermo initially suggested a version where he separated each action as a standalone controller which is good to reduce the number of dependency but it does not sound right to me. At the bottom of that post Neal Blomfield hinted about the Command Pattern which makes absolute sense. Since the release of our initial version, I have got some time to breathe, so I decided to give it a try. Initially the idea was, each Controller action method will accept a command object as parameter and the Controller will call the Execute method. The Command object will be responsible to perform the action which the Controller action methods usually do. Let me show you a sample controller which has nothing but some CRUD methods with Command objects:

public class ProductController : Controller
{
    public ActionResult Index(ProductListCommand command)
    {
        return command.Execute();
    }

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

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(ProductCreateCommand command)
    {
        return command.Execute();
    }

    public ActionResult Edit(ProductGetCommand command)
    {
        return command.Execute();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(ProductUpdateCommand command)
    {
        return command.Execute();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Delete(ProductDeleteCommand command)
    {
        return command.Execute();
    }
}

Now, let see what is happening inside the Commands, first the ProductCreateCommand:

public class ProductCreateCommand : RESTifyCommand
{
    private readonly IRepository<Product> repository;

    public ProductCreateCommand(IRepository<Product> repository)
    {
        this.repository = repository;
    }

    public Product Product
    {
        get;
        set;
    }

    protected override void ExecuteCore()
    {
        // You should use a powerful validation engine like DataAnnotation or xVal 
        // over here, I am using the dumb if/else for the shake of simplicity.

        if (string.IsNullOrEmpty(Product.Name))
        {
            ModelState.AddModelError("Product.Name", "Name cannot be blank.");
        }

        if (!Product.Price.HasValue)
        {
            ModelState.AddModelError("Product.Price", "Price cannot be blank.");
        }

        if (Product.Price.HasValue && (Product.Price <= 0))
        {
            ModelState.AddModelError("Product.Price", "Invalid price. Price cannot be zero or negative.");
        }

        if (ModelState.IsValid)
        {
            repository.Create(Product);
            ViewData.Model = Product;
        }
    }
}

First, it is inherited from a special base class RESTifyCommand, next we are injecting our dependency in the constructor as we do for the Controllers and at last, we are overriding the ExecuteCore method to perform our actual operation. Also check that we not writing any code to populate the Product, instead we are relying on the ASP.NET MVC Framework to populate it for us. The little difference between the ExecuteCore with a regular Controller action method is instead of returning any ActionResult(the method is a void()) we are only setting up the Model and ModelState. I will get back to the Execute() method after a little while, for the moment lets see how this Command object is created and passed backed to the Controller. One of the important thing when creating the Command Object is, we have to inject the dependencies in its constructor or more specifically we have to give the opportunity so that you can use your prefered IoC/DI framework to create these commands, also we need to utilize the rich ASP.NET MVC underlying features to automatically populate the other parts of the Command (for ex the Product property of the above example) and the thing that I really love of ASP.NET MVC framework is its extensibility. To create and populate the Command object we use a custom ModelBinder. Our ModelBinder is inherited from the DefaultModelBinder of ASP.NET MVC as we want to reuse the common behavior of the default binder, but instead of overriding the BindModel that you often see in most of the available ModelBinders, we are overriding the CreateModel method.

public class CommandBinder : DefaultModelBinder
{
    private readonly Func<Type, IRESTifyCommand> factory;
    private readonly IFormatDetector formatDetector;

    public CommandBinder(Func<Type, IRESTifyCommand> factory, IFormatDetector formatDetector)
    {
        Invariant.IsNotNull(factory, "factory");
        Invariant.IsNotNull(formatDetector, "formatDetector");

        this.factory = factory;
        this.formatDetector = formatDetector;
    }

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        Invariant.IsNotNull(controllerContext, "controllerContext");
        Invariant.IsNotNull(modelType, "modelType");

        IRESTifyCommand command = factory(modelType);
        RESTifyFormats format = formatDetector.Detect(controllerContext.HttpContext.Request);

        command.SetRequestFormat(format);
        command.SetControllerContext(controllerContext);

        return command;
    }
}

As you can see in the constructor we are accepting a Func to create the Command so that we do not have to refer to any external IoC/DI component, we are also accepting another object IFormatDetector. Initially, my plan was to introduce the Command object in ASP.NET MVC, but after seeing both Nate Kohari’s Siesta and MS ASP.NET MVC REST SDK, I thought why not add some REST love into this. So the idea was after the Command objects performs its actual operation which we have seen in the above, it should also decide in which format it will return the data, the associated controller should not have any clue about this, all it has to do is call the Excecute() method of command and the rest it taken care by the Command. In the above, the formatDetector detects the requested format from HttpRequest and sets the Command requested format, so that Command can later return the result depending upon that. Although it is in very early stages and I am still working on it, yet there are some advantages of using it comparing to the above:

  1. Out of the box support for HTML/XML/JSON/JSONP. There is no generic representation of ATOM/RSS, you need to write special formatters(IResultFormatter) to support those.
  2. Serialization support other than DataContractSerializer, which means you can serialize anything including the anonymous objects.
  3. Support for Registering JavaScriptConverter to shape up the json output. (It is still missing in the ASP.NET MVC Framework).
  4. Support for Registering KnownTypes for the DataContractSerializer.
  5. Fully extensible, anything can be replaced without modifying the core.
  6. 200+ Unit Tests which include more than 99% code coverage.

You can view the complete live demo, over here. As you can see the same Controller method is responsible for returning different formats, no condition checking, no if/else nothing, just call the Execute() of the Command. In the demo, I am using plain javascript instead of any library like jQuery/ASP.NET AJAX to show you that it is completely independent of the client side, you can use your prefered javascript library with this extension when working with your browser application.

Now lets get back to the original discussion, so far we have set the Model and ModelState, but did not see how the result is delivered to the client, lets see what is happening in the RESTIfyCommand’s Execute method:

public virtual ActionResult Execute()
{
    if (!CanExecute)
    {
        return Unsupported();
    }

    ExecuteCore();

    return BuildResult();
}

First, we are checking whether the Command can execute, there are severeal reasons a Command cannot execute, for example the request format is not supported by the Command, which usually happens when you want to restrict certain formats from delivering to the client or formatter for the requested type is not registered or your code as you can override the method, if the command cannot execute we are returning a special ActionResult RESTifyUnsupportedResult which is basically returning HttpStatusCode 415(Unsupported Media Type). If the command is allowed to execute then it is calling the ExecuteCore() which means the code that we have written in the above and finally it is returning the result. Now lets see the code of BuildResult method.

protected virtual ActionResult BuildResult()
{
    if (RequestedFormat == RESTifyFormats.Html)
    {
        return new ViewResult { ViewData = ViewData, TempData = TempData };
    }

    IResultFormatter formatter = ResultFormatters[RequestedFormat];

    if (RequestedFormat == RESTifyFormats.Jsonp)
    {
        IJsonpResultFormatter jsonpFormatter = formatter as IJsonpResultFormatter;

        if (jsonpFormatter == null)
        {
            throw new InvalidOperationException(TextResource.InvalidResultFormatterTypeRegisteredForJsonp.FormatWith(formatter.GetType().FullName));
        }

        jsonpFormatter.JsonpCallbackFunction = JsonpFunctionNameDetector.Detect(HttpContext.Request);
    }

    return new RESTifyResult(RequestedFormat, formatter, HttpContext.Request.ContentEncoding, Model, ModelState);
}

As you can see, for the Html, we are returning the regular ViewResult, next for JSONP we are checking whether the registered formatter is appropriate for JSONP, as JSONP needs the additional callback method name, the rest of the part is very simple, we are returning our custom RESTifyResult with the required data, the RESTifyResult uses the provided ResultFormatter to write in the Response.

public override void ExecuteResult(ControllerContext context)
{
    Invariant.IsNotNull(context, "context");

    HttpResponseBase response = context.HttpContext.Response;

    response.Clear();

    response.ContentType = contentTypes[Format];

    if (ContentEncoding != null)
    {
        response.ContentEncoding = ContentEncoding;
    }

    string output = Formatter.Format(ContentEncoding, Model, ModelState);

    response.Write(output);
}

One last thing that you have to do, is adding some bootstrapping code in the global.asax or more preferably in your Bootstrapper.

public void ConfigureRESTify()
{
    RESTifySettings.Current
                   .SetCommandFactory(type => ServiceLocator.Current.GetInstance(type) as IRESTifyCommand)
                   .RegisterCommands();
}

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

    ConfigureServiceLocator();
    ConfigureRESTify();
}

We are setting the factory method which you have seen in the CommandBinder in the above in the SetCommandFactory, I usually prefer to abstract the underlying IoC/DI with the CommonServiceLocator, if you do not like it, you use your preferred DI directly over here. Next, we are registering all Commands with our CommandBinder by scanning the referenced assemblies, if you do not like to scan you can use the overloaded version which accepts specific types or single assembly.

Now let me answer some of the common questions:

Q: How do I redirect to a action after the command executes, for example, in a CRUD operation when an object is created I want to redirect back to Index for the Html and for the others formats I just want to return the data.

A: There are few overloaded methods in the Command which you can use to redirect to other actions for a special format after the command executes. For example, in the above ProductController’s Create you can use the following:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreateCommand command)
{
    return command.ExecuteAndRedirectToAction("Index").WhenFormatIsHtml();
}

The WhenFormatIsHtml is an extension methods there are other versions of it. Don’t worry about the validation it will not redirect if the validation fails (ModelState.IsValid == false).

Q: What about the other facilities that we often gets in the regular Controllers like Try/UpdateModel, UrlHelper etc etc.

A: Most of the facilities that are available in Controller is also available in Command but it does not includes those which are contradictory (methods that returns ActionResult (w/o redirecting) are excluded).

Q: Looks like the Command object itself inherited from Controller.

A: No, it is not. This is the declaration:

public abstract class RESTifyCommand : IRESTifyCommand, IHideObjectMembers

Q: How do I on/off supported format?

A: To on/off supported formats globally use the RESTifySettings like the following:

RESTifySettings.Current
               .SetCommandDefaultFormats(RESTifyFormats.Html | RESTifyFormats.Json);

Now all your command will only support html and json or you can override the SupportedFormats property if you want to set it for a specific command, for example, now the ProductListCommand will only support Html and ATOM:

public class ProductListCommand : RESTifyCommand
{
    protected override RESTifyFormats SupportedFormats
    {
        get
        {
            return RESTifyFormats.Html | RESTifyFormats.Atom;
        }
    }
}

Q: How do I register custom Result Formatter?

A: To register a formatter that will be applied to all, use the RegisterResultFormatter method, there is also Command specific version which will set the formatter for only that Command.

Q: How does the Serialization works?

A: Well when it comes to serialization in .NET Framework, we have two sets of serializers the regular serializers like XmlSerializer/JavaScriptSerializer and the new DataContractSerializers, but none of it can serve our purpose. For example, DataContractSerializers can only work when the serialized object is decorated with its required attributes, which means you cannot serialize anonymous objects or the objects that are beyond your control also if your object is buildup with other objects it requires to know those other objects. There are also issues with regular serializers. So instead of supporting only a single set, we are supporting both. When serializing we are first first checking whether the object is a known type to us, if it is a known type we use the DataContractSerializers if not we are using the regular serializers. By known type, I mean an object is decorated with DataContractSerializers attributes and it is registered to this extension. To register known types, you can use the RegisterKnownTypes method of RESTifySettings, it has similar overloaded versions of RegisterCommands.

Q: How can I play with it, where is the source code located?

A: You can check the live version over here or if you want to get the source, checkout my first open source project in GitHub.

That’s it for the day.
Shout it

[Update: We have just upload the source code in CodePlex. Check it out.]
[Update: Also checkout the Atanas post where he detailed our plans and goals of this product.]

Today, we have released our first public community technology preview of our ASP.NET MVC Extensions. This is a very lightweight framework for developing ASP.NET MVC View Components that we are working for last few months. There are few key points that I like to mention:

  • It is Open Source (dual licensed), yes you heard me. It is the first Open Source project of Telerik released under MS-PL.
  • It is free for the time being we will be developing our commercial components based upon this tiny framework.
  • It is based upon jQuery, yes we know how much the community loves it.
  • It supports multiple view engines. The sample contains the Webform, Spark and NHaml examples.
  • The CTP contains the popular jQueryUI components as a proof of concepts but you can use it in your application with complete confidence. Since, we will be releasing our own version, it would be difficult for us to give it the same priority, instead we expect the open source community will be responsible for its further development(enhancing, bug fixing and adding new components) and we will be accepting patch/new components for the jQuery components from the community.

You can download it with complete source/tests/samples/docs from our ASP.NET MVC Product page (we will be also uploading it in CodePlex), please check back if the page is still not available.

You can also check the jQueryUI Webform demo over here.

I will be blogging more about this very soon.

Stay tuned!!!

Shout it

As many of you know that I am currently involved in developing few UI Components for the ASP.NET MVC Framework (Hint: It is not a personal project, and we do have the plan to make it the source open dual license). In this post, I will discuss about our design decisions regarding how we plan to manage the javascript files with our UI Components.

When developing a typical web application we usually find four kinds of javascript files.

  • Framework scripts like jQuery, ExtJS or maybe MS Ajax etc.
  • UI Component/Plug-in scripts like jQuery UI, jQuery Tools, jQuery validation/forms plug-ins etc that depends upon the framework script.
  • Application level common scripts that are shared among multiple pages and might depends upon the above two.
  • Page scripts(not embedded in html, rather as external file) that might depends upon on the above three.

To make the application really responsive/fast we often have to merge/compress/cache these javascript files. Currently most of the framework in .NET world (including the latest ASP.NET AJAX 3.5) supports combining the scripts in a single response. But this is not an optimal option in most of the cases. Why? Because we are either downloading the same file content in different pages or we are downloading some unnecessary file content for a specific page (assuming that you have specified all your merged script in your master page). Certainly it is a one time issue, once the file is downloaded it will be cached and the user does not have to download it again, but does not it also indicate the incapability of your script management components, also this is not viable option in today's heavily ajax sites.. There are also few other considerations like how can I load the scripts from a CDN (free/paid), does it render the scripts at the bottom of the pages etc etc. While considering all the above facts, we think the best way to serve scripts is, if it is merged in groups. Lets consider the following scenario, each url is using the listed javascript files:

http://mysite.com/List http://mysite.com/View/3 http://mysite.com/Edit/3
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.template.js
  4. jquery.pagination.js
  5. Utility.js
  6. Search.js
  7. List.js
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.template.js
  4. jquery.pagination.js
  5. Utility.js
  6. View.js
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.validate.js
  4. jquery.form.js
  5. jquery.watermark.js
  6. Utility.js
  7. Edit.js

We can group the above scripts, in the following groups:

  1. jQueryBase: jquery-1.3.2.js, jquery-ui 1.7.2.custom.js.
  2. ListView: jquery.template.js,jquery.pagination.js.
  3. Form: jquery.validate.js,  jquery.form.js, jquery.watermark.js.
  4. ListLocal: Utility.js, Search.js, List.js.
  5. ViewLocal: Utility.js, View.js.
  6. EditLocal: Utility.js, Edit.js.

Now, we can replace with the following:

http://mysite.com/List http://mysite.com/View/3 http://mysite.com/Edit/3
  1. jQueryBase
  2. ListView
  3. ListLocal
  1. jQueryBase
  2. ListView
  3. ViewLocal
  1. jQueryBase
  2. Form
  3. EditLocal

The benefits of the above comparing to individual file or single file combining are:

  1. We are sending less request to our web server (as the files are now grouped).
  2. We are not downloading the same file between the page visits (comparing to single file response).
  3. We are downloading the files that are only required for that visiting page.

With our Script Management Component it becomes really easy to achieve the above, for example, for the List you can use the following syntax:

<% Html.jQuery().ScriptRegistrar().Scripts(script => script.AddGroup( "jqueryBase",
                                                                        group => group.Add("~/Scripts/jquery-1.3.2.js")
                                                                                      .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
                                                                  )

                                                            .AddGroup( "ListView",
                                                                        group => group.Add("~/Scripts/jquery.template.js")
                                                                                      .Add("~/Scripts/jquery.pagination.js")
                                                                  )

                                                            .AddGroup("ListLocal",
                                                                        group => group.Add("~/Scripts/Utility.js")
                                                                                      .Add("~/Scripts/Search.js")
                                                                                      .Add("~/Scripts/List.js")
                                                                  )
                                        )
                                .Render(); %>

to configure each group setting you can use:

group => group.Add("~/Scripts/jquery-1.3.2.js")
              .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
              .Version("2.1)
              .Compress(true)
              .CacheDurationInDays(365)
              .Combined(true)

you can also use a CDN instead of loading the each group, it becomes really handy when your application becomes popular, to set the CDN you can use:

group => group.UseContentDeliveryNetwork(true)
              .ContentDeliveryNetworkPath("http//mycdn.com/myScriptGroup.js")

If you do not want to specify the same setting for each group again and again (DRY) you can set the default settings in the application start (global.asax) like the following:

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

    WebAssetDefaultSettings.CacheDurationInDays = 365;
    WebAssetDefaultSettings.Combined = true;
    WebAssetDefaultSettings.Compress = true;
    WebAssetDefaultSettings.Version = "2.1";
}

When you run the application and open the Firebug, you will find that each group is merged/compressed/cached like the following:

FB 

We have included some default behavior (AKA convention over configuration) backed into the script management components, for example when you are running the application in development mode (debug="true" in web.config) it will include the .debug.js and in release (debug="false") .min.js files no matter what filename you have mentioned in the ScriptRegistrar. If the file does not exist (.min.js/.debug.js) it will automatically fallback to the original value. When developing these components we take the YSlow rules very seriously, for example, when you use the ScriptRegistrar, it will render the script tags at bottom of the page, no matter how many ScriptRegistrar placed in the Master/Content/User Controls. Other than script files, you can also mention your startup and cleanup javascript statements in the ScriptRegistrar. For example, if you have the following:

In Master page:

<% Html.jQuery().ScriptRegistrar().Scripts(script => script.AddGroup( "jqueryBase",
                                                                        group => group.Add("~/Scripts/jquery-1.3.2.js")
                                                                                      .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
                                                                      )
                                            )
                                    .OnPageLoad(() =>
                                                {%>
                                                    test1.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test1.dispose();
                                                  <%}
                                                 )
                                .Render(); %>

and in Content Page:

<% Html.jQuery().ScriptRegistrar().OnPageLoad(() =>
                                                {%>
                                                    test2.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test2.dispose();
                                                  <%}
                                                 ); %>

and in User Control:

<% Html.jQuery().ScriptRegistrar().OnPageLoad(() =>
                                                {%>
                                                    test3.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test3.dispose();
                                                  <%}
                                                 ); %>

When the page renders, it will write the following:

<script type="text/javascript" src="http://weblogs.asp.net/asset.axd?id=eyJjdCI6ImJuIjoic2hCcnVzaFhtbC5qcyJ9XX1dfQ%3d%3d"></script>
<script type="text/javascript"> 
//<![CDATA[
jQuery(document).ready(function(){
test1.init();
test2.init();
test3.init();
});
jQuery(window).unload(function(){
test3.dispose();
test2.dispose();
test1.dispose();
});
//]]>
</script>
</body>
</html>

There should be a little difference in the Content and User Control code. There will no Render() for those two. Only the Master will have the Render() method.

And it is View Engine independent, we have already tested it in Webforms, Spark and NHaml.

What do you think? What features it is currently missing? Comments and suggestions are really appreciated.

Shout it

In my last post, two important issues are raised

  1. The justification of having server side components for jQuery UI.
  2. The style of syntax.

The intension of my last post was to get the feedback of the type of syntax the ASP.NET MVC developer prefers, so I did not mention anything on the server side side integration, this might be the reason why few people were unable to find the benefits of this server side support. In this post, I will try to show few simple examples of the server side integration, lets say that you are creating a Task submit form, you can use the Slider as completed percent field instead of regular input field, like the following:

View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Task>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Slider Value Form Submit Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% using(Html.BeginForm()){%>
        <fieldset>
            <legend>Submit Task</legend>
            <span style="color:Blue;font-weight:bold">
                <%= ViewData.Get<string>("successMessage") %>
            </span>
            <%= Html.ValidationSummary("Please correct the following errors:") %>
            <div>
                <label for="name">Name:</label>
            </div>
            <div style="margin-top:5px">
                <%= Html.TextBox("task.Name", null, new { style = "width:200px" })%>
            </div>
            <div style="margin-top:5px">
                <%= Html.ValidationMessage("task.Name")%>
            </div>
            <div style="margin-top:10px">
                Completed: <span id="completedPercent" style="color:Black"></span>%
            </div>
            <div style="margin-top:5px;width:200px">
                <% Html.jQuery().Slider()
                                .Name("task.Completed")
                                .UpdateElements("#completedPercent")
                                .Render(); %>
            </div>
            <div style="margin-top:5px">
                <%= Html.ValidationMessage("task.Completed")%>
            </div>
            <div style="margin-top:10px">
                <input type="submit" value="Submit"/>
            </div>
        </fieldset>
    <% }%>
</asp:Content>

Controller:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult FormSubmitWithValue()
{
    return View(new Task());
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FormSubmitWithValue([Bind]Task task)
{
    if (string.IsNullOrEmpty(task.Name))
    {
        ModelState.AddModelError("task.Name", "Name cannot be blank.");
    }

    if (task.Completed <= 0)
    {
        ModelState.AddModelError("task.Completed", "Invalid task complete percent.");
    }

    if (ModelState.IsValid)
    {
        //Save here;
        ViewData.Set("successMessage", "Task saved successfully.");
    }

    return View(task);
}

[Live Version]

Check that (line 26- 29 in the above View) you are using the same kind of naming as you do in strongly typed view for regular input fields, you can use the slider for non-strongly typed view too.

In case of Slider is ranged, we can use the following:

View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Slider Values Form Submit Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% using(Html.BeginForm()){%>
        <fieldset>
            <legend>Submit Department</legend>
            <span style="color:Blue;font-weight:bold">
                <%= ViewData.Get<string>("successMessage") %>
            </span>
            <%= Html.ValidationSummary("Please correct the following errors:") %>
            <div>
                <label for="name">Name:</label>
            </div>
            <div style="margin-top:5px">
                <%= Html.TextBox("name", null, new { style = "width:200px" })%>
            </div>
            <div style="margin-top:5px">
                <%= Html.ValidationMessage("name") %>
            </div>
            <div style="margin-top:10px">
                Salary: $<span id="rangeFrom" style="color:Black"></span> - $<span id="rangeTo" style="color:Black"></span>
            </div>
            <div style="margin-top:5px;width:600px">
                <% Html.jQuery().Slider()
                                .Name("salary")
                                .Range(jQuerySliderRange.True)
                                .Values(1000, 2500)  //Initial value
                                .UpdateElements("#rangeFrom", "#rangeTo")
                                .Minimum(1000)
                                .Maximum(10000)
                                .Steps(500)
                                .Render(); %>
            </div>
            <div style="margin-top:5px">
                <%= Html.ValidationMessage("salary")%>
            </div>
            <div style="margin-top:10px">
                <input type="submit" value="Submit"/>
            </div>
        </fieldset>
    <% }%>
</asp:Content>

Controller:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult FormSubmitWithValues()
{
    return View();
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FormSubmitWithValues(string name, IList<int> salary)
{
    if (string.IsNullOrEmpty(name))
    {
        ModelState.AddModelError("name", "Name cannot be blank.");
    }

    if (salary.Count != 2)
    {
        ModelState.AddModelError("salary", "Invalid salary range.");
    }

    if (salary.Count == 2)
    {
        if (salary[0] <= 1000)
        {
            ModelState.AddModelError("salary", "Salary minimum range should be greater than 1000.");
        }

        if (salary[1] <= 2500)
        {
            ModelState.AddModelError("salary", "Salary maximum range should be greater than 2500.");
        }
    }

    if (ModelState.IsValid)
    {
        //Save here;
        ViewData.Set("successMessage", "Department saved successfully.");
    }

    return View();
}

[Live Version]

Since the slider is a range slider, it will have an array of values, that is why the controllers accepts IList<int> for the slider. This will also work for strongly typed view.

You can also use the ProgressBar to auto retrieve the value directly from ViewData, for example,

View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    ProgressBar Auto Retrieve Value Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% Html.jQuery().ProgressBar()
                    .Name("myProgressBar")
                    .Render(); %>
</asp:Content>

Controller:

public ActionResult AutoRetrieve()
{
    ViewData["myProgressBar"] = 40;

    return View();
}

[Live Version]

As mentioned in the past that the goal of this component is to add some RAD support for the ASP.NET MVC Developers. For example, in the original jQuery UI both Slider and ProgressBar does not have any built-in support to show the value(s) in an associated html element(s), but it does have this support to attach any html elements to show the numeric value(s), check the UpdateElements method in the above (works on jQuery Selector), behind the scene it generates the necessary javascript codes to hook the events and update the elements, the same is true for the above slider form submit examples, it generates the required hidden input(s) to support form submit. It will not discourage you from writing javascript codes, instead it automates some common repetitive tasks. The next thing of my previous blog post was the benefits of server side generating the necessary html and javascript codes even when there is no integration of server side like the above slider or progressbar, to demonstrate it, I will first create a dynamic jQuery UI tab with raw html and javascripts, lets assume the tab items are disabled and selected based upon some condition.

Raw Html and JavaScript:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<DynamicTabContent>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Tab Dynamic Item Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% var disabledIndexes = new List<string>(); %>
    <% var selectedIndex = 0; %>
    <div id="tabs">
        <ul>
            <% for (var i = 0; i < Model.Count; i++) {%>
                <li><a href="#section-<%= i %>"><%= Html.Encode(Model[i].Header)%></a></li>
            <% }%>
        </ul>
        <% for (var i = 0; i < Model.Count; i++) {%>
            <% var dynamicContent = Model[i]; %>
            <div id="section-<%= i %>">
                <p><%= Html.Encode(dynamicContent.Content) %></p>
            </div>
            <% if (dynamicContent.IsDisabled)
               {
                   disabledIndexes.Add(i.ToString());
               }
               //The last selected will win
               if (dynamicContent.IsSelected && (i > selectedIndex))
               {
                   selectedIndex = i;
               }
            %>
        <% }%>
    </div>
    <script type="text/javascript">
        $(document).ready(function(){
            $('#tabs').tabs({
                                selected : <%= selectedIndex %>,
                                disabled : [<%= string.Join(", ", disabledIndexes.ToArray()) %>]
                            });
        });
    </script>
</asp:Content>

In the above, we are first iterating the Model to generate the tab headers, next we are again iterating it to generate the content panes and this time we are also populating the disabled index list and selecting the selected index. At last, we are dumping the selected index and converting the disabled indexes to a comma delimited string to create the tab in javascript.

Now lets see how this can simplify the above situation, we can do the exact same thing with the following codes.

Simple Fluent:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<DynamicTabContent>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Tab Dynamic Item Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% Html.jQuery().Tab().Name("tabs")
                          .Items(item =>
                                         {
                                             for(var i = 0; i < Model.Count; i++)
                                             {
                                                 var dynamicContent = Model[i];

                                                 item.Create()
                                                     .HeaderText(dynamicContent.Header)
                                                     .Content(() =>
                                                                      {%>
                                                                        <p><%= Html.Encode(dynamicContent.Content) %></p>
                                                                      <%}
                                                             )
                                                     .Selected(dynamicContent.IsSelected)
                                                     .Disabled(dynamicContent.IsDisabled);
                                             }
                                         }
                                )
                          .Render(); %>
</asp:Content>

[Live Version]

As you can see it is much more simpler and concise and with the above, we do not have to iterate the model again and again, no need to maintain the selected index and disabled index list, the component takes these responsibility and generates the Tab. I hope this clarifies the issue.

The second issue is the style of syntax, first, let me post the result of my previous Poll (total 48 votes):

  • Regular Methods (9 votes, 19%)
  • Simple Fluent (14 votes, 29%)
  • Progressive Fluent(25 votes, 52%)

Though the progressive has more votes, but I think once you are familiar with the syntax, the verboseness of the progressive fluent will start to irritate you and this is the reason why I have changed it to simple fluent from progressive fluent. You can download the latest version from the bottom of the post. Another interesting syntax proposed my Chad Myer. The difference between mine with this is that it first creates the component, but instead of returning the component it provides an Action to configure the component. Let us do a side by side comparison of this syntax with the simple fluent, here we will see the basic tab:

Factory + Action Syntax:

<% Html.jQuery<jQueryTab>(tab =>
                                       {
                                           tab.Name = "myTab";
                                           tab.Items(items =>
                                                             {
                                                                 items.Create(item =>
                                                                                        {
                                                                                            item.HeaderText = "Nunc tincidunt";
                                                                                            item.Content = () =>
                                                                                                                {%>
                                                                                                                     <p>
                                                                                                                         Proin elit arcu, rutrum commodo, vehicula tempus,
                                                                                                                         commodo a, risus. Curabitur nec arcu. Donec
                                                                                                                         sollicitudin mi sit amet mauris. Nam elementum
                                                                                                                         quam ullamcorper ante. Etiam aliquet massa et
                                                                                                                         lorem. Mauris dapibus lacus auctor risus. Aenean
                                                                                                                         tempor ullamcorper leo. Vivamus sed magna quis
                                                                                                                         ligula eleifend adipiscing. Duis orci. Aliquam
                                                                                                                         sodales tortor vitae ipsum. Aliquam nulla. Duis
                                                                                                                         aliquam molestie erat. Ut et mauris vel pede
                                                                                                                         varius sollicitudin. Sed ut dolor nec orci
                                                                                                                         tincidunt interdum. Phasellus ipsum. Nunc
                                                                                                                         tristique tempus lectus.
                                                                                                                    </p>
                                                                                                                 <%};
                                                                                        }
                                                                               );

                                                                 items.Create(item =>
                                                                                        {
                                                                                            item.HeaderText = "Proin dolor";
                                                                                            item.Content = () =>
                                                                                                                {%>
                                                                                                                     <p>
                                                                                                                        Morbi tincidunt, dui sit amet facilisis feugiat,
                                                                                                                        odio metus gravida ante, ut pharetra massa metus
                                                                                                                        id nunc. Duis scelerisque molestie turpis. Sed
                                                                                                                        fringilla, massa eget luctus malesuada, metus eros
                                                                                                                        molestie lectus, ut tempus eros massa ut dolor.
                                                                                                                        Aenean aliquet fringilla sem. Suspendisse sed
                                                                                                                        ligula in ligula suscipit aliquam. Praesent in
                                                                                                                        eros vestibulum mi adipiscing adipiscing. Morbi
                                                                                                                        facilisis. Curabitur ornare consequat nunc. Aenean
                                                                                                                        vel metus. Ut posuere viverra nulla. Aliquam erat
                                                                                                                        volutpat. Pellentesque convallis. Maecenas feugiat,
                                                                                                                        tellus pellentesque pretium posuere, felis lorem
                                                                                                                        euismod felis, eu ornare leo nisi vel felis.
                                                                                                                        Mauris consectetur tortor et purus.
                                                                                                                    </p>
                                                                                                                 <%};
                                                                                        }
                                                                               );

                                                                 items.Create(item =>
                                                                                        {
                                                                                            item.HeaderText = "Aenean lacinia";
                                                                                            item.Content = () =>
                                                                                                                {%>
                                                                                                                    <p>
                                                                                                                        Mauris eleifend est et turpis. Duis id erat.
                                                                                                                        Suspendisse potenti. Aliquam vulputate, pede vel
                                                                                                                        vehicula accumsan, mi neque rutrum erat, eu congue
                                                                                                                        orci lorem eget lorem. Vestibulum non ante. Class
                                                                                                                        aptent taciti sociosqu ad litora torquent per
                                                                                                                        conubia nostra, per inceptos himenaeos. Fusce
                                                                                                                        sodales. Quisque eu urna vel enim commodo
                                                                                                                        pellentesque. Praesent eu risus hendrerit ligula
                                                                                                                        tempus pretium. Curabitur lorem enim, pretium nec,
                                                                                                                        feugiat nec, luctus a, lacus.
                                                                                                                    </p>
                                                                                                                    <p>
                                                                                                                        Duis cursus. Maecenas ligula eros, blandit nec,
                                                                                                                        pharetra at, semper at, magna. Nullam ac lacus.
                                                                                                                        Nulla facilisi. Praesent viverra justo vitae neque.
                                                                                                                        Praesent blandit adipiscing velit. Suspendisse
                                                                                                                        potenti. Donec mattis, pede vel pharetra blandit,
                                                                                                                        magna ligula faucibus eros, id euismod lacus dolor
                                                                                                                        eget odio. Nam scelerisque. Donec non libero sed
                                                                                                                        nulla mattis commodo. Ut sagittis. Donec nisi
                                                                                                                        lectus, feugiat porttitor, tempor ac, tempor vitae,
                                                                                                                        pede. Aenean vehicula velit eu tellus interdum
                                                                                                                        rutrum. Maecenas commodo. Pellentesque nec elit.
                                                                                                                        Fusce in lacus. Vivamus a libero vitae lectus
                                                                                                                        hendrerit hendrerit.
                                                                                                                    </p>
                                                                                                                 <%};
                                                                                        }
                                                                               );
                                                             }
                                                    );
                                       }
                            ); %>

Simple Fluent:

<% Html.jQuery().Tab()
                .Name("myTab")
                .Items(items =>
                              {
                                 items.Create()
                                      .HeaderText("Nunc tincidunt")
                                      .Content(() =>
                                                     {%>
                                                         <p>
                                                             Proin elit arcu, rutrum commodo, vehicula tempus,
                                                             commodo a, risus. Curabitur nec arcu. Donec
                                                             sollicitudin mi sit amet mauris. Nam elementum
                                                             quam ullamcorper ante. Etiam aliquet massa et
                                                             lorem. Mauris dapibus lacus auctor risus. Aenean
                                                             tempor ullamcorper leo. Vivamus sed magna quis
                                                             ligula eleifend adipiscing. Duis orci. Aliquam
                                                             sodales tortor vitae ipsum. Aliquam nulla. Duis
                                                             aliquam molestie erat. Ut et mauris vel pede
                                                             varius sollicitudin. Sed ut dolor nec orci
                                                             tincidunt interdum. Phasellus ipsum. Nunc
                                                             tristique tempus lectus.
                                                        </p>
                                                     <%}
                                                );

                                  items.Create()
                                       .HeaderText("Proin dolor")
                                       .Content(() =>
                                                     {%>
                                                         <p>
                                                            Morbi tincidunt, dui sit amet facilisis feugiat,
                                                            odio metus gravida ante, ut pharetra massa metus
                                                            id nunc. Duis scelerisque molestie turpis. Sed
                                                            fringilla, massa eget luctus malesuada, metus eros
                                                            molestie lectus, ut tempus eros massa ut dolor.
                                                            Aenean aliquet fringilla sem. Suspendisse sed
                                                            ligula in ligula suscipit aliquam. Praesent in
                                                            eros vestibulum mi adipiscing adipiscing. Morbi
                                                            facilisis. Curabitur ornare consequat nunc. Aenean
                                                            vel metus. Ut posuere viverra nulla. Aliquam erat
                                                            volutpat. Pellentesque convallis. Maecenas feugiat,
                                                            tellus pellentesque pretium posuere, felis lorem
                                                            euismod felis, eu ornare leo nisi vel felis.
                                                            Mauris consectetur tortor et purus.
                                                        </p>
                                                     <%}
                                                );

                                  items.Create()
                                       .HeaderText("Aenean lacinia")
                                       .Content(() =>
                                                     {%>
                                                        <p>
                                                            Mauris eleifend est et turpis. Duis id erat.
                                                            Suspendisse potenti. Aliquam vulputate, pede vel
                                                            vehicula accumsan, mi neque rutrum erat, eu congue
                                                            orci lorem eget lorem. Vestibulum non ante. Class
                                                            aptent taciti sociosqu ad litora torquent per
                                                            conubia nostra, per inceptos himenaeos. Fusce
                                                            sodales. Quisque eu urna vel enim commodo
                                                            pellentesque. Praesent eu risus hendrerit ligula
                                                            tempus pretium. Curabitur lorem enim, pretium nec,
                                                            feugiat nec, luctus a, lacus.
                                                        </p>
                                                        <p>
                                                            Duis cursus. Maecenas ligula eros, blandit nec,
                                                            pharetra at, semper at, magna. Nullam ac lacus.
                                                            Nulla facilisi. Praesent viverra justo vitae neque.
                                                            Praesent blandit adipiscing velit. Suspendisse
                                                            potenti. Donec mattis, pede vel pharetra blandit,
                                                            magna ligula faucibus eros, id euismod lacus dolor
                                                            eget odio. Nam scelerisque. Donec non libero sed
                                                            nulla mattis commodo. Ut sagittis. Donec nisi
                                                            lectus, feugiat porttitor, tempor ac, tempor vitae,
                                                            pede. Aenean vehicula velit eu tellus interdum
                                                            rutrum. Maecenas commodo. Pellentesque nec elit.
                                                            Fusce in lacus. Vivamus a libero vitae lectus
                                                            hendrerit hendrerit.
                                                        </p>
                                                     <%}
                                                );
                              }
                       )
                .Render(); %>

To me the simple fluent is much simpler than Chad’s Factory + Action. But I think, it completely depends upon the personal preference. If you think, Factory + Action is much preferable than the simple fluent or even you want to revert back to the initial progressive fluent, do let me know I will change it  based upon your feedback.

[Live Version]

[Download Sample]

Shout it

Few days back I ran a Poll to gather the feedback on ASP.NET MVC View Components that I am planning to build building. Though it is certainly not possible to get everyone’s vote of the ASP.NET MVC Community, but I think the result has enough votes to represents the whole community:

Which View Engine do you use in ASP.NET MVC (Total votes 276)?

  • Webform (185 votes, 67%)
  • Spark (60 votes, 22%)
  • NHaml (16 votes, 6%)
  • NVelocity (6 votes, 2%)
  • Brail (4 votes 1%)
  • Others (5 Votes, 2%)

What kind of UI Components do you prefer for Webform view engine? (Total votes 186)?

  • UI components as HtmlHelper methods (108 votes, 58%)
  • UI components as lightweight controls similar to mvc future assembly (38 votes, 20%)
  • Any of the above (40 votes, 22%)

Which Javascript framework your ASP.NET MVC UI component should depend? (Total votes 214)?

  • jQuery (185 votes, 86%)
  • ExtJS (11 votes, 5%)
  • MS ASP.NET AJAX (3 votes, 1%)
  • Can depend any of the above (8 votes, 4%)
  • Do not bother if it has multiple dependency (7 votes, 3%)

As you can see the Webform+HtmlHelper+jQuery is far ahead from its competitor. Now, let us asses the options that we have to create UI components that extends the HtmlHelper. In this post I will use the jQuery UI Slider for discussing the options. If you are not familiar with jQuery UI Slider, I would suggest to visit the previous link,  the slider has properties like value, min, max, step, range and events start, stop change, slide etc. Lets assume that the method we will have in the HtmlHelper will create the necessary html elements and emits required javascripts in the page.

Option#1 Create Regular Methods

We can create regular methods like the ASP.NET MVC Framework, for example:

public static class HtmlHelperExtension
{
    public static void Slider(this HtmlHelper htmlHelper, string id, int value, int min, int max, object htmlAttributes)
    {
        // Implementation
    }
}

And few more overloads, the number of parameter will vary based upon the complexity of the object that we are building:

//Range
public static void Slider(this HtmlHelper htmlHelper, string id, int value1, int value2, int min, int max, object htmlAttributes)
{
    // Implementation
}

public static void Slider(this HtmlHelper htmlHelper, string id, int value)
{
    // Implementation
}

So, in the view we will be able to use it like the following:

<% Html.Slider("mySlider", 10, 20, 0, 100, new { style = "border: #000 1px solid" }); %>
<% Html.Slider("mySlider", 10, 0, 100, null); %>
<% Html.Slider("mySlider", 10, 20, 0, 100, null); %>
<% Html.Slider("mySlider", 10); %>

Option#2 Create Simple Fluent Syntax

We can create a slider object similar to the following:

public class jQuerySlider
{
    private readonly HtmlHelper _htmlHelper;

    private string _id;
    private RouteValueDictionary _htmlAttributes;
    private int _value;
    private int[] _values;
    private int _minimum;
    private int _maximum;

    public jQuerySlider(HtmlHelper htmlHelper)
    {
        _htmlHelper = htmlHelper;
    }

    public jQuerySlider Id(string elementId)
    {
        _id = elementId;
        return this;
    }

    public jQuerySlider HtmlAttributes(object attributes)
    {
        _htmlAttributes = new RouteValueDictionary(attributes);

        return this;
    }

    public jQuerySlider Value(int sliderValue)
    {
        _value = sliderValue;
        return this;
    }

    // Range
    public jQuerySlider Values(int slider1Value, int slider2Value)
    {
        _values = new int[2];

        _values[0] = slider1Value;
        _values[2] = slider1Value;

        return this;
    }

    public jQuerySlider Minimum(int value)
    {
        _minimum = value;

        return this;
    }

    public jQuerySlider Maximum(int value)
    {
        _maximum = value;

        return this;
    }

    public void Render()
    {
        //Write html and register script
    }
}

And create an extension method of HtmlHelper:

public static jQuerySlider Slider(this HtmlHelper helper)
{
    return new jQuerySlider(helper);
}

Now, we will be able to use it in the view like:

<% Html.Slider()
       .Id("mySlider")
       .HtmlAttributes(new { style = "border:#000 1px solid" })
       .Values(10, 20)
       .Minimum(0)
       .Maximum(100)
       .Render(); %>

<% Html.Slider()
       .Id("mySlider")
       .Value(10)
       .Minimum(0)
       .Maximum(100)
       .Render(); %>

<% Html.Slider()
       .Id("mySlider")
       .Value(10)
       .Render(); %>

A bit verbose, but lot more readable comparing to the regular methods (option #1), but the problem with this approach is, it is really easy to write:

<% Html.Slider()
       .Id("mySlider")
       .Value(10)
       .Value(20)
       .Value(30)
       .Render(); %>

And VS will always show the method names no matter how many times it is called:

VS-AutoComplete

This makes the syntax a bit confusing and ambiguous.

Option#3 Create Progressive Fluent Syntax

This solves the exact problem that I have just mentioned. Rather than returning the same object in the methods, it returns different interface that is applicable in that context. For example:

VS-AutoComplete-p1

VS-AutoComplete-p2

VS-AutoComplete-p3

As you can see, once a method is called it does not appears in the auto complete list, also the next available methods in that context are shown. I have implemented the jQuery UI Accordion, Tab, ProgressBar, Slider and Theme Switcher (DatePicker and Dialog are under development) following this approach. You can find the fully functional version:

[Live version]

[Download Demo]

This option also has a drawback, it does not support method overlapping like the option #2, which means, we always have to call the methods in the exact same order and it becomes a bit painful when we want to change a specific value that is a few level deep. For example, if we want to change only the Steps of the Slider we have to use:

<% Html.jQuery().Slider()
                .Id("mySlider")
                .NoExtraHtmlAttributes()
                .DoNotAnimate()
                .UseDefaultOrientation()
                .NoRange()
                .Value(0)
                .UseDefaultMinimum()
                .UseDefaultMaximum()
                .Steps(5)
                .Render(); %>

Instead of:

<% Html.jQuery().Slider()
                .Id("mySlider")
                .Steps(5)
                .Render(); %>

Though I prefer it over the above two, but it completely depends upon you, which way you want to shape up the API. So dear readers, please download the demo, do play with it and leave your opinion in this poll.

Shout it

Justin Etheredge recently wrote about the RAD support that he would like to see in ASP.NET MVC and I do agree with him very much (as a side note, I highly recommend his blog you should subscribe). I am also planning to create some reusable UI components for ASP.NET MVC and I need your feedback prior starting it.

Please answer the following three Polls (Yes I am a Twitter fan):

  1. Which View Engine do you use in ASP.NET MVC – Webform Vs. Spark Vs. NHaml Vs. ….?
  2. What kind of UI Components do you prefer for Webform View Engine - HtmlHelper Vs. Control?
  3. Which Javascript framework your ASP.NET MVC UI component should depend – jQuery Vs. ExtJS Vs. MS Ajax?

Thanks for participating.

Shout it

My last post on Script and CSS Management in ASP.NET MVC has well accepted by the community and I have got few valuable suggestions, specially from Jake Scott. Beside those, there are one more awkward things I found while working with the Fluent html version which makes me to change few public signatures. Lets see the issue that I am talking about, in the initial version when you want to add multiple script lines, you have to do:

Html.jQuery().OnPageLoad("utility.init();\r\ntest.init();")

Did you get the issue, yes for formatting I have to add the \r\n at the end of each statement and enclose the whole thing in a “”, a better version would be:

Html.jQuery().OnPageLoad(
                             () =>
                             {%>
                                 utility.init();
                                 test.init();
                             <%}
                         );

With the above, I do not have to worry about the formatting or enclosing, I can type the same way as I do for regular javascript codes. This is the change in this version with just one exception, you have to explicitly call the Render() method instead of <%= like the previous version. A complete example of the previous version’s master page would be:

<% Html.jQuery().Scripts(
                            script =>
                            {
                                script.Source("http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js");
                                script.Source("~/Scripts/utility.js");
                            }
                         )
                 .OnPageLoad(
                                () =>
                                {%>
                                    utility.init();
                                    test.init();
                                <%}
                            )
                 .OnPageUnload(
                                () =>
                                {%>
                                    alert('Cleanup for master page.');
                                <%}
                              )
                .Render();%>

Since this version use Lambda syntax for javascript statements and to capture the output, I have to use a fake ASP.NET Response filter to capture and then rewrite it in the original stream, one of the pitfall of using ASP.NET Response filter and using response.Flush() in it is,  it is not possible to modify the ASP.NET Response header later on in that request. But I don’t think modifying the response header is not all required in our regular scenarios. Next, in the previous version I was using an implicit operator to convert the helper to string which no longer works when using the lambda in view, that is why it is required to call the Render() where you want to dump the output.

The next two changes does not have any impact on the public signatures, First the file caching, although the cache duration that is specified in the web.config applies to the http response header nothing to do with the server side caching but in this version, to make the development life easier, the server side caching is also depends upon this value (otherwise we have to compile the project each time we make a change in the script/css file), if you want to disable caching in server side just set this value to 0 (zero).

And the last one is adding an overloaded extension method for UrlHelper, as Jake mentioned he wants to use separate handlers for css and js files, with the overloaded version now you will be able specify the handler when mentioning the asset.

Enjoy!!!

Download: Full Source

Shout it

No, this is not at all a post of FubuMVC, I just borrowed the words for this post.

Jeff Atwood & Joel Spolsky thinks it is a compliment when they found there site design is copied by a Chinese site and I do agree it completely, specially when it is serving the same community that I belong to. And I also agree with Joel Spolsky that most (at least in my case) English as second language speaking developers prefer the local language when they are communicating between them. We made KiGG an Open Source Project from the very beginning and since the v2.0 is released people are really picking it for creating local .NET community sites in their own language:

 

dotnetomaniak_pl
Polish version

 

progg_ru
Russian version

 

9efish_com
Chinese version (I guess it is still under development)

And this is the power of Open Source, the existing version contains absolutely zero support for localization, but the community picked and made it local for them, it has the same features and power as the original version and my sincere thanks to them who are behind these. As a side note I want to mention that localization is the highest priority that we will be adding in v3.0

Recently, I got few requests about the story publishing process from the peoples who are also planning to launch sites based upon KiGG. So instead of answering them individually, I preferred to write a post to explain it.

Like the many social news/links site the story appearing in the front-page is done based upon some algorithms and it has the complete support to add/replace/remove any of those algorithms. By default, it comes with 6 different algorithms.

The story publish process starts when the Publish method of StoryService is called.

public virtual void Publish()
{
    using(IUnitOfWork unitOfWork = UnitOfWork.Begin())
    {
        DateTime currentTime = SystemTime.Now();

        IList<PublishedStory> publishableStories = GetPublishableStories(currentTime);

        if (!publishableStories.IsNullOrEmpty())
        {
            // First penalty the user for marking the story as spam;
            // It is obvious that the Moderator has already reviewed the story
            // before it gets this far.
            PenaltyUsersForIncorrectlyMarkingStoriesAsSpam(publishableStories);

            //Then Publish the stories
            PublishStories(currentTime, publishableStories);

            unitOfWork.Commit();
        }
    }
}

As you can see, it first calls the GetPublishableStories to prepare a list which is publishable at this moment, next it reduces the score of the users who have incorrectly marked any of those stories as spam (will explain  later) and at last it calls another method PublishStories to actually publish it.

private IList<PublishedStory> GetPublishableStories(DateTime currentTime)
{
    List<PublishedStory> publishableStories = new List<PublishedStory>();

    DateTime minimumDate = currentTime.AddHours(-_settings.MaximumAgeOfStoryInHoursToPublish);
    DateTime maximumDate = currentTime.AddHours(-_settings.MinimumAgeOfStoryInHoursToPublish);

    int publishableCount = _storyRepository.CountByPublishable(minimumDate, maximumDate);

    if (publishableCount > 0)
    {
        ICollection<IStory> stories = _storyRepository.FindPublishable(minimumDate, maximumDate, 0, publishableCount).Result;

        foreach (IStory story in stories)
        {
            PublishedStory publishedStory = new PublishedStory(story);

            foreach (IStoryWeightCalculator strategy in _storyWeightCalculators)
            {
                publishedStory.Weights.Add(strategy.Name, strategy.Calculate(currentTime, story));
            }

            publishableStories.Add(publishedStory);
        }
    }

    return publishableStories;
}

The GetPublishableStories first gets a list of active stories (the stories that has been voted, viewed, commented since the last publish, there is also an age factor which means story in specific age range will only qualify, default is 4-240 hour) form the database, next it applies the different algorithm to calculate its weight. This algorithm (Strategy Pattern) is defined as IStoryWeightCalculator interface and injected in the StoryService by the DI container. Once the calculation is done the PublishStories method is called.

private void PublishStories(DateTime currentTime, IList<PublishedStory> publishableStories)
{
    // Now sort it based upon the score
    publishableStories = publishableStories.OrderByDescending(ps => ps.TotalScore).ToList();

    // Now assign the Rank
    publishableStories.ForEach(ps => ps.Rank = (publishableStories.IndexOf(ps) + 1));

    // Now take the stories for front page
    ICollection<PublishedStory> frontPageStories = publishableStories.OrderBy(ps => ps.Rank).Take(_settings.HtmlStoryPerPage).ToList();

    if (!frontPageStories.IsNullOrEmpty())
    {
        foreach (PublishedStory ps in frontPageStories)
        {
            ps.Story.Publish(currentTime, ps.Rank);
        }

        _eventAggregator.GetEvent<StoryPublishEvent>().Publish(new StoryPublishEventArgs(frontPageStories, currentTime));
    }

    // Mark the Story that it has been processed
    publishableStories.ForEach(ps => ps.Story.LastProcessed(currentTime));
}

The PublishStories first ranks the stories based upon its total weight, then it takes the first 20 story (defined in the web.config) for the front-page and marks it as for front-page by updating its rank and published date, at last it updates the last process date of all the stories regardless its publish status so that we can verify its active status for the next publish.

As you can see the story qualifying is completely decided based upon those weight calculators and by adding/removing/replacing the new calculators you can tweak the whole story publishing process. Now lets see how the default calculators works.

VoteWeight: Returns higher value if the vote is given from a different IP address than the story was actually submitted, for example 10 if it is given from a different IP address and 5 from the same IP address. If you do not like it, you can level it in the web.config.

<type name="vote" type="IStoryWeightCalculator" mapTo="VoteWeightCalculator">
    <lifetime type="PerWebRequest"/>
    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration">
        <constructor>
            <param name="voteRepository" parameterType="IVoteRepository">
                <dependency/>
            </param>
            <param name="sameIPAddressWeight" parameterType="System.Single">
                <value type="System.Single" value="5"/>
            </param>
            <param name="differentIPAddressWeight" parameterType="System.Single">
                <value type="System.Single" value="10"/>
            </param>
        </constructor>
    </typeConfig>
</type>

CommentWeight: Returns higher value if comment is given from a different Ip  and not by the actual submitter. For example, 4 for different IP, 2 for same IP and 1 for the actual submitter no matter from which IP it was submitted. This can be changed from web.config:

<type name="comment" type="IStoryWeightCalculator" mapTo="CommentWeightCalculator">
    <lifetime type="PerWebRequest"/>
    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration">
        <constructor>
            <param name="commentRepository" parameterType="ICommentRepository">
                <dependency/>
            </param>
            <param name="ownerWeight" parameterType="System.Single">
                <value type="System.Single" value="1"/>
            </param>
            <param name="sameIPAddressWeight" parameterType="System.Single">
                <value type="System.Single" value="2"/>
            </param>
            <param name="differentIPAddressWeight" parameterType="System.Single">
                <value type="System.Single" value="4"/>
            </param>
        </constructor>
    </typeConfig>
</type>

ViewWeight: Returns the sum of each unique IP address view (view means when a user clicks a link which takes the user to the original source) multiplied by a factor, this can be also changed from the web.config.

<type name="view" type="IStoryWeightCalculator" mapTo="ViewWeightCalculator">
    <lifetime type="PerWebRequest"/>
    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration">
        <constructor>
            <param name="storyViewRepository" parameterType="IStoryViewRepository">
                <dependency/>
            </param>
            <param name="weightMultiply" parameterType="System.Single">
                <value type="System.Single" value="0.1"/>
            </param>
        </constructor>
    </typeConfig>
</type>

UserScoreWeight: Returns the sum of user score multiplied by a factor who voted the story, so that stories voted by the higher score users has much more chance to appear in the front-page. The factor can be also changed from the web.config.

Freshness: Returns value depending upon the story age, the young the story the higher the value. The freshness threshold and interval can also be changed from web.config.

KnownSource: Returns a value based upon the source grade. This ensures that stories from an well known source get higher chance to appear in the front-page. For example, a blog post from Gu should take precedence than a blog post of mine. These sources are maintained in the KnownSource database table. If the source is not known, then it returns nothing.

So, as you can understand with the above algorithms even a story that got less votes might rank better than a more voted story and another important things I would like to mention that it does not update any specific part (top/bottom) of the front-page, instead it updates the whole page, the existing story of the front-page might also appear in the front-page at the same or different location based upon its recent rank, the benifit is it will never replace much more popular but old story by a less popular new story.

Okay, if you think the above is a solid publishing process or the process su*ks or you need a much simpler algorithm, my recommendation is to create a new strategy inheriting from the StoryWeightBaseCalculator. Let us see how to create a dumb strategy which rank the story based upon the number of votes and comments it got.

public class SimpleWeightCalculator : StoryWeightBaseCalculator
{
    private readonly IVoteRepository _voteRepository;
    private readonly ICommentRepository _commentRepository;

    public SimpleWeightCalculator(IVoteRepository voteRepository, ICommentRepository commentRepository) : base("Simple")
    {
        _voteRepository = voteRepository;
        _commentRepository = commentRepository;
    }

    public override double Calculate(DateTime publishingTimestamp, IStory story)
    {
        ICollection<IVote> votes = _voteRepository.FindAfter(story.Id, story.LastProcessedAt ?? story.CreatedAt);
        ICollection<IComment> comments = _commentRepository.FindAfter(story.Id, story.LastProcessedAt ?? story.CreatedAt);

        return (votes.Count + comments.Count);
    }
}

Next, remove the existing strategies and add it in the web.config (I am assuming you know the configuration of MS Unity application block):

<type name="simple" type="IStoryWeightCalculator" mapTo="SimpleWeightCalculator">
    <lifetime type="PerWebRequest"/>
    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration">
        <constructor>
            <param name="voteRepository" parameterType="IVoteRepository">
                <dependency/>
            </param>
            <param name="commentRepository" parameterType="ICommentRepository">
                <dependency/>
            </param>
        </constructor>
    </typeConfig>
</type>

Now, when you publish stories, it will rank based upon the vote and comments it got since the last publish.

I hope the above clarifies the story publish process of KiGG and do let me what else of KiGG you want to highlight next.

Shout it
More Posts « Previous page - Next page »