WebAPI:自定义路由到异步加载的ApiController

时间:2014-02-13 14:10:44

标签: c# asp.net-mvc asp.net-web-api routes

我之前在以前的位置做过这个,但我不记得是怎么做的。

我有一个基于插件的WebAPI应用程序。每个插件程序集都有一个实现IApiServiceEntryPoint的类,类似于:

public interface IApiServiceEntryPoint : IDisposable
{
    /// <summary>
    /// Gets the name of the API Plugin
    /// </summary>
    string Name { get; }

    /// <summary>
    /// Registers the assembly in the application, sets up the routes, and enables invocation of API requests
    /// </summary>
    void Register(RouteCollection routes);

    /// <summary>
    /// Gets the routing namespace of the plugin
    /// </summary>
    string UrlNameSpace { get; }
 }

除其他外,Register(RouteCollection routes)从主Web应用程序(MVC4)获取路径集合,并添加此插件程序集将支持的自定义路径。

假设我在我的新MyApi.Plugins.Foo.FooServiceEntryPoint程序集中的MyApi.Plugins.Foo类中实现了此接口。 Name值为“Foo”,UrlNameSpace为“Foo”,并且假设api控制器类为MyApi.Plugins.Foo.FooController。目的是当消费者点击http://myapi.something.com/Foo/GiveMeRecords时,将调用MyApi.Plugins.Foo.FooController.GiveMeRecords方法。

我的MyApi.Plugins.Foo.FooServiceEntryPoint.Register(RouteCollection routes)应该是什么样的?

1 个答案:

答案 0 :(得分:0)

好的,我想出来了。它比我原先想象的要复杂一点,但这是需要发生的事情:

Register方法中,我将以下代码放在实现IApiServiceEntryPoint的抽象类中:

public virtual void Register(RouteCollection routes)
{
    var rt = string.Format("{0}/{{controller}}/{{id}}", UrlNameSpace);
    var nameSpace = this.GetType().Namespace;
    Logger.DebugFormat("Route Template: {0} in namespace {1}...", rt, nameSpace);
    var r = routes.MapHttpRoute(
        name: Name,
        routeTemplate: rt,
        defaults: new { id = RouteParameter.Optional, controller = "Default", @namespace = nameSpace }
        );
    r.DataTokens = r.DataTokens ?? new RouteValueDictionary();
    r.DataTokens["Namespaces"] = new[] { nameSpace };

    Logger.InfoFormat("Plugin '{0}' registered namespace '{1}'.", Name, nameSpace);
}

然后我必须实现我自己的IHttpControllerSelector(我在我发现here的博文中无耻地修改了这些内容:

public class ApiHttpControllerSelector : IHttpControllerSelector
{
    private const string NamespaceKey = "namespace";
    private const string ControllerKey = "controller";

    private readonly HttpConfiguration _configuration;
    private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
    private readonly HashSet<string> _duplicates;
    private static readonly List<Type> _pluginControllers = new List<Type>();

    public ApiHttpControllerSelector(HttpConfiguration config)
    {
        _configuration = config;
        _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
        _pluginControllers.AddRange(ApiFramework.PluginHelper.Controllers);
    }

    private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
    {
        var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

        var assembliesResolver = _configuration.Services.GetAssembliesResolver();
        var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

        var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver)
            .Concat(_pluginControllers).ToList();               

        foreach (var t in controllerTypes)
        {
            var key = string.Format("{0}.{1}", t.Namespace, t.Name);
            dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
        }
        return dictionary;
    }

    public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request)
    {
        var routeData = request.GetRouteData();
        if (routeData == null)
        {
            throw new HttpResponseException(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
        }

        string namespaceName = GetRouteVariable<string>(routeData, "namespace");
        if (namespaceName == null)
        {
            throw new HttpResponseException(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
        }

        string controllerName = GetRouteVariable<string>(routeData, "controller");
        if (controllerName == null)
        {
            throw new HttpResponseException(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
        }

        // Find a matching controller.
        var baseKey = String.Format(CultureInfo.InvariantCulture, "{0}.{1}Controller", namespaceName, controllerName);
        var key = String.Format("{0}Controller", baseKey);
        HttpControllerDescriptor controllerDescriptor;
        if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) // the default should include the "Controller" at the end of the class name
        {
            return controllerDescriptor;
        }
        else if (_controllers.Value.TryGetValue(baseKey, out controllerDescriptor)) //explicit class name, sans "Controller"
        {
            return controllerDescriptor;
        }
        else if (_duplicates.Contains(key))
        {
            throw new HttpResponseException(
                request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                "Multiple controllers were found that match this request."));
        }
        else
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

    }

    private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
    {
        object result = null;
        if (routeData.Values.TryGetValue(name, out result))
        {
            return (T)result;
        }
        return default(T);
    }

    public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
    {
        return _controllers.Value;
    }
}

然后我必须替换IHttpControllerSelector中的默认Global.asax.cs,如下所示:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
            new ApiHttpControllerSelector(GlobalConfiguration.Configuration));

......然后一切正常。