views:

5333

answers:

9

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:

+65  A: 

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();
}
Koistya Navin
stop adding new stuff every 5 minutes, just do it in one time!
Fredou
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
@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
+8  A: 

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 for cache busting.

Cherian
+3  A: 

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" %>
Chris Simpson
Huh? So are you opposed to strongly-typed views in principal?
Portman
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
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?
borisCallens
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
+1  A: 

This is the best place you can start learning ASP.NET MVC.

http://weblogs.asp.net/scottgu/archive/2009/01/27/asp-net-mvc-1-0-release-candidate-now-available.aspx

Kthevar
+10  A: 

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.

Khaja Minhajuddin
Jonathan Bates
+5  A: 

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

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

+3  A: 

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

bingle
Can you provide more detail on this topic? I'm very interested in this technique.
Nathan Taylor
+3  A: 

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

Omu
+9  A: 

Post-Redirect-Get

Use the Post-Redirect-Get 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 the 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.
MJ Richardson