public async Task <LoginUserTxnPayload> Execute( LoginUserTxnInput input, IConfiguration configuration, UserManager <IdentityUser> userManager, SignInManager <IdentityUser> signInManager, ApplicationDbContext contextMVC, bool autoCommit) { LoginUserTxnPayload _payload = new LoginUserTxnPayload(); //***** Declare variables and messages string errorMessage = "LoginUserTxn***LOGIN_FAIL"; string okMessage = "LoginUserTxn***LOGIN_PASS"; string errorMessageExceptionCode = "LoginUserTxn***TXN_EXCEPTION"; string errorMessageException = "TXN_EXCEPTION"; // Error handling bool error = false; // To Handle Only One Error try { // DBContext by convention is a UnitOfWork, track changes and commits when SaveChanges is called // Multithreading issue: so if we use only one DBContext, it will track all entities (_context.Customer.Add) and commit them when SaveChanges is called, // No Matter what transactions or client calls SaveChanges. // Note: Rollback will not remove the entities from the tracker in the context. so better dispose it. //***** Make The Validations - Be careful : Concurrency. Same name can be saved multiple times if called at the exact same time. Better have an alternate database constraint if (!error) { // To check later - RefreshToken // https://stackoverflow.com/questions/53659247/using-aspnetusertokens-table-to-store-refresh-token-in-asp-net-core-web-api var result = await signInManager.PasswordSignInAsync(userName : input.Email, password : input.Password, isPersistent : false, lockoutOnFailure : false); if (!result.Succeeded) { _payload.PayloadResult = PayloadResult.ResultBad(Messages: new List <MessageItem>() { new MessageItem { Code = errorMessage, Message = errorMessage, Detail = "" } }); return(_payload); } var user = await signInManager.UserManager.FindByEmailAsync(input.Email); if (user == null) { _payload.PayloadResult = PayloadResult.ResultBad(Messages: new List <MessageItem>() { new MessageItem { Code = errorMessage, Message = errorMessage, Detail = "" } }); return(_payload); } // Get the UserApp var userApp = await contextMVC.UserApp.Where(x => x.IdentityId == user.Id).FirstOrDefaultAsync(); if (userApp == null) { _payload.PayloadResult = PayloadResult.ResultBad(Messages: new List <MessageItem>() { new MessageItem { Code = errorMessage, Message = errorMessage, Detail = "" } }); return(_payload); } //***** Save and Commit to the Database (Atomic because is same Context DB) if (!error && autoCommit) { await contextMVC.SaveChangesAsync(); // Call it only once so do all other operations first } //***** Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database _payload.PayloadResult = PayloadResult.ResultGood(Messages: new List <MessageItem> { new MessageItem { Code = okMessage, Message = okMessage, Detail = "" } }); _payload.JWTInfo = await this.CreateTokenAsync(user, userApp, configuration, userManager); }// if (!error) } catch (Exception ex) // Main try { System.Diagnostics.Debug.WriteLine("Error: " + ex.Message); string innerError = (ex.InnerException != null) ? ex.InnerException.Message : ""; System.Diagnostics.Debug.WriteLine("Error Inner: " + innerError); _payload = new LoginUserTxnPayload { PayloadResult = PayloadResult.ResultBad(Messages: new List <MessageItem> { new MessageItem { Code = errorMessageExceptionCode, Message = errorMessageException, Detail = ex.Message } }) }; // Restart variable to avoid returning any already saved data } finally { // Save Logs if needed } return(_payload); }
public async Task <RegisterUserTxnPayload> Execute( RegisterUserTxnInput input, UserManager <IdentityUser> userManager, ApplicationDbContext contextMVC, bool autoCommit) { RegisterUserTxnPayload _payload = new RegisterUserTxnPayload { PayloadResult = new PayloadResult { Messages = new List <MessageItem>() } }; //***** Declare variables and messages string okMessage = "RegisterUserTxn***USER_REGISTERED"; string errorMessageExceptionCode = "RegisterUserTxn***TXN_EXCEPTION"; string errorMessageException = "TXN_EXCEPTION"; string claimType = "UserType"; string claimUserType1 = "User"; string claimUserType2 = "RegisteredUser"; string validatorCode = "InputValidationError"; string errorWrongConfirmationPassMessage = "RegisterUserTxnInput***Compare***ConfirmPassword"; // To translate to: Password and Confirm Password don't match. string errorWrongConfirmationPassDetail = "ConfirmPassword"; // To translate to: Password and Confirm Password don't match. // Error handling bool error = false; // To Handle Only One Error try { // DBContext by convention is a UnitOfWork, track changes and commits when SaveChanges is called // Multithreading issue: so if we use only one DBContext, it will track all entities (_context.Customer.Add) and commit them when SaveChanges is called, // No Matter what transactions or client calls SaveChanges. // Note: Rollback will not remove the entities from the tracker in the context. so better dispose it. //***** Make The Validations - Be careful : Concurrency. Same name can be saved multiple times if called at the exact same time. Better have an alternate database constraint // Validate Model Annotations (if needed) var validationResults = new List <ValidationResult>(); DataAnnotationsValidator.TryValidateObjectRecursive(input, validationResults); if (validationResults.Count > 0) { // "code": "InputValidationError", // "message": "RegisterUserTxnInput***LastName***Required", // To be transalated // "detail": "LastName" // To be used by the FrontEnd _payload.PayloadResult = PayloadResult.ResultBad(Messages: validationResults.Select(o => new MessageItem { Code = validatorCode, Message = o.ErrorMessage, Detail = o.MemberNames.FirstOrDefault() }).ToList()); error = true; } // Validate Confirm email, the Annotation is not working if (!input.Password.Equals(input.ConfirmPassword)) { _payload.PayloadResult.Messages.Add(new MessageItem { Code = validatorCode, Message = errorWrongConfirmationPassMessage, Detail = errorWrongConfirmationPassDetail }); error = true; } if (error) // Validations { return(_payload); } if (!error) { var newUser = new IdentityUser { UserName = input.Email, Email = input.Email }; var result = await userManager.CreateAsync(newUser, input.Password); if (!result.Succeeded) { /* 1. Password validation: PasswordTooShort, PasswordRequiresNonAlphanumeric, PasswordRequiresLower, PasswordRequiresUpper, PasswordRequiresDigit * "code": "InputValidationError", * "message": "RegisterUserTxnInput***PasswordTooShort", * "detail": "Password" * * 2. Any other error validation: * "code": "RegisterUserTxnInput***DuplicateUserName", * "message": "RegisterUserTxnInput***DuplicateUserName", * "detail": "" */ _payload.PayloadResult = PayloadResult.ResultBad(Messages: (result.Errors.Select(o => new MessageItem { Code = o.Code.StartsWith("Password") ? validatorCode : "RegisterUserTxnInput***" + o.Code, Message = "RegisterUserTxnInput***" + o.Code, Detail = o.Code.StartsWith("Password") ? "Password" : "" })).ToList()); return(_payload); } // Create the user in MVC Database UserApp userApp = new UserApp { IdentityId = newUser.Id, Email = input.Email, FirstName = input.FirstName, LastName = input.LastName, Active = true, LastSeen = DateTime.Now }; contextMVC.UserApp.Add(userApp); //***** Save and Commit to the Database (Atomic because is same Context DB) if (!error && autoCommit) { await contextMVC.SaveChangesAsync(); // Call it only once so do all other operations first } // Save the Name Claim into the database, add the claims in the JWT, encrypt the JWT, and use an interceptor in middleware to get this values an use them globally in the request var _claims = new[] { new Claim(ClaimTypes.Name, userApp.Id), new Claim(ClaimTypes.Email, userApp.Email), new Claim("Identity", userApp.IdentityId), new Claim(ClaimTypes.Role, claimUserType1), new Claim(ClaimTypes.Role, claimUserType2), new Claim(claimType, claimUserType1) // Create all user equal, add other claims later in database }; var resultClaims = await userManager.AddClaimsAsync(newUser, _claims); // Save claims in the Database if (!resultClaims.Succeeded) { _payload.PayloadResult = PayloadResult.ResultBad(Messages: (resultClaims.Errors.Select(o => new MessageItem { Code = "RegisterUserTxn***" + o.Code, Message = "RegisterUserTxn***" + o.Code, Detail = "" })).ToList()); return(_payload); } //***** Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database _payload.PayloadResult = PayloadResult.ResultGood(Messages: new List <MessageItem> { new MessageItem { Code = okMessage, Message = okMessage, Detail = "" } }); _payload.User = userApp; }// if (!error) } catch (Exception ex) // Main try { System.Diagnostics.Debug.WriteLine("Error: " + ex.Message); string innerError = (ex.InnerException != null) ? ex.InnerException.Message : ""; System.Diagnostics.Debug.WriteLine("Error Inner: " + innerError); _payload = new RegisterUserTxnPayload { PayloadResult = PayloadResult.ResultBad(Messages: new List <MessageItem> { new MessageItem { Code = errorMessageExceptionCode, Message = errorMessageException, Detail = ex.Message } }) }; // Restart variable to avoid returning any already saved data } finally { // Save Logs if needed } return(_payload); }