Asp.net-Web-Api

模組化 ASP.NET Web API:如何在執行時向 Web API 添加/刪除路由

  • September 9, 2016

我正在嘗試設計一個模組化 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()到程式碼中,但它沒有解決問題(同樣的錯誤)。

問題

  1. 這種設計有意義嗎?它會起作用嗎?
  2. 如何向路由集合添加新路由或完全刷新路由集合?

我解決了。

首先,感謝@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

引用自:https://stackoverflow.com/questions/39088765