前情提要:
現有一個網站框架,包括主體專案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擺出一萬種姿勢。