This article describes the design concept of restful Web APIs: Understanding and Design of restful Web APIs
Next, this article describes how to create a "pure" Asp.net web API application: Asp.net web API module dependency.
This article describes how to add some content to this "pure" application. In fact, there have been many articles on ASP.net Web APIs, but they seldom talk about how to classify controllers. Why do we need to classify them? If a project is a little larger and the controller is a little larger, and all controllers are accessed through the URI "API/controllername", it will inevitably lead to confusion, your controller may have a duplicate name, or you may feel that the structural hierarchy is unclear.
Maybe you think of it now: area! Yes. Does Asp.net MVC provide the area function? With area, we can divide functions and organize them well. Unfortunately, area is really a MVC function, not a web API function. web API does not directly support area. The solution exists. Please refer to this article: Asp. net MVC 4 RC: Getting webapi and areas to play nicely, you can follow the methods it provides to solve this problem, but this time I plan to use my own method, because I created a pure web API application without webpage output, and I want a simpler structure. This is my web API:
I am afraid this example is not very close to reality, but it is easy to understand. The interface provided by the company portal I designed is divided into two parts: "Contact Us ", the second is "Product Introduction", and "Product Introduction" is divided into two categories: "Office Products" and "Game Products". For the sake of simplicity, all controllers are web API controllers created by default, with no more specific functions.
I will list all Uris and controllers into a table:
Controller name |
Namespace |
Uri |
Advisecontroller |
Webapiroutedemo. controllers. contactus |
/Apix/contactus/advise |
Productfeedbackcontroller |
Webapiroutedemo. controllers. contactus |
/Apix/contactus/productfeedback |
Financialcontroller |
Webapiroutedemo. controllers. Products. Enterprise |
/Apixx/products/enterprise/Financial |
Officecontroller |
Webapiroutedemo. controllers. Products. Enterprise |
/Apixx/products/enterprise/office |
Puzcontroller |
Webapiroutedemo. controllers. Products. Game |
/Apixx/products/GAME/puz |
Rpgcontroller |
Webapiroutedemo. controllers. Products. Game |
/Apixx/products/GAME/RPG |
Rtscontroller |
Webapiroutedemo. controllers. Products. Game |
/Apixx/products/GAME/RTS |
Allproductscontroller |
Webapiroutedemo. controllers. Products |
/Apix/products/allproducts |
Aboutcontroller |
Webapiroutedemo. Controllers |
/API/about |
The URI Prefixes "API", "apix", and "apixx" seem a little weird. I don't have a better way to design this URI route, we have to use this most stupid method to distinguish different URI routing rules. apixx indicates that area (main type) and category (small class) will be taken next, apix indicates that area will be taken next without category, while the API indicates that the following is the Controller directly, and there is no area or category. This is my path defined by the rule:
config.Routes.MapHttpRoute("AreaCategoryApi", "apixx/{area}/{category}/{controller}/{id}",new {id = RouteParameter.Optional}); config.Routes.MapHttpRoute("AreaApi", "apix/{area}/{controller}/{id}", new {id = RouteParameter.Optional}); config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
Then, how does the web API framework ultimately find the Controller it needs? It is actually based on the name. By default, but now, apart from the name and namespace, so you notice that the namespace I listed above exactly reflects the location of the controller and its functional level. To change the default controller selection behavior, we need to re-write a class to implement the ihttpcontrollerselector interface. The default class is "defaulthttpcontrollerselector". We can use it as the parent class:
public class ClassifiedHttpControllerSelector : DefaultHttpControllerSelector { private const string AREA_ROUTE_VARIABLE_NAME = "area"; private const string CATEGORY_ROUTE_VARIABLE_NAME = "category"; private const string THE_FIX_CONTROLLER_FOLDER_NAME = "Controllers"; private readonly HttpConfiguration m_configuration; private readonly Lazy<ConcurrentDictionary<string, Type>> m_apiControllerTypes; public ClassifiedHttpControllerSelector(HttpConfiguration configuration) : base(configuration) { m_configuration = configuration; m_apiControllerTypes = new Lazy<ConcurrentDictionary<string, Type>>(GetAllControllerTypes); } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { return GetApiController(request); } private static string GetRouteValueByName(HttpRequestMessage request, string strRouteName) { IHttpRouteData data = request.GetRouteData(); if (data.Values.ContainsKey(strRouteName)) { return data.Values[strRouteName] as string; } return null; } private static ConcurrentDictionary<string, Type> GetAllControllerTypes() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Dictionary<string, Type> types = assemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) && typeof (IHttpController).IsAssignableFrom(t))).ToDictionary(t => t.FullName, t => t); return new ConcurrentDictionary<string, Type>(types); } private HttpControllerDescriptor GetApiController(HttpRequestMessage request) { string strAreaName = GetRouteValueByName(request, AREA_ROUTE_VARIABLE_NAME); string strCategoryName = GetRouteValueByName(request, CATEGORY_ROUTE_VARIABLE_NAME); string strControllerName = GetControllerName(request); Type type; try { type = GetControllerType(strAreaName, strCategoryName, strControllerName); } catch (Exception) { return null; } return new HttpControllerDescriptor(m_configuration, strControllerName, type); } private Type GetControllerType(string areaName, string categoryName, string controllerName) { IEnumerable<KeyValuePair<string, Type>> query = m_apiControllerTypes.Value.AsEnumerable(); string strControllerSearchingName; if (string.IsNullOrEmpty(areaName)) { strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + controllerName; } else { if (string.IsNullOrEmpty(categoryName)) { strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + areaName + "." + controllerName; } else { strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + areaName + "." + categoryName + "." + controllerName; } } return query.Where(x => x.Key.IndexOf(strControllerSearchingName, StringComparison.OrdinalIgnoreCase) != -1).Select(x => x.Value).Single(); } }
The above code is largely based on ASP. net MVC 4 RC: Getting webapi and areas to play nicely, whose role is based on Routing Parameters (the parameters include "area", "category", and "controller ") find the corresponding controller from the Assembly.
In this way, we have completed the classification of the controller. You can also adjust the routing rules to design better, but don't forget to share it with me.
Final convention, with: complete code