ASP.NET Web API 'sinde yönlendirme ve eylem seçimiRouting and Action Selection in ASP.NET Web API

, Mike te sonby Mike Wasson

Bu makalede, ASP.NET Web API 'sinin bir denetleyicideki belirli bir eyleme HTTP isteğini nasıl yönlendirdiğini açıklanmaktadır.This article describes how ASP.NET Web API routes an HTTP request to a particular action on a controller.

Note

Yönlendirmeye yönelik üst düzey bir genel bakış için bkz. ASP.NET Web API 'de yönlendirme.For a high-level overview of routing, see Routing in ASP.NET Web API.

Bu makale, yönlendirme sürecinin ayrıntılarına bakar.This article looks at the details of the routing process. Bir Web API projesi oluşturur ve bazı isteklerin istediğiniz şekilde yönlendirilmeyeceğini görürseniz, bu makalede yardımcı olacak.If you create a Web API project and find that some requests don't get routed the way you expect, hopefully this article will help.

Yönlendirme üç ana aşamaya sahiptir:Routing has three main phases:

  1. URI ile bir yol şablonuna eşleme.Matching the URI to a route template.
  2. Denetleyici seçiliyor.Selecting a controller.
  3. Bir eylem seçme.Selecting an action.

İşlemin bazı parçalarını kendi özel davranışlarınız ile değiştirebilirsiniz.You can replace some parts of the process with your own custom behaviors. Bu makalede, varsayılan davranışı açıklıyorum.In this article, I describe the default behavior. Sonda, davranışı özelleştirebileceğiniz yerleri de göz önünde koydum.At the end, I note the places where you can customize the behavior.

Rota şablonlarıRoute Templates

Bir yol şablonu bir URI yoluna benzer, ancak küme ayraçları ile gösterilen yer tutucu değerlerine sahip olabilir:A route template looks similar to a URI path, but it can have placeholder values, indicated with curly braces:

"api/{controller}/public/{category}/{id}"

Bir yol oluşturduğunuzda, yer tutucuların bazıları veya tümü için varsayılan değer sağlayabilirsiniz:When you create a route, you can provide default values for some or all of the placeholders:

defaults: new { category = "all" }

Ayrıca, bir URI segmentinin bir yer tutucu ile nasıl eşleşeceğini kısıtlayan kısıtlamalar da sağlayabilirsiniz:You can also provide constraints, which restrict how a URI segment can match a placeholder:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.

Çerçeve, URI yolundaki kesimleri şablonla eşleştirmeye çalışır.The framework tries to match the segments in the URI path to the template. Şablondaki değişmez değerler tam olarak eşleşmelidir.Literals in the template must match exactly. Bir yer tutucu, kısıtlama belirtmediğiniz müddetçe herhangi bir değerle eşleşir.A placeholder matches any value, unless you specify constraints. Çerçeve, URI 'nin ana bilgisayar adı veya sorgu parametreleri gibi diğer bölümleriyle eşleşmez.The framework does not match other parts of the URI, such as the host name or the query parameters. Framework, yol tablosundaki URI ile eşleşen ilk yolu seçer.The framework selects the first route in the route table that matches the URI.

İki özel yer tutucu vardır: "{Controller}" ve "{Action}".There are two special placeholders: "{controller}" and "{action}".

  • "{Controller}" denetleyicinin adını sağlar."{controller}" provides the name of the controller.
  • "{Action}", eylemin adını sağlar."{action}" provides the name of the action. Web API 'de, her zamanki kural "{Action}" öğesini atlayamaz.In Web API, the usual convention is to omit "{action}".

VarsayılanlarıDefaults

Varsayılan değer sağlarsanız, yol, bu kesimleri eksik olan bir URI ile eşleştirecektir.If you provide defaults, the route will match a URI that is missing those segments. Örneğin:For example:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}",
    defaults: new { category = "all" }
);

URI 'Ler http://localhost/api/products/all ve http://localhost/api/products önceki rotayla eşleşir.The URIs http://localhost/api/products/all and http://localhost/api/products match the preceding route. İkinci URI 'de, eksik {category} kesimine allvarsayılan değer atanır.In the latter URI, the missing {category} segment is assigned the default value all.

Yol sözlüğüRoute Dictionary

Çerçeve bir URI için eşleşme bulursa, her yer tutucunun değerini içeren bir sözlük oluşturur.If the framework finds a match for a URI, it creates a dictionary that contains the value for each placeholder. Anahtarlar, küme ayraçları dahil değil yer tutucu adlarıdır.The keys are the placeholder names, not including the curly braces. Değerler URI yolundan veya varsayılandan alınır.The values are taken from the URI path or from the defaults. Sözlük ıhttproutedata nesnesinde depolanır.The dictionary is stored in the IHttpRouteData object.

Bu rota eşleştirme aşamasında, özel "{Controller}" ve "{Action}" yer tutucuları, diğer yer tutucuları gibi değerlendirilir.During this route-matching phase, the special "{controller}" and "{action}" placeholders are treated just like the other placeholders. Yalnızca diğer değerlerle sözlükte depolanırlar.They are simply stored in the dictionary with the other values.

Varsayılan, RouteParameter özel değerine sahip olabilir . Isteğe bağlı.A default can have the special value RouteParameter.Optional. Bir yer tutucu bu değere atanmışsa, değer yol sözlüğüne eklenmez.If a placeholder gets assigned this value, the value is not added to the route dictionary. Örneğin:For example:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{category}/{id}",
    defaults: new { category = "all", id = RouteParameter.Optional }
);

"API/Products" URI yolu için yol sözlüğü şunları içerir:For the URI path "api/products", the route dictionary will contain:

  • Denetleyici: "Ürünler"controller: "products"
  • Kategori: "tümü"category: "all"

Bununla birlikte, "API/Products/Toys/123" için yol sözlüğü şunları içerir:For "api/products/toys/123", however, the route dictionary will contain:

  • Denetleyici: "Ürünler"controller: "products"
  • Kategori: "Toys"category: "toys"
  • Kimlik: "123"id: "123"

Varsayılanlar, yol şablonunda hiçbir yerde görünmeyen bir değer içerebilir.The defaults can also include a value that does not appear anywhere in the route template. Yol eşleşiyorsa, bu değer sözlükte depolanır.If the route matches, that value is stored in the dictionary. Örneğin:For example:

routes.MapHttpRoute(
    name: "Root",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "customers", id = RouteParameter.Optional }
);

URI yolu "api/root/8" ise, sözlük iki değer içerir:If the URI path is "api/root/8", the dictionary will contain two values:

  • Denetleyici: "müşteriler"controller: "customers"
  • Kimlik: "8"id: "8"

Denetleyici seçmeSelecting a Controller

Denetleyici seçimi IHttpControllerSelector. SelectController yöntemi tarafından işlenir.Controller selection is handled by the IHttpControllerSelector.SelectController method. Bu yöntem bir HttpRequestMessage örneği alır ve bir httpcontrollerdescriptordöndürür.This method takes an HttpRequestMessage instance and returns an HttpControllerDescriptor. Varsayılan uygulama DefaultHttpControllerSelector sınıfı tarafından sağlanır.The default implementation is provided by the DefaultHttpControllerSelector class. Bu sınıf, basit bir algoritma kullanır:This class uses a straightforward algorithm:

  1. "Denetleyici" anahtarı için yol sözlüğüne bakın.Look in the route dictionary for the key "controller".
  2. Bu anahtar için değeri alın ve denetleyicinin tür adını almak için "Controller" dizesini ekleyin.Take the value for this key and append the string "Controller" to get the controller type name.
  3. Bu tür adıyla bir Web API denetleyicisi arayın.Look for a Web API controller with this type name.

Örneğin, yol sözlüğü anahtar-değer çifti "denetleyici" = "Ürünler" içeriyorsa, denetleyici türü "ProductsController" olur.For example, if the route dictionary contains the key-value pair "controller" = "products", then the controller type is "ProductsController". Eşleşen bir tür veya birden fazla eşleşme yoksa, çerçeve istemciye bir hata döndürür.If there is no matching type, or multiple matches, the framework returns an error to the client.

3. adım için, DefaultHttpControllerSelector Web API denetleyici türlerinin listesini almak Için ıhttpcontrollertyperesolver arabirimini kullanır.For step 3, DefaultHttpControllerSelector uses the IHttpControllerTypeResolver interface to get the list of Web API controller types. Ihttpcontrollertyperesolver 'in varsayılan uygulaması, (a) ıhttpcontrolleruygulayan tüm genel sınıfları döndürür, (b) soyut değildir ve (c) "Controller" ile biten bir ada sahiptir.The default implementation of IHttpControllerTypeResolver returns all public classes that (a) implement IHttpController, (b) are not abstract, and (c) have a name that ends in "Controller".

Eylem seçimiAction Selection

Denetleyiciyi seçtikten sonra Framework, ıhttpactionselector. SelectAction yöntemini çağırarak eylemi seçer.After selecting the controller, the framework selects the action by calling the IHttpActionSelector.SelectAction method. Bu yöntem bir HttpControllerContext alır ve bir HttpActionDescriptordöndürür.This method takes an HttpControllerContext and returns an HttpActionDescriptor.

Varsayılan uygulama ApiControllerActionSelector sınıfı tarafından sağlanır.The default implementation is provided by the ApiControllerActionSelector class. Bir eylem seçmek için, aşağıdakilere bakar:To select an action, it looks at the following:

  • İsteğin HTTP yöntemi.The HTTP method of the request.
  • Varsa, yol şablonunda "{Action}" yer tutucusu.The "{action}" placeholder in the route template, if present.
  • Denetleyicideki eylemlerin parametreleri.The parameters of the actions on the controller.

Seçim algoritmasına bakmadan önce, denetleyici eylemleriyle ilgili bazı şeyleri anladık.Before looking at the selection algorithm, we need to understand some things about controller actions.

Denetleyicideki hangi Yöntemler "eylemler" olarak değerlendirilir?Which methods on the controller are considered "actions"? Bir eylem seçerken, çerçeve yalnızca denetleyicideki ortak örnek yöntemlerine bakar.When selecting an action, the framework only looks at public instance methods on the controller. Ayrıca, "özel ad" yöntemlerini (oluşturucular, olaylar, operatör aşırı yüklemeleri vs.) ve Apicontroller sınıfından devralınan yöntemleri dışlar.Also, it excludes "special name" methods (constructors, events, operator overloads, and so forth), and methods inherited from the ApiController class.

HTTP yöntemleri.HTTP Methods. Framework yalnızca isteğin HTTP yöntemiyle eşleşen eylemleri seçer, şöyle belirlenir:The framework only chooses actions that match the HTTP method of the request, determined as follows:

  1. HTTP yöntemini bir özniteliğiyle belirtebilirsiniz: Acceptverbs, httpdelete, HttpGet, httphead, HttpOptions, httppatch, HttpPostveya httpput.You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
  2. Aksi halde, denetleyici yönteminin adı "Get", "Post", "put", "Delete", "Head", "Options" veya "Patch" ile başlıyorsa ve bu HTTP yöntemini desteklediği kurala göre yapılır.Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
  3. Yukarıdakilerden hiçbiri, yöntemi GÖNDERIYI destekler.If none of the above, the method supports POST.

Parametre bağlamaları.Parameter Bindings. Bir parametre bağlama, Web API 'sinin bir parametre için bir değer oluşturmasıdır.A parameter binding is how Web API creates a value for a parameter. Parametre bağlama için varsayılan kural aşağıda verilmiştir:Here is the default rule for parameter binding:

  • Basit türler URI 'den alınır.Simple types are taken from the URI.
  • Karmaşık türler, istek gövdesinden alınır.Complex types are taken from the request body.

Basit türler .NET Framework ilkel türler, ayrıca DateTime, Decimal, Guid, dizeve TimeSpan değerleriiçerir.Simple types include all of the .NET Framework primitive types, plus DateTime, Decimal, Guid, String, and TimeSpan. Her eylem için, en fazla bir parametre istek gövdesini okuyabilir.For each action, at most one parameter can read the request body.

Note

Varsayılan bağlama kurallarını geçersiz kılmak mümkündür.It is possible to override the default binding rules. Bkz. bir çocuk altındaki WebAPI parametre bağlama.See WebAPI Parameter binding under the hood.

Bu arka plan ile eylem seçim algoritması aşağıda verilmiştir.With that background, here is the action selection algorithm.

  1. Denetleyicide HTTP istek yöntemiyle eşleşen tüm eylemlerin bir listesini oluşturun.Create a list of all actions on the controller that match the HTTP request method.

  2. Yol sözlüğünde bir "Action" girişi varsa, adı bu değerle eşleşmeyen eylemleri kaldırın.If the route dictionary has an "action" entry, remove actions whose name does not match this value.

  3. Aşağıdaki gibi eylem parametrelerini URI ile eşleştirmeye çalışın:Try to match action parameters to the URI, as follows:

    1. Her eylem için, bağlamanın URI 'den parametreyi aldığı basit türde parametrelerin bir listesini alın.For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. İsteğe bağlı parametreleri dışlayın.Exclude optional parameters.
    2. Bu listeden, yol sözlüğünde veya URI sorgu dizesinde her bir parametre adı için bir eşleşme bulmayı deneyin.From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Eşleşmeler büyük/küçük harfe duyarlıdır ve parametre sırasına bağlı değildir.Matches are case insensitive and do not depend on the parameter order.
    3. Listedeki her parametrenin URI 'de eşleşen bir eylem seçin.Select an action where every parameter in the list has a match in the URI.
    4. Bu ölçütlere uyan birden fazla eylem varsa, en fazla parametre eşleştirmelerle birini seçin.If more that one action meets these criteria, pick the one with the most parameter matches.
  4. [Nonactıon] özniteliğiyle eylemleri yoksayın.Ignore actions with the [NonAction] attribute.

Adım #3, büyük olasılıkla en karmaşıktır.Step #3 is probably the most confusing. Temel düşünce, bir parametrenin değerini URI 'den, istek gövdesinden veya özel bir bağlamadan alabilir.The basic idea is that a parameter can get its value either from the URI, from the request body, or from a custom binding. URI 'den gelen parametreler için, URI 'nin gerçekten bu parametre için bir değer (yol sözlüğü aracılığıyla) ya da sorgu dizesinde bir değer içerdiğinden emin olmak istiyoruz.For parameters that come from the URI, we want to ensure that the URI actually contains a value for that parameter, either in the path (via the route dictionary) or in the query string.

Örneğin, aşağıdaki eylemi göz önünde bulundurun:For example, consider the following action:

public void Get(int id)

ID parametresi URI 'ye bağlanır.The id parameter binds to the URI. Bu nedenle, bu eylem yalnızca yol sözlüğünde veya sorgu dizesinde "ID" değeri içeren bir URI ile eşleşemez.Therefore, this action can only match a URI that contains a value for "id", either in the route dictionary or in the query string.

İsteğe bağlı parametreler, isteğe bağlı oldukları için bir özel durumdur.Optional parameters are an exception, because they are optional. İsteğe bağlı bir parametre için bağlama URI 'den değeri alamazsanız Tamam olur.For an optional parameter, it's OK if the binding can't get the value from the URI.

Karmaşık türler, farklı bir nedenden dolayı özel durumdur.Complex types are an exception for a different reason. Karmaşık bir tür yalnızca özel bir bağlama aracılığıyla URI 'ye bağlanabilir.A complex type can only bind to the URI through a custom binding. Ancak bu durumda, parametrenin belirli bir URI 'ye bağlanıp bağlanamayacağını önceden bilemezsiniz.But in that case, the framework cannot know in advance whether the parameter would bind to a particular URI. Bunu öğrenmek için bağlamayı çağırması gerekir.To find out, it would need to invoke the binding. Seçim algoritmasının hedefi, herhangi bir bağlama çağırmadan önce statik açıklamadan bir eylem seçmektir.The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Bu nedenle, karmaşık türler eşleşen algoritmadan dışlanır.Therefore, complex types are excluded from the matching algorithm.

Eylem seçildikten sonra tüm parametre bağlamaları çağrılır.After the action is selected, all parameter bindings are invoked.

Özet:Summary:

  • Eylemin, isteğin HTTP yöntemiyle eşleşmesi gerekir.The action must match the HTTP method of the request.
  • Varsa, yol sözlüğünde eylem adı "Action" girdisiyle eşleşmelidir.The action name must match the "action" entry in the route dictionary, if present.
  • İşlemin her parametresi için parametre URI 'den alınsaydı, parametre adının yol sözlüğünde ya da URI sorgu dizesinde bulunması gerekir.For every parameter of the action, if the parameter is taken from the URI, then the parameter name must be found either in the route dictionary or in the URI query string. (Karmaşık türler içeren isteğe bağlı parametreler ve parametreler dışarıda bırakılır.)(Optional parameters and parameters with complex types are excluded.)
  • En fazla parametre sayısını eşleştirmeye çalışın.Try to match the most number of parameters. En iyi eşleşme parametresi olmayan bir yöntem olabilir.The best match might be a method with no parameters.

Genişletilmiş örnekExtended Example

YollarınRoutes:

routes.MapHttpRoute(
    name: "ApiRoot",
    routeTemplate: "api/root/{id}",
    defaults: new { controller = "products", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Denetleyici:Controller:

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAll() {}
    public Product GetById(int id, double version = 1.0) {}
    [HttpGet]
    public void FindProductsByName(string name) {}
    public void Post(Product value) {}
    public void Put(int id, Product value) {}
}

HTTP isteği:HTTP request:

GET http://localhost:34701/api/products/1?version=1.5&details=1

Rota eşleştirmeRoute Matching

URI, "DefaultApi" adlı yol ile eşleşiyor.The URI matches the route named "DefaultApi". Yol sözlüğü aşağıdaki girişleri içerir:The route dictionary contains the following entries:

  • Denetleyici: "Ürünler"controller: "products"
  • Kimlik: "1"id: "1"

Yol sözlüğü sorgu dizesi parametreleri, "sürüm" ve "Ayrıntılar" içeremez, ancak bunlar eylem seçimi sırasında göz önünde bulundurulacaktır.The route dictionary does not contain the query string parameters, "version" and "details", but these will still be considered during action selection.

Denetleyici seçimiController Selection

Yol sözlüğündeki "denetleyici" girdisinden, denetleyici türü ProductsController.From the "controller" entry in the route dictionary, the controller type is ProductsController.

Eylem seçimiAction Selection

HTTP isteği bir GET isteği.The HTTP request is a GET request. GET 'i destekleyen denetleyici eylemleri GetAll, GetByIdve FindProductsByName.The controller actions that support GET are GetAll, GetById, and FindProductsByName. Yol sözlüğü "eylem" için bir giriş içermiyor, bu nedenle eylem adıyla eşleşmesi gerekmez.The route dictionary does not contain an entry for "action", so we don't need to match the action name.

Ardından, eylemler için parametre adlarını eşleştirmeye çalışırız ve yalnızca GET eylemlerine bakmaya çalışıyoruz.Next, we try to match parameter names for the actions, looking only at the GET actions.

EylemAction Eşleştirilecek parametrelerParameters to Match
GetAll yoknone
GetById numarasını"id"
FindProductsByName ada"name"

İsteğe bağlı bir parametre olduğu için GetById Sürüm parametresinin değerlendirilmediğine dikkat edin.Notice that the version parameter of GetById is not considered, because it is an optional parameter.

GetAll yöntemi, önemli ölçüde eşleşir.The GetAll method matches trivially. Yol sözlüğü "ID" içerdiğinden GetById yöntemi de eşleşir.The GetById method also matches, because the route dictionary contains "id". FindProductsByName yöntemi eşleşmiyor.The FindProductsByName method does not match.

GetById yöntemi, bir parametresiyle eşleştiğinden, GetAlliçin hiçbir parametre olmadığından WINS.The GetById method wins, because it matches one parameter, versus no parameters for GetAll. Yöntemi aşağıdaki parametre değerleriyle çağrılır:The method is invoked with the following parameter values:

  • kimlik = 1id = 1
  • Sürüm = 1,5version = 1.5

Sürüm seçim algoritmasında kullanılmasa da, PARAMETRENIN değeri URI sorgu dizesinden geliyor olduğuna dikkat edin.Notice that even though version was not used in the selection algorithm, the value of the parameter comes from the URI query string.

Uzantı noktalarıExtension Points

Web API 'SI, yönlendirme sürecinin bazı bölümleri için uzantı noktaları sağlar.Web API provides extension points for some parts of the routing process.

ArabirimInterface AçıklamaDescription
IHttpControllerSelectorIHttpControllerSelector Denetleyiciyi seçer.Selects the controller.
IhttpcontrollertyperesolverIHttpControllerTypeResolver Denetleyici türleri listesini alır.Gets the list of controller types. DefaultHttpControllerSelector bu listeden denetleyici türünü seçer.The DefaultHttpControllerSelector chooses the controller type from this list.
IbirleştiricliesresolverIAssembliesResolver Proje derlemelerinin listesini alır.Gets the list of project assemblies. Ihttpcontrollertyperesolver arabirimi, denetleyici türlerini bulmak için bu listeyi kullanır.The IHttpControllerTypeResolver interface uses this list to find the controller types.
IhttpcontrolleretkinleştiriciIHttpControllerActivator Yeni denetleyici örnekleri oluşturur.Creates new controller instances.
IhttpactionselectorIHttpActionSelector Eylemi seçer.Selects the action.
IhttpactionınvokerIHttpActionInvoker Eylemi çağırır.Invokes the action.

Bu arabirimlerin herhangi birine yönelik uygulamanızı sağlamak için HttpConfiguration nesnesindeki Hizmetler koleksiyonunu kullanın:To provide your own implementation for any of these interfaces, use the Services collection on the HttpConfiguration object:

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));