2012-12-19 11 views
9

Ich arbeite an einem ASP.NET-Web-API-Projekt in C# für eine JSON-Schnittstelle zu einer mobilen Anwendung. Meine Idee war, Schnittstellen für alle Anfragen zu erstellen und diese Schnittstellen dann nur im Web-API-Code zu verwenden.Abhängigkeitsinjektion für ASP.NET-Web-API-Aktionsmethodenparameter

endete ich mit so etwas wie dies oben:

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

Mein Controller sieht wie folgt aus:

public class MyApiController : ApiController 
{ 

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

auch My Web API-Projekt enthält Implementierungen dieser Schnittstellen.

Ich nahm an, dass Web-API-Projekte Modellbindung wie MVC-Projekte verwenden, also erstellte ich inheritance aware ModelBinderProvider für die Bereitstellung einer Sammelmappe für alle IApiObjects und einen benutzerdefinierten Modellbinder mit einem Unity-Container zum Auflösen der Schnittstellen zu ihren Implementierungen.

Jedoch, nach einigen weiteren Untersuchungen, stieß ich auf How Web API does parameter binding und fand heraus, dass Web-API Formatierer statt Modellbinder für komplexe Typen verwendet. Der verknüpfte Blogpost empfiehlt die Verwendung eines ModelBinderAttribute für meine Aktionsparameter, aber dieses Attribut akzeptiert nur einen Typ als Parameter. Mein benutzerdefiniertes Modellbinder enthält jedoch keinen leeren Konstruktor (es benötigt einen Einheitscontainer), also müsste ich eine Instanz davon übergeben.

Die andere Möglichkeit, die ich mir vorstellen kann, ist die Verwendung der Abhängigkeitsinjektion für die Formatierer. Leider kenne ich sie nicht, da ich sie vorher noch nie benutzt habe.

Welches ist der richtige Weg? Und wie mache ich das?

Antwort

5

Das ist, was ich kam mit jetzt und es funktioniert.

Ich entschied mich, einen benutzerdefinierten Formatierer zu erstellen, der die Unity-Aufrufe ausführt und alle weiteren Operationen an einen anderen Formatierer mit dem aufgelösten Typ weiterleitet. Es sieht nach viel Code aus, aber das liegt nur daran, dass alle Methoden überschrieben werden müssen, damit der Typ immer aufgelöst werden kann.

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

Registrieren Sie schließlich unseren benutzerdefinierten Formatierer in der Anwendungskonfiguration (Global.asax Application_Start). Ich habe gewählt, alle aktuellen Formatierer durch eine Instanz meiner eigenen zu ersetzen, deshalb erhalte ich eine Reflexion für alle Datentypen.

// 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)); 
} 
Verwandte Themen