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 konu başlığında, öznitelik yönlendirmenin nasıl etkinleştirileceği gösterilir ve öznitelik yönlendirme için çeşitli seçenekler açıklanı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'ı ve ardından konsol Paket Yöneticisi'ı 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 tür bir yönlendirmede, temelde parametreli dizeler olan bir veya daha fazla yol şablonu tanımlarsınız. Çerçeve bir istek aldığında, URI'yi yol şablonuna karşı eşleştirir. 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, filmlerin aktörleri, kitapların yazarları vb. vardır. 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 fazla 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 basit bir işlemdir. Denetleyici eylemine bir öznitelik eklemeniz yeterlidir:

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

Öznitelik yönlendirme ile kolaylaşan diğer bazı desenler şunlardır:

API sürümü oluşturma

Bu örnekte, "/api/v1/products" "/api/v2/products"tan farklı bir denetleyiciye yönlendirilir.

/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şleşir.

/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'nin öncesinde, Web API proje şablonları aşağıdaki gibi kodlar oluşturdu:

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

Öznitelik yönlendirmesi etkinleştirilirse, 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);
}

Uyarı

Daha fazla bilgi için bkz. ASP.NET Barındırma ile Web API'sini yapılandırma.

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 segmentlerdir ve "{customerId}" değişken bir parametredir. Aşağıdaki URI'ler bu şablonla eşleşer:

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

Bu konunun ilerleyen bölümlerinde açıklanan kısıtlamaları kullanarak eşleştirmeyi kısıtlayabilirsiniz.

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'si ayrıca isteğin HTTP yöntemine (GET, POST vb.) göre eylemleri seçer. 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 bir denetleyici yöntemi bir HTTP PUT isteğiyle eşleşir.

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

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

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

[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. Örneğin:

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 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. Örneğin:

[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 Example
alfa Büyük veya küçük Latin alfabesi karakterleriyle eşleşir (a-z, A-Z) {x:alpha}
bool Boole değeriyle eşleşir. {x:bool}
tarih/zaman DateTime değeriyle eşleşir. {x:datetime}
ondalık Ondalık değerle eşleşir. {x:decimal}
double 64 bit kayan nokta değeriyle eşleşir. {x:double}
yüzmek 32 bit kayan nokta değeriyle eşleşir. {x:float}
Kılavuz 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)}
uzun 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)}
Maksimum uzunluk Uzunluk üst sınırı olan bir dizeyle eşleşir. {x:maxlength(10)}
min Bir tamsayıyı en düşük değerle eşleştirir. {x:min(10)}
minimum uzunluk Minimum uzunlukta bir dizeyle eşleşir. {x:minlength(10)}
menzil Bir değer aralığındaki bir tamsayıyla eşleşir. {x:range(10,50)}
regex Normal ifadeyle eşleşir. {x:regex(^\d{3}-\d{3}-\d{4}$)}

"min" gibi bazı kısıtlamaların bağımsız değişkenleri parantez içinde almasına dikkat edin. Bir parametreye, iki nokta üst üste ile ayrılan 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) { ... }

Ayrıca, IInlineConstraintResolver arabirimini uygulayarak DefaultInlineConstraintResolver sınıfının tamamını da değiştirebilirsiniz. Bunu yaptığınızda, IInlineConstraintResolver uygulamanız bunları özel olarak 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, /api/books/locale/1033 ve /api/books/locale aynı kaynağı döndürür.

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

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

Bu, önceki örnekle hemen hemen 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 bu tam 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 işlem gerçekleştirebilen ö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ı 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 rota ö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ğıdadır:

  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 gibi sıralayın:

    1. Harfiyen bölümler.
    2. Kısıtlamalarla rota parametreleri.
    3. Kısıtlamasız rota parametreleri.
    4. Kısıtlamaları olan joker karakter parametre kesimleri.
    5. Kısıtlama olmadan joker parametre bölümleri.
  3. Eşitlik durumunda, yollar yol şablonunun büyük/küçük harfe duyarlı olmayan sıralı dize karşılaştırması (OrdinalIgnoreCase) ile sıralanır.

İşte bir örnek. 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. siparisler/{id}
  3. siparişler/{customerName}
  4. siparişler/{*date}
  5. siparişler/beklemede

"Details" öğesinin sabit bir segment 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)