From 13ae7b21f3459e97cd5853b6e6ccb229dac1bafe Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 17 Nov 2016 15:06:29 -0800 Subject: [PATCH] Improve generic support for AddEntityFrameworkStores --- ...dentityEntityFrameworkBuilderExtensions.cs | 50 +++++++++++++------ .../Properties/Resources.Designer.cs | 32 ++++++++++++ .../Resources.resx | 8 +++ .../UserStoreGuidKeyTest.cs | 8 +++ .../UserStoreIntKeyTest.cs | 10 ++++ .../UserStoreStringKeyTest.cs | 10 ++++ .../UserStoreWithGenericsTest.cs | 18 +++++++ 7 files changed, 121 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs index 6d195c903e..0c92871186 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Reflection; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; @@ -23,7 +24,8 @@ namespace Microsoft.Extensions.DependencyInjection public static IdentityBuilder AddEntityFrameworkStores(this IdentityBuilder builder) where TContext : DbContext { - builder.Services.TryAdd(GetDefaultServices(builder.UserType, builder.RoleType, typeof(TContext))); + var keyType = InferKeyType(typeof(TContext)); + AddStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext), keyType); return builder; } @@ -38,26 +40,44 @@ namespace Microsoft.Extensions.DependencyInjection where TContext : DbContext where TKey : IEquatable { - builder.Services.TryAdd(GetDefaultServices(builder.UserType, builder.RoleType, typeof(TContext), typeof(TKey))); + AddStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext), typeof(TKey)); return builder; } - private static IServiceCollection GetDefaultServices(Type userType, Type roleType, Type contextType, Type keyType = null) + private static void AddStores(IServiceCollection services, Type userType, Type roleType, Type contextType, Type keyType) { - Type userStoreType; - Type roleStoreType; - keyType = keyType ?? typeof(string); - userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType); - roleStoreType = typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType); - - var services = new ServiceCollection(); - services.AddScoped( + var identityUserType = typeof(IdentityUser<>).MakeGenericType(keyType); + if (!identityUserType.GetTypeInfo().IsAssignableFrom(userType.GetTypeInfo())) + { + throw new InvalidOperationException(Resources.NotIdentityUser); + } + var identityRoleType = typeof(IdentityRole<>).MakeGenericType(keyType); + if (!identityRoleType.GetTypeInfo().IsAssignableFrom(roleType.GetTypeInfo())) + { + throw new InvalidOperationException(Resources.NotIdentityRole); + } + services.TryAddScoped( typeof(IUserStore<>).MakeGenericType(userType), - userStoreType); - services.AddScoped( + typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType)); + services.TryAddScoped( typeof(IRoleStore<>).MakeGenericType(roleType), - roleStoreType); - return services; + typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType)); + } + + private static Type InferKeyType(Type contextType) + { + var type = contextType.GetTypeInfo(); + while (type.BaseType != null) + { + type = type.BaseType.GetTypeInfo(); + var genericType = type.IsGenericType ? type.GetGenericTypeDefinition() : null; + if (genericType != null && genericType == typeof(IdentityDbContext<,,>)) + { + return type.GenericTypeArguments[2]; + } + } + // Default is string + return typeof(string); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs index a36175488e..d9bc0a88a8 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs @@ -10,6 +10,38 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNetCore.Identity.EntityFrameworkCore.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// AddEntityFrameworkStores can only be called with a role that derives from IdentityRole<TKey>. If you are specifying more generic arguments, use AddRoleStore<TStore>() where TStore is your custom IRoleStore that uses your generics instead. + /// + internal static string NotIdentityRole + { + get { return GetString("NotIdentityRole"); } + } + + /// + /// AddEntityFrameworkStores can only be called with a role that derives from IdentityRole<TKey>. If you are specifying more generic arguments, use AddRoleStore<TStore>() where TStore is your custom IRoleStore that uses your generics instead. + /// + internal static string FormatNotIdentityRole() + { + return GetString("NotIdentityRole"); + } + + /// + /// AddEntityFrameworkStores can only be called with a user that derives from IdentityUser<TKey>. If you are specifying more generic arguments, use IdentityBuilder.AddUserStore<TStore>() where TStore is your custom IUserStore that uses your generics instead. + /// + internal static string NotIdentityUser + { + get { return GetString("NotIdentityUser"); } + } + + /// + /// AddEntityFrameworkStores can only be called with a user that derives from IdentityUser<TKey>. If you are specifying more generic arguments, use IdentityBuilder.AddUserStore<TStore>() where TStore is your custom IUserStore that uses your generics instead. + /// + internal static string FormatNotIdentityUser() + { + return GetString("NotIdentityUser"); + } + /// /// Role {0} does not exist. /// diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Resources.resx b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Resources.resx index 64377b6996..b46be0a6f2 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Resources.resx +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Resources.resx @@ -117,6 +117,14 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + AddEntityFrameworkStores can only be called with a role that derives from IdentityRole<TKey>. If you are specifying more generic arguments, use AddRoleStore<TStore>() where TStore is your custom IRoleStore that uses your generics instead. + error when the role does not derive from IdentityRole + + + AddEntityFrameworkStores can only be called with a user that derives from IdentityUser<TKey>. If you are specifying more generic arguments, use IdentityBuilder.AddUserStore<TStore>() where TStore is your custom IUserStore that uses your generics instead. + error when the user does not derive from IdentityUser + Role {0} does not exist. error when a role does not exist diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs index 92a4a9af36..9887736d89 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs @@ -51,5 +51,13 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { services.AddSingleton>(new ApplicationRoleStore((TestDbContext)context)); } + + [Fact] + public void AddEntityFrameworkStoresCanInferKey() + { + var services = new ServiceCollection(); + // This used to throw + var builder = services.AddIdentity().AddEntityFrameworkStores(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs index 86c1f4581a..265a467b93 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.DependencyInjection; +using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { @@ -27,5 +29,13 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test : base(fixture) { } + + [Fact] + public void AddEntityFrameworkStoresCanInferKey() + { + var services = new ServiceCollection(); + // This used to throw + var builder = services.AddIdentity().AddEntityFrameworkStores(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreStringKeyTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreStringKeyTest.cs index ec636813b7..d5ec26951d 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreStringKeyTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreStringKeyTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.DependencyInjection; +using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { @@ -27,7 +29,15 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { public UserStoreStringKeyTest(ScratchDatabaseFixture fixture) : base(fixture) + { } + + [Fact] + public void AddEntityFrameworkStoresCanInferKey() { + var services = new ServiceCollection(); + // This used to throw + var builder = services.AddIdentity().AddEntityFrameworkStores(); } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs index a1f2c9913d..af19cfc35a 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs @@ -83,6 +83,24 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test protected override Expression> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName); + [Fact] + public void AddEntityFrameworkStoresWithInvalidUserThrows() + { + var services = new ServiceCollection(); + var builder = services.AddIdentity(); + var e = Assert.Throws(() => builder.AddEntityFrameworkStores()); + Assert.Contains("AddEntityFrameworkStores", e.Message); + } + + [Fact] + public void AddEntityFrameworkStoresWithInvalidRoleThrows() + { + var services = new ServiceCollection(); + var builder = services.AddIdentity(); + var e = Assert.Throws(() => builder.AddEntityFrameworkStores()); + Assert.Contains("AddEntityFrameworkStores", e.Message); + } + [Fact] public async Task CanAddRemoveUserClaimWithIssuer() {