Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore exceptions caused by missing controller parameters

I have an internet-facing website that's built using MVC4 and I occasionally get error reports from bots or curious users who send requests for incomplete URLs.

For example:

public class ProductController : Controller
{
    [HttpGet]
    public void View(int id)
    {
        // ...
  • A GET request to /product/view/1 is valid.
  • A GET request to /product/view is invalid as the argument is not specified.

Such invalid requests raise exceptions resembling:

System.ArgumentException: The parameters dictionary contains a null entry
for parameter 'id' of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult View(Int32)' in 'Foo.ProductController'. An
optional parameter must be a reference type, a nullable type, or be declared
as an optional parameter.

Parameter name: parameters
   at System.Web.Mvc.ActionDescriptor.ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary`2 parameters, MethodInfo methodInfo)
   at System.Web.Mvc.ReflectedActionDescriptor.<>c__DisplayClass1.<Execute>b__0(ParameterInfo parameterInfo)
   ...

As the exception message states, I could make the id argument nullable and check within the action method, but I have many controllers with many actions.

I'd like to return a BadRequest/NotFound response to any request that fails to bind arguments to action parameters, and specify this in a single place in code to apply across all controllers.

How can this be done?

like image 214
Drew Noakes Avatar asked Oct 15 '13 10:10

Drew Noakes


1 Answers

One approach that seems to work is to override OnActionExecuted in the controller (I use a base controller, so would put it there.)

protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
    if (filterContext.Exception == null)
        return;

    // Avoid 'action parameter missing' exceptions by simply returning an error response
    if (filterContext.Exception.TargetSite.DeclaringType == typeof(ActionDescriptor) &&
        filterContext.Exception.TargetSite.Name == "ExtractParameterFromDictionary")
    {
        filterContext.ExceptionHandled = true;
        filterContext.Result = new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);
    }
}

It feels a little uncomfortable to do this as it may break in future versions of the framework. However if it does break, then the site will revert to returning 500's instead of 400's.

like image 89
Drew Noakes Avatar answered Oct 06 '22 00:10

Drew Noakes