ASP.NET MVC Best Practices (Part 2)

This is the second part of the series and may be the last, till I find some thing new. My plan was to start with routing, controller, controller to model, controller to view and last of all the view, but some how I missed one important thing in routing, so I will begin with that in this post.

15. Routing consideration

If you are developing a pure ASP.NET MVC application, turn off existing file check of routes, it will eliminate unnecessary file system check. Once you do it there are few more things you have to consider. Remember when you are hosting application in IIS7 integrated mode, your ASP.NET application will intercept all kind of request, no matter what the file extension is. So you have to add few more things in the ignore list which your ASP.NET MVC application will not process. This might include static files like html, htm, text file specially robots.txt, favicon.ico, script, image and css etc. This is one of the reason, why I do not like the default directory structure (Contents and Scripts folder) mentioned in my #2 in the previous post. The following is somewhat my standard template for defining routes when hosting in IIS7:

_routes.Clear();

// Turns off the unnecessary file exists check
_routes.RouteExistingFiles = true;

// Ignore text, html, files.
_routes.IgnoreRoute("{file}.txt");
_routes.IgnoreRoute("{file}.htm");
_routes.IgnoreRoute("{file}.html");

// Ignore axd files such as assest, image, sitemap etc
_routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

// Ignore the assets directory which contains images, js, css & html
_routes.IgnoreRoute("assets/{*pathInfo}");

// Ignore the error directory which contains error pages
_routes.IgnoreRoute("ErrorPages/{*pathInfo}");

//Exclude favicon (google toolbar request gif file as fav icon which is weird)
_routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.([iI][cC][oO]|[gG][iI][fF])(/.*)?" });

//Actual routes of my application

Next, few of my personal preference rather than guideline, by default ASP.NET MVC generates url like {controller}/{action} which is okay when you are developing multi-module application,  for a small application, I usually prefer the action name without the controller name, so instead of www.yourdomain.com/Story/Dashboard, www.yourdomain.com/Membership/SignIn it will generate www.yourdomain.com/Dashboard, www.yourdomain.com/Signin. So I add few more routes:

_routes.MapRoute("SignUp", "SignUp", new { controller = "Membership", action = "SignUp" });
_routes.MapRoute("SignIn", "SignIn", new { controller = "Membership", action = "SignIn" });
_routes.MapRoute("ForgotPassword", "ForgotPassword", new { controller = "Membership", action = "ForgotPassword" });
_routes.MapRoute("SignOut", "SignOut", new { controller = "Membership", action = "SignOut" });
_routes.MapRoute("Profile", "Profile", new { controller = "Membership", action = "Profile" });
_routes.MapRoute("ChangePassword", "ChangePassword", new { controller = "Membership", action = "ChangePassword" });

_routes.MapRoute("Dashboard", "Dashboard/{tab}/{orderBy}/{page}", new { controller = "Story", action = "Dashboard", tab = StoryListTab.Unread.ToString(), orderBy = OrderBy.CreatedAtDescending.ToString(), page = 1 });
_routes.MapRoute("Update", "Update", new { controller = "Story", action = "Update" });
_routes.MapRoute("Submit", "Submit", new { controller = "Story", action = "Submit" });

_routes.MapRoute("Home", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = string.Empty });

16. Create new ActionResult if required

ASP.NET MVC has quite a number of ActionResult for different purposes, but still we might need new ActionResult. For example xml, rss, atom etc. In those cases, I would suggest instead of using the generic ContentResult, create new ActionResult. The MVCContrib has an XmlResult which you can use for returning xml but no support for feed. Yes it is obviously tricky to convert an unknown object into rss/atom, in those cases you can create model specific ActionResult. For example:

public class AtomResult : ActionResult
{
    public AtomResult(string siteTitle, string feedTitle, IEnumerable<IStory> stories)
    {
        SiteTitle = siteTitle;
        FeedTitle = feedTitle;
        Stories = stories;
    }

    public string SiteTitle
    {
        get;
        private set;
    }

    public string FeedTitle
    {
        get;
        private set;
    }

    public IEnumerable<IStory> Stories
    {
        get;
        private set;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        string xml = Build(context);

        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = "application/atom+xml";
        response.Write(xml);
    }
}

And in Controller:

[AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Atom")]
public ActionResult Shared()
{
    IEnumerable<stories> stories = GetSharedStories();

    return new AtomResult("My Site", "My shared stories in atom", stories);
}

17. Split your View into multiple ViewUserControl

Split your view into multiple ViewUserControl when it is getting bigger, it really does not matter whether the same UserControl is reused in another page, it makes the very view much more readable. Consider the following view:

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    My Secret App : Dashboard
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div id="heading"></div>
    <div class="columns">
        <div id="main" class="column">
            <div id="storyListTabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all">
                <% Html.RenderPartial("TabHeader", Model);%>
                <div id="tabContent" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
                    <div id="storyList">
                        <% Html.RenderPartial("SortBar", Model);%>
                        <div class="clear"></div>
                        <% Html.RenderPartial("NoLinkMessage", Model);%>
                        <form id="update" action="<%= Url.Update()%>" method="post">
                            <% Html.RenderPartial("List", Model);%>
                            <% Html.RenderPartial("ActionBar", Model);%>
                            <% Html.RenderPartial("Pager", Model);%>
                        </form>
                    </div>
                </div>
            </div>
            <%Html.RenderPartial("Submit", new StorySubmitViewModel());%>
        </div>
        <div id="sideBar" class="column"></div>
    </div>
</asp:Content>

18. HtmlHelper extension

First, read this great post of Rob Conery and I completely agree, you should create helper methods for each condition and I would also suggest to create helper methods for reusable UI elements like the ASP.NET MVC team did, but I have a different suggestion about the placing of these methods that we are currently practicing.

Application Developer

Do only create extension methods of HtmlHelper if you are using it in more than one view. Otherwise create a view specific helper and create an extension method of the HtmlHelper which returns the view specific helper. for example:

    public class DashboardHtmlHelper
    {
        private readonly HtmlHelper _htmlHelper;

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

        public string DoThis()
        {
            //Your Code
        }

        public string DoThat()
        {
            //Your Code
        }
    }

    public static class HtmlHelperExtension
    {
        public static DashboardHtmlHelper Dashboard(this HtmlHelper htmlHelper)
        {
            return new DashboardHtmlHelper(htmlHelper);
        }
    }

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

<%= Html.Dashboard().DoThis() %>

UI Component Developer

If you are developing some family of UI components that will be reusable across different ASP.NET MVC application, create a helper with your component family name like the above, if you are a commercial vendor maybe your company name then add those methods in that helper. Otherwise there is a very good chance of method name collision.

The same is also applied if you are extending the IViewDataContainer like the MVCContrib.org.

19. Encode

Whatever you receive from the User always use Html.Encode(“User Input”) for textNode and Html.AttributeEncode(“User Input”) for html element attribute.

20. Do not put your JavaScript codes in your View

Do not intermix your javascript with the html, create separate js files and put your java script in those files. Some time, you might need to pass your view data in your java script codes, in those cases only put your initialization in the view. For example, consider you are developing Web 2.0 style app where you want to pass ajax method url, and some other model data  in the java script codes, in those cases you can use some thing like the following:

The View:

        <div id="sideBar" class="column"></div>
        <script type="text/javascript">
            $(document).ready(function(){
                Story.init('<%= Model.UrlFormat %>', '<%= Url.NoIcon() %>', <%= Model.PageCount %>, <%= Model.StoryPerPage %>, <%= Model.CurrentPage %>, '<%= Model.SelectedTab %>', '<%= Model.SelectedOrderBy %>');
            });
        </script>
    </div>
</asp:Content>

And JavaScript:

var Story =
{
    _urlFormat: '',
    _noIconUrl: '',
    _pageCount: 0,
    _storyPerPage: 0,
    _currentPage: 0,
    _currentTab: '',
    _currentOrderBy: '',

    init: function(urlFormat, noIconUrl, pageCount, storyPerPage, currentPage, currentTab, currentOrderBy)
    {
        Story._urlFormat = urlFormat;
        Story._noIconUrl = noIconUrl;
        Story._pageCount = pageCount;
        Story._storyPerPage = storyPerPage;
        Story._currentPage = currentPage;
        Story._currentTab = currentTab;
        Story._currentOrderBy = currentOrderBy;

		//More Codes
    }
}

And those who are not familiar with the above  JavaScript code, it is an example of creating static class in JavaScript. And one more thing before I forgot to mention, never hard code your ajax method url in your javascript file, no  matter Rob Conery or Phil Haack does it in their demo. It is simply a bad practice and demolish the elegance of ASP.NET Routing.

21. Use jQuery and jQuery UI

Use jQuery and jQuery UI, nothing can beats it and use Google CDN to load these libraries.

<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/themes/{YOUR Prefered Theme}/jquery-ui.css" rel="stylesheet"/>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/jquery-ui.js"></script>

Or much better:

<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/themes/{YOUR Prefered Theme}/jquery-ui.css" rel="stylesheet"/>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
	google.load("jquery", "1.3.2");
	google.load("jqueryui", "1.7.1");
</script>

And that's it for the time being.

At the end, I just want congratulate the ASP.NET MVC Team for developing such an excellent framework and specially the way they took the feedback from the community and I look forward to develop few more killer apps in this framework.

Shout it

6 Comments

Comments have been disabled for this content.