Внедрение зависимостей для параметров метода действий ASP.NET Web API

Я работаю над проектом ASP.NET Web API в C# для интерфейса JSON с мобильным приложением. Моя идея состояла в том, чтобы создать интерфейсы для всех запросов, а затем использовать только эти интерфейсы в коде Web API.

Я закончил с чем-то вроде этого:

public interface IApiObject {}
public interface IApiResponse<T> : IApiObject where T : IApiObject {}
public interface IApiRegistrationRequest : IApiObject {}

Мой контроллер выглядит так:

public class MyApiController : ApiController
{

    public IApiResponse<IApiObject> Register(IApiRegistrationRequest request) {
        // do some stuff
    }
}

Мой проект веб-API также содержит реализации этих интерфейсов.

Я предполагал, что проекты Web API используют привязку модели, как и проекты MVC, поэтому я создал объект ModelBinderProvider, поддерживающий наследование, для предоставления связывателя для всех объектов IApiObject и связывателя пользовательской модели, использующего контейнер Unity для разрешения интерфейсов в их реализациях.

Однако после еще одного исследования я наткнулся на то, как Web API выполняет привязку параметров, и обнаружил, что Web API использует средства форматирования вместо связывателей моделей для сложных типов. В связанном посте блога рекомендуется использовать ModelBinderAttribute для моих параметров действия, но этот атрибут принимает в качестве параметра только тип. Однако мой пользовательский связыватель модели не содержит пустого конструктора (ему нужен контейнер для единства), поэтому мне нужно передать его экземпляр.

Другой способ, о котором я могу думать, - это использование зависимости для форматировщиков. К сожалению, я не знаком с ними, так как никогда не использовал их раньше.

Какой правильный путь? И как мне это сделать?

2 ответа

Решение

Это то, что я придумал сейчас, и это работает.

Я решил создать пользовательский модуль форматирования, который выполняет вызовы Unity и перенаправляет все дальнейшие операции в другой модуль форматирования, используя разрешенный тип. Похоже, много кода, но это только потому, что все методы должны быть перезаписаны, так что тип всегда может быть разрешен.

public class UnityFormatter : MediaTypeFormatter
{
    private MediaTypeFormatter formatter;

    private IUnityContainer container;

    public UnityFormatter(MediaTypeFormatter formatter, IUnityContainer container)
    {
        this.formatter = formatter;
        this.container = container;

        foreach (var supportedMediaType in this.formatter.SupportedMediaTypes)
        {
            this.SupportedMediaTypes.Add(supportedMediaType);
        }

        foreach (var supportedEncoding in this.formatter.SupportedEncodings)
        {
            this.SupportedEncodings.Add(supportedEncoding);
        }

        foreach (var mediaTypeMapping in this.MediaTypeMappings)
        {
            this.MediaTypeMappings.Add(mediaTypeMapping);
        }

        this.RequiredMemberSelector = this.formatter.RequiredMemberSelector;
    }

    private Type ResolveType(Type type)
    {
        return this.container.Registrations.Where(n => n.RegisteredType == type).Select(n => n.MappedToType).FirstOrDefault() ?? type;
    }

    public override bool CanReadType(Type type)
    {
        return this.formatter.CanReadType(this.ResolveType(type));
    }

    public override bool CanWriteType(Type type)
    {
        return this.formatter.CanWriteType(this.ResolveType(type));
    }

    public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
    {
        return this.formatter.GetPerRequestFormatterInstance(this.ResolveType(type), request, mediaType);
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        return this.formatter.ReadFromStreamAsync(this.ResolveType(type), readStream, content, formatterLogger);
    }

    public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
    {
        this.formatter.SetDefaultContentHeaders(this.ResolveType(type), headers, mediaType);
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        return this.formatter.WriteToStreamAsync(this.ResolveType(type), value, writeStream, content, transportContext);
    }
}

Наконец, зарегистрируйте наш пользовательский форматтер в конфигурации приложения (Global.asax Application_Start). Я решил заменить все текущие средства форматирования экземпляром моего собственного, так что я получаю отражение для всех типов данных.

// set up unity container, register all types
UnityContainer container = new UnityContainer();
container.RegisterType<IApiRegistrationRequest, ApiRegistrationRequest>();

// save existing formatters and remove them from the config
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>(GlobalConfiguration.Configuration.Formatters);
GlobalConfiguration.Configuration.Formatters.Clear();

// create an instance of our custom formatter for each existing formatter
foreach (MediaTypeFormatter formatter in formatters)
{
    GlobalConfiguration.Configuration.Formatters.Add(new UnityFormatter(formatter, container));
}

Предлагаю вам взглянуть на сервисный стек http://www.servicestack.net/

Он имеет тот же дизайн, что и asp.net-WebApi, но в нем есть такие плюсы, как IOC.

Существует удивительная серия статей о стеке услуг по адресу http://pluralsight.com/training/Courses/TableOfContents/service-stack

Другие вопросы по тегам