Asp.net-Web-Api
模組化 ASP.NET Web API:如何在執行時向 Web API 添加/刪除路由
我正在嘗試設計一個模組化 Web API 應用程序(它不是 MVC 應用程序!),其中管理員角色的使用者可以添加或刪除模組而無需重新啟動 ASP.NET 應用程序。
- 模組:每個模組都是一個程序集(.dll 文件),其中至少包含一個從
ApiController.- 路由基於ASP.NET Web API 2 中的屬性路由
- 模組(組件)的生產不在此問題的範圍內。
- 模組(彙編文件)被複製到項目根目錄中的 `~/plugins/ 文件夾中/從中刪除。這個過程也不在這個問題的範圍內。
- 主要的 ASP.NET Web API 項目基本上只有一個控制器來管理(添加/刪除)模組。其他控制器將作為模組添加。
所以主 Web API 項目中唯一的控制器是:
[RoutePrefix("api/modules")] public class ModulesController : ApiController { private ModuleService _moduleService = new ModuleService(); // GET: api/Modules [Route] public IEnumerable<string> Get() { return _moduleService.Get().Select(a => a.FullName); } // POST: api/Modules/{moduleName} [Route("{id}")] public void Post(string id) { Assembly _assembly; var result = _moduleService.TryLoad(id, out _assembly); if(!result) throw new Exception("problem loading " + id); // Refresh routs or add the new rout Configuration.Routes.Clear(); Configuration.MapHttpAttributeRoutes(); // ^ it does not work :( } // DELETE: api/Modules/{moduleName} [Route("{id}")] public void Delete(string id) { _moduleService.Remove(id); } }
ModuleService.TryLoad()只需使用 . 查找程序集並將其載入到應用程序域即可AppDomain.CurrentDomain.Load()。這部分運作良好。
Configuration.MapHttpAttributeRoutes()不會引發任何錯誤,但會破壞整個路由系統。在該行之後,任何路由嘗試都會導致此錯誤:該對象尚未初始化。確保在所有其他初始化程式碼之後在應用程序的啟動程式碼中呼叫 HttpConfiguration.EnsureInitialized()。
我添加
HttpConfiguration.EnsureInitialized()到程式碼中,但它沒有解決問題(同樣的錯誤)。問題
- 這種設計有意義嗎?它會起作用嗎?
- 如何向路由集合添加新路由或完全刷新路由集合?
我解決了。
首先,感謝@Aleksey L.
ModuleController,對(添加)進行了一些更改Configuration.Initializer(Configuration):[RoutePrefix("api/modules")] public class ModulesController : ApiController { private ModuleService _moduleService = new ModuleService(); // Other codes public void Post(string id) { _moduleService.Load(id); Configuration.Routes.Clear(); Configuration.MapHttpAttributeRoutes(); Configuration.Initializer(Configuration); } // Other codes }然後我們應該擴展
DefaultHttpControllerSelector:public class ModularHttpControllerSelector : DefaultHttpControllerSelector { private readonly HttpConfiguration _configuration; public ModularHttpControllerSelector(HttpConfiguration configuration) : base(configuration) { _configuration = configuration; } public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { var result = base.GetControllerMapping(); AddPluginsControllerMapping(ref result); return result; } private void AddPluginsControllerMapping(ref IDictionary<string, HttpControllerDescriptor> controllerMappings) { var custom_settings = _getControllerMapping(); foreach (var item in custom_settings) { if (controllerMappings.ContainsKey(item.Key)) controllerMappings[item.Key] = item.Value; else controllerMappings.Add(item.Key, item.Value); } } private ConcurrentDictionary<string, HttpControllerDescriptor> _getControllerMapping() { var result = new ConcurrentDictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase); var duplicateControllers = new HashSet<string>(); Dictionary<string, ILookup<string, Type>> controllerTypeGroups = GetControllerTypeGroups(); foreach (KeyValuePair<string, ILookup<string, Type>> controllerTypeGroup in controllerTypeGroups) { string controllerName = controllerTypeGroup.Key; foreach (IGrouping<string, Type> controllerTypesGroupedByNs in controllerTypeGroup.Value) { foreach (Type controllerType in controllerTypesGroupedByNs) { if (result.Keys.Contains(controllerName)) { duplicateControllers.Add(controllerName); break; } else { result.TryAdd(controllerName, new HttpControllerDescriptor(_configuration, controllerName, controllerType)); } } } } foreach (string duplicateController in duplicateControllers) { HttpControllerDescriptor descriptor; result.TryRemove(duplicateController, out descriptor); } return result; } private Dictionary<string, ILookup<string, Type>> GetControllerTypeGroups() { IAssembliesResolver assembliesResolver = new DefaultAssembliesResolver(); //was: _configuration.Services.GetAssembliesResolver(); IHttpControllerTypeResolver controllersResolver = new DefaultHttpControllerTypeResolver(); //was: _configuration.Services.GetHttpControllerTypeResolver(); ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver); var groupedByName = controllerTypes.GroupBy( t => t.Name.Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length), StringComparer.OrdinalIgnoreCase); return groupedByName.ToDictionary( g => g.Key, g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase); } }當然,我們必須用我們的 HttpControllerSelector 替換預設的 HttpControllerSelector,在
App_start\WebApiConfig.cs:public static class WebApiConfig { public static void Register(HttpConfiguration config) { GlobalConfiguration.Configuration.Services.Replace( typeof(System.Web.Http.Dispatcher.IHttpControllerSelector), new ModularHttpControllerSelector(config)); config.MapHttpAttributeRoutes(); } }
如果有人對我的實現方式感興趣ModuleService,我可以將程式碼上傳到 GitHub。這是 GitHub 中的完整原始碼:https ://github.com/tohidazizi/modular-web-api-poc