Base solution for your next web application
Open Closed

Uow Transaction dosen't role back #2352


User avatar
0
mentium created

Hi

I have created a method similar to the tenant registration page but when i run the code and it breaks after the first changes the transaction dosen't role back.

Register in controller:

    [HttpPost]
    [UnitOfWork]
    public virtual async Task<ActionResult> Register(WorkerRegistrationViewModel model)
    {
        try
        {
            if (UseCaptchaOnRegistration())
            {
                var recaptchaHelper = this.GetRecaptchaVerificationHelper();
                if (recaptchaHelper.Response.IsNullOrEmpty())
                {
                    throw new UserFriendlyException(L("CaptchaCanNotBeEmpty"));
                }

                if (recaptchaHelper.VerifyRecaptchaResponse() != RecaptchaVerificationResult.Success)
                {
                    throw new UserFriendlyException(L("IncorrectCaptchaAnswer"));
                }
            }

            //Getting host-specific settings
            var isNewRegisteredTenantActiveByDefault = await SettingManager.GetSettingValueForApplicationAsync<bool>(AppSettings.TenantManagement.IsNewRegisteredTenantActiveByDefault);
            var isEmailConfirmationRequiredForLogin = await SettingManager.GetSettingValueForApplicationAsync<bool>(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin);
            var defaultEditionIdValue = await SettingManager.GetSettingValueForApplicationAsync(AppSettings.TenantManagement.DefaultEdition);
            Guid? defaultEditionId = null;

            if (!string.IsNullOrEmpty(defaultEditionIdValue) && (await _editionManager.FindByIdAsync(Guid.Parse(defaultEditionIdValue)) != null))
            {
                defaultEditionId = Guid.Parse(defaultEditionIdValue);
            }

            Guid tenantId = AbpSession.TenantId.Value;

            CurrentUnitOfWork.SetTenantId(tenantId);


            ViewBag.UseCaptcha = UseCaptchaOnRegistration();

            var tenant = await _tenantManager.GetByIdAsync(tenantId);


            DateTime? dateOfBirth = null;
            try
            {
                dateOfBirth = DateTime.Parse($"{model.Dob_Year}-{model.Dob_Month}-{model.Dob_Day}");
            }
            catch (Exception)
            {
                throw new UserFriendlyException(L("DateOfBirthFormatError"));
            }

            var userId = await _candidateManager.CreateCandidateAsync(tenantId,
                model.UserName,
                model.FirstName,
                model.LastName,
                dateOfBirth.Value,
                model.Password,
                model.EmailAddress,
                true);

            var user = await _userManager.FindByIdAsync(userId);

            //Directly login if possible
            if (tenant.IsActive && user.IsActive && (user.IsEmailConfirmed || !isEmailConfirmationRequiredForLogin))
            {
                var loginResult = await GetLoginResultAsync(user.UserName, model.Password, tenant.TenancyName);

                if (loginResult.Result == AbpLoginResultType.Success)
                {
                    await SignInAsync(loginResult.User, loginResult.Identity);
                    return Redirect(Url.Action("Index", "Application"));
                }

                Logger.Warn("New registered user could not be login. This should not be normally. login result: " + loginResult.Result);
            }

            //await _appNotifier.NewTenantRegisteredAsync(tenant);

            return View("RegisterResult", new WorkerRegisterResultViewModel
            {
                TenancyName = tenant.TenancyName,
                Name = model.FirstName,
                UserName = Authorization.Users.User.AdminUserName,
                EmailAddress = model.EmailAddress,
                IsActive = isNewRegisteredTenantActiveByDefault,
                IsEmailConfirmationRequired = isEmailConfirmationRequiredForLogin
            });


        }
        catch (UserFriendlyException ex)
        {
            ViewBag.UseCaptcha = UseCaptchaOnRegistration();
            ViewBag.ErrorMessage = ex.Message;

            return View("Index", model);
        }
    }

I have wrote this code to create the candidate

    public async Task<Guid> CreateCandidateAsync(Guid tenantId, string userName, string firstName, string lastName, DateTime dateOfBirth, string password, string emailAddress, bool isActive, Guid? editionId = null, bool shouldChangePasswordOnNextLogin = false, bool sendActivationEmail = false)
    {
        Guid newUserId;

        using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
        {
            using (_unitOfWorkManager.Current.SetTenantId(tenantId))
            {
                //Create admin user for the tenant
                if (password.IsNullOrEmpty())
                {
                    password = User.CreateRandomPassword();
                }

                var user = User.CreateCandidateUser(tenantId, userName, firstName, lastName, emailAddress, password);
                user.ShouldChangePasswordOnNextLogin = shouldChangePasswordOnNextLogin;
                user.IsActive = isActive;

                CheckErrors(await _userManager.CreateAsync(user));
                await _unitOfWorkManager.Current.SaveChangesAsync(); //To get candidate user's id

                var userRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Candidate);

                //Assign admin user to admin role!
                CheckErrors(await _userManager.AddToRoleAsync(user.Id, userRole.Name));

                var ca = new Candidate();
                ca.Id = user.Id;
                ca.TenantId = tenantId;
                ca.Number = 1;
                ca.FirstName = firstName;
                ca.LastName = lastName;
                ca.Address = new Address();
                ca.MobileNumber = new PhoneNumber();
                ca.PhoneNumber = new PhoneNumber();
                await _candidateRepository.InsertAsync(ca);
                await _unitOfWorkManager.Current.SaveChangesAsync(); //To get candidate's id

<span style="color:#FF0000">//Is is here the exception is thrown</span>

                //Notifications
                await _appNotifier.WelcomeToTheApplicationAsync(user);

                //Send activation email
                if (sendActivationEmail)
                {
                    user.SetNewEmailConfirmationCode();
                    await _userEmailer.SendEmailActivationLinkAsync(user, password);
                }

                await _unitOfWorkManager.Current.SaveChangesAsync();

                newUserId = user.Id;
            }

            await uow.CompleteAsync();
        }


        //Used a second UOW since UOW above sets some permissions and _notificationSubscriptionManager.SubscribeToAllAvailableNotificationsAsync needs these permissions to be saved.
        using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
        {
            using (_unitOfWorkManager.Current.SetTenantId(tenantId))
            {
                await _notificationSubscriptionManager.SubscribeToAllAvailableNotificationsAsync(new UserIdentifier(tenantId, newUserId));
                await _unitOfWorkManager.Current.SaveChangesAsync();
                await uow.CompleteAsync();
            }
        }

        return newUserId;
    }

Hope somone can help.


3 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Since you use a try-catch block in your controller and don't handle uow in your catch block, this is the expected behavior. If you want to use a try-catch block, you can disable UOW for your controller (using it's attribute [UnitOfWork(IsDisabled = true)]), and then manually start a new UOW in your controller. In this way, you can rollback this transaction in your catch block.

  • User Avatar
    0
    mentium created

    Thanks for the answer.

    I have used the method Register from TenantRegistrationController, would this code not role back either?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    We will also test this and fix if there is a problem. <a class="postlink" href="https://github.com/aspnetzero/aspnet-zero/issues/429">https://github.com/aspnetzero/aspnet-zero/issues/429</a>. Probably same thing will happen.