如何使用不同的构造函数参数重用相同的控制器类

时间:2016-10-10 08:39:04

标签: c# asp.net asp.net-web-api dependency-injection unity-container

我有一个控制器接受一些依赖作为构造函数参数:

public class AccountsController : ApiController
{
    public AccountsController(IAccountsService accountService)
    {
        this.accountService = accountService;
    }
    // actions
}

public interface IAccountsService
{
    IEnumerable<AccountDto> GetAccounts(string userName);
}

要解决此依赖关系,请使用Unity.WebApi package:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // other configuration
        config.DependencyResolver = new UnityDependencyResolver(myContainer);
    }
}

我有两个不同的IAccountsService实现,我想使用相同的控制器类公开它们。从路由角度来看,我想为操作和参数使用不同的控制器级路径和相同的底层路径结构。

我的方法是从AccountsController继承两个控制器并在UnityContainer中注册它们以使用不同的IAccountsService实现。

public class Accounts1Controller : AccountsController
{
    public Accounts1Controller([Dependency("Service1")]IAccountsService accountService) :
        base(accountService) { }
}

public class Accounts2Controller : AccountsController
{
    public Accounts2Controller([Dependency("Service2")]IAccountsService accountService) :
        base(accountService) { }
}

有更简单的方法吗?

我更倾向于让控制器容器不知道并避免冗余继承(无论是DI框架 - Unity都不是单一选项)。

1 个答案:

答案 0 :(得分:2)

这是一种方法:

假设这两条路线如下:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi2",
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

您可以根据请求的Url使Unity解析正确的服务,如下所示:

//Map IAccountsService to AccountsService1 with name Service1
container.RegisterType<IAccountsService, AccountsService1>("Service1");

//Map IAccountsService to AccountsService2 with name Service2
container.RegisterType<IAccountsService, AccountsService2>("Service2");

//At composition time, map IAccountsService to appropriate
//service based on Url
container.RegisterType<IAccountsService>(new InjectionFactory(c =>
{
    var pathAndQuery = HttpContext.Current.Request.Url.PathAndQuery;

    if(pathAndQuery.StartsWith("/api2"))
        return c.Resolve<IAccountsService>("Service2");
    else if(pathAndQuery.StartsWith("/api"))
        return c.Resolve<IAccountsService>("Service1");

    throw new Exception("Unexpected Url");
}));

<强>更新

在使用自托管的情况下,HttpContext.Current将为空。

您可以做的是创建自定义IHttpControllerActivator。这将允许您在当前HttpRequestMessage可用的上下文中自定义控制器的创建方式。

以下是此类自定义IHttpControllerActivator的示例:

public class MyControllerActivator : IHttpControllerActivator
{
    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        if (controllerType == typeof (ValuesController))
        {
            var pathAndQuery = request.RequestUri.PathAndQuery;

            IAccountsService svc;

            if (pathAndQuery.StartsWith("/api2"))
                svc = new Service2();
            else if (pathAndQuery.StartsWith("/api"))
                svc = new Service1();
            else 
                throw new Exception("Unexpected Url");

            return new ValuesController(svc);
        }

        throw new Exception("Unexpected Controller Type");
    }
}

您可以详细了解此方法here

请注意,虽然我提供的示例不使用DI容器(因此使用Pure DI),但您应该能够使用容器。

不要忘记删除设置DependencyResolver的代码,因为我们使用不同的接缝来扩展框架。