2016-06-01 45 views
10

Ho una Web API che è un'infrastruttura davvero sottile che non contiene nient'altro che due implementazioni DelegatingHandler che inviano messaggi alle implementazioni del gestore di messaggi definite nel livello aziendale. Ciò significa che non ci sono controller e nessuna azione del controller; l'API è definita basandosi esclusivamente sui messaggi. Ciò significa che non sono necessarie modifiche al codice in questo livello infrastruttura quando vengono implementate nuove funzionalità.Configurazione di Swashbuckle con DelegatingHandler come dispatcher di messaggi

Per esempio, abbiamo messaggi come:

  • CreateOrderCommand
  • ShipOrderCommand
  • GetOrderByIdQuery
  • GetUnshippedOrdersForCurrentCustomerQuery

I gestori di delega determinare l'esatto messaggio in base alla URL e il contenuto della richiesta è deserializzato su un'istanza di tha t tipo di messaggio, dopo il quale quel messaggio viene inoltrato al gestore messaggi appropriato. Ad esempio, questi messaggi sono (attualmente) associati ai seguenti URL:

  • api/comandi/CreateOrder
  • api/comandi/ShipOrder
  • API/query/GetOrderById
  • api/query/GetUnshippedOrdersForCurrentCustomer

Come potete immaginare, questo modo di lavorare con Web API semplifica lo sviluppo e aumenta le prestazioni di sviluppo; c'è meno codice da scrivere e meno codice da testare.

Ma dal momento che non ci sono controller, ho problemi a eseguire il bootstrap in Swashbuckle; dopo aver letto la documentazione, non ho trovato un modo per registrare questi tipi di URL in Swashbuckle. C'è un modo per configurare Swashbuckle in modo tale che possa ancora emettere la documentazione dell'API?

Per completezza, un'applicazione architettura di riferimento che illustra questo può essere trovato here.

+0

Avete bisogno di un po 'di soluzione pronta all'uso, o semplicemente un modo per estendere Swashbuckle di includere i vostri gestori personalizzati documentazione? – Evk

+0

@Evk che estende lo Swashbuckle farebbe assolutamente. – Steven

+0

E come (o sta andando a) documentare i gestori dei messaggi? Decora tu stesso le classi dei messaggi con alcuni attributi personalizzati? – Evk

risposta

9

Sembra Swashbuckle non supporta questa out of the box, ma si può allungare per ottenere il risultato desiderato, pur riutilizzando la maggior parte delle infrastrutture di spavalderia. Potrebbe volerci un po 'di tempo e sforzi, ma non molto in generale, ma troppo per me per fornire una soluzione completa in questa risposta. Comunque cercherò almeno di iniziare. Nota che tutto il codice qui sotto non sarà molto pulito e pronto per la produzione.

Ciò che è necessario prima è creare e registrare personalizzato IApiExplorer. Questa è un'interfaccia utilizzata da Swashbuckle per recuperare le descrizioni della tua API ed è responsabile dell'esplorazione di tutti i controller e le azioni per raccogliere le informazioni richieste. Estenderemo sostanzialmente ApiExplorer esistente con il codice per esplorare le nostre classi di messaggi e creare una descrizione API da loro. Interfaccia stessa è semplice: Descrizione

public interface IApiExplorer 
{  
    Collection<ApiDescription> ApiDescriptions { get; } 
} 

Api classe contiene varie informazioni sul funzionamento api, ed è quello che viene utilizzato da Swashbuckle per costruire la pagina ui spavalderia. Ha una proprietà problematica: ActionDescriptor. Rappresenta l'azione asp.net mvc e non abbiamo azioni, non abbiamo controller.È possibile utilizzare un'implementazione fittizia di questo o simulare il comportamento di asp.net HttpActionDescriptor e fornire valori reali. Per semplicità andremo con il primo itinerario:

class DummyActionDescriptor : HttpActionDescriptor { 
    public DummyActionDescriptor(Type messageType, Type returnType) { 
     this.ControllerDescriptor = new DummyControllerDescriptor() { 
      ControllerName = "Message Handlers" 
     }; 
     this.ActionName = messageType.Name; 
     this.ReturnType = returnType; 
    } 

    public override Collection<HttpParameterDescriptor> GetParameters() { 
     // note you might provide properties of your message class and HttpParameterDescriptor here 
     return new Collection<HttpParameterDescriptor>(); 
    } 

    public override string ActionName { get; } 
    public override Type ReturnType { get; } 

    public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken) { 
     // will never be called by swagger 
     throw new NotSupportedException(); 
    } 
} 

class DummyControllerDescriptor : HttpControllerDescriptor { 
    public override Collection<T> GetCustomAttributes<T>() { 
     // note you might provide some asp.net attributes here 
     return new Collection<T>(); 
    } 
} 

Qui forniamo solo alcune sostituzioni che spavalderia chiamerà e fallirà se non abbiamo fornire i valori per loro.

Ora definiamo alcuni attributi per decorare le classi di messaggio con:

class MessageAttribute : Attribute { 
    public string Url { get; } 
    public string Description { get; } 
    public MessageAttribute(string url, string description = null) { 
     Url = url; 
     Description = description; 
    } 
} 

class RespondsWithAttribute : Attribute { 
    public Type Type { get; } 
    public RespondsWithAttribute(Type type) { 
     Type = type; 
    } 
} 

E alcuni messaggi:

abstract class BaseMessage { 

} 

[Message("/api/commands/CreateOrder", "This command creates new order")] 
[RespondsWith(typeof(CreateOrderResponse))] 
class CreateOrderCommand : BaseMessage { 

} 

class CreateOrderResponse { 
    public long OrderID { get; set; } 
    public string Description { get; set; } 
} 

ApiExplorer Ora personalizzato:

class MessageHandlerApiExplorer : IApiExplorer { 
    private readonly ApiExplorer _proxy; 

    public MessageHandlerApiExplorer() { 
     _proxy = new ApiExplorer(GlobalConfiguration.Configuration); 
     _descriptions = new Lazy<Collection<ApiDescription>>(GetDescriptions, true); 
    } 

    private readonly Lazy<Collection<ApiDescription>> _descriptions; 

    private Collection<ApiDescription> GetDescriptions() { 
     var desc = _proxy.ApiDescriptions; 
     foreach (var handlerDesc in ReadDescriptionsFromHandlers()) { 
      desc.Add(handlerDesc); 
     } 
     return desc; 
    } 

    public Collection<ApiDescription> ApiDescriptions => _descriptions.Value; 

    private IEnumerable<ApiDescription> ReadDescriptionsFromHandlers() { 
     foreach (var msg in Assembly.GetExecutingAssembly().GetTypes().Where(c => typeof(BaseMessage).IsAssignableFrom(c))) { 
      var urlAttr = msg.GetCustomAttribute<MessageAttribute>(); 
      var respondsWith = msg.GetCustomAttribute<RespondsWithAttribute>(); 
      if (urlAttr != null && respondsWith != null) { 
       var desc = new ApiDescription() { 
        HttpMethod = HttpMethod.Get, // grab it from some attribute 
        RelativePath = urlAttr.Url, 
        Documentation = urlAttr.Description, 
        ActionDescriptor = new DummyActionDescriptor(msg, respondsWith.Type) 
       };      

       var response = new ResponseDescription() { 
        DeclaredType = respondsWith.Type, 
        ResponseType = respondsWith.Type, 
        Documentation = "This is some response documentation you grabbed from some other attribute" 
       };      
       desc.GetType().GetProperty(nameof(desc.ResponseDescription)).GetSetMethod(true).Invoke(desc, new object[] {response}); 
       yield return desc; 
      } 
     } 
    } 
} 

E infine registrare IApiExplorer (dopo hai registrato le tue cose Swagger) con:

GlobalConfiguration.Configuration.Services.Replace(typeof(IApiExplorer), new MessageHandlerApiExplorer()); 

Dopo aver fatto tutto ciò che possiamo vedere il nostro comando messaggio personalizzato nell'interfaccia spavalderia:

enter image description here

+0

Ho sperimentato con il tuo codice ma sebbene possa trovare il tipo e la proprietà 'ResponseDescription' nei documenti MSDN, non si trova da nessuna parte nell'ultimo' System.Web.Http.dll '. Strano merda. – Steven

+0

Che piattaforma usi? Ho usato regolarmente .net 4.5 come ricordo di testare il codice precedente. – Evk

+0

.NET 4.5. Quale versione di System.Web.Net hai usato? – Steven