static void

ASP MVC Actions


If an argument type is a value type (eg int) it may fail (YSOD ArgumentException). Best practice is to set a default (int id = 0), in which case they will just be an error in ModelState.

public ActionResult Edit(int id = 0)
    var isValid = ModelState.IsValid; //false if id="a"

Action Results

To return simple values (such as an int) add [NoAction] attribute.

Post-Redirect-Get pattern

HttpPost actions usually redirect, with a RedirectToAction (or RedirectToRoute)

return RedirectToAction("Edit", new { id });

return RedirectToRoute(new {controller = "Person", action = "Edit", id});

To pass data across, use TempData["x"] = x.

TempData is Session and is deleted on first read ... unless you TempData.Peek("x") or TempData.Keep("x")- Keep is one-time only.
View HtmlHelpers use the POSTed values by preference (not the Model). Setting a model.Property in a postback does nothing. To make it work, do this:

ModelState.Remove("HasBeenPosted"); //so the model value isn't overridden
model.HasBeenPosted = true;

Partial Actions

An Html.Partial can just call a partial view/user control; Html.Action (and Html.RenderAction) calls a controller which in turn calls a partial view. In other words, Html.Action allows the view to do processing- the view calls a controller to call a view.

<div>@Html.Action("CategorySummary", "Category", Model)</div>

In CategoryController. Mark as [ChildActionOnly] if required. ControllerContext.IsChildAction can detect if within an Action.

public ActionResult CategorySummary(CategoryModel category)
    return PartialView(category);

If you use dependency injection, you'll get the error A single instance of controller 'Web.Controllers.MyController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.. The DI container is supplying the same instance per request (request-scoped).
Change the DI configuration to be scoped per call- eg StructureMap is For<MyController>().AlwaysUnique();

Action Filters


[OutputCache] Duration, VaryByParams etc
[Authorize] Any authenticated user - specifics with Users = "", Roles = ""
[AllowAnonymous] MVC 4: whitelist for logon actions when you have a global [Authorize]
[ValidateAntiForgeryToken] In conjunction with Html.AntiForgeryToken()
[HandleError] Error trapping, It goes to the default Shared/Errors.cshtml.
  • Attribute on method (specific action) or class (=all controller actions)
  • CustomErrors=RemoteOnly by default
  • If you don't use HandleError, it falls through to global.asax Application_Error
  • Alternatively (in MVC3) apply global filters

HandleErrors in GlobalFilters

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    //multiple filters are applied with ascending Order (default = -1)
    //database errors
    filters.Add(new HandleErrorAttribute
        ExceptionType = typeof(System.Data.Common.DbException),
        View = "DatabaseError", //-> Shared/DatabaseError.cshtml
        Order = 1
    //generic errors (default order is -1)
    filters.Add(new HandleErrorAttribute { Order = 2 });
protected void Application_Start()

Error views have no controller. The model is @model System.Web.Mvc.HandleErrorInfo

Action Input Binding

You can still access Request.Form, or accept a FormCollection, but AspMVC's DefaultModelBinder is quite good at mapping the HttpPosted fields (and Route data) into simple model classes.

You can use simple arguments (which can have defaults), with UpdateModel (or TryUpdateModel) - which can take an interface (as well as inclusions/ exclusions).

public ActionResult Edit(int categoryId, string categoryName)
    var category = new CategoryModel();
    UpdateModel(category, new[] { "CategoryName", "CategoryId" });


public ActionResult Edit(CategoryModel category)


This applies to the individual parameters - or to your model class.

public ActionResult Edit(
    [Bind(Include = "CategoryName,CategoryId")]
    CategoryModel category)

Custom ModelBinders