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

Do you want your ASP.NET MVC application to auto redirect to a specific url after a certain interval, yes you can use javascript window.location, what about without javascript? Check this codes:

[AutoRefresh(ControllerName = "Home", ActionName = "About", DurationInSeconds = 10)]
public ActionResult Index1()

[AutoRefresh(ActionName = "About", DurationInSeconds = 15)]
public ActionResult Index2()

[AutoRefresh(RouteName = "ByFavoriteRoute", DurationInSeconds = 30)]
public ActionResult Index3()

[AutoRefresh(DurationInSeconds = 45)]
public ActionResult Index4()

If the browsers is idle for the specified period, it will automatically redirect to that Action. if you do not specify any action/controller/route (Index4) it will auto refresh the current url. How? Well I am just using/abusing some http header, check it out:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class AutoRefreshAttribute : ActionFilterAttribute
{
    public const int DefaultDurationInSeconds = 300; // 5 Minutes

    public AutoRefreshAttribute()
    {
        DurationInSeconds = DefaultDurationInSeconds;
    }

    public int DurationInSeconds
    {
        get;
        set;
    }

    public string RouteName
    {
        get;
        set;
    }

    public string ControllerName
    {
        get;
        set;
    }

    public string ActionName
    {
        get;
        set;
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        string url = BuildUrl(filterContext);
        string headerValue = string.Concat(DurationInSeconds, ";Url=", url);

        filterContext.HttpContext.Response.AppendHeader("Refresh", headerValue);

        base.OnResultExecuted(filterContext);
    }

    private string BuildUrl(ControllerContext filterContext)
    {
        UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
        string url;

        if (!string.IsNullOrEmpty(RouteName))
        {
            url = urlHelper.RouteUrl(RouteName);
        }
        else if (!string.IsNullOrEmpty(ControllerName) && !string.IsNullOrEmpty(ActionName))
        {
            url = urlHelper.Action(ActionName, ControllerName);
        }
        else if (!string.IsNullOrEmpty(ActionName))
        {
            url = urlHelper.Action(ActionName);
        }
        else
        {
            url = filterContext.HttpContext.Request.RawUrl;
        }

        return url;
    }
}

Can’t think any proper usage of this action filter, may be you can use it for any iframe kind of page where you want to show some live statistics. Okay now let’s try one more time to abuse this Refresh header, this time it is a very common scenario.

You want your secured (marked as Authorized) actions to automatically redirect to login page when the session expires. Now try this:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class AutoRedirectToLogin : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        string url = FormsAuthentication.LoginUrl;
        int durationInSeconds = ((filterContext.HttpContext.Session.Timeout * 60) + 10); // Extra 10 seconds

        string headerValue = string.Concat(durationInSeconds, ";Url=", url);

        filterContext.HttpContext.Response.AppendHeader("Refresh", headerValue);

        base.OnResultExecuted(filterContext);
    }
}

When the session expires it will automatically redirect to the login page without requiring the extra click from the user.

Word of Caution: Do not use the Refresh meta header if you want your url to include in the search engine index (verified in Google, not sure about the others). For example, your Page A wants to auto redirect to Page B, the Page A only has this header, in that case Page A will not be indexed.

Roni Schuetz just informed me that he has started a new project in CodePlex to gather the mvc action filters, these two might be good candidates for his project.

Shout it

If you are familiar with YSlow recommendations, I guess you know that it recommends to put your CSS files at the top(#5)  and JavaScript files at the bottom(#6) of the pages. Placing the CSS files at the top is not an issue but putting the JavaScript files at the bottom of the pages has some gotchas, specially in heavily ajaxed based site with Master and Content page environment. When developing a Web 2.0 ajax site it is obvious that we will be using quite a number of plug-ins/widgets along with the core framework like jQuery, ExtJS, Prototype etc and of course there will be our hand coded javascript files, depending upon the size and the functionalities, the number of files can vary. This is the screenshot just to show you the usages of javascript files in DotNetShoutout/KiGG.

JSFiles

Certainly we can merge all these files into one large file and put it in the page bottom but it wont be very optimal solution, it will make our page unnecessary heavy and add delays prior making the page usable. Some will argue that it is just for the first time as the browser will cache the file and the visitor does not have to download it again. Yes true, but for a new site where a large percentage of visitors is visiting it for the first time, it is really important that it is lightening fast and reducing the number and size of the external files we can make our pages to load faster. So, instead of downloading a single gigantic file, we preferred to divide and merge those files in terms of functionality into different file sets and add specific sets to each page. The Master page contains the core javascript framework(jQuery), common parts/initialization that are reused in the content pages and the content pages have its own specific file sets and initialization scripts. And this is where it starts throwing javascript exceptions. Consider the following simple scenario:

In your master page at the bottom you have added the Utility.js with the jQuery framework:

    <script type="text/javascript" src="<%= Url.Content("~/Scripts/jquery-1.3.2.min.js")%>"></script>
    <script type="text/javascript" src="<%= Url.Content("~/Scripts/utillity.js")%>"></script>
    <script type="text/javascript">
        $(document).ready(
                            function()
                            {
                                utility.init();
                            }
                        );
    </script>
</body>

And in content page that is using the above master page you have added the dummyObject.js file at the bottom of the content page:

    <script type="text/javascript" src="<%= Url.Content("~/Scripts/dummyObject.js")%>"></script>
    <script type="text/javascript">
        $(document).ready(
                            function()
                            {
                                dummyObject.init();
                            }
                        );
    </script>
</asp:Content>

If you run the above in VS, you will find that VS enters into debug mode with a message “object expected”  like the following:

VS-Exception

Can you guess what is the problem in the above? yes, before the jQuery framework is downloaded the browser encounter the $ which is not yet defined and starts shooting exceptions.This is a very common gotchas working with Master and Content Pages and it is not ASP.NET MVC specific, the Web Forms also falls into this trap. I guess this is the reason why we have to put the ASP.NET AJAX ScriptManager prior any ajaxable control when working with the Web Forms.

There are quite a few things, we would like to solve:

  • Ensure that all script tags are rendered first no matter where it is located (Master Page/Content Page/User Control) before the initialization (document.ready of jQuery).
  • Merge all initialization statements and put into a single initialization block. The ordering should be Master –> Content Page –> User Control, again no matter how deep the nesting is.
  • Merge all cleanup statements and put into a single cleanup block ($(window).unload of jQuery) same as above initialization but the ordering should be reverse - User Control –> Content Page – > Master Page.

Now meet my tiny little component AssetManagement which solves the above issues very elegantly.

First we will see how the above issues can be solved with the controls of AssetManagement. Yes, it contains very similar controls like ASP.NET Ajax, but the key differences are, you can place it anywhere in the page, it does not generate any extra server tags, just the script tag, no view state, no hidden control nothing, you can easily use it in your ASP.NET MVC application. For the above, we will first put the jQueryScriptManager in the master page like the following:

    <readZ:jQueryScriptManager id="scriptManager" runat="Server">
        <Scripts>
            <readZ:JavaScriptReference Path="~/Scripts/jquery-1.3.2.min.js"/>
            <readZ:JavaScriptReference Path="~/Scripts/utility.js"/>
        </Scripts>
        <OnPageLoad>
            utility.init();
        </OnPageLoad>
    </readZ:jQueryScriptManager>
</body>

Next, in the Content Page:

    <readZ:JavaScriptManagerProxy id="scriptManagerProxy" runat="Server">
        <Scripts>
            <readZ:JavaScriptReference Path="~/Scripts/dummyObject.js"/>
        </Scripts>
        <OnPageLoad>
            dummyObject.init();
        </OnPageLoad>
    </readZ:JavaScriptManagerProxy>
</asp:Content>

Now, when you run, it will generate the following html output:

<script type="text/javascript" src="Scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="Scripts/utility.js"></script>
<script type="text/javascript" src="Scripts/dummyObject.js"></script>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready(function(){
            utility.init();
            dummyObject.init();
});
//]]>
</script>
</body>
</html>

You can also use the OnPageUnload property for cleanup. for example:

<readZ:jQueryScriptManager id="scriptManager" runat="Server">
    <Scripts>
        <readZ:JavaScriptReference Path="~/Scripts/jquery-1.3.2.min.js"/>
        <readZ:JavaScriptReference Path="~/Scripts/utility.js"/>
    </Scripts>
    <OnPageLoad>
        utility.init();
    </OnPageLoad>
    <OnPageUnload>
        alert('Cleanup for master page.');
    </OnPageUnload>
</readZ:jQueryScriptManager>

And

<readZ:JavaScriptManagerProxy id="scriptManagerProxy" runat="Server">
    <Scripts>
        <readZ:JavaScriptReference Path="~/Scripts/dummyObject.js"/>
    </Scripts>
    <OnPageLoad>
        dummyObject.init();
    </OnPageLoad>
    <OnPageUnload>
        alert('Cleanup for content page.');
    </OnPageUnload>
</readZ:JavaScriptManagerProxy>

It will generate:

<script type="text/javascript" src="Scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="Scripts/utility.js"></script>
<script type="text/javascript" src="Scripts/dummyObject.js"></script>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready(function(){
            utility.init();        
            dummyObject.init();
});

jQuery(window).unload(function(){
            alert('Cleanup for content page.');
            alert('Cleanup for master page.');
});
//]]>
</script>

Okay, if you have promised that you will not use any server control in your ASP.NET MVC application anymore, here is the fluent version of the exact same thing:

Master Page:

<%= 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();")
                 .OnPageUnload("alert('Cleanup for master page.');")
%>

Content Page:

<% Html.jQuery().Scripts(script => script.Source("~/Scripts/dummyObject.js"))
                 .OnPageLoad("dummyObject.init();")
                 .OnPageUnload("alert('Cleanup for content page.');");
%>

And it will generate the same output as the controls. Note that for Content Page we are only using <% but for the master page we are using <%= which means we want to dump the result. Also the fluent syntax is progressive interface (inspired by this excellent post of  Jan Van Ryswyck) which means once you call a method, it will show the only available methods in that context like the following:

AssetFluentSyntax

The component also contains an HttpHandler which you can use to combine multiple css and javascript files, the above codes can also refer these assets. To use the asset handler, you have to first define the assets in the web.config like the following:

<configSections>
    <section name="assetSettings" type="ReadZ.AssetManagement.AssetSettingsSection, ReadZ.AssetManagement" requirePermission="false"/>
</configSections>
<assetSettings version="1.0.0.0" cacheDurationInDays="365" compress="true">
    <assets>
        <clear/>
        <add
            name="css"
            contentType="text/css"
            directory="~/assets/css"
            files="site.min.css;ui.jquery.min.css;marItUp.min.css;colorPicker.min.css"
        />
        <add
            name="js2"
            contentType="application/x-javascript"
            directory="~/assets/scripts"
            files="OpenID.min.js;jquery-1.2.6.min.js;jquery.form.min.js;jquery.validate.min.js;ui.core.min.js;ui.tabs.min.js;ui.draggable.min.js;ui.resizable.min.js;ui.dialog.min.js;Utility.min.js;Search.min.js;Tag.min.js;Membership.min.js;Story.min.js;Analytics.min.js"
        />
        <add
            name="js3"
            contentType="application/x-javascript"
            directory="~/assets/scripts"
            files="ui.autocomplete.min.js;jquery.markitup.min.js;showdown.js;colorpicker.min.js;RichEditor.min.js;ImageCode.min.js;Comment.min.js"
        />
    </assets>
    <system.web>
        <pages>
            <controls>
                <add tagPrefix="readZ" namespace="ReadZ.AssetManagement" assembly="ReadZ.AssetManagement"/>
                <add tagPrefix="readZ" namespace="ReadZ.AssetManagement.Controls" assembly="ReadZ.AssetManagement"/>
            </controls>
            <namespaces>
                <add namespace="ReadZ.AssetManagement"/>
                <add namespace="ReadZ.AssetManagement.HtmlHelpers"/>
            </namespaces>
        </pages>
        <httpHandlers>
            <add verb="GET,HEAD" path="asset.axd" validate="false" type="ReadZ.AssetManagement.AssetHandler, ReadZ.AssetManagement"/>
        </httpHandlers>
    </system.web>
    <system.webServer>
        <handlers>
            <remove name="AssetHandler"/>
            <add name="AssetHandler" preCondition="integratedMode" verb="GET,HEAD" path="asset.axd" type="ReadZ.AssetManagement.AssetHandler, ReadZ.AssetManagement"/>
        </handlers>
    </system.webServer>
</assetSettings>

As you can see, each asset has an unique name, the content type and number of files with the location. When an asset is requested it will merge the specified files into a single response, cache and compress it before sending the response. You can define global version, cache duration and compression (line 4) which will apply to all, you can also override the global settings as per asset. When referring these assets in your code you can use:

For Control:

<readZ:jQueryScriptManager id="scriptManager" runat="Server">
    <Scripts>
        <readZ:JavaScriptReference AssetName="js2"/>
    </Scripts>
</readZ:jQueryScriptManager>

For Fluent Html:

<%= Html.jQuery().Scripts(script => script.Asset("js2")) %>

When referring the CSS, you can use:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="<%= Url.Asset("css")%>" rel="stylesheet" type="text/css"/>
</head>

The component has the extension methods for both UrlHelper and HtmlHelper, so that you can use it conveniently in your ASP.NET MVC views.

So far the codes that I have shown is for jQuery only, If you are thinking what about the others like ExtJS, prototype etc. The component has some nice extensibilities which you can use to support other popular libraries as well. For example, lets add the support for the ExtJS. First you have to create a class which implements the IScriptWrapper interface, next add codes for WrapOnPageLoad and WrapOnPageUnload methods like the following:

public class ExtJSScriptWrapper : IScriptWrapper
{
    public string WrapOnPageLoad(string scripts)
    {
        return string.IsNullOrEmpty((scripts ?? string.Empty).Trim()) ? string.Empty : string.Concat("Ext.onReady(function(){\r\n", scripts, "\r\n});");
    }

    public string WrapOnPageUnload(string scripts)
    {
        return string.IsNullOrEmpty((scripts ?? string.Empty).Trim()) ? string.Empty : string.Concat("Ext.EventManager.on(window, 'unload', function(){\r\n", scripts, "\r\n});");
    }
}

Control:

Next create a new control which inherits from base JavaScriptManager and create a new instance of the above wrapper in the constructor and that’s it.

public class ExtJSScriptManager : JavaScriptManager
{
    public ExtJSScriptManager() : base(new ExtJSScriptWrapper())
    {
    }
}

Now you will be able to use the ExtJSScriptManager like the same way as we did with jQueryScriptManager in the above.

Fluent Html:

First, create a class which inherits from base AbstractScriptHtmlHelper and create a new instance of the above wrapper in the constructor:

public class ExtJSScriptHtmlHelper : AbstractScriptHtmlHelper
{
    public ExtJSScriptHtmlHelper(HtmlHelper htmlHelper) : base(new ExtJSScriptWrapper(), htmlHelper)
    {
    }
}

Next create an extension method for the HtmlHelper which will return it.

public static class HtmlHelperExtension
{
    public static ExtJSScriptHtmlHelper ExtJS(this HtmlHelper helper)
    {
        return new ExtJSScriptHtmlHelper(helper);
    }
}

And that’s it, now you will be able to refer ExtJS in the mvc view.

The component also has the unit tests with the amazing duo (xUnit + Moq) and interestingly I was able to achieve 100% code coverage which is worth to check (Yes you will find some unnecessary tests, I did it intentionally to achieve more code coverage).

CodeCoverage

You can download the above sample code and the component from the bottom of this post.  If you have any feedback/bug report/enhancements, do let me know.

Happy YSlow scoring in your ASP.NET MVC App!!!

Download: Source Code

Shout it

Recently there has been quite some talk on HtmlHelper.RenderPartial() in ASP.NET MVC space which you will find over here and here. After reading those posts, I did an in depth analysis of the view location part, I am sharing the details with you so that it can help  both of us better understanding the internal details of the ASP.NET MVC framework. But before that I would like to suggest that the on going discussions should not be on partial view specific as there is no difference between the logic finding partial or regular view of default the WebFormViewEngine.

The default WebFormViewEngine is inherited from the base VirtualPathProviderViewEngine which in turns uses DefaultViewLocationCache for caching the location when running in web server in release mode. The DefaultViewLocationCache uses the ASP.NET built-in cache to cache the location. The following shows the code of DefaultViewLocationCache(I have excluded the other parts of the codes that are irrelevant in this discussion):

public class DefaultViewLocationCache : IViewLocationCache
{
    private static readonly TimeSpan _defaultTimeSpan = new TimeSpan(0, 15, 0);

    public static readonly IViewLocationCache Null = new NullViewLocationCache();

    public DefaultViewLocationCache() : this(_defaultTimeSpan)
    {
    }

    public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }

        httpContext.Cache.Insert(key, virtualPath, null /* dependencies */, Cache.NoAbsoluteExpiration, TimeSpan);
    }
}

The first thing which I like to point is the default cache duration is 15 minutes(line 3 & 7) and this duration is never overridden in the ASP.NET MVC framework. Next, when caching it uses the relative expiration which means if the view is not accessed in this duration, the cache will become invalid (line 18).  So there should be a file exists check for each inactive view after the 15 minutes, so the maximum (15 x 4 x 24) = 1440 number of file checks in a day assuming that the view is accessed just after the cache becomes invalid. I did a small benchmark with the following code(FileExists method is exactly taken from the WebFormViewEngine)  and it took around 8.5 seconds for 1440 checks to complete.

public ActionResult Index()
{
    Stopwatch watch = new Stopwatch();

    watch.Start();

    for (int i = 1; i <= 1440; i++)
    {
        FileExists(ControllerContext, "~/Views/Home/TopMenu.ascx"); //Does not exist
        FileExists(ControllerContext, "~/Views/Shared/TopMenu.ascx");
    }

    watch.Stop();
    TimeSpan elapsed = watch.Elapsed;

    ViewData["elapsed"] = elapsed;

    return View();
}

private static bool FileExists(ControllerContext controllerContext, string virtualPath)
{
    try
    {
        object viewInstance = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(object));

        return viewInstance != null;
    }
    catch (HttpException he)
    {
        if (he.GetHttpCode() == (int)HttpStatusCode.NotFound)
        {
            // If BuildManager returns a 404 (Not Found) that means the file did not exist
            return false;
        }
        else
        {
            // All other error codes imply other errors such as compilation or parsing errors
            throw;
        }
    }
    catch
    {
        return false;
    }
}

I don’t think 8.5 seconds for 1440 checks is a performance issue. But anyway, if you are wondering how to reduce the number of checks, here is the code that you can put in your global.asax or bootstrapper.

foreach (var viewEngine in ViewEngines.Engines.OfType<VirtualPathProviderViewEngine>())
{
    viewEngine.ViewLocationCache = new DefaultViewLocationCache(TimeSpan.FromHours(24));
}

The above code will cache the location for 24 hours that means there will be only one check for an inactive view in a day.

So if you are wondering why this post shows so many exceptions, the reason is as Simone pointed out that it was running in debug mode which means the path is never cached. There might be also some exceptions generated (which is gracefully handled by the ASP.NET MVC framework) when a view is accessed for the first time even running in release mode. The reason behind it, the view itself or the part of the view(partial view) is located under the shared folder instead of the controller specific view folder, the framework first tries to find the view in the controller view folder, when it does not find the view, it throws an exception, which the framework handles itself(line 31- 35 in the above FileExists method), then it again tries the shared folder and finds the view and caches the file path, so the next request for the same view in that cached period returns the path from the cache, instead of generating the exception in finding the view.

I hope the above will clarify the confusion related with view location performance concerns.

Before ending this post I would like to comment that the reason behind making the above thing bit complex(caching, handling exception internally etc etc) is due to a missing public method of asp.net BuildManager. It is obvious that the BuildManager must have the virtual path existence checking as well as file location caching and once again the Reflector proves that it does have these methods.

Reflector1

The GetVPathBuildResultInternal is called by the CreateInstanceFromVirtualPath method which is used by the WebFormViewEngine. Next, the caching, by default the ASP.NET comes with two built-in internal VirtualPathProvider: 1. MapPathBasedVirtualPathProvider and 2. ClientVirtualPathProvider. The following shows that the ClientVirtualPathProvider itself maintaining a cache:

Reflector2

So the question remain does the ClientVirtualPathProvider comes into action when resolving the virtual path which I would like to left for the ASP.NET Team to answer.

Shout it

I was looking  for an Ad Rotator for DotNetShoutout, as the sponsors are coming and most of them wants to show different images for the site and as well as in the feed. Certainly, I can use the built-in AdRotator control for the site, but it does not at all feels MVC-ish. So I decided to create a small class which mimics the same behavior as the original control.

namespace Kigg.Web
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;
    using System.Web.Routing;

    public class Ad
    {
        public string NavigateUrl
        {
            get;
            set;
        }

        public string Target
        {
            get;
            set;
        }

        public object LinkAttributes
        {
            get;
            set;
        }

        public string ImageUrl
        {
            get;
            set;
        }

        public string AlternateText
        {
            get;
            set;
        }

        public object ImageAttributes
        {
            get;
            set;
        }

        public string Keyword
        {
            get;
            set;
        }

        public int Impressions
        {
            get;
            set;
        }

        public static string Rotate(string keywordFilter, params Ad[] ads)
        {
            Ad ad = PickAd(keywordFilter, ads);

            string html = (ad == null) ? string.Empty : GenerateHtml(ad);

            return html;
        }

        public static string Rotate(params Ad[] ads)
        {
            return Rotate(null, ads);
        }

        private static Ad PickAd(string keywordFilter, params Ad[] ads)
        {
            Ad targetAd = null;

            IList<Ad> matchedAds = ads.Where(ad => string.Compare(ad.Keyword, keywordFilter, StringComparison.InvariantCultureIgnoreCase) == 0)
                                      .OrderBy(ad => ad.Impressions)
                                      .ToList();

            if (matchedAds.Count > 0)
            {
                int max = matchedAds.Sum(ad => ad.Impressions);
                int random = new Random().Next(max + 1);
                int runningTotal = 0;

                foreach(Ad ad in matchedAds)
                {
                    runningTotal += ad.Impressions;

                    if (random <= runningTotal)
                    {
                        targetAd = ad;
                        break;
                    }
                }

                if (targetAd == null)
                {
                    targetAd = matchedAds.Last();
                }
            }

            return targetAd;
        }

        private static string GenerateHtml(Ad ad)
        {
            Action<TagBuilder, object> merge = (builder, values) =>
                                               {
                                                   if (values != null)
                                                   {
                                                       builder.MergeAttributes(new RouteValueDictionary(values));
                                                   }
                                               };

            Action<TagBuilder, string, string> mergeIfNotBlank =    (builder, name, value) =>
                                                                    {
                                                                         if (!string.IsNullOrEmpty(value))
                                                                         {
                                                                             builder.MergeAttribute(name, value, true);
                                                                         }
                                                                    };

            TagBuilder imageBuilder = new TagBuilder("img");

            merge(imageBuilder, ad.ImageAttributes);
            mergeIfNotBlank(imageBuilder, "src", ad.ImageUrl);
            mergeIfNotBlank(imageBuilder, "alt", ad.AlternateText);

            if (!imageBuilder.Attributes.ContainsKey("alt"))
            {
                imageBuilder.Attributes.Add("alt", string.Empty);
            }

            TagBuilder linkBuilder = new TagBuilder("a");

            merge(linkBuilder, ad.LinkAttributes);
            mergeIfNotBlank(linkBuilder, "href", ad.NavigateUrl);
            mergeIfNotBlank(linkBuilder, "target", ad.Target);

            linkBuilder.InnerHtml = imageBuilder.ToString(TagRenderMode.SelfClosing);

            return linkBuilder.ToString();
        }
    }
}

And in the view I can use it like the following:

<%= Ad.Rotate(
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=1", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/1.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0"}, Impressions = 60 },
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=2", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/2.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0" }, Impressions = 40 },
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=3", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/3.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0"}, Impressions = 20 }
             )%>

It will randomly rotate based upon the impression value like the original control.  Though keyword filtering is not required for DotNetShoutout, but I have implemented it, maybe you can find it useful.

Shout it

[Edit 2: Kobe is now pulled off from the ASP.NET front page as well as from the news section, the msdn page also has a red notice – congrats MS for this step.]

[Edit: There are few people who mentioned that MS is not claiming it as a Best Practice, but I think they are wrong as the moment I am typing this the www.asp.net still has this tag

“The resources are intended to guide you with the planning, architecting, and implementing of Web 2.0 applications and services”

and I do have the objection when it is labeled as guide]

If you recently visited the home page of www.asp.net you will find that Microsoft has released a new Resource Kit for developing Web 2.0 Applications. The resource kit contains a sample reference application that is developed in ASP.NET MVC framework. Since both of these are my area of interest, I downloaded the codes and give a quick walkthrough. Prior getting to the main discussion, I just want to remind you that it is the second reference application (after Oxite) that is named under Microsoft, so you are suppose to get some quality architecture/design, codes etc etc. But unfortunately, it does not even qualify as an average application that is written by joe developer,  it is filled with serious flaws in both architectural design and coding and I would consider it as a worst practise of developing ASP.NET MVC application. Let me list the serious issues that I found:

Full of Magic Values in Controller

The controllers contains interesting magic values and left me wondering what's wrong with Enum. Consider the following Action of Home Controller:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Discovery(string query, string type, string section, string subSection, string page)
{
    query = Server.HtmlEncode(query);

    //Paging Section
    if (subSection != string.Empty)
    {
        if (type != "widget")
            return new ContentResult { Content = "" };

        int pageNo = 1;
        Int32.TryParse(page, out pageNo);
        if (pageNo == 0)
            pageNo = 1;

        if (section == "members")
        {
            List<UserSummary> users = null;

            switch (subSection)
            {
                case "members-mostactive":
                    users = _userService.GetMostActiveUsers(query, 8, pageNo);
                    break;
                case "members-all":
                    users = _userService.GetAllUsers(query, 8, pageNo);
                    break;
                default:
                    users = _userService.GetAllUsers(query, 8, pageNo);
                    break;
            }
            return View("MembersList", users);
        }
        else if (section == "groups")
        {
            List<Group> groups = null;

            switch (subSection)
            {
                case "groups-mostactive":
                    groups = _groupService.GetMostActiveGroupByKeyword(query, 8, pageNo);
                    break;
                case "groups-all":
                    groups = _groupService.GetAllGroupByKeyword(query, 8, pageNo);
                    break;
                default:
                    groups = _groupService.GetAllGroupByKeyword(query, 8, pageNo);
                    break;
            }
            return View("GroupsList", groups);
        }
        else if (section == "presentations")
        {

            List<PresentationSummary> presentations = null;
            switch (subSection)
            {
                case "presentations-all":
                    presentations = _presentationService.GetAllPresentationsByKewordTimeLine(query, "Day", "0", 10, pageNo);
                    break;
                case "presentations-mostpopular":
                    presentations = _presentationService.GetMostPopularPresentationsByKewordTimeLine(query, "Day", "0", 10, pageNo);
                    break;
                case "presentations-mostviewed":
                    presentations = _presentationService.GetMostViewedPresentationsByKewordTimeLine(query, "Day", "0", 10, pageNo);
                    break;
                case "presentations-mostdownload":
                    presentations = _presentationService.GetMostDownloadedPresentationsByKewordTimeLine(query, "Day", "0", 10, pageNo);
                    break;
                case "presentations-new":
                    presentations = _presentationService.GetNewPresentations(query, "Day", "0", 10, pageNo);
                    break;
                default:
                    presentations = _presentationService.GetAllPresentationsByKewordTimeLine(query, "Day", "0", 10, pageNo);
                    break;
            }
            return View("PresentationsList", presentations);
        }

        //    return new ContentResult { Content = "" };
    }

    return View();

}

Can you count the number of Magic string and integers in the above method?

Lack of Knowledge of ActionResult

Whenever the Action methods requires to return an empty result (which I think is also a design flaw) instead of EmptyResult it is returning new ContentResult { Content = "" }. Also when downloading a file, instead of using those nice FileResult, FilePathResult etc the people behind this application decided to modify the Response object directly (Check the Download method of Presentation Controller)  which clearly indicates the lack of knowledge of ASP.NET MVC Framework.

Action Methods are Populating Too Many View Data

Rather than populating the common view data with action filters or dividing the parts by Controller.RenderAction, the action methods are resposible to populate too many view data, consider the following action method:

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Index()
        {
            UserSummary memberOfDay = _communityService.GetUserOfDay();
            PresentationSummary presentationOfDay = _presentationService.GetPresentationOfDay();
            Group groupOfDay = _communityService.GetGroupOfDay();

            List<PresentationSummary> favouritePresentation = _communityService.GetCommunityFavorite(10,1); 
            List<PresentationSummary> userPresentationCount = _userService.GetAuthoredPresentations(memberOfDay.UserName);
            
            List<UserSummary> newUsers = _communityService.GetNewUsers(8,1);
            List<UserSummary> mostActiveUsers = _communityService.GetMostActiveUsers(8, 1);
            List<UserSummary> allUsers = _communityService.GetAllUsers(8, 1);
            int iNewMemberPageCount = decimal.Divide(_communityService.GetAllUsers().Count,8).ToInt();

            List<Group> newGroups = _communityService.GetNewGroups(8, 1);
            List<Group> mostActiveGroups = _communityService.GetMostActiveGroups(8, 1);
            List<Group> allGroups = _communityService.GetAllGroups(8, 1);
            int iNewGroupPageCount = decimal.Divide(_communityService.GetAllGroups().Count, 8).ToInt();
            int ifavouritesPageCount = 3;

            List<Group> groupsCommunity = _communityService.GetNewGroups(8,1);

            ViewData.Add("favouritePresentations", favouritePresentation.ToArray());
            ViewData.Add("UserOfDay", memberOfDay);
            ViewData.Add("GroupOfDay", groupOfDay);
            ViewData.Add("UserPresentationCount", userPresentationCount.Count);
            ViewData.Add("presentationsOfDay", presentationOfDay);            
            ViewData.Add("IsCommunityPage", true);

            ViewData.Add("members-new", newUsers);
            ViewData.Add("members-mostactive", mostActiveUsers);
            ViewData.Add("members-all", allUsers);
            ViewData.Add("members-totalcount", iNewMemberPageCount);

            ViewData.Add("groups-new", newGroups);
            ViewData.Add("groups-mostactive", mostActiveGroups);
            ViewData.Add("groups-all", allGroups);
            ViewData.Add("groups-totalcount", iNewGroupPageCount);

            ViewData.Add("favourites-totalcount", ifavouritesPageCount);


            ViewData.Add("GroupsListCommunity", groupsCommunity);


            return View();
        }

Same Action Method Returns Multiple Irrelevant View

Consider the following action:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Profile(string userId, string type, string section, string subSection, string page)
{
    string argUserName = userId;

    UserSummary _user = null;
    _user = _userService.GetUserByName(argUserName);

    if (argUserName == HttpContext.User.Identity.Name)
        ViewData["UserLogged"] = "Yes";
    else
        ViewData["UserLogged"] = "No";

    ViewData["User"] = _user;
    //Paging Section
    if (subSection != string.Empty)
    {
        if (type != "widget")
            return new ContentResult { Content = "" };

        int pageNo = 1;
        Int32.TryParse(page, out pageNo);
        if (pageNo == 0)
            pageNo = 1;

        switch (section)
        {
            case "profileContacts":
                switch (subSection)
                {
                    case "profile-contacts":
                        AddContactsToViewData(_user, pageNo);
                        return View("UserContactList");
                    case "profile-groups":
                        AddGroupsToViewData(_user, pageNo);
                        return View("UserGroupList");
                    case "profile-authors":
                        AddAuthorsToViewData(_user, pageNo);
                        return View("UserAuthorList");
                }
                break;
            case "messages":
                switch (subSection)
                {
                    case "wall-messages":
                        AddUserMessagesToViewData(_user, pageNo);
                        return View("Messages", ViewData["wallMessage"]);
                    case "wall-userinvites":
                        AddUserInvitationToViewData(_user, pageNo);
                        return View("UserInvites", ViewData["ContactInvites"]);
                    case "wall-groupinvites":
                        AddGroupInviteToViewData(_user, pageNo);
                        return View("GroupInvites", ViewData["GroupInvites"]);
                }
                break;
            case "profile":
                switch (subSection)
                {
                    case "profile-recommendedForYou":
                        AddSystemRecommendationToViewData(_user, pageNo);
                        return View("SystemRecommended", ViewData["profile-recommendedForYou"]);
                    case "profile-contactRecommendations":
                        AddContactRecommendationToViewData(_user, pageNo);
                        return View("ContactsRecommended", ViewData["profile-contactRecommendations"]);
                    case "profile-myFavorites":
                        AddFavoriteToViewData(_user, pageNo);
                        return View("FavoritePresentations", ViewData["profile-myFavorites"]);
                    case "profile-myPresentations":
                        AddPresentationAuthoredToViewData(_user, pageNo);
                        return View("AuthoredPresentations", ViewData["profile-myPresentations"]);
                }
                break;

        }
    }

    return null;
}

The same action is responsible for returning 11 different view!!!

VB6 Style variable naming

I thought we have already passed those days, but it proves me wrong:

string regStatus = _userService.RegisterUser(username, passwordRegistration, firstname, lastname, email, arrInterests);

if (regStatus == "Success")
{
    UserSummary user = _userService.GetUserByName(username);
    string callBackURL = Request.Url.ToString().Replace("Registration", "EmailConfirmation") + "/" + user.UserId.ToString();
    string strMessageBody = GetRegistrationConfirmationMailBody(firstname, callBackURL);
    string strMailSubject = "PlanetPPT - confirm you email";
    this.SendMail("admin@planetppt.com", user.Email, strMessageBody, strMailSubject);

	//More Codes
}

Check that the UserService is returning string to indicate success/failure and the controller itself is responsible for formatting and sending the email.

Still Provider Model

The Data Access is designed with the similar style of Oxite v1.0, which the community has rejected a long time ago.

Zero Unit Test

The application does not contain a single line of unit test and the worst thing is that the way it has been architect there is zero chance that you will be able to write it without refactoring the architecture.

This is so far I think it worth to investigate and I can assure you that you will be able to find more flaws if you have the time and patience.

At last, my urge to Microsoft, please take down this application from both www.asp.net and MSDN or put a notice that the application is not up to the standard and do not follow it anyway as it is full of bad practices. Do introduce a review board and get the approval before releasing this kind of application. Remember this is not the first time that this kind of controversy was born, so please ensure the quality and standard.

Shout it
More Posts Next page »