14

E 'possibile separare GET e POST in tipi di controller di API separati e l'accesso utilizzando lo stesso percorso prefisso?tipi di controller multipli con lo stesso percorso prefisso ASP.NET Web Api

qui sono i miei controller:

[RoutePrefix("api/Books")] 
public class BooksWriteController : EventStoreApiController 
{ 
    [Route("")] 
    public void Post([FromBody] CommandWrapper commandWrapper){...} 
} 

[RoutePrefix("api/Books")] 
public class BooksReadController : MongoDbApiController 
{ 
    [Route("")] 
    public Book[] Get() {...} 


    [Route("{id:int}")] 
    public Book Get(int id) {...} 
} 

risposta

29

Web API (1.x-2.x) non supporta più percorsi di attributo con lo stesso percorso su diversi controllori. Il risultato è una 404, perché tutti il ​​percorso corrisponde a più di un controller ea quel API punto Web prenderà in considerazione il risultato ambiguo.

Nota che MVC Core supporta questa nota di scenario: MVC Core funge da API Web MVC &.

Se si sceglie di utilizzare Web API 2.11 (o più recente) è possibile creare un vincolo di route per il metodo http per controller e utilizzarlo al posto dell'attributo Route in uso. L'esempio seguente mostra che è possibile utilizzare RoutePrefix o direttamente Routes (come la risposta di kmacdonald).

using System.Collections.Generic; 
using System.Net.Http; 
using System.Web.Http; 
using System.Web.Http.Routing; 

public class BooksWriteController : ApiController 
{ 
    [PostRoute("api/Books")] 
    public void Post() { } 
} 

[RoutePrefix("api/books")] 
public class BooksReadController : ApiController 
{ 
    [GetRoute] 
    public void Get() { } 

    [GetRoute("{id:int}")] 
    public void Get(int id) { } 
} 

Queste due classi semplificare l'uso dell'attributo percorso vincolo

class GetRouteAttribute : MethodConstraintedRouteAttribute 
{ 
    public GetRouteAttribute(string template) : base(template ?? "", HttpMethod.Get) { } 
} 

class PostRouteAttribute : MethodConstraintedRouteAttribute 
{ 
    public PostRouteAttribute(string template) : base(template ?? "", HttpMethod.Post) { } 
} 

Questa classe permette di aggiungere vincoli al percorso generato

class MethodConstraintedRouteAttribute : RouteFactoryAttribute 
{ 
    public MethodConstraintedRouteAttribute(string template, HttpMethod method) 
     : base(template) 
    { 
     Method = method; 
    } 

    public HttpMethod Method 
    { 
     get; 
     private set; 
    } 

    public override IDictionary<string, object> Constraints 
    { 
     get 
     { 
      var constraints = new HttpRouteValueDictionary(); 
      constraints.Add("method", new MethodConstraint(Method)); 
      return constraints; 
     } 
    } 
} 

Questo è solo uno standard vincolo percorso, nit : è possibile che si desideri memorizzare nella cache l'oggetto constraint per ridurre le allocazioni.

class MethodConstraint : IHttpRouteConstraint 
{ 
    public HttpMethod Method { get; private set; } 

    public MethodConstraint(HttpMethod method) 
    { 
     Method = method; 
    } 

    public bool Match(HttpRequestMessage request, 
         IHttpRoute route, 
         string parameterName, 
         IDictionary<string, object> values, 
         HttpRouteDirection routeDirection) 
    { 
     return request.Method == Method; 
    } 
} 
+0

Questo funziona perfettamente. – user1145404

+1

@YishaiGalatzer: qualsiasi motivo specifico per cui è necessario il 'MethodConstraint' personalizzato qui invece di riutilizzare [System.Web.Http.Routing.HttpMethodConstraint] (https://msdn.microsoft.com/en-us/library/system.web. http.routing.httpmethodconstraint (v = vs.118) .aspx)? – Serguei

+1

Il punto sta vincolando la rotta stessa. La proprietà built-in agisce solo nella selezione dell'azione che viene eseguita troppo tardi. –

3

Non è sempre necessario specificare un RoutePrefix sul controller. si può solo mettere il percorso direttamente sui metodi web:

public class BooksWriteController : EventStoreApiController 
{ 
    [Route("api/Books")] 
    public void Post([FromBody] CommandWrapper commandWrapper){...} 
} 

public class BooksReadController : MongoDbApiController 
{ 
    [Route("api/Books")] 
    public TaskTypeInfo[] Get() {...} 


    [Route("api/Books/{id:int}")] 
    public TaskTypeInfo Get(int id) {...} 
} 

Tuttavia, mi immagino che il vostro RoutePrefix avrebbe funzionato bene su entrambi i controller. Credo l'attributo RoutePrefix viene utilizzato in combinazione con l'attributo Route che definisce effettivamente il percorso. Ciò significa che se non si hanno percorsi in conflitto (questo è un problema), si dovrebbe andare bene.

+2

Grazie, non è questo il problema. Il problema sta selezionando il tipo di controller corretto sulla base di GET o POST – user1145404

Problemi correlati