Směrování atributů ve webovém rozhraní API ASP.NET 2

Směrování je způsob, jakým webové rozhraní API porovnává identifikátor URI s akcí. Webové rozhraní API 2 podporuje nový typ směrování označovaný jako směrování atributů. Jak název napovídá, směrování atributů používá atributy k definování tras. Směrování atributů poskytuje větší kontrolu nad identifikátory URI ve webovém rozhraní API. Můžete například snadno vytvořit identifikátory URI, které popisují hierarchie prostředků.

Starší styl směrování, označovaný jako směrování založené na konvencích, je stále plně podporovaný. Ve skutečnosti můžete kombinovat obě techniky ve stejném projektu.

Toto téma ukazuje, jak povolit směrování atributů, a popisuje různé možnosti směrování atributů. Kompletní kurz, který používá směrování atributů, najdete v tématu Vytvoření rozhraní REST API se směrováním atributů ve webovém rozhraní API 2.

Požadavky

Visual Studio 2017 Edice Community, Professional nebo Enterprise

Případně můžete k instalaci potřebných balíčků použít Správce balíčků NuGet. V nabídce Nástroje v sadě Visual Studio vyberte Správce balíčků NuGet a pak vyberte Konzola Správce balíčků. V okně konzoly Správce balíčků zadejte následující příkaz:

Install-Package Microsoft.AspNet.WebApi.WebHost

Proč směrování atributů?

První verze webového rozhraní API používala směrování na základě konvencí . V takovém typu směrování definujete jednu nebo více šablon tras, což jsou v podstatě parametrizované řetězce. Když rozhraní obdrží požadavek, odpovídá identifikátoru URI šablony trasy. Další informace o směrování založeném na konvencích najdete v tématu Směrování v ASP.NET webovém rozhraní API.

Jednou z výhod směrování založeného na konvencích je, že šablony jsou definovány na jednom místě a pravidla směrování se používají konzistentně napříč všemi řadiči. Směrování založené na konvencích bohužel ztěžuje podporu určitých vzorů identifikátorů URI, které jsou běžné v rozhraních RESTful API. Prostředky například často obsahují podřízené prostředky: zákazníci mají objednávky, filmy mají herce, knihy mají autory atd. Je přirozené vytvářet identifikátory URI, které odpovídají těmto vztahům:

/customers/1/orders

Tento typ identifikátoru URI je obtížné vytvořit pomocí směrování založeného na konvencích. I když je to možné, výsledky se neškálit dobře, pokud máte mnoho kontrolerů nebo typů prostředků.

Při směrování atributů je jednoduché definovat trasu pro tento identifikátor URI. Jednoduše přidáte atribut do akce kontroleru:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

Tady jsou některé další vzory, které směrování atributů usnadňuje.

Správa verzí rozhraní API

V tomto příkladu by se /api/v1/products směrovala do jiného kontroleru než /api/v2/products.

/api/v1/products /api/v2/products

Přetížené segmenty identifikátorů URI

V tomto příkladu je "1" číslo objednávky, ale "čekající" se mapuje na kolekci.

/orders/1 /orders/pending

Více typů parametrů

V tomto příkladu je "1" číslo objednávky, ale "2013/06/16" určuje datum.

/orders/1 /orders/2013/06/16

Povolení směrování atributů

Pokud chcete povolit směrování atributů, během konfigurace volejte MapHttpAttributeRoutes . Tato metoda rozšíření je definována ve třídě System.Web.Http.HttpConfigurationExtensions .

using System.Web.Http;

namespace WebApplication
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();

            // Other Web API configuration not shown.
        }
    }
}

Směrování atributů je možné kombinovat se směrováním založeným na konvencích . Pokud chcete definovat trasy založené na konvencích, zavolejte metodu MapHttpRoute .

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

        // Convention-based routing.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Další informace o konfiguraci webového rozhraní API najdete v tématu Konfigurace ASP.NET webového rozhraní API 2.

Poznámka: Migrace z webového rozhraní API 1

Před webovým rozhraním API 2 šablony projektů webového rozhraní API vygenerovaly kód takto:

protected void Application_Start()
{
    // WARNING - Not compatible with attribute routing.
    WebApiConfig.Register(GlobalConfiguration.Configuration);
}

Pokud je povolené směrování atributů, tento kód vyvolá výjimku. Pokud upgradujete existující projekt webového rozhraní API tak, aby používal směrování atributů, nezapomeňte aktualizovat tento konfigurační kód na následující:

protected void Application_Start()
{
    // Pass a delegate to the Configure method.
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

Poznámka

Další informace najdete v tématu Konfigurace webového rozhraní API s využitím hostování ASP.NET.

Přidání atributů trasy

Tady je příklad trasy definované pomocí atributu:

public class OrdersController : ApiController
{
    [Route("customers/{customerId}/orders")]
    [HttpGet]
    public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }
}

Řetězec customers/{customerId}/orders je šablona identifikátoru URI pro trasu. Webové rozhraní API se pokusí shodovat identifikátor URI požadavku s šablonou. V tomto příkladu jsou "customers" a "orders" literální segmenty a "{customerId} je parametr proměnné. Následující identifikátory URI by odpovídaly této šabloně:

  • http://localhost/customers/1/orders
  • http://localhost/customers/bob/orders
  • http://localhost/customers/1234-5678/orders

Shodná nastavení můžete omezit pomocí omezení popsaných dále v tomto tématu.

Všimněte si, že parametr {customerId} v šabloně trasy odpovídá názvu parametru customerId v metodě. Když webové rozhraní API vyvolá akci kontroleru, pokusí se vytvořit vazbu parametrů trasy. Pokud je http://example.com/customers/1/ordersnapříklad identifikátor URI , webové rozhraní API se pokusí vytvořit vazbu hodnoty "1" na parametr customerId v akci.

Šablona identifikátoru URI může mít několik parametrů:

[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }

Všechny metody kontroleru, které nemají atribut trasy, používají směrování založené na konvencích. Tímto způsobem můžete zkombinovat oba typy směrování ve stejném projektu.

Metody HTTP

Webové rozhraní API také vybírá akce na základě metody HTTP požadavku (GET, POST atd.). Ve výchozím nastavení webové rozhraní API hledá shodu nerozlišující malá a velká písmena se začátkem názvu metody kontroleru. Například metoda kontroleru s názvem PutCustomers odpovídá požadavku HTTP PUT.

Tuto konvenci můžete přepsat tak, že metodu vyzdobete některým z následujících atributů:

  • [HttpDelete]
  • [HttpGet]
  • [HttpHead]
  • [HttpOptions]
  • [HttpPatch]
  • [HttpPost]
  • [HttpPut]

V následujícím příkladu webové rozhraní API mapuje metodu CreateBook na požadavky HTTP POST.

[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }

Pro všechny ostatní metody HTTP, včetně nestandardních metod, použijte atribut AcceptVerbs , který přebírá seznam metod HTTP.

// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }

Předpony tras

Trasy v kontroleru často začínají stejnou předponou. Příklad:

public class BooksController : ApiController
{
    [Route("api/books")]
    public IEnumerable<Book> GetBooks() { ... }

    [Route("api/books/{id:int}")]
    public Book GetBook(int id) { ... }

    [Route("api/books")]
    [HttpPost]
    public HttpResponseMessage CreateBook(Book book) { ... }
}

Společnou předponu pro celý kontroler můžete nastavit pomocí atributu [RoutePrefix] :

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET api/books
    [Route("")]
    public IEnumerable<Book> Get() { ... }

    // GET api/books/5
    [Route("{id:int}")]
    public Book Get(int id) { ... }

    // POST api/books
    [Route("")]
    public HttpResponseMessage Post(Book book) { ... }
}

K přepsání předpony trasy použijte u atributu metody vlnovku (~):

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET /api/authors/1/books
    [Route("~/api/authors/{authorId:int}/books")]
    public IEnumerable<Book> GetByAuthor(int authorId) { ... }

    // ...
}

Předpona trasy může obsahovat parametry:

[RoutePrefix("customers/{customerId}")]
public class OrdersController : ApiController
{
    // GET customers/1/orders
    [Route("orders")]
    public IEnumerable<Order> Get(int customerId) { ... }
}

Omezení trasy

Omezení trasy umožňují omezit shodu parametrů v šabloně trasy. Obecná syntaxe je {parameter:constraint}. Příklad:

[Route("users/{id:int}")]
public User GetUserById(int id) { ... }

[Route("users/{name}")]
public User GetUserByName(string name) { ... }

V této části se vybere první trasa pouze v případě, že je segment id identifikátoru URI celé číslo. V opačném případě bude zvolena druhá trasa.

Následující tabulka uvádí podporovaná omezení.

Omezení Popis Příklad
alpha Odpovídá znakům latinky s velkými nebo malými písmeny (a-z, A-Z). {x:alpha}
bool Odpovídá logické hodnotě. {x:bool}
datetime Odpovídá hodnotě DateTime . {x:datetime}
decimal Odpovídá desetinné hodnotě. {x:decimal}
double Odpovídá 64bitové hodnotě s plovoucí desetinou čárkou. {x:double}
float Odpovídá 32bitové hodnotě s plovoucí desetinou čárkou. {x:float}
guid Odpovídá hodnotě GUID. {x:guid}
int Odpovídá 32bitové celočíselné hodnotě. {x:int}
length Odpovídá řetězci se zadanou délkou nebo v zadaném rozsahu délek. {x:length(6)} {x:length(1,20)}
long Odpovídá 64bitové celočíselné hodnotě. {x:long}
max Porovná celé číslo s maximální hodnotou. {x:max(10)}
Maxlength Odpovídá řetězci s maximální délkou. {x:maxlength(10)}
min Porovná celé číslo s minimální hodnotou. {x:min(10)}
Minlength Odpovídá řetězci s minimální délkou. {x:minlength(10)}
range Odpovídá celé číslo v rozsahu hodnot. {x:range(10,50)}
Regex Odpovídá regulárnímu výrazu. {x:regex(^\d{3}-\d{3}-\d{4}$)}

Všimněte si, že některá omezení, například "min", přebírají argumenty v závorkách. U parametru můžete použít více omezení oddělených dvojtečkami.

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { ... }

Omezení vlastních tras

Vlastní omezení tras můžete vytvořit implementací rozhraní IHttpRouteConstraint . Například následující omezení omezuje parametr na nenulovou celočíselnou hodnotu.

public class NonZeroConstraint : IHttpRouteConstraint
{
    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, 
        IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            long longValue;
            if (value is long)
            {
                longValue = (long)value;
                return longValue != 0;
            }

            string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
            if (Int64.TryParse(valueString, NumberStyles.Integer, 
                CultureInfo.InvariantCulture, out longValue))
            {
                return longValue != 0;
            }
        }
        return false;
    }
}

Následující kód ukazuje, jak toto omezení zaregistrovat:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var constraintResolver = new DefaultInlineConstraintResolver();
        constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));

        config.MapHttpAttributeRoutes(constraintResolver);
    }
}

Teď můžete použít omezení v trasách:

[Route("{id:nonzero}")]
public HttpResponseMessage GetNonZero(int id) { ... }

Můžete také nahradit celou třídu DefaultInlineConstraintResolver implementací IInlineConstraintResolver rozhraní. Tím nahradíte všechna předdefinované omezení, pokud je vaše implementace IInlineConstraintResolver výslovně nezačne přidat.

Volitelné parametry identifikátoru URI a výchozí hodnoty

Parametr URI můžete nastavit jako volitelný přidáním otazníku do parametru trasy. Pokud je parametr trasy volitelný, musíte pro parametr metody definovat výchozí hodnotu.

public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int?}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}

V tomto příkladu /api/books/locale/1033 a /api/books/locale vrátí stejný prostředek.

Případně můžete v šabloně trasy zadat výchozí hodnotu následujícím způsobem:

public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int=1033}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
}

To je téměř stejné jako v předchozím příkladu, ale při použití výchozí hodnoty je mírně rozdíl v chování.

  • V prvním příkladu ({lcid:int?}) je výchozí hodnota 1033 přiřazena přímo parametru metody, takže parametr bude mít tuto přesnou hodnotu.
  • Ve druhém příkladu ({lcid:int=1033}) prochází výchozí hodnota 1033 procesem vazby modelu. Výchozí pořadač modelu převede hodnotu 1033 na číselnou hodnotu 1033. Můžete ale připojit vlastní pořadač modelu, který může dělat něco jiného.

(Ve většině případů, pokud v kanálu nemáte vlastní pořadače modelů, budou tyto dva formuláře ekvivalentní.)

Názvy tras

Ve webovém rozhraní API má každá trasa název. Názvy tras jsou užitečné pro generování odkazů, abyste mohli zahrnout odkaz do odpovědi HTTP.

Pokud chcete zadat název trasy, nastavte vlastnost Name u atributu. Následující příklad ukazuje, jak nastavit název trasy a také jak použít název trasy při generování odkazu.

public class BooksController : ApiController
{
    [Route("api/books/{id}", Name="GetBookById")]
    public BookDto GetBook(int id) 
    {
        // Implementation not shown...
    }

    [Route("api/books")]
    public HttpResponseMessage Post(Book book)
    {
        // Validate and add book to database (not shown)

        var response = Request.CreateResponse(HttpStatusCode.Created);

        // Generate a link to the new book and set the Location header in the response.
        string uri = Url.Link("GetBookById", new { id = book.BookId });
        response.Headers.Location = new Uri(uri);
        return response;
    }
}

Pořadí směrování

Když se rozhraní pokusí spárovat identifikátor URI s trasou, vyhodnotí trasy v určitém pořadí. Pokud chcete zadat pořadí, nastavte vlastnost Order v atributu route. Nižší hodnoty jsou vyhodnoceny jako první. Výchozí hodnota objednávky je nula.

Tady je způsob, jak se určí celkové pořadí:

  1. Porovnejte vlastnost Order atributu trasy.

  2. Podívejte se na jednotlivé segmenty identifikátoru URI v šabloně trasy. Pro každý segment sespořádejte takto:

    1. Literálové segmenty.
    2. Parametry trasy s omezeními.
    3. Parametry trasy bez omezení.
    4. Segmenty parametrů se zástupným znakem s omezeními
    5. Zástupné segmenty parametrů bez omezení.
  3. V případě vazby jsou trasy seřazeny porovnáním řadových řetězců (OrdinalIgnoreCase) v šabloně trasy, které nerozlišují velká a malá písmena.

Zde je příklad. Předpokládejme, že definujete následující kontroler:

[RoutePrefix("orders")]
public class OrdersController : ApiController
{
    [Route("{id:int}")] // constrained parameter
    public HttpResponseMessage Get(int id) { ... }

    [Route("details")]  // literal
    public HttpResponseMessage GetDetails() { ... }

    [Route("pending", RouteOrder = 1)]
    public HttpResponseMessage GetPending() { ... }

    [Route("{customerName}")]  // unconstrained parameter
    public HttpResponseMessage GetByCustomer(string customerName) { ... }

    [Route("{*date:datetime}")]  // wildcard
    public HttpResponseMessage Get(DateTime date) { ... }
}

Tyto trasy jsou seřazené následujícím způsobem.

  1. objednávky/podrobnosti
  2. orders/{id}
  3. orders/{customerName}
  4. orders/{*date}
  5. objednávky / čekající na vyřízení

Všimněte si, že "details" je literál segment a zobrazuje se před {id}, ale "pending" se zobrazuje jako poslední, protože vlastnost Order je 1. (Tento příklad předpokládá, že neexistují žádní zákazníci s názvem "details" nebo "pending". Obecně se snažte vyhnout nejednoznačným trasám. V tomto příkladu je lepší šablona trasy pro GetByCustomer customers/{customerName}).