ASP.NET Web API 2'de Öznitelik Yönlendirme

Yönlendirme , Web API'lerinin bir URI'yi bir eylemle eşleştirme şeklidir. Web API 2, öznitelik yönlendirme olarak adlandırılan yeni bir yönlendirme türünü destekler. Adından da anlaşılacağı gibi, öznitelik yönlendirme yolları tanımlamak için öznitelikleri kullanır. Öznitelik yönlendirme, web API'nizdeki URI'ler üzerinde daha fazla denetim sağlar. Örneğin, kaynakların hiyerarşilerini açıklayan URI'leri kolayca oluşturabilirsiniz.

Kural tabanlı yönlendirme olarak adlandırılan önceki yönlendirme stili hala tam olarak desteklenmektedir. Aslında, her iki tekniği de aynı projede birleştirebilirsiniz.

Bu konuda, öznitelik yönlendirmenin nasıl etkinleştirileceği gösterilmektedir ve öznitelik yönlendirme için çeşitli seçenekler açıklanmaktadır. Öznitelik yönlendirme kullanan uçtan uca bir öğretici için bkz. Web API 2'de Öznitelik Yönlendirme ile REST API oluşturma.

Önkoşullar

Visual Studio 2017 Community, Professional veya Enterprise sürümü

Alternatif olarak, gerekli paketleri yüklemek için NuGet Paket Yöneticisi'ni kullanın. Visual Studio'daki Araçlar menüsünde NuGet Paket Yöneticisi'ni ve ardından Paket Yöneticisi Konsolu'nu seçin. Paket Yöneticisi Konsolu penceresine aşağıdaki komutu girin:

Install-Package Microsoft.AspNet.WebApi.WebHost

Neden Öznitelik Yönlendirmesi?

Web API'sinin ilk sürümünde kural tabanlı yönlendirme kullanılmıştır. Bu yönlendirme türünde, temelde parametreli dizeler olan bir veya daha fazla yol şablonu tanımlarsınız. Çerçeve bir istek aldığında, yol şablonuyla URI ile eşleşir. Kural tabanlı yönlendirme hakkında daha fazla bilgi için bkz . ASP.NET Web API'sinde yönlendirme.

Kural tabanlı yönlendirmenin avantajlarından biri, şablonların tek bir yerde tanımlanması ve yönlendirme kurallarının tüm denetleyicilere tutarlı bir şekilde uygulanmasıdır. Ne yazık ki kural tabanlı yönlendirme, RESTful API'lerinde yaygın olan belirli URI desenlerini desteklemeyi zorlaştırır. Örneğin, kaynaklar genellikle alt kaynaklar içerir: Müşterilerin siparişleri vardır, filmlerin aktörleri vardır, kitapların yazarları vardır vb. Bu ilişkileri yansıtan URI'ler oluşturmak doğaldır:

/customers/1/orders

Bu tür bir URI'nin kural tabanlı yönlendirme kullanılarak oluşturulması zordur. Yapılabilmesine rağmen, çok sayıda denetleyiciniz veya kaynak türünüz varsa sonuçlar iyi ölçeklendirilemez.

Öznitelik yönlendirme ile, bu URI için bir yol tanımlamak önemsizdir. Denetleyici eylemine bir öznitelik eklemeniz yeterlidir:

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

Öznitelik yönlendirmenin kolaylaştırdığını diğer bazı desenleri aşağıda bulabilirsiniz.

API sürümü oluşturma

Bu örnekte, "/api/v1/products" "/api/v2/products" değerinden farklı bir denetleyiciye yönlendirilebilir.

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

Aşırı yüklenmiş URI kesimleri

Bu örnekte , "1" bir sipariş numarasıdır, ancak "beklemede" bir koleksiyonla eşler.

/orders/1 /orders/pending

Birden çok parametre türü

Bu örnekte , "1" bir sipariş numarasıdır, ancak "2013/06/16" bir tarih belirtir.

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

Öznitelik Yönlendirmeyi Etkinleştirme

Öznitelik yönlendirmeyi etkinleştirmek için yapılandırma sırasında MapHttpAttributeRoutes çağrısı yapın. Bu uzantı yöntemi System.Web.Http.HttpConfigurationExtensions sınıfında tanımlanır.

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.
        }
    }
}

Öznitelik yönlendirme, kural tabanlı yönlendirme ile birleştirilebilir. Kural tabanlı yollar tanımlamak için MapHttpRoute yöntemini çağırın.

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

Web API'sini yapılandırma hakkında daha fazla bilgi için bkz. ASP.NET Web API 2'yi yapılandırma.

Not: Web API 1'den Geçiş

Web API 2'ye başlamadan önce, Web API proje şablonları aşağıdaki gibi kod oluşturur:

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

Öznitelik yönlendirme etkinse, bu kod bir özel durum oluşturur. Öznitelik yönlendirmesini kullanmak için mevcut bir Web API projesini yükseltirseniz, bu yapılandırma kodunu aşağıdakilerle güncelleştirdiğinizden emin olun:

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

Yol Öznitelikleri Ekleme

Öznitelik kullanılarak tanımlanan bir yol örneği aşağıda verilmiştir:

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

"customers/{customerId}/orders" dizesi, yolun URI şablonudur. Web API'si, istek URI'sini şablonla eşleştirmeye çalışır. Bu örnekte, "müşteriler" ve "siparişler" değişmez değerlerdir ve "{customerId}" değişken bir parametredir. Aşağıdaki URI'ler bu şablonla eşleşmelidir:

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

Eşleştirmeyi kısıtlamak için bu konunun ilerleyen bölümlerinde açıklanan kısıtlamaları kullanabilirsiniz.

Yol şablonundaki "{customerId}" parametresinin yöntemindeki customerId parametresinin adıyla eşleştiklerine dikkat edin. Web API'si denetleyici eylemini çağırdığında, yol parametrelerini bağlamaya çalışır. Örneğin, URI ise http://example.com/customers/1/orders, Web API'si eylemdeki customerId parametresine "1" değerini bağlamaya çalışır.

Bir URI şablonunun çeşitli parametreleri olabilir:

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

Yol özniteliği olmayan tüm denetleyici yöntemleri kural tabanlı yönlendirme kullanır. Bu şekilde, aynı projede her iki yönlendirme türünü de birleştirebilirsiniz.

HTTP Yöntemleri

Web API'sinde ayrıca isteğin HTTP yöntemine (GET, POST vb.) göre eylemler seçilir. Varsayılan olarak, Web API denetleyici yöntemi adının başlangıcıyla büyük/küçük harfe duyarlı olmayan bir eşleşme arar. Örneğin, adlı PutCustomers denetleyici yöntemi bir HTTP PUT isteğiyle eşleşir.

Yöntemini aşağıdaki özniteliklerden biriyle süsleyerek bu kuralı geçersiz kılabilirsiniz:

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

Aşağıdaki örnekte Web API,CreateBook yöntemini HTTP POST istekleriyle eşlemektedir.

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

Standart olmayan yöntemler de dahil olmak üzere diğer tüm HTTP yöntemleri için, HTTP yöntemlerinin listesini alan AcceptVerbs özniteliğini kullanın.

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

Yol Ön Ekleri

Genellikle, bir denetleyicideki yolların tümü aynı ön ek ile başlar. Örnek:

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) { ... }
}

[RoutePrefix] özniteliğini kullanarak denetleyicinin tamamı için ortak bir ön ek ayarlayabilirsiniz:

[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) { ... }
}

Yol ön ekini geçersiz kılmak için yöntem özniteliğinde bir tilde (~) kullanın:

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

    // ...
}

Yol ön eki şu parametreleri içerebilir:

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

Yol Kısıtlamaları

Yol kısıtlamaları, yol şablonundaki parametrelerin eşleşme biçimini kısıtlamanıza olanak sağlar. Genel söz dizimi "{parameter:constraint}" şeklindedir. Örnek:

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

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

Burada ilk yol yalnızca URI'nin "id" kesimi bir tamsayı olduğunda seçilir. Aksi takdirde, ikinci yol seçilir.

Aşağıdaki tabloda desteklenen kısıtlamalar listelenmektedir.

Kısıtlama Açıklama Örnek
alfa Büyük veya küçük Harfli Latin alfabesi karakterleriyle (a-z, A-Z) eşleşir {x:alpha}
bool Boole değeriyle eşleşir. {x:bool}
datetime DateTime değeriyle eşleşir. {x:datetime}
decimal Ondalık değerle eşleşir. {x:decimal}
double 64 bit kayan nokta değeriyle eşleşir. {x:double}
float 32 bit kayan nokta değeriyle eşleşir. {x:float}
guid GUID değeriyle eşleşir. {x:guid}
int 32 bit tamsayı değeriyle eşleşir. {x:int}
length Belirtilen uzunlukta veya belirtilen uzunluk aralığındaki bir dizeyle eşleşir. {x:length(6)} {x:length(1,20)}
long 64 bit tamsayı değeriyle eşleşir. {x:long}
max En büyük değere sahip bir tamsayıyla eşleşir. {x:max(10)}
Maxlength Uzunluk üst sınırı olan bir dizeyle eşleşir. {x:maxlength(10)}
dk En düşük değere sahip bir tamsayıyla eşleşir. {x:min(10)}
Minlength En az uzunlukta bir dizeyle eşleşir. {x:minlength(10)}
aralık Değer aralığındaki bir tamsayıyla eşleşir. {x:range(10,50)}
Regex Normal ifadeyle eşleşir. {x:regex(^\d-\d{3}-\d{4}{3}$)}

"min" gibi bazı kısıtlamaların parantez içinde bağımsız değişkenler almasına dikkat edin. Bir parametreye iki nokta üst üste ile ayrılmış olarak birden çok kısıtlama uygulayabilirsiniz.

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

Özel Yol Kısıtlamaları

IHttpRouteConstraint arabirimini uygulayarak özel yol kısıtlamaları oluşturabilirsiniz. Örneğin, aşağıdaki kısıtlama bir parametreyi sıfır olmayan bir tamsayı değeriyle kısıtlar.

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

Aşağıdaki kod kısıtlamanın nasıl kaydedileceklerini gösterir:

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

        config.MapHttpAttributeRoutes(constraintResolver);
    }
}

Artık kısıtlamayı rotalarınıza uygulayabilirsiniz:

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

IInlineConstraintResolver arabirimini uygulayarak DefaultInlineConstraintResolver sınıfının tamamını da değiştirebilirsiniz. Bunu yaptığınızda , IInlineConstraintResolver uygulamanız bunları özellikle eklemediği sürece tüm yerleşik kısıtlamaların yerini alır.

İsteğe Bağlı URI Parametreleri ve Varsayılan Değerler

Yol parametresine soru işareti ekleyerek URI parametresini isteğe bağlı hale getirebilirsiniz. Yol parametresi isteğe bağlıysa, yöntem parametresi için varsayılan bir değer tanımlamanız gerekir.

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

Bu örnekte aynı /api/books/locale/1033/api/books/locale kaynağı döndürebilirsiniz.

Alternatif olarak, yol şablonunun içinde aşağıdaki gibi bir varsayılan değer belirtebilirsiniz:

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

Bu, önceki örnekle neredeyse aynıdır, ancak varsayılan değer uygulandığında küçük bir davranış farkı vardır.

  • İlk örnekte ("{lcid:int?}"), varsayılan değer olan 1033 doğrudan yöntem parametresine atanır, bu nedenle parametre tam olarak bu değere sahip olur.
  • İkinci örnekte ("{lcid:int=1033}"), varsayılan "1033" değeri model bağlama işleminden geçer. Varsayılan model bağlayıcısı "1033" değerini 1033 sayısal değerine dönüştürür. Ancak, farklı bir şey yapabilen özel bir model bağlayıcısı takabilirsiniz.

(Çoğu durumda, işlem hattınızda özel model bağlayıcıları yoksa, iki form eşdeğer olur.)

Yol Adları

Web API'sinde her yolun bir adı vardır. Yol adları, http yanıtına bağlantı ekleyebilmeniz için bağlantılar oluşturmak için kullanışlıdır.

Yol adını belirtmek için özniteliğinde Name özelliğini ayarlayın. Aşağıdaki örnekte yol adının nasıl ayarlanacağı ve bağlantı oluşturulurken yol adının nasıl kullanılacağı gösterilmektedir.

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

Rota Sırası

Çerçeve bir URI'yi bir yolla eşleştirmeye çalıştığında, yolları belirli bir sırada değerlendirir. Sırayı belirtmek için route özniteliğinde Order özelliğini ayarlayın. Önce daha düşük değerler değerlendirilir. Varsayılan sipariş değeri sıfırdır.

Toplam sıralamanın nasıl belirlendiği aşağıda açıklanıyor:

  1. Route özniteliğinin Order özelliğini karşılaştırın.

  2. Yol şablonundaki her URI kesimine bakın. Her segment için aşağıdaki şekilde sıralayın:

    1. Değişmez değerler.
    2. Kısıtlamaları olan yönlendirme parametreleri.
    3. Parametreleri kısıtlama olmadan yönlendirin.
    4. Kısıtlamaları olan joker karakter parametre kesimleri.
    5. Kısıtlama olmadan joker karakter parametre kesimleri.
  3. Bağlama durumunda yollar, yol şablonunun büyük/küçük harfe duyarlı olmayan sıralı dize karşılaştırması (OrdinalIgnoreCase) tarafından sıralanır.

Aşağıda bir örnek verilmiştir. Aşağıdaki denetleyiciyi tanımladığınız varsayın:

[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) { ... }
}

Bu yollar aşağıdaki gibi sıralanır.

  1. siparişler/ayrıntılar
  2. orders/{id}
  3. orders/{customerName}
  4. orders/{*date}
  5. orders/pending

"Details" öğesinin değişmez bir kesim olduğuna ve "{id}" öğesinden önce göründüğüne, ancak Order özelliği 1 olduğundan "beklemede" ifadesinin en son göründüğüne dikkat edin. (Bu örnekte "details" veya "pending" adlı bir müşteri olmadığı varsayılır. Genel olarak, belirsiz yollardan kaçınmaya çalışın. Bu örnekte için daha iyi bir yol şablonu GetByCustomer "customers/{customerName}" şeklindedir)