[译]基于请求的依赖注入

原文

最近有读者问我有没有可能创建一个基于请求的依赖注入。实际上,他想基于不同的登陆用户提供不同的依赖注入。

这是个好问题 - 事实上这是一个特别好的依赖注入的使用用例。依赖注入能让我们在整个应用使用某个对象,而不需要关心它是怎么被创建的。

例子

让我们来看看一个简单的例子。想象下我要为我的网站创建一个后台。其中提供一个简单功能:根据name来删除一个用户。 接口如下:

public interface IControlPanel
{
    string DeleteUser(string name);
}

在实现这个接口前,我们来说说删除一个用户的业务规则:

  • 游客不能删除任何用户
  • 普通用户只能删除自己
  • 管理员可以删除任何用户

实现

来看看怎么实现上面的业务规则。为了简化, 我们决定根据请求头中的loggedInUser来判断登陆用户。

public class ControlPanel : IControlPanel
{
    private readonly IHttpContextAccessor _contextAccessor;
 
    public ControlPanel(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }
 
    public string DeleteUser(string name)
    {
        var currentUser = _contextAccessor.HttpContext.Request.Headers["loggedInUser"];
        // No user logged in: guest
        if (string.IsNullOrEmpty(currentUser))
        {
            return "Guests cannot delete users.";
        }
        // Administrator logged in
        if ("admin" == currentUser)
        {
            return $"Deleted the user {name} as Administrator";
        }
        // Normal user logged in
        if (name == currentUser)
        {
            return $"Deleted user {name} as {currentUser}";
        }
        return $"Cannot delete {name} because {currentUser} is a regular user";
    }
}

配置服务

现在我们有了一个服务实现了,现在将它添加到service collection中去,并且创建一个删除的用户的接口:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IControlPanel, ControlPanel>();
    }
 
    public void Configure(IApplicationBuilder app)
    {
        app.UseIISPlatformHandler();
 
        app.Map("/panel/delete", DeleteUserApi);
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Welcome to the demo.");
        });
    }
 
    public void DeleteUserApi(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            var controlPanel = context.RequestServices.GetRequiredService<IControlPanel>();
            var userToDelete = context.Request.Query["user"];
            await context.Response.WriteAsync(controlPanel.DeleteUser(userToDelete));
        });
    }
 
    public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}

测试

现在使用curl来测试:

$ curl -s localhost:49480/panel/delete?user=joe
Guests cannot delete users.
$ curl -s -H "loggedInUser: admin" localhost:49480/panel/delete?user=joe
Deleted the user joe as Administrator
$ curl -s -H "loggedInUser: bob" localhost:49480/panel/delete?user=joe
Cannot delete joe because bob is a regular user
$ curl -s -H "loggedInUser: joe" localhost:49480/panel/delete?user=joe
Deleted user joe as joe

结果和我们预期的一样。

现在不同的用户类型的不同业务规则都包含在ControlPanel类中。 我们可以想象下如果又多了些不同的用户类型, ControlPanel类会变的越来越大,越来越复杂。

实现多个IControlPanel

另外一个更好的解决方案是根据请求提供不同的IControlPanel实现。 首先, 我们可以将上面的ControlPanel分割成3个不同的类 - AdminControlPanel,NormalUserControlPanel,GuestControlPanel:

public class AdminControlPanel : IControlPanel
{
    public string DeleteUser(string name)
    {
        return $"Deleted the user {name} as Administrator";
    }
}
 
public class NormalUserControlPanel : IControlPanel
{
    private readonly string _name;
 
    public NormalUserControlPanel(string name)
    {
        _name = name;
    }
 
    public string DeleteUser(string name)
    {
        // Regular users can only delete themselves
        if (name == _name)
        {
            return $"Deleted user {name} as {_name}";
        }
        return $"Cannot delete {name} because {_name} is a regular user";
    }
}
 
public class GuestControlPanel : IControlPanel
{
    public string DeleteUser(string name)
    {
        return "Guests cannot delete users.";
    }
}

可以看到,每一个IControlPanel的实现都仅包含某一个用户类型的业务逻辑。 每个类都比之前的ControlPanel简单。

实时依赖注入

现在到了最核心最重要的部分了。我们更新ConfigureServices方法根据request提供不同的IControlPanel实现:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
            services.AddTransient<IControlPanel>(serviceProvider =>
            {
                var context = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
                var userHeader = context.Request.Headers["loggedInUser"];
                if (string.IsNullOrEmpty(userHeader)) return new GuestControlPanel();
                if ("admin" == userHeader) return new AdminControlPanel();
                return new NormalUserControlPanel(userHeader);
            });
        }
 
    public void Configure(IApplicationBuilder app)
    {
        app.UseIISPlatformHandler();
 
        app.Map("/panel/delete", DeleteUserApi);
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Welcome to the demo.");
        });
    }
 
    public void DeleteUserApi(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            var controlPanel = context.RequestServices.GetRequiredService<IControlPanel>();
            var userToDelete = context.Request.Query["user"];
            await context.Response.WriteAsync(controlPanel.DeleteUser(userToDelete));
        });
    }
 
    public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}

再次运行程序,结果和之前的结果一样:

$ curl -s localhost:49480/panel/delete?user=joe
Guests cannot delete users.
$ curl -s -H "loggedInUser: admin" localhost:49480/panel/delete?user=joe
Deleted the user joe as Administrator
$ curl -s -H "loggedInUser: bob" localhost:49480/panel/delete?user=joe
Cannot delete joe because bob is a regular user
$ curl -s -H "loggedInUser: joe" localhost:49480/panel/delete?user=joe
Deleted user joe as joe
posted @ 2017-07-24 00:12  irocker  阅读(215)  评论(0编辑  收藏  举报