Свойство интерфейса Model-bind с Web API

У меня есть команда, похожая на:

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; }
}

Запрос REST:

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"
}

У меня есть действие API-контроллера:

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

Свойство Title в моей модели заполняется телом запроса, но я хотел бы привязать свойство command.UserId, используя некоторые значения из заголовков запроса (например, из токена аутентификации).

Как связать свойство IUserAware с значением заголовка запроса, например. образцовое связующее, без необходимости создания связующего для конкретного класса CreateSomething?

Я пробовал различные комбинации интерфейса IModelBinder в веб-API, но без реальной удачи.

Также кажется излишним использовать:

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

Или получить UserId из зависимости от контроллера и установить его, как указано выше.

Как это делается в ASP.NET MVC

В ASP.NET MVC можно выполнить следующие действия:

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;
    }
}

И подключите его при запуске с помощью:

ModelBinders.Binders.DefaultBinder = new UserAwareModelBinder();

Ответы

Ответ 1

На основе последнего комментария @Todd ответ на вопрос:

Создайте класс HttpParameterBinding:

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!
        }
    }
}

И подключите его в HttpConfiguration:

configuration.ParameterBindingRules.Insert(0, descriptor => typeof(IUserAware).IsAssignableFrom(descriptor.ParameterType) ? new UserAwareHttpParameterBinding(descriptor) : null);

Если кто-то знает, как это делается в .NET Core MVC - отредактируйте это сообщение или комментарий.

Ответ 2

public class CreateSomethingModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string key = bindingContext.ModelName;
        ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
        if (val != null)
        {
            string s = val.AttemptedValue as string;
            if (s != null)
            {
                return new CreateSomething(){Title = s; UserId = new Guid(ControllerContext.HttpContext.Request.Headers["userId"]);}
            }
        }
        return null;
    }
}

и добавить атрибут объявления типа

[ModelBinder(typeof(CreateSomethingModelBinder))]
public class CreateSomething  { ... }