Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IdentityResult returned from DAL (tables) + incremental bug fixes #26

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/Daarto.WebUI/Daarto.WebUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<UserSecretsId>aspnet-Daarto.WebUI-0BC4D06A-1285-4595-85EA-07A68DD02CF3</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\AspNetCore.Identity.Dapper\AspNetCore.Identity.Dapper.csproj" />
Expand Down
20 changes: 14 additions & 6 deletions samples/Daarto.WebUI/Data/Tables/ExtendedRolesTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace Daarto.WebUI.Data.Tables
{
public class ExtendedRolesTable : RolesTable<ExtendedIdentityRole, string, IdentityRoleClaim<string>>
{
public ExtendedRolesTable(IDbConnectionFactory dbConnectionFactory) : base(dbConnectionFactory) { }
public ExtendedRolesTable(IDbConnectionStore dbConnectionFactory) : base(dbConnectionFactory) { }

public override async Task<bool> CreateAsync(ExtendedIdentityRole role) {
public override async Task<IdentityResult> CreateAsync(ExtendedIdentityRole role) {
const string sql = "INSERT INTO [dbo].[AspNetRoles] " +
"VALUES (@Id, @Name, @NormalizedName, @ConcurrencyStamp, @Description);";
var rowsInserted = await DbConnection.ExecuteAsync(sql, new {
Expand All @@ -21,10 +21,15 @@ public override async Task<bool> CreateAsync(ExtendedIdentityRole role) {
role.ConcurrencyStamp,
role.Description
});
return rowsInserted == 1;
return rowsInserted == 1
? IdentityResult.Success
: IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"Role '{role.Name}' could not be created."
});
}

public override async Task<bool> UpdateAsync(ExtendedIdentityRole role, IList<IdentityRoleClaim<string>> claims = null) {
public override async Task<IdentityResult> UpdateAsync(ExtendedIdentityRole role, IList<IdentityRoleClaim<string>> claims = null) {
const string updateRoleSql = "UPDATE [dbo].[AspNetRoles] " +
"SET [Name] = @Name, [NormalizedName] = @NormalizedName, [ConcurrencyStamp] = @ConcurrencyStamp, [Description] = @Description " +
"WHERE [Id] = @Id;";
Expand Down Expand Up @@ -55,10 +60,13 @@ public override async Task<bool> UpdateAsync(ExtendedIdentityRole role, IList<Id
transaction.Commit();
} catch {
transaction.Rollback();
return false;
return IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"Role '{role.Name}' could not be updated."
});
}
}
return true;
return IdentityResult.Success;
}
}
}
6 changes: 3 additions & 3 deletions src/AspNetCore.Identity.Dapper/Abstract/IRolesTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ public interface IRolesTable<TRole, TKey, TRoleClaim>
/// Creates a new role in the store.
/// </summary>
/// <param name="role">The role to create in the store.</param>
Task<bool> CreateAsync(TRole role);
Task<IdentityResult> CreateAsync(TRole role);
/// <summary>
/// Deletes a role from the store.
/// </summary>
/// <param name="roleId">The id of the role to delete from the store.</param>
Task<bool> DeleteAsync(TKey roleId);
Task<IdentityResult> DeleteAsync(TKey roleId);
/// <summary>
/// Finds the role who has the specified id.
/// </summary>
Expand All @@ -41,6 +41,6 @@ public interface IRolesTable<TRole, TKey, TRoleClaim>
/// </summary>
/// <param name="role">The role to update in the store.</param>
/// <param name="claims">The claims of the role.</param>
Task<bool> UpdateAsync(TRole role, IList<TRoleClaim> claims = null);
Task<IdentityResult> UpdateAsync(TRole role, IList<TRoleClaim> claims = null);
}
}
8 changes: 4 additions & 4 deletions src/AspNetCore.Identity.Dapper/Abstract/IUsersTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public interface IUsersOnlyTable<TUser, TKey, TUserClaim, TUserLogin, TUserToken
/// Creates a new user in the store.
/// </summary>
/// <param name="user">The user to create in the store.</param>
Task<bool> CreateAsync(TUser user);
Task<IdentityResult> CreateAsync(TUser user);
/// <summary>
/// Deletes a user from the store.
/// </summary>
/// <param name="userId">The id of the user to delete from the store.</param>
Task<bool> DeleteAsync(TKey userId);
Task<IdentityResult> DeleteAsync(TKey userId);
/// <summary>
/// Finds the user who has the specified id.
/// </summary>
Expand All @@ -53,7 +53,7 @@ public interface IUsersOnlyTable<TUser, TKey, TUserClaim, TUserLogin, TUserToken
/// <param name="claims">The claims of the user.</param>
/// <param name="logins">The logins of the user.</param>
/// <param name="tokens">The tokens of the user.</param>
Task<bool> UpdateAsync(TUser user, IList<TUserClaim> claims, IList<TUserLogin> logins, IList<TUserToken> tokens);
Task<IdentityResult> UpdateAsync(TUser user, IList<TUserClaim> claims, IList<TUserLogin> logins, IList<TUserToken> tokens);
/// <summary>
/// Gets the users that own the specified claim.
/// </summary>
Expand Down Expand Up @@ -86,7 +86,7 @@ public interface IUsersTable<TUser, TKey, TUserClaim, TUserRole, TUserLogin, TUs
/// <param name="roles">The roles of the user.</param>
/// <param name="logins">The logins of the user.</param>
/// <param name="tokens">The tokens of the user.</param>
Task<bool> UpdateAsync(TUser user, IList<TUserClaim> claims, IList<TUserRole> roles, IList<TUserLogin> logins, IList<TUserToken> tokens);
Task<IdentityResult> UpdateAsync(TUser user, IList<TUserClaim> claims, IList<TUserRole> roles, IList<TUserLogin> logins, IList<TUserToken> tokens);
/// <summary>
/// Gets the users that belong to the specified role.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
<UserSecretsId>ea4ca2df-aa2d-4174-8f13-b1e594019d86</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.10" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
</Project>
7 changes: 4 additions & 3 deletions src/AspNetCore.Identity.Dapper/DapperStoreOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data;
using System;
using System.Data;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCore.Identity.Dapper
Expand All @@ -14,8 +15,8 @@ public class DapperStoreOptions
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// A factory for creating instances of <see cref="IDbConnection"/>.
/// The type of the DbConnectionFactory (A factory for creating instances of <see cref="IDbConnection"/>)
/// </summary>
public IDbConnectionFactory DbConnectionFactory { get; set; }
public Type DbConnectionStoreType { get; set; }
}
}
7 changes: 4 additions & 3 deletions src/AspNetCore.Identity.Dapper/IDbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System.Data;
using System;
using System.Data;

namespace AspNetCore.Identity.Dapper
{
/// <summary>
/// A factory for creating instances of <see cref="IDbConnection"/>.
/// </summary>
public interface IDbConnectionFactory
public interface IDbConnectionStore : IDisposable
{
/// <summary>
/// The connection string to use for connecting to Microsoft SQL Server.
Expand All @@ -14,6 +15,6 @@ public interface IDbConnectionFactory
/// <summary>
/// Creates a new instance of the underlying <see cref="IDbConnection"/>.
/// </summary>
IDbConnection Create();
IDbConnection GetOrCreateConnection();
}
}
22 changes: 14 additions & 8 deletions src/AspNetCore.Identity.Dapper/IdentityBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reflection;
using AspNetCore.Identity.Dapper;
using Microsoft.AspNetCore.Identity;
Expand Down Expand Up @@ -32,14 +33,14 @@ private static void AddStores(IServiceCollection services, Type userType, Type r
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var dbConnectionContextOptions = new DapperStoreOptions {
ConnectionString = configuration.GetConnectionString("DefaultConnection"),
DbConnectionFactory = new SqlServerDbConnectionFactory(),
DbConnectionStoreType = typeof(SqlServerDbConnectionStore),
Services = services
};
configureAction?.Invoke(dbConnectionContextOptions);
dbConnectionContextOptions.Services = null;
var keyType = identityUserType.GenericTypeArguments[0];
services.TryAddScoped(typeof(IDbConnectionFactory), x => {
var dbConnectionFactoryInstance = (IDbConnectionFactory)Activator.CreateInstance(dbConnectionContextOptions.DbConnectionFactory.GetType());
services.TryAddScoped(typeof(IDbConnectionStore), x => {
var dbConnectionFactoryInstance = (IDbConnectionStore)Activator.CreateInstance(dbConnectionContextOptions.DbConnectionStoreType);
dbConnectionFactoryInstance.ConnectionString = dbConnectionContextOptions.ConnectionString;
return dbConnectionFactoryInstance;
});
Expand All @@ -49,13 +50,14 @@ private static void AddStores(IServiceCollection services, Type userType, Type r
var userLoginType = typeof(IdentityUserLogin<>).MakeGenericType(keyType);
var roleClaimType = typeof(IdentityRoleClaim<>).MakeGenericType(keyType);
var userTokenType = typeof(IdentityUserToken<>).MakeGenericType(keyType);
var usersTableServiceType = typeof(IUsersTable<,,,,,>).MakeGenericType(userType, keyType, userClaimType, userRoleType, userLoginType, userTokenType);
if (roleType != null) {
var identityRoleType = FindGenericBaseType(roleType, typeof(IdentityRole<>));
if (identityRoleType == null) {
throw new InvalidOperationException($"Method {nameof(AddDapperStores)} can only be called with a role that derives from IdentityRole<TKey>.");
}
services.TryAddScoped(
typeof(IUsersTable<,,,,,>).MakeGenericType(userType, keyType, userClaimType, userRoleType, userLoginType, userTokenType),
usersTableServiceType,
typeof(UsersTable<,,,,,>).MakeGenericType(userType, keyType, userClaimType, userRoleType, userLoginType, userTokenType)
);
services.TryAddScoped(typeof(IRolesTable<,,>).MakeGenericType(roleType, keyType, roleClaimType), typeof(RolesTable<,,>).MakeGenericType(roleType, keyType, roleClaimType));
Expand All @@ -64,10 +66,14 @@ private static void AddStores(IServiceCollection services, Type userType, Type r
services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(roleType), typeof(RoleStore<,,,>).MakeGenericType(roleType, keyType, userRoleType, roleClaimType));
userStoreType = typeof(UserStore<,,,,,,,>).MakeGenericType(userType, roleType, keyType, userClaimType, userRoleType, userLoginType, userTokenType, roleClaimType);
} else {
services.TryAddScoped(
typeof(IUsersOnlyTable<,,,,>).MakeGenericType(userType, keyType, userClaimType, userLoginType, userTokenType),
typeof(UsersTable<,,,,,>).MakeGenericType(userType, keyType, userClaimType, roleType, userLoginType, userTokenType)
);
var matchingServiceType = services.FirstOrDefault(s => s.ServiceType == usersTableServiceType);
usersTableServiceType = typeof(IUsersOnlyTable<,,,,>).MakeGenericType(userType, keyType, userClaimType, userLoginType, userTokenType);
if (matchingServiceType == null) {
services.TryAddScoped(usersTableServiceType, typeof(UsersTable<,,,,,>).MakeGenericType(userType, keyType, userClaimType, userRoleType, userLoginType, userTokenType));
} else {
services.Remove(matchingServiceType);
services.TryAddScoped(usersTableServiceType, matchingServiceType.ImplementationType);
}
userStoreType = typeof(UserOnlyStore<,,,,>).MakeGenericType(userType, keyType, userClaimType, userLoginType, userTokenType);
}
services.TryAddScoped(typeof(IUserClaimsTable<,>).MakeGenericType(keyType, userClaimType), typeof(UserClaimsTable<,>).MakeGenericType(keyType, userClaimType));
Expand Down
28 changes: 23 additions & 5 deletions src/AspNetCore.Identity.Dapper/SqlServerDbConnectionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,36 @@ namespace AspNetCore.Identity.Dapper
/// <summary>
/// Creates a new <see cref="SqlConnection"/> instance for connecting to Microsoft SQL Server.
/// </summary>
public class SqlServerDbConnectionFactory : IDbConnectionFactory
public class SqlServerDbConnectionStore : IDbConnectionStore
{
/// <summary>
/// The connection string to use for connecting to Microsoft SQL Server.
/// </summary>
public string ConnectionString { get; set; }

private IDbConnection _sqlConnection;
/// <inheritdoc/>
public IDbConnection Create() {
var sqlConnection = new SqlConnection(ConnectionString);
sqlConnection.Open();
return sqlConnection;
public IDbConnection GetOrCreateConnection() {
// we could open the connection here and it will remain open until explicitly closed, but if we return a closed connection (as below),
// dapper will open and close with each query or transaction
return _sqlConnection ??= new SqlConnection(ConnectionString);
}

private bool _disposed = false;
/// <inheritdoc/>
public void Dispose() {
Dispose(true);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">Indicates whether the method call comes from a Dispose method (its value is true) or from a finalizer (its value is false).</param>
protected virtual void Dispose(bool disposing) {
if (!_disposed && disposing) {
// Free any other managed objects here.
_sqlConnection?.Dispose();
_disposed = true;
}
}
}
}
24 changes: 6 additions & 18 deletions src/AspNetCore.Identity.Dapper/Stores/RoleStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,27 +99,19 @@ public override async Task AddClaimAsync(TRole role, Claim claim, CancellationTo
}

/// <inheritdoc/>
public override async Task<IdentityResult> CreateAsync(TRole role, CancellationToken cancellationToken) {
public override Task<IdentityResult> CreateAsync(TRole role, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
role.ThrowIfNull(nameof(role));
var created = await RolesTable.CreateAsync(role);
return created ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"Role '{role.Name}' could not be created."
});
return RolesTable.CreateAsync(role);
}

/// <inheritdoc/>
public override async Task<IdentityResult> DeleteAsync(TRole role, CancellationToken cancellationToken) {
public override Task<IdentityResult> DeleteAsync(TRole role, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
role.ThrowIfNull(nameof(role));
var deleted = await RolesTable.DeleteAsync(role.Id);
return deleted ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"Role '{role.Name}' could not be deleted."
});
return RolesTable.DeleteAsync(role.Id);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -204,16 +196,12 @@ public override Task SetRoleNameAsync(TRole role, string roleName, CancellationT
}

/// <inheritdoc/>
public override async Task<IdentityResult> UpdateAsync(TRole role, CancellationToken cancellationToken) {
public override Task<IdentityResult> UpdateAsync(TRole role, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
role.ThrowIfNull(nameof(role));
role.ConcurrencyStamp = Guid.NewGuid().ToString();
var updated = await RolesTable.UpdateAsync(role, RoleClaims);
return updated ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"Role '{role.Name}' could not be updated."
});
return RolesTable.UpdateAsync(role, RoleClaims);
}
}
}
24 changes: 6 additions & 18 deletions src/AspNetCore.Identity.Dapper/Stores/UserOnlyStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,27 +95,19 @@ public override async Task AddLoginAsync(TUser user, UserLoginInfo login, Cancel
}

/// <inheritdoc/>
public override async Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken) {
public override Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
user.ThrowIfNull(nameof(user));
var created = await UsersTable.CreateAsync(user);
return created ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"User '{user.UserName}' could not be created."
});
return UsersTable.CreateAsync(user);
}

/// <inheritdoc/>
public override async Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken) {
public override Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
user.ThrowIfNull(nameof(user));
var deleted = await UsersTable.DeleteAsync(user.Id);
return deleted ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"User '{user.UserName}' could not be deleted."
});
return UsersTable.DeleteAsync(user.Id);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -212,16 +204,12 @@ public override async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newC
}

/// <inheritdoc/>
public override async Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken) {
public override Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
user.ThrowIfNull(nameof(user));
user.ConcurrencyStamp = Guid.NewGuid().ToString();
var updated = await UsersTable.UpdateAsync(user, UserClaims, UserLogins, UserTokens);
return updated ? IdentityResult.Success : IdentityResult.Failed(new IdentityError {
Code = string.Empty,
Description = $"User '{user.UserName}' could not be deleted."
});
return UsersTable.UpdateAsync(user, UserClaims, UserLogins, UserTokens);
}

/// <inheritdoc/>
Expand Down
Loading