前情提要: 現有一個網站框架,包括主體項目WebApp一個,包含 IIdentityUser 介面的基架項目 A。用於處理用戶身份驗證的服務 AuthenticationService 位於命名空間B。用於保存數據的實體 User : IIdentityUser 位置項目C。項目之間的關係是B和C依 ...
前情提要:
現有一個網站框架,包括主體項目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擺出一萬種姿勢。