[译]基于请求的依赖注入
最近有读者问我有没有可能创建一个基于请求的依赖注入。实际上,他想基于不同的登陆用户提供不同的依赖注入。
这是个好问题 - 事实上这是一个特别好的依赖注入的使用用例。依赖注入能让我们在整个应用使用某个对象,而不需要关心它是怎么被创建的。
例子
让我们来看看一个简单的例子。想象下我要为我的网站创建一个后台。其中提供一个简单功能:根据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