前情提要:
現有一個網站框架,包括主體項目WebApp一個,包含 IIdentityUser 接口的基架項目 A。用於處理用戶身份驗證的服務 AuthenticationService 位於命名空間B。用於保存數據的實體 User : IIdentityUser 位置項目C。項目之間的關系是B和C依賴項目A。
需求:
現在有一個新項目D,在這個項目里有一個DUser : IIdentityUser 。如何處理才能最優雅的在不添加引用和修改項目B的前提下,將用戶保存至DUser。
實際例子:
在ASP.NET CORE中,有一個東西叫IdentityServer。里面就有這個東西,他寫的是類似IdentityServerBuilder.AddService<TUser, TRole>()這種代碼,如何實現?
解決方案:
1、新建一個泛類(這個類可以標記為internal,外部不需要了解,也不需要訪問):
public class UserContext<TUser>
where TUser : class, IIdentityUser, new ()
{
public YourContext dbContext;
public UserContext(YourContext ctx) => dbContext = ctx;
public DbSet<TUser> Users
{
get
{
return dbContext.Set<TUser>();
}
}
public void SaveChanges()
{
dbContext.SaveChanges();
}
}
2、新建一個用以操作的服務(注意,所有需要的操作都往這個里面寫,未來暴露的也是這個接口)
public class UserService<TUser> : IUserService
where TUser: class, IIdentityUser, new()
{
private UserContext<TUser> dbContext;
public UserService(YourContext ctx, IServiceProvider provider)
{
dbContext = new PermissionContext<TUser>(ctx.DbContext);
}
public TUser GetUserById(Guid id)
{
return dbContext.Users.FirstOrDefault(e => e.ID == id);
}
}
3、添加一個注射器
public static class AuthenticationInject
{
public static IServiceCollection AddAuthenticationContext<TUser>(this IServiceCollection services)
where TUser: IIdentityUser
{
var serviceType = typeof(UserService<>).MakeGenericType(typeof(TUser));
services.AddSingleton(typeof(IUserService), serviceType );
return services;
}
}
技術點:使用MakeGenericType方法可以動態加載泛類實例。如果類型是 UserService<TUser, TRole>,則寫成 typeof(UserService<,>).MakeGenericType(typeof(T1), typeof(T2))
至此,我們就已經將泛類的類型名拆到了變量里面。然后就可以玩出一萬種花樣了。
4、在WebApp里,注入相關變量
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthenticationContext<DUser>();
}
分析依賴關系:
執行項目WebApp依賴A,B,D,B和D項目只依賴A。甚至於,這里還能再解耦。把函數AddAuthenticationContext從泛型函數改成 AddAuthenticationContext(Type userType),還可以再進一步,改成AddAuthenticationContext(string type),通過反射和配置來取類型,做到A項目和D項目解耦。
擴展性:
在未來,有新項目E,EUser。只需要將D和A解除分離,再將E和A進行關聯。只需要修改 AddAuthenticationContext 函數,即可滿足要求。當然,如果要有心情,你甚至可以搞一個自動發現的代碼(比如我項目里就是這么搞的,自動分析IIdentityUser的對象,然后附加給Context,為了保證有多個實現時能正確附加,我做了一個Attribute用來標記這個項目要用哪個User)。再有心情還可以做成配置式的,反正你可以把EF Core擺出一萬種姿勢。
