share
Stack OverflowASP.NET MVC Best Practices, Tips and Tricks
[+167] [12] Tarkus
[2009-04-02 12:03:55]
[ asp.net-mvc tips-and-tricks samples ]
[ http://stackoverflow.com/questions/709429] [DELETED]

Please, share your ideas which could serve as best practices or guidelines for creating ASP.NET MVC web applications. These ideas and/or coding samples should be relevant to ASP.NET MVC application creation itself and not to TDD or similar practices.

Other resources:

[+154] [2009-04-02 12:06:00] Tarkus

Here are a few of my tips. And I'd like to hear yours.

Create extension methods of UrlHelper

public static class UrlHelperExtensions  
{   
    // for regular pages:

    public static string Home(this UrlHelper helper)   
    {   
        return helper.Content("~/");   
    }   

    public static string SignUp(this UrlHelper helper)   
    {
        return helper.RouteUrl("SignUp");   
    }

    // for media and css files:

    public static string Image(this UrlHelper helper, string fileName)   
    {   
        return helper.Content("~/Content/Images/{0}".
               FormatWith(fileName));   
    }

    public static string Style(this UrlHelper helper, string fileName)   
    {   
        return helper.Content("~/Content/CSS/{0}".
               FormatWith(fileName));   
    } 

    public static string NoAvatar(this UrlHelper helper)   
    {   
        return Image(helper, "NoAvatar.png");
    }
}

Use Bootstrapper in Global.asax

If you're doing lots of things in Application_Start, it's better to encapsulate that logic using bootstrapper design pattern.

Decorate your action methods with proper AcceptVerbs (and CacheControl for some actions) attributes

[HttpPost, OutputCache(CacheProfile = "Products")]   
public ActionResult Products(string category)   
{
    // implementation goes here   
}

Use Dependency Injection pattern

This will make your application loosely coupled and more testable.

Use typed views

Avoid passing data to views via ViewData, create custom models instead for your data and typed views.

Async Controllers

Here is an example:

public Func<ActionResult> Foo(int id) {
     AsyncManager.RegisterTask(
        callback => BeginGetUserByID(id, callback, null),
        a => {
            ViewData["User"] = EndGetUserByID(a);
        });
    AsyncManager.RegisterTask(
        callback => BeginGetTotalUsersOnline(callback, null),
        a => {
            ViewData["OnlineUsersCount"] = EndGetTotalUsersOnline(a);
        });

    return View;
}

What is happening here is that the MVC system executes both BeginGetUserByID and BeginGetTotalUsersOnline in parallel and when both complete, it will then process the View (display the web page). Simple, clean, efficient.

Security considerations

Don't forget to set [AcceptVerbs(HttpVerbs.Post)], [AntiForgeryToken] and [Authorize] attributes for your submit action methods. Example:

[Authorize("Clients"), HttpPost, ValidateAntiForgeryToken]
public ActionResult SendMoney(int accountID, int amount) {
     // your code here
}

And also put this into your form view:

<%= Html.AntiForgeryToken() %>

Avoid having a huge list of route registrations in Global.asax.cs file

Consider using this approach: http://maproutes.codeplex.com/

[Url("store/{?category}")]
public ActionResult Products(string category = null)
{
    return View();
}

(21) Why on earth was this downvoted? +1 For helpful information. Constantly updating is also designed behaviour in SO. - Damien
@Damien, I agree with you but when it's 6 edits in less than 35 minutes and some are only minor stuff... my point is, do it(the author) correctly the first or second time, at least - Fredou
(60) @Fredou, why do you care how often this is edited? I don't think you get the nature of the wiki-style of this site. He should continue to edit, refine, and make his answer better every chance he gets. - Simucal
(1) wow, good tips and tricks. thanks. - Zain Shaikh
awesome, that's why I love .net community. Thanks! - Kim Tranjan
1
[+36] [2010-08-10 14:40:45] MJ Richardson

Post-Redirect-Get

Use the Post-Redirect-Get [1] pattern when designing Controller actions.

To apply this pattern to MVC actions:

You have a particular view (e.g. Search).

    [HttpGet]
    public ViewResult Search()
    {
        return View("Search");
    }

The user will enter some data into the rendered form, and submit it. Give the 'Post' action the same name. If no validation is required, or validation is successful, then redirect to another action which will be responsible for displaying the result.

If validation fails, return the View with the invalid model.

    [HttpPost]
    public ActionResult Search(string searchTerm)
    {
        //if validation fails
        if (ValidateSearchTerm(searchTerm)==false)
        {
            return View("Search", searchTerm);
        }

        //if validation was successful, then redirect
        return RedirectToAction("SearchResults");
    }

The 'Get' portion of the Post-Redirect-Get comes from the fact the when a RedirectToAction result is returned, a HTTP redirect is returned to the client-browser, which will then issue a 'get' for the next action.

    [HttpGet]
    public ViewResult SearchResults()
    {
        return View("SearchResults");
    }

Using this pattern brings at least following benefits:

  • When validation fails, you return the same view with the necessary validation errors, and importantly, the URL in the browser will remain the same (e.g. if a user enters invalid data on the view ~\Products\Search, when the validation errors are displayed, they will still be on ~\Products\Search). In situations where this pattern has not been followed, you will see symptoms where the data-entry form will be displayed with validation errors, but the URL will appear as if you should be on the next view.
  • This pattern is compatible with standard 'back' and 'refresh' web functions. For example, when validation succeeds, and the user proceeds to the receipt/results/confirmation page, they will be able to refresh the page, as it was a HTTP get. In contrast, if a user refreshes a page which has been displayed directly from a Post action, it will resubmit the form, which could lead to undesired results.
[1] http://en.wikipedia.org/wiki/Post/Redirect/Get

(1) +1 for the comment at the end about 'back' and 'refresh'. - saille
Note that TempData is especially useful in the PRG pattern. It allows you to send data (e.g., a success message) to the destination that won't be lost in the redirect step. - Tuan
2
[+16] [2009-04-02 15:56:58] Khaja Minhajuddin

Create Helpers to generate URLs for the stylesheets, javascript files and any other resources (via Storefront). This is very important since you might end up changing the structure of your resources directories a lot.

public static class UrlHelperExtensions  
{   
    public static string CssResource(this UrlHelper helper, string filename)   
    {   
        return helper.Content(string.Format("~/Contents/CSS/{0}", filename));   
    }  


    public static string ImageResource(this UrlHelper helper, string filename)   
    {   
        return helper.Content(string.Format("~/Contents/Images/{0}", filename));   
    }  
}

Hope that helps.


(5) check out the MvcContrib project, specifically T4MVC. I think it does this very well. mvccontrib.codeplex.com/… - Jonathan Bates
3
[+12] [2009-04-02 12:23:03] Cherian

Checkout Kazi Manzur Rashid's Blog post on this: http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx

The 2nd part too: http://weblogs.asp.net/rashid/archive/2009/04/03/asp-net-mvc-best-practices-part-2.aspx

Karl Seguin has some entension methods [1] for cache busting.

[1] http://codebetter.com/blogs/karlseguin/archive/2010/01/11/asp-net-performance-part-3-cache-busting.aspx

4
[+7] [2009-04-25 06:30:56] community_owned

Good thoughts on both architecture and the details:

Jimmy Bogard: How we do MVC

http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/04/24/how-we-do-mvc.aspx [1]

Jeremy D. Miller: Our “Opinions” on the ASP.NET MVC (Introducing the Thunderdome Principle)

http://codebetter.com/blogs/jeremy.miller/archive/2008/10/23/our-opinions-on-the-asp-net-mvc-introducing-the-thunderdome-principle.aspx

[1] http://www.lostechies.com/blogs/jimmy%5Fbogard/archive/2009/04/24/how-we-do-mvc.aspx

5
[+3] [2009-05-22 00:46:54] bingle

Don't bother with extending UrlHelper, just use Html.BuildUrlFromExpression.


(4) Can you provide more detail on this topic? I'm very interested in this technique. - Nathan Taylor
6
[+2] [2010-06-15 06:20:26] Chuck Norris

use ValueInjecter [1] for mapping ViewModels to Entities and back (you could also map viewdata to entities if you need that or request, or formcollection)

[1] http://valueinjecter.codeplex.com/

7
[+2] [2009-04-02 12:47:50] Kthevar
8
[+1] [2009-04-02 12:32:53] Chris Simpson

Here's a simple one:

Do not reference the models namespace from any of the views. I've seen in numerous examples where the controller fetches a list of objects from the model and this is passed directly into the view data rather than it being transformed by the controller into something appropriate to that view. This creates a direct dependency between the model structure and the view.

So in short, avoid code like this in your markup:

<%@ Import Namespace="MyProject.Models" %>

Huh? So are you opposed to strongly-typed views in principal? - Portman
(2) No, not at all. I'm just opposed to taking an object directly from the model and exposing that to a view. I don't know how that could be read as opposition to strong types but I'll be glad to edit the answer to make it clearer. - Chris Simpson
I think what your trying to say is use some sort of presentation wrapper around your models right? - Micah
(3) Yes, some sort of DTO between the model and the view - Chris Simpson
I have a seperate library with only my business objects. This is referenced in the model, the controller and the view. Would this solve the issue or make it worse? Why? - Boris Callens
(1) So I guess you are mapping your model data into these business objects and then passing them between the layers? This avoids the issue I mentioned because if your model schema changes this only impacts one layer. The only thing I would ask is, does the shape of the data in your business objects fit nicely into what needs to be represented in your view? If not, this means you need to do a lot of mapping work within the view code. - Chris Simpson
@Chris: I can see your point, but I think you'd be better to do it only when the need arises, rather than add a whole layer and a whole lot of mapping code for the relatively few cases when it adds value. - saille
(1) The idea here is that you have Model, View, and ViewModel. Something like AutoMapper is designed for this exactly. - goldenratio
9
[+1] [2011-06-29 10:21:32] mirkasha

Make folder "App_Code", and add eg. Content.cshtml there. make methods for resources there like:

@helper Script(string scriptName, UrlHelper url)
{
    <script src="@scriptName" type="text/javascript"></script>
}

@helper Css(string css, UrlHelper url)
{
    <link href="@css" rel="stylesheet" type="text/css" />
}

so You can reference script/css in page like this:

@Content.Css("/Content/style.css", Url)

T4MVC is very useful, especially for larger projects because you can have strongly typed views, actions...

@Content.Css(Links.Content.style_css, Url)
@Html.ActionLink("Home", MVC.Home.Index())

You can then unload your web project, then edit project.csproj, and set MvcBuildView to true, then your views will be compiled before build so errors will be shown immediately.

I've always had problems with javascripts when deploying, virtual directory gives me another level on app, so my referenced actions in scripts didn't work. So, in my layout.cshtml I made this script:

@Html.Hidden("HiddenCurrentUrl", Url.Action("DummyAction"))
    <script type="text/javascript">
        var baseUrl = "";
        baseUrl = $("#HiddenCurrentUrl").val();
        baseUrl = baseUrl.substring(0, baseUrl.indexOf("DummyAction"));
    </script>

//and in my scripts i call actions like this:

$.post(**baseUrl + "ActionName"**, data, function success)

//baseUrl is on your controller, so if you want level below, just type ../

and now it is simple to have style on menu links(or anything else) for current controller. Just put this in _layout.cshtml

$(document).ready(function () {
    $('#menu li:has(a[href="' + baseUrl.substring(0, baseUrl.length - 1) + '"]) a').attr('class', 'current');
  });

In your Views\Shared make folder EditorTemplates, and there you can make cshtml file, that will be template for some data type. Example: String.cshtml

@model System.String
//Template for string input, label + textbox + validation           
<div class="editor-label">
    @Html.LabelFor(model => model)
</div>
<div class="editor-field">
    @Html.TextBoxFor(model => model, ViewData)
</div>
<div>
    @Html.ValidationMessageFor(model => model)
</div>

and in View you just call

@Html.EditorFor(model => model.Text)

and you get textbox with label and validation with less code.

//I'll put more when i get time.


10
[0] [2011-09-13 08:13:27] simply denis

How create lowercase routes in ASP.NET MVC. [1]

But I want to make an addendum to the.

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
    VirtualPathData path = base.GetVirtualPath(requestContext, values);

    if (path != null && path.VirtualPath.Length>0)
    {
        var strings = path.VirtualPath.Split('/');
        path.VirtualPath = "";
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Char.ToLowerInvariant(strings[i][0]) + strings[i].Substring(1);
        }
        path.VirtualPath = String.Join("/", strings, 0, strings.Length);
    }

    return path;
}

Example work this code. If you have path "Home/CreateNews" code makes it more readable your url path "home/createNews"

[1] http://coderjournal.com/2008/03/force-mvc-route-url-lowercase/

11
[-1] [2011-09-13 10:03:47] simply denis

Redirect with http code [1] 301.

public class PermanentlyRedirectResult : ActionResult
{
    public string Url { get; set; }

    public PermanentlyRedirectResult(string url)
    {
        if (string.IsNullOrEmpty(url))
        {
            throw new ArgumentException("Url is null or empty", "url");
        }
        this.Url = url;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        context.HttpContext.Response.StatusCode = 301;
        context.HttpContext.Response.RedirectLocation = Url;
        context.HttpContext.Response.End();
    }
}

use: new PermanentlyRedirectResult(Url.Action("Index"));

[1] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

12