You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core MVC注册表单邮箱字段无法正确保存问题排查

注册时邮箱字段无法正确保存到数据库

我开发了一个小型电影应用,支持用户注册、登录、添加电影条目及评论。但注册表单的邮箱字段无法正确传递并保存到数据库。

调试发现,在AccountController的var identityResult = await userManager.CreateAsync(user, registerViewModel.Password);行,user对象的UserName、Email、Password均正常传入,但执行后identityResult显示失败。原错误处理代码返回“Email '' is invalid”,修改为显示user.Email时则正常显示输入的邮箱(如tim123@gmail.com)。


相关代码

AccountController

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;  
using MovieOrganizer.Models.ViewModels;
using MovieOrganizer.Models.Domain;

namespace MovieOrganizer.Controllers
{
    public class AccountController : Controller
    {
        private readonly UserManager<User> userManager;
        private readonly SignInManager<User> signInManager;

        public AccountController(UserManager<User> userManager,
            SignInManager<User> signInManager)
        {
            this.userManager = userManager;
            this.signInManager = signInManager;
        }

        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
        {
            var user = new User
            {
                UserName = registerViewModel.UserName,
                Email = registerViewModel.Email,
                //PasswordHash = registerViewModel.Password
            };

            var identityResult = await userManager.CreateAsync(user, registerViewModel.Password);

            if (identityResult.Succeeded)
            {
                var roleIdentityResult = await userManager.AddToRoleAsync(user, "User");

                if (roleIdentityResult.Succeeded)
                {
                    await signInManager.SignInAsync(user, isPersistent: false);

                    return RedirectToAction("Index", "Movies");
                }
            }
            else
            {
                foreach (var error in identityResult.Errors)
                {
                    ModelState.AddModelError(string.Empty, user.Email);
                }
            }

            return View(registerViewModel);
        }

        [HttpGet]
        public IActionResult Login()
        {
            var model = new LoginViewModel
            {
               
            };

            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel)
        {
            if (!ModelState.IsValid)
            {
                return View(loginViewModel);
            }

            var user = await userManager.FindByNameAsync(loginViewModel.UserName);

            if (user == null)
            {
                ModelState.AddModelError(string.Empty, "Invalid username.");
                return View(loginViewModel);
            }

            var signInResult = await signInManager.PasswordSignInAsync(loginViewModel.UserName,
                loginViewModel.Password, false, false);

            if (signInResult != null && signInResult.Succeeded)
            {
                return RedirectToAction("Index", "Movies");
            }

            if (signInResult.IsLockedOut)
            {
                ModelState.AddModelError(string.Empty, "User account locked out.");
            }
            else if (signInResult.IsNotAllowed)
            {
                ModelState.AddModelError(string.Empty, "User account not allowed.");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid password.");
            }

            return View(loginViewModel);
        }

        [HttpGet]
        public async Task<IActionResult> Logout()
        {
            await signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

        [HttpGet]
        public IActionResult AccessDenied()
        {
            return View();
        }
    }
}

RegisterViewModel

using System.ComponentModel.DataAnnotations;

namespace MovieOrganizer.Models.ViewModels
{
    public class RegisterViewModel
    {
        [Required]
        public string UserName { get; set; }

        [Required]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [MinLength(6, ErrorMessage = "Password has to be at least 6 characters")]
        public string Password { get; set; }
    }
}

Register.cshtml

@model MovieOrganizer.Models.ViewModels.RegisterViewModel

<h1>Sign Up for Movie Library</h1>

@if (ViewData.ModelState.IsValid == false && ViewData.ModelState.ErrorCount > 0)
{
    foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
    {
        <p class="error">@error.ErrorMessage</p>
    }
}

<div class="container mx-auto">
    <div class="row justify-content-center">
        <div class="col-12 col-lg-6">
            <h1 class="mt-3 mb-3 h3">
                Register
            </h1>

            <form method="post">
                <div class="mb-3">
                    <label class="form-label">Username</label>
                    <input type="text" id="username" class="form-control" asp-for="UserName" registered />
                    <span class="text-danger" asp-validation-for="UserName"></span>
                </div>

                <div class="mb-3">
                    <label class="form-label">Email</label>
                    <input type="email" id="email" class="form-control" asp-for="Email" required />
                    <span class="text-danger" asp-validation-for="Email"></span>
                </div>

                <div class="mb-3">
                    <label class="form-label">Password</label>
                    <input type="password" id="password" class="form-control" asp-for="Password" required minlength="6" />
                    <span class="text-danger" asp-validation-for="Password"></span>
                </div>

                <div class="mb-3">
                    <label class="form-label">Password Confirmation</label>
                    <input type="password" id="PasswordConfirmation" name="PasswordConfirmation" class="form-control" asp-for="Password" required minlength="6" />
                    <span class="text-danger" asp-validation-for="Password"></span>
                </div>

                <div class="mb-3">
                    <button type="submit" class="btn btn-dark">Register</button>
                </div>
            </form>
        </div>

        <h4><a href="/auth/google_oauth2">Log in with Google</a></h4>
    </div>
</div>

Program.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MovieOrganizer.Data;
using MovieOrganizer.Models.Domain;
using MovieOrganizer.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<MovieDBContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MovieOrganizerCon")));

builder.Services.AddIdentity<User, IdentityRole<Guid>>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.User.RequireUniqueEmail = true;
})
    .AddEntityFrameworkStores<MovieDBContext>()
    .AddDefaultTokenProviders();

builder.Services.AddScoped<IMovieRepository, MovieRepository>();
builder.Services.AddScoped<IMovieLogRepository, MovieLogRepository>();

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

builder.Services.AddScoped<UserManager<User>>();
builder.Services.AddScoped<SignInManager<User>>();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole<Guid>>>();
    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();

    string[] roleNames = { "User" };

    foreach(var roleName in roleNames)
    {
        var roleExist = await roleManager.RoleExistsAsync(roleName);

        if (!roleExist)
        {
            await roleManager.CreateAsync(new IdentityRole<Guid>(roleName));
        }
    }
}

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseSession();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Movies}/{action=Index}/{id?}");

app.Run();

问题排查与解决步骤

  1. 修复错误处理逻辑,查看真实错误
    当前错误处理代码把user.Email当成错误信息返回,无法看到Identity的真实失败原因。修改AccountController中的错误处理代码:

    else
    {
        foreach (var error in identityResult.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }
    

    这样就能看到具体的失败原因,比如邮箱格式不符合Identity的验证规则、数据库中已存在该邮箱,或者User实体类的Email属性配置问题。

  2. 检查自定义User实体类
    确保你的User类正确继承了IdentityUser<Guid>,并且Email属性没有被错误覆盖或隐藏。例如:

    public class User : IdentityUser<Guid>
    {
        // 自定义属性,不要重写Email属性除非有特殊需求
    }
    
  3. 验证数据库迁移与字段配置
    若使用自定义User类,需确保已生成并应用正确的数据库迁移,数据库表中的Email字段存在、类型为可变长度字符串(如nvarchar(256)),且允许非空(因为你在Program.cs中配置了options.User.RequireUniqueEmail = true)。

  4. 完善密码确认验证
    视图中的密码确认输入框绑定错误,会导致ModelState无效。修改RegisterViewModel,添加密码确认属性:

    [Required]
    [Compare("Password", ErrorMessage = "密码与确认密码不匹配")]
    public string PasswordConfirmation { get; set; }
    

    同时修改Register.cshtml中的密码确认框:

    <div class="mb-3">
        <label class="form-label">Password Confirmation</label>
        <input type="password" id="PasswordConfirmation" class="form-control" asp-for="PasswordConfirmation" required minlength="6" />
        <span class="text-danger" asp-validation-for="PasswordConfirmation"></span>
    </div>
    

    并在Register方法开头添加ModelState验证:

    [HttpPost]
    public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
    {
        if (!ModelState.IsValid)
        {
            return View(registerViewModel);
        }
        // 后续代码...
    }
    
  5. 确认前端数据传递
    在调用userManager.CreateAsync前添加日志输出,确认registerViewModel.Email确实接收到了前端输入的值:

    Console.WriteLine($"Received Email: {registerViewModel.Email}");
    var user = new User
    {
        UserName = registerViewModel.UserName,
        Email = registerViewModel.Email,
    };
    

内容的提问来源于stack exchange,提问作者bradd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 07:22:03