Using identity to store data with dapper in. Net core2.0

Time:2020-10-23

Preface

I haven’t written a blog for many days. In view of the rise of leisure and boredom, I want to write a blog, so I should make a note for myself. After so many days, my writing is still so bad, please forgive me. Today, I’d like to share some of the actual situations encountered in using. Net core 2.0. Identity is used for user authentication when using webapi. The official document uses EF to store data to use dapper. Because of personal preference, we don’t want to use EF. So he went to make trouble. Instead of using dapper for data storage. So we have the following experience.

1、 Using identity services

Find it first Startup.cs This class file finds the configureservices method

services.AddIdentity < applicationuser, applicationrole > (). Adddefaulttokenproviders(); // add identity
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
string connectionString = Configuration.GetConnectionString("SqlConnectionStr");
services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
services.AddTransient<DapperUsersTable>();

Then, in the app.UseMvc Add the following code before () when net core 1.0 is app.UseIdentity () has now been abandoned and replaced with the following method.

//Use validation
app.UseAuthentication();

Here, applicationuser is a user-defined user model, which inherits some attributes of identityuser


public class ApplicationUser :IdentityUser
 {
  public string AuthenticationType { get; set; }
  public bool IsAuthenticated { get; set; }
  public string Name { get; set; }
 }

Here, the customuserstore is a class that provides user-defined methods for all data operations. It needs to inherit three interfaces: iuserstore, iuserpasswordstore and iuseremail store

Iuserstore < tuser > interface is the only interface that must be implemented in user storage. It defines methods for creating, updating, deleting, and retrieving users.

The iuserpasswordstore < tuser > interface defines the method implemented to hold the hashed password. It contains methods for getting and setting hashed passwords for work, and methods for indicating whether the user has set a password.

The iusermailstore < tuser > interface defines the method to store the user’s e-mail address. It contains methods for getting and setting the e-mail address and confirming the e-mail.

This is a bit different from the implementation interface of. Net core 1.0. It is necessary to implement iusermailstore more to avoid error reporting

The specific codes are as follows. For your reference.

CustomUserStore


using Microsoft.AspNetCore.Identity;
using System;
using System.Threading.Tasks;
using System.Threading;

namespace YepMarsCRM.Web.CustomProvider
{
 /// <summary>
 /// This store is only partially implemented. It supports user creation and find methods.
 /// </summary>
 public class CustomUserStore : IUserStore<ApplicationUser>,
  IUserPasswordStore<ApplicationUser>,
  IUserEmailStore<ApplicationUser>
 {
  private readonly DapperUsersTable _usersTable;

  public CustomUserStore(DapperUsersTable usersTable)
  {
   _usersTable = usersTable;
  }

  #region createuser
  public async Task<IdentityResult> CreateAsync(ApplicationUser user,
   CancellationToken cancellationToken = default(CancellationToken))
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return await _usersTable.CreateAsync(user);
  }
  #endregion

  public async Task<IdentityResult> DeleteAsync(ApplicationUser user,
   CancellationToken cancellationToken = default(CancellationToken))
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return await _usersTable.DeleteAsync(user);

  }

  public void Dispose()
  {
  }

  public Task<ApplicationUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public async Task<ApplicationUser> FindByIdAsync(string userId,
   CancellationToken cancellationToken = default(CancellationToken))
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (userId == null) throw new ArgumentNullException(nameof(userId));
   Guid idGuid;
   if (!Guid.TryParse(userId, out idGuid))
   {
    throw new ArgumentException("Not a valid Guid id", nameof(userId));
   }

   return await _usersTable.FindByIdAsync(idGuid);

  }

  public async Task<ApplicationUser> FindByNameAsync(string userName,
   CancellationToken cancellationToken = default(CancellationToken))
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (userName == null) throw new ArgumentNullException(nameof(userName));

   return await _usersTable.FindByNameAsync(userName);
  }

  public Task<string> GetEmailAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return Task.FromResult(user.Email);
  }

  public Task<bool> GetEmailConfirmedAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task<string> GetNormalizedEmailAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task<string> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return Task.FromResult(user.PasswordHash);
  }

  public Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return Task.FromResult(user.Id.ToString());
  }

  public Task<string> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));

   return Task.FromResult(user.UserName);
  }

  public Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task SetEmailAsync(ApplicationUser user, string email, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task SetEmailConfirmedAsync(ApplicationUser user, bool confirmed, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task SetNormalizedEmailAsync(ApplicationUser user, string normalizedEmail, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));
   if (normalizedEmail == null) throw new ArgumentNullException(nameof(normalizedEmail));

   user.NormalizedEmail = normalizedEmail;
   return Task.FromResult<object>(null);
  }

  public Task SetNormalizedUserNameAsync(ApplicationUser user, string normalizedName, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));
   if (normalizedName == null) throw new ArgumentNullException(nameof(normalizedName));

   user.NormalizedUserName = normalizedName;
   return Task.FromResult<object>(null);
  }

  public Task SetPasswordHashAsync(ApplicationUser user, string passwordHash, CancellationToken cancellationToken)
  {
   cancellationToken.ThrowIfCancellationRequested();
   if (user == null) throw new ArgumentNullException(nameof(user));
   if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash));

   user.PasswordHash = passwordHash;
   return Task.FromResult<object>(null);

  }

  public Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
  {
   throw new NotImplementedException();
  }

  public Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken)
  {
   return _usersTable.UpdateAsync(user);
  }
 }
}

2、 Using dapper for data storage

The next step is to use dapper for data storage. The methods of this class are used to operate the database through the call of customuserstore. The specific codes are as follows. Operate dapper according to the actual user table.

DapperUsersTable


using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
using System;
using Dapper;
using YepMarsCRM.Enterprise.DataBase.Model;
using YepMarsCRM.Enterprise.DataBase.Data;

namespace YepMarsCRM.Web.CustomProvider
{
 public class DapperUsersTable
 {
  private readonly SqlConnection _connection;
  private readonly Sys_AccountData _sys_AccountData;
  public DapperUsersTable(SqlConnection connection)
  {
   _connection = connection;
   _sys_AccountData = new Sys_AccountData();
  }

  private Sys_Account ApplicationUserToAccount(ApplicationUser user)
  {
   return new Sys_Account
   {
    Id = user.Id,
    UserName = user.UserName,
    PasswordHash = user.PasswordHash,
    Email = user.Email,
    EmailConfirmed = user.EmailConfirmed,
    PhoneNumber = user.PhoneNumber,
    PhoneNumberConfirmed = user.PhoneNumberConfirmed,
    LockoutEnd = user.LockoutEnd?.DateTime,
    LockoutEnabled = user.LockoutEnabled,
    AccessFailedCount = user.AccessFailedCount,
   };
  }

  #region createuser
  public async Task<IdentityResult> CreateAsync(ApplicationUser user)
  {
   int rows = await _sys_AccountData.InsertAsync(ApplicationUserToAccount(user));
   if (rows > 0)
   {
    return IdentityResult.Success;
   }
   return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
  }
  #endregion

  public async Task<IdentityResult> DeleteAsync(ApplicationUser user)
  {
   //string sql = "DELETE FROM Sys_Account WHERE Id = @Id";
   //int rows = await _connection.ExecuteAsync(sql, new { user.Id });

   int rows = await _sys_AccountData.DeleteForPKAsync(ApplicationUserToAccount(user));

   if (rows > 0)
   {
    return IdentityResult.Success;
   }
   return IdentityResult.Failed(new IdentityError { Description = $"Could not delete user {user.Email}." });
  }


  public async Task<ApplicationUser> FindByIdAsync(Guid userId)
  {
   string sql = "SELECT * FROM Sys_Account WHERE Id = @Id;";
   return await _connection.QuerySingleOrDefaultAsync<ApplicationUser>(sql, new
   {
    Id = userId
   });
  }


  public async Task<ApplicationUser> FindByNameAsync(string userName)
  {
   string sql = "SELECT * FROM Sys_Account WHERE UserName = @UserName;";

   return await _connection.QuerySingleOrDefaultAsync<ApplicationUser>(sql, new
   {
    UserName = userName
   });

   //var user = new ApplicationUser() { UserName = userName, Email = userName, EmailConfirmed = false };
   //user.PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(user, "test");
   //return await Task.FromResult(user);
  }

  public async Task<IdentityResult> UpdateAsync(ApplicationUser applicationUser)
  {
   var user = ApplicationUserToAccount(applicationUser);
   var result = await _sys_AccountData.UpdateForPKAsync(user);
   if (result > 0)
   {
    return IdentityResult.Success;
   }
   return IdentityResult.Failed(new IdentityError { Description = $"Could not update user {user.Email}." });
  }
 }
}

3、 Use usermanager and signinmanager to verify the operation

Create a new accountcontroller controller and get the dependency injection objects usermanager and signinmanager in the constructor as follows:


[Authorize]
  public class AccountController : Controller
 {
  private readonly UserManager<ApplicationUser> _userManager;
  private readonly SignInManager<ApplicationUser> _signInManager;
  private readonly ILogger _logger;

public AccountController(UserManager<ApplicationUser> userManager,
   SignInManager<ApplicationUser> signInManager,
   ILoggerFactory loggerFactory)
  {
   _userManager = userManager;
   _signInManager = signInManager;
   _logger = loggerFactory.CreateLogger<AccountController>();
  }
 }

Signinmanager is an API that provides user login and logout, and usermanager is an API that provides user management.

Then let’s do a simple login and logout.

/// <summary>
  ///Login
  /// </summary>
  [HttpPost]
  [AllowAnonymous]
  public async Task<IActionResult> Login(ReqLoginModel req)
  {
   var json = new JsonResultModel<object>();
   if (ModelState.IsValid)
   {
    var result = await _signInManager.PasswordSignInAsync(req.UserName, req.Password, isPersistent: true, lockoutOnFailure: false);
    if (result.Succeeded)
    {
     json.code = "200";
     json.message  ="Login succeeded";
    }
    else
    {
     json.code = "400";
     json.message  ="Login failed";
    }
    if (result.IsLockedOut)
    {
     json.code = "401";
     json.message  ="The account password has been wrong for 3 times and the account has been locked. Please try again in 30 minutes";
    }
   }
   else
   {
    var errorMessges = ModelState.GetErrorMessage();
    json.code = "403";
    json.message = string.Join(",", errorMessges);
   }
   return json.ToJsonResult();
  }
/// <summary>
  ///Log out
  /// </summary>
  /// <returns></returns>
  [HttpPost]
  public async Task<IActionResult> LogOut()
  {await _signInManager.SignOutAsync();
   var json = new JsonResultModel<object>()
   {
    code = "200",
    data = null,
    Message: "logout succeeded.",
    remark = string.Empty
   };
   return json.ToJsonResult();
  }

4、 Using identity configuration

Add to the configureservices method

services.Configure<IdentityOptions>(options =>
   {
    //Password configuration
    options.Password.RequireDigit  =False; // whether the number (0-9) is required
    options.Password.RequiredLength  =6; // set the minimum password length to 6
    options.Password.RequireNonAlphanumeric  =False; // whether it contains non alphabetic or numeric characters.
    options.Password.RequireUppercase  =False; // whether capital letters (A-Z) are required
    options.Password.RequireLowercase  =False; // whether lower case letters (A-Z) are required
    //options.Password.RequiredUniqueChars = 6;

    //Lock settings
    options.Lockout.DefaultLockoutTimeSpan  = TimeSpan.FromMinutes (30); // the account is locked for 30 minutes
    options.Lockout.MaxFailedAccessAttempts  =3; // 10 failed attempts to lock the account
    //options.Lockout.AllowedForNewUsers = true;

    //User settings
    options.User.RequireUniqueEmail  =False; // is the email address unique
   });

   services.ConfigureApplicationCookie(options =>
   {
    // Cookie settings
    options.Cookie.HttpOnly = true;
    // options.Cookie.Expiration  = TimeSpan.FromMinutes (30); // 30 minutes
    options.Cookie.Expiration  = TimeSpan.FromHours (12) ; // 12 hours
    options.LoginPath = "/api/Account/NotLogin"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
    //options.LogoutPath = "/api/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
    //options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
    options.SlidingExpiration = true;
   });

5、 Others

In the process of implementation, we encountered some small problems. For example, identity does not work. It’s not there app.UseMvc () caused by previous use. If you are not logged in, it will cause a jump. Later, after checking the source code of. Net core identity, it was found that if it was Ajax, it would not jump but return to 401 status code page.

Then the password encryption of identity is encrypted by the passwordhasher class. If you want to use your own encryption. You can only change the original mode by inheriting the interface. And then I’ll talk about that in general. Take notes for yourself. If it’s not done well, please give us more opinions. Forgive me. thank you.

I hope you can share all of the above examples.

Recommended Today

Let me also summarize the knowledge of nginx

Recently, I want to deeply study the related knowledge of nginx, so I summarize the following contents. Nginx configuration parameters Nginx common commands Nginx variable Virtual host configuration Nginx’s own module Fastcgi related configuration Common functions Load balancing configuration Static and dynamic separation configuration Anti theft chain What is nginx? Nginx is a free, open […]