public async Task <CreateUserTxn_Output> Execute(CreateUserTxn_Input input, IServiceProvider serviceProvider, MVCDbContext contextFatherMVC = null, bool autoCommit = true) { CreateUserTxn_Output _output = new CreateUserTxn_Output { ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "TXN_NOT_STARTED") }; String userType = "User"; // Error handling bool error = false; // To Handle Only One Error try { MVCDbContext contextMVC = (contextFatherMVC != null) ? contextFatherMVC : new MVCDbContext(); // An using statement is in reality a try -> finally statement, disposing the element in the finally. So we need to take advance of that to create a DBContext inheritance 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. //***** 0. 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 // Company company = await contextMVC.Company.Where(x => x.Id.Equals(input.Company.Id)).FirstOrDefaultAsync(); company company = await contextMVC.Company.Where(x => x.companyId.Equals(input.Company.companyId)).FirstOrDefaultAsync(); if (company == null) { error = true; _output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "COMPANY_DOES_NOT_EXIST_ERROR", _ResultDetail: ""); } UserManager <IdentityUser> _userManager; IdentityUser user = new IdentityUser(); if (!error) { _userManager = serviceProvider.GetService <UserManager <IdentityUser> >(); user = new IdentityUser { UserName = input.User.userName, Email = input.User.email, PasswordHash = input.User.password }; var result = await _userManager.CreateAsync(user); if (!result.Succeeded) { error = true; _output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "USER_NOT_CREATED_ERROR", _ResultDetail: result.Errors.FirstOrDefault().Description); // If OK List <itemKey> resultKeys = new List <itemKey>(); foreach (var errorDesc in result.Errors) { resultKeys.Add(new itemKey(errorDesc.Code, errorDesc.Description)); } _output.ResultConfirmation.resultDictionary = resultKeys; } else // No error { // Save the Name Claim into the database var _claims = new[] { new Claim(ClaimTypes.Name, user.Id) , new Claim("UserType", userType) // Create the token without Manager status, then add the claim in database }; var resultClaims = await _userManager.AddClaimsAsync(user, _claims); // Save claims in the Database } } if (!error) { //***** 1. Create the token (Atomic because is same Context DB) // Call transaction to get a new Token (we call the Mutation not the class itself) var tokenString = ""; CreateTokenTxn_Input createTokenTxn_Input = new CreateTokenTxn_Input { userAppId = user.Id }; CreateTokenTxn_Output createTokenTxn = await new GraphQLMutation().CreateTokenTxn(input: createTokenTxn_Input, serviceProvider: serviceProvider); if (createTokenTxn.resultConfirmation.resultPassed) { tokenString = createTokenTxn.token; } input.User.userAppId = user.Id; // To create Key input.User.companyId = company.companyId; input.User.password = ""; // Dont Save the password input.User.tokenAuth = tokenString; // The token contextMVC.UserApp.Add(input.User); //***** 4. 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 } //***** 5. Execute Send e-mails or other events once the database has been succesfully saved //***** If this task fails, there are options -> 1. Retry multiple times 2. Save the event as Delay, 3.Rollback Database, Re // Create the Login Token for the User try { if (!error) { input.User.loginTokenId = LoginTokenFunction.CreateTokenId(input.User.id, DateTime.UtcNow); contextMVC.UserApp.Update(input.User); await contextMVC.SaveChangesAsync(); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message); } //***** 6. Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database _output.ResultConfirmation = resultConfirmation.resultGood(_ResultMessage: "USER_SUCESSFULLY_CREATED"); // If OK _output.User = input.User; }// if (!error) } finally { // If the context Father is null the context was created on his own, so dispose it if (contextMVC != null && contextFatherMVC == null) { contextMVC.Dispose(); } } } 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); _output = new CreateUserTxn_Output(); // Restart variable to avoid returning any already saved data _output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "EXCEPTION", _ResultDetail: ex.Message); } finally { // Save Logs if needed } return(_output); }
public async Task <CreateCompanyAndXUsersTxn_Output> Execute(CreateCompanyAndXUsersTxn_Input input, IServiceProvider serviceProvider, MVCDbContext contextFatherMVC = null, bool autoCommit = true) { CreateCompanyAndXUsersTxn_Output output = new CreateCompanyAndXUsersTxn_Output(); output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "TXN_NOT_STARTED"); //String userType = "User"; // Error handling bool error = false; // To Handle Only One Error try { MVCDbContext contextMVC = (contextFatherMVC != null) ? contextFatherMVC : new MVCDbContext(); // An using statement is in reality a try -> finally statement, disposing the element in the finally. So we need to take advance of that to create a DBContext inheritance 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. //***** 0. 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 // Call the Transaction to Create the Company //CreateCompanyTxn_Input CreateCompanyTxn_Input = new CreateCompanyTxn_Input { Company = input.Company }; //CreateCompanyTxn_Output CreateCompanyTxn = await new GraphQLMutation().CreateCompanyTxn(input: CreateCompanyTxn_Input, serviceProvider: serviceProvider); CreateCompanyTxn_Input CreateCompanyTxn_Input = new CreateCompanyTxn_Input { Company = input.Company }; CreateCompanyTxn_Output CreateCompanyTxn = await new CreateCompanyTxn().Execute(input: CreateCompanyTxn_Input, serviceProvider: serviceProvider); if (!CreateCompanyTxn.ResultConfirmation.resultPassed) { // Return the error as it is from the transaction error = true; output.ResultConfirmation = CreateCompanyTxn.ResultConfirmation; return(output); } output.Company = CreateCompanyTxn.Company; // Now create a loop to create the X users. Use the same database for (int x = 1; x <= input.UsersToCreate; x++) { userApp newUser = new userApp { firstName = "Name" + x, lastName = "LastName" + x, nickname = "Nickname" + x, userName = CreateCompanyTxn.Company.companyId + "--" + x.ToString() }; CreateUserTxn_Input CreateUserTxn_Input = new CreateUserTxn_Input { Company = CreateCompanyTxn.Company, User = newUser }; CreateUserTxn_Output CreateUserTxn = await new CreateUserTxn().Execute(input: CreateUserTxn_Input, serviceProvider: serviceProvider, contextFatherMVC: contextMVC); if (!CreateUserTxn.ResultConfirmation.resultPassed) { // Return the error as it is from the transaction error = true; output.ResultConfirmation = CreateUserTxn.ResultConfirmation; return(output); } } // Logic, first create All the users in MVC Database. Commit, if something goes wrong return Error, if OK create users in master user App table. // // UserManager<IdentityUser> _userManager = serviceProvider.GetService<UserManager<IdentityUser>>(); /* * UserManager<IdentityUser> _userManager = serviceProvider.GetService<UserManager<IdentityUser>>(); * * var user = new IdentityUser { UserName = input.user.UserName, Email = input.user.Email, PasswordHash = input.user.PasswordHash }; * var result = await _userManager.CreateAsync(user); * * if (!result.Succeeded) * { * error = true; * _output.ResultConfirmation = ResultConfirmation.resultBad(_ResultMessage: "USER_NOT_CREATED_ERROR", _ResultDetail: result.Errors.FirstOrDefault().Description); // If OK * List<ItemKey> resultKeys = new List<ItemKey>(); * * foreach (var errorDesc in result.Errors) * { * resultKeys.Add(new ItemKey(errorDesc.Code, errorDesc.Description)); * } * * _output.ResultConfirmation.ResultDictionary = resultKeys; * * } */ if (!error) { //***** 1. Create the token (Atomic because is same Context DB) /* * // Save the Name Claim into the database * var _claims = new[]{ * new Claim(ClaimTypes.Name, user.Id) * ,new Claim("UserType", userType) // Create the token without Manager status, then add the claim in database * }; * * var resultClaims = await _userManager.AddClaimsAsync(user, _claims); // Save claims in the Database * * * * // Call transaction to get a new Token (we call the Mutation not the class itself) * var tokenString = ""; * CreateTokenTxn_Input createTokenTxn_Input = new CreateTokenTxn_Input { userAppId = user.Id }; * CreateTokenTxn_Output createTokenTxn = await new GraphQLMutation().CreateTokenTxn(input: createTokenTxn_Input, serviceProvider: serviceProvider); * if (createTokenTxn.resultConfirmation.ResultPassed) * { * tokenString = createTokenTxn.token; * } */ //***** 4. 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 } //***** 5. Execute Send e-mails or other events once the database has been succesfully saved //***** If this task fails, there are options -> 1. Retry multiple times 2. Save the event as Delay, 3.Rollback Database, Re //***** 6. Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database output.ResultConfirmation = resultConfirmation.resultGood(_ResultMessage: "COMPANY_SUCESSFULLY_CREATED"); // If OK //_output.token = tokenString; // The token //_output.user = input.user; }// if (!error) } finally { // If the context Father is null the context was created on his own, so dispose it if (contextMVC != null && contextFatherMVC == null) { contextMVC.Dispose(); } } } 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); output = new CreateCompanyAndXUsersTxn_Output(); // Restart variable to avoid returning any already saved data output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "EXCEPTION", _ResultDetail: ex.Message); } finally { // Save Logs if needed } return(output); }