模块化ASP.NET Web API:如何在运行时向Web API添加/删除路由

Toh*_*hid 7 asp.net-web-api asp.net-web-api-routing asp.net-web-api2

我正在尝试设计模块化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);
    }
}
Run Code Online (Sandbox Code Playgroud)

ModuleService.TryLoad()通过使用简单地查找并将程序集加载到应用程序域AppDomain.CurrentDomain.Load().这部分运作良好.

Configuration.MapHttpAttributeRoutes()没有引起任何错误,但它打破了整个路由系统.在该行之后,任何路由尝试都会导致此错误:

该对象尚未初始化.确保在所有其他初始化代码之后在应用程序的启动代码中调用HttpConfiguration.EnsureInitialized().

我添加HttpConfiguration.EnsureInitialized()到代码中,但它没有解决问题(相同的错误).

问题

  1. 这种设计有意义吗?它会起作用吗?
  2. 如何将新路由添加到路径集合或完全刷新路径集合?

Toh*_*hid 7

我解决了

首先,感谢@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
}
Run Code Online (Sandbox Code Playgroud)

然后我们应该扩展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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,我们必须用我们的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果有人对我如何实现它感兴趣ModuleService,我可以将代码上传到GitHub.

以下是GitHub中的完整源代码:https://github.com/tohidazizi/modular-web-api-poc