Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid null model when no data is posted in Web API

This question is similar to what I want to achieve:

Avoiding null model in ASP.Net Web API when no posted properties match the model

But it's gone un-answered.

I have a route which takes a model that is a GET:

    [HttpGet, Route("accounts")]
    public AccountListResult Post(AccountListRequest loginRequest)
    {
        return accountService.GetAccounts(loginRequest);
    }

The model is populated with additional data from an action filter.

In this case all that needs to be known is the UserId, which the action filter add's to the model based on cookie/header into passed in with the request.

I want to use all the default model binding in WebAPI but I want to avoid null objects.

I don't believe model binding solves my problem.

How do I replace the behaviour of Web API model binding so that instead of Null I receive a new instance when no parameters are passed in

This is closer to what I want to do except its per type which is tedious.

like image 993
Phill Avatar asked Feb 10 '15 11:02

Phill


2 Answers

EDIT: Since the question is for Web API, I am posting the Web API solution also below.

You can do this as below in an Action Filter. The below code works only if your model contains default constructor.

Web API Implementation:

public override void OnActionExecuting(HttpActionContext actionContext)
{
     var parameters = actionContext.ActionDescriptor.GetParameters();

     foreach (var parameter in parameters)
     {
         object value = null;

         if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
             value = actionContext.ActionArguments[parameter.ParameterName];

         if (value != null)
            continue;

         value = CreateInstance(parameter.ParameterType);
         actionContext.ActionArguments[parameter.ParameterName] = value;
     }

     base.OnActionExecuting(actionContext);
}

protected virtual object CreateInstance(Type type)
{
   // Check for existence of default constructor using reflection if needed
   // and if performance is not a constraint.

   // The below line will fail if the model does not contain a default constructor.
   return Activator.CreateInstance(type);
}

MVC Implementation:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var parameters = filterContext.ActionDescriptor.GetParameters();

    foreach (var parameter in parameters)
    {
        if (filterContext.ActionParameters.ContainsKey(parameter.ParameterName))
        {
            object value = filterContext.ActionParameters[parameter.ParameterName];

            if (value == null)
            {
                 // The below line will fail if the model does not contain a default constructor.
                 value = Activator.CreateInstance(parameter.ParameterType);                          
                 filterContext.ActionParameters[parameter.ParameterName] = value;
            }
        }                
    }

    base.OnActionExecuting(filterContext);
}
like image 101
Parthasarathy Avatar answered Oct 18 '22 07:10

Parthasarathy


@Sarathy's solution works, but doesn't trigger model validation on objects it creates. This can cause situations where an empty model was passed in to an action but ModelState.IsValid still evaluates true.

For my own purposes, it was necessary to trigger re-validation of the model in the event that an empty model object was created:

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var parameters = actionContext.ActionDescriptor.GetParameters();

        foreach (var parameter in parameters)
        {
            object value = null;

            if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
                value = actionContext.ActionArguments[parameter.ParameterName];

            if (value != null)
                continue;

            value = Activator.CreateInstance(parameter.ParameterType);
            actionContext.ActionArguments[parameter.ParameterName] = value;

            var bodyModelValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator();
            var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();

            bodyModelValidator.Validate(value, value.GetType(), metadataProvider, actionContext, string.Empty);
        }

        base.OnActionExecuting(actionContext);
    }
like image 4
Jesse Hallam Avatar answered Oct 18 '22 06:10

Jesse Hallam