Model-bind interface property with Web API

asked9 years ago
last updated9 years ago
viewed4.1k times
Up Vote15Down Vote

I have a command looking like:

public interface ICommand {
    // Just a marker interface
}

public interface IUserAware {
    Guid UserId { get; set; }
}

public class CreateSomething : ICommand, IUserAware
{
    public string Title { get; set; }

    public Guid UserId { get; set; }
}

The REST request is:

PUT /create HTTP/1.1
UserId: 7da6f9ee-2bfc-70b1-f93c-10c950c8f6b0 // Possible an Auth token and not a userId like here.
Host: localhost:63079
Content-Type: application/json
Cache-Control: no-cache
{
    "title": "This is a test title"
}

I have a API controller action looking:

[HttpPut, Route("create")]
public IHttpActionResult CreateSomething([FromBody]CreateSomething command)
{
    // I would like command.UserId already binded here
}

The Title property on my model is filled out with the body of the request, but I would like to bind the command.UserId property using some values from the request headers (e.g. from a authentication token).

IUserAware``CreateSomething

I've tried various combinations of the IModelBinder interface in Web API, but with no real luck.

It also feels redundant to to use:

[HttpPut, Route("create")]
public IHttpActionResult CreateSomething([FromBody]CreateSomething command)
{
    command.UserId = GetUserIdFromTheRequest();
}

Or getting the UserId from a dependency on the controller and set it like the above.

In ASP.NET MVC it is possible to do the following to get it work:

public class UserAwareModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
    {
        var baseModel = base.CreateModel(controllerContext, bindingContext, modelType);
        var commandModel = baseModel as IUserAware;
        if (commandModel != null) 
        {
             commandModel.UserId = controllerContext.HttpContext.User; // or get it from the HttpContext headers.
        }

        return baseModel;
    }
}

And wire it up at startup with:

ModelBinders.Binders.DefaultBinder = new UserAwareModelBinder();