如何使用 asp.net 身份创建交易?

How to create transaction with asp.net identity?

我正在注册,我要求 5 件事:

全名、电子邮箱、密码、联系电话、性别

现在我用注册方法存储的电子邮件 ID 和密码在下面两个 link:

public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.Email, Email = model.Email };

            using var context = new MyEntities())
            {
                using (var transaction = context.Database.BeginTransaction())
                {
                    try
                    {
                        var DataModel = new UserMaster();
                        DataModel.Gender = model.Gender.ToString();
                        DataModel.Name = string.Empty;
                        var result = await UserManager.CreateAsync(user, model.Password);//Doing entry in AspnetUser even if transaction fails
                        if (result.Succeeded)
                        {
                            await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
                            this.AddUser(DataModel, context);
                            transaction.Commit();
                            return View("DisplayEmail");
                        }
                        AddErrors(result);
                    }
                    catch (Exception ex)
                    {
                        transaction.Rollback();
                        return null;
                    }

                }
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

public int AddUser(UserMaster _addUser, MyEntities _context)
    {
        _context.UserMaster.Add(_addUser);           
        _context.SaveChanges();
        return 0;
    }

下面两行:

var result = await UserManager.CreateAsync(user, model.Password);//entry is done in AspnetUsers table.
await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());//entry is done is Aspnetuserrole table

现在这个全名、联系方式、性别我在另一个 table 中,即 UserMaster

因此,当 我将提交我的注册表时,我会将此详细信息 保存在 UserMaster 和 AspnetUsers,AspnetUserinrole table 中。

但是考虑一下如果在UserMaster中保存记录时出现任何问题那么我不想在Aspnetuser和Aspnetuserinrole中保存条目。

我想创建一个事务,如果在任何 table 中保存任何记录期间出现任何问题,我将回滚,即不应在 AspnetUser、AspnetUserinRole 和 userMaster 中进行任何输入。

只有在这3table秒内保存记录没有问题,才能保存记录成功,否则whiole transaction应该被角色返回。

我正在使用 Microsoft.AspNet.Identity 进行登录、注册、角色管理等,并遵循本教程:

http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset

http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

但是由于 await UserManager.CreateAsync 和 UserManager.AddToRoleAsync 方法是内置方法,我如何同步它以与 entity framework 一起工作。

那么有人可以指导我如何创建此类交易或任何可以解决此问题的方法吗?

IdentityConfig:

public class ApplicationUserManager : UserManager<ApplicationUser>
    {
        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = true,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.
            manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is {0}"
            });
            manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "Security Code",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();
            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider = 
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }

你可以用TransactionScope解决class:

using (TransactionScope scope = new TransactionScope())
{
    var result = await UserManager.CreateAsync(user, model.Password);
    if (result.Succeeded)
    {
        await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
        string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
        return View("DisplayEmail");
    }
    scope.Complete();
}

因此,这两个操作将在一个事务中完成,如果方法 Comlete 未调用,则两个操作都将被取消 (roolback)。

如果你只想用EF解决它(没有TransactionScope),你需要重构你的代码。我不知道 class UserManager 和方法 CreateAsyncAddToRoleAsync 的实现,但我猜他们会为每个操作创建新的 DBContext。因此,首先,对于所有事务性操作,您需要一个 DBContext(用于 EF 解决方案)。如果您添加此方法,我将根据 EF 解决方案修改我的答案。

您不应创建新的数据库上下文,而应使用现有的数据库上下文。

var context = Request.GetOwinContext().Get<MyEntities>()

如果您使用默认实现,它是根据请求创建的。

app.CreatePerOwinContext(ApplicationDbContext.Create);

更新:

好的,因为您使用的是两个不同的上下文,所以您的代码将如下所示:

public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };

        var appDbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
        using( var context = new MyEntities())
        using (var transaction = appDbContext.Database.BeginTransaction())
        {
            try
            {
                var DataModel = new UserMaster();
                DataModel.Gender = model.Gender.ToString();
                DataModel.Name = string.Empty;

                // Doing entry in AspnetUser even if transaction fails
                var result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    await this.UserManager.AddToRoleAsync(user.Id, model.Role.ToString());
                    this.AddUser(DataModel, context);
                    transaction.Commit();
                    return View("DisplayEmail");
                }
                AddErrors(result);
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                return null;
            }
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

public int AddUser(UserMaster _addUser, MyEntities _context)
{
    _context.UserMaster.Add(_addUser);
    _context.SaveChanges();
    return 0;
}

此处,appDbContextUserManager 使用的上下文相同。

当我使用此方法时,支持替代方案对我有用:TransactionScope(TransactionScopeAsyncFlowOption.Enabled)

来源:https://docs.microsoft.com/en-us/ef/ef6/saving/transactions