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).
How can I bind the property of IUserAware
from a request header value, with e.g. a model-binder, without having to create a binder for the concrete class 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.
How it is done in ASP.NET MVC
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();
Model Binding is the most powerful mechanism in Web API 2. It enables the response to receive data as per requester choice. i.e. it may be from the URL either form of Query String or Route data OR even from Request Body. It's just the requester has to decorate the action method with [FromUri] and [FromBody] as desired.
Easiest way is to add ModelBinder attribute to the parameter. In following example, I have added ModelBinder attribute to the "data" parameter, so web API can understand how to use custom model binder, while binding the data. Another way is to add ModelBinder attribute to the type.
Based on @Todd last comment, the answer to the question is:
Create a HttpParameterBinding
class:
public class UserAwareHttpParameterBinding : HttpParameterBinding
{
private readonly HttpParameterBinding _paramaterBinding;
private readonly HttpParameterDescriptor _httpParameterDescriptor;
public UserAwareHttpParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
{
_httpParameterDescriptor = descriptor;
_paramaterBinding = new FromBodyAttribute().GetBinding(descriptor);
}
public override async Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
await _paramaterBinding.ExecuteBindingAsync(metadataProvider, actionContext, cancellationToken);
var baseModel = actionContext.ActionArguments[_httpParameterDescriptor.ParameterName] as IUserAware;
if (baseModel != null)
{
baseModel.UserId = new Guid("6ed85eb7-e55b-4049-a5de-d977003e020f"); // Or get it form the actionContext.RequestContext!
}
}
}
And wire it up in the HttpConfiguration
:
configuration.ParameterBindingRules.Insert(0, descriptor => typeof(IUserAware).IsAssignableFrom(descriptor.ParameterType) ? new UserAwareHttpParameterBinding(descriptor) : null);
If anyone know how this is done in .NET Core MVC - please edit this post or comment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With