It is considered good practice to use strongly-typed models when working with ASP.NET MVC. That is, instead of doing something like
you would do
return View(customer);
Using this approach however, has a minor issue. Let’s say someone makes the following request:
http://server.com/customer/details/34
and the corresponding Action that processes this is:
<p> </p> <div style="display:inline;float:none;margin:0;padding:0;" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:05c9dbc1-19ee-4fe1-9464-e5c7dd4e92c0" class="wlWriterEditableSmartContent"> <div class="le-pavsc-container"> <div style="background-color:#000000;max-height:300px;overflow:auto;white-space:nowrap;padding:2px 5px;">public ActionResult Details(int id)
{
var customer = _customerServices.GetCustomerById(id);
return View(customer);
}</div> </div> </div> <p> </p> <p>if customer with 34 does not exist, you’ll have a problem at runtime if you’ve not made the necessary null checks. This occurs because the View will most likely display the name via the Model property, which is null. </p> <p><p>
Name:
<%: Model.Name %>
</p> </p> <p>The most common solution to this problem is to do a null check, either at the controller level, by checking to see if the model is null before passing it to the View, or alternatively in the View, before accessing the model property. If doing the latter, the normal thing would be to extract this into an Html helper to avoid having continuous checks for each property. </p> <h2>Using Action Filters</h2> <p> </p> <p>ASP.NET MVC has a series of filters, which are applied before and after certain events take place. An Action filter has four methods that are of particular interest:</p> <ul> <li>OnActionExecuting: Takes places before an action is executed</li> <li>OnActionExecuted: Takes place after an action is executed</li> <li>OnResultExecuting: Takes place before a result is executed</li> <li>OnResultExecuted: Takes place after a result is executed</li> </ul> <p>We can leverage these methods to intercept the request/response when an action is executed. For instance we could override OnActionExecuting to perform authorization or override OnActionExecuting to implement some kind of auditing. </p> <p>In our case, what is interesting however is the OnResultExecuting, which occurs after an Action has executed and before the actual view is rendered. If we could hook into this, we could check to see if the Model passed in is null and if so, do something about it, thus preventing the View from giving a null reference exception while trying to access a null model. </p> <p>One way to accomplish this is to create a new filter by inheriting from ActionFilterAttribute and overriding the corresponding method:</p> <div style="display:inline;float:none;margin:0;padding:0;" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:843c2c62-7de3-4e53-ae5f-72afecf2edae" class="wlWriterEditableSmartContent"> <div class="le-pavsc-container"> <div style="background-color:#000000;max-height:300px;overflow:auto;white-space:nowrap;padding:2px 5px;"> public class HandleRecordNotFoundAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null && viewResult.ViewData.Model == null)
{
filterContext.Result = new ResourceNotFoundResult();
filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext);
}
}
}</div> </div> </div> <p> </p> <p>What we’ve done is first get the View that is going to be returned. If
it is not null we then check to see if the Model is null. If it is, we return a 404 result, which indicates that the resource requested does not exist. If none of the previous conditions are met, we then just let the response take its course and not interfere. The ResourceNotFoundResult is simply an HttpException wrapper that returns an ActionResult. </p> <div style="display:inline;float:none;margin:0;padding:0;" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:14182158-f153-4571-8d68-c74f90b1ce47" class="wlWriterEditableSmartContent"> <div class="le-pavsc-container"> <div style="background-color:#000000;max-height:300px;overflow:auto;white-space:nowrap;padding:2px 5px;">public class ResourceNotFoundResult : ActionResult
{
public string Message { get; private set; }
public ResourceNotFoundResult()
{
Message = "The requested resource was not found";
}
public ResourceNotFoundResult(string message)
{
Message = message;
}
public override void ExecuteResult(ControllerContext context)
{
throw new HttpException(404, Message);
}
}</div> </div> </div> <p> </p> <p>Finally, to use this, all we need to do is just tag the corresponding action with the attribute. This way, instead of continuously having code like this: </p> <div style="display:inline;float:none;margin:0;padding:0;" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:705af26a-8f7d-4e3c-a081-478b847e3941" class="wlWriterEditableSmartContent"> <div class="le-pavsc-container"> <div style="background-color:#000000;max-height:300px;overflow:auto;white-space:nowrap;padding:2px 5px;">public ActionResult Details(int id)
{
var customer = _customerServices.GetCustomerById(id);
if (customer == null)
{
return new ResourceNotFoundResult();
}
return View(customer);
}</div> </div> </div> <p> </p> <p>we can have:</p> <div style="display:inline;float:none;margin:0;padding:0;" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:2c4e09fb-74bf-4721-9a30-e2a82c745ae3" class="wlWriterEditableSmartContent"> <div class="le-pavsc-container"> <div style="background-color:#000000;max-height:300px;overflow:auto;white-space:nowrap;padding:2px 5px;">[HandleRecordNotFound]
public ActionResult Details(int id)
{
var customer = _customerServices.GetCustomerById(id);
return View(customer);
}</div> </div> </div> <p></p> <p>We could extend this to the controller by first checking the type of the result and not casting it directly to a ViewResult and set the attribute at the Controller level. I’ll leave that to anyone that wants to fork the code. One step further is to modify pipeline to not even require an attribute (but I’ve yet to play with that). </p> <p>You can get the entire example from here. </p>