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

ASP.NET Core MVC如何实现用户名或邮箱均可登录的模型验证

方案1:使用自定义数据注解特性(符合DataAnnotations使用规范)

System.ComponentModel.DataAnnotations 命名空间下没有原生支持多属性二选一必填的内置特性,你可以继承ValidationAttribute自定义跨属性校验特性来实现需求,代码如下:

自定义校验特性代码

using System.ComponentModel.DataAnnotations;

/// <summary>
/// 指定的多个属性中至少填写一个非空值
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AtLeastOneRequiredAttribute : ValidationAttribute
{
    private readonly string[] _targetProperties;

    public AtLeastOneRequiredAttribute(params string[] targetProperties)
    {
        _targetProperties = targetProperties;
        ErrorMessage = $"至少需要填写 {string.Join(" 或 ", _targetProperties)} 中的一项";
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // 遍历所有指定属性,只要有一个不为空就校验通过
        foreach (var propName in _targetProperties)
        {
            var propInfo = validationContext.ObjectType.GetProperty(propName);
            if (propInfo == null)
            {
                return new ValidationResult($"不存在属性 {propName}");
            }
            var propValue = propInfo.GetValue(validationContext.ObjectInstance);
            if (!string.IsNullOrWhiteSpace(propValue?.ToString()))
            {
                return ValidationResult.Success;
            }
        }
        // 所有属性都为空,校验失败
        return new ValidationResult(ErrorMessage);
    }
}

模型使用示例

因为是跨属性校验,所以特性直接加在LoginModel类上即可:

[AtLeastOneRequired(nameof(UserName), nameof(Email))]
public class LoginModel
{
    public string UserName { get; set; }
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

配置完成后ModelState.IsValid就会自动判断用户名和邮箱是否至少填了一个。

其他可行实现方案

  • Controller手动校验:如果只有这一处需要二选一校验,可以直接在动作方法里加校验逻辑,不需要额外写自定义类:
    [HttpPost]
    public async Task<IActionResult> Login(LoginModel model)
    {
        if(string.IsNullOrWhiteSpace(model.UserName) && string.IsNullOrWhiteSpace(model.Email))
        {
            ModelState.AddModelError(string.Empty, "用户名或邮箱至少填写一项");
        }
        if(ModelState.IsValid)
        {
            // 后续业务逻辑
        }
        return View(model);
    }
    
    优点是逻辑直观实现快,缺点是复用性差,多场景需要同规则时会产生重复代码。
  • 合并输入字段优化体验:可以直接去掉模型里的两个字段,改成一个LoginAccount字段,前端输入框提示为「用户名/邮箱」,后续查询用户时分别匹配用户名和邮箱即可:
    // 改造后的模型
    public class LoginModel
    {
        [Required(ErrorMessage = "用户名或邮箱不能为空")]
        public string LoginAccount { get; set; }
    
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
    
    // 后续查询用户逻辑示例(假设用Identity)
    var user = await _userManager.FindByNameAsync(model.LoginAccount) 
               ?? await _userManager.FindByEmailAsync(model.LoginAccount);
    
    这种方案对用户更友好,大部分主流网站都采用这种设计,同时省去了二选一校验的逻辑。
  • 使用FluentValidation库:如果项目中有大量复杂校验规则,可以引入第三方校验库FluentValidation,用声明式语法写校验规则,更灵活易维护:
    先安装FluentValidation.AspNetCore包,然后编写校验类:
    public class LoginModelValidator : AbstractValidator<LoginModel>
    {
        public LoginModelValidator()
        {
            RuleFor(x => x.Password).NotEmpty();
            RuleFor(x => x.UserName).NotEmpty().When(x => string.IsNullOrWhiteSpace(x.Email));
            RuleFor(x => x.Email).NotEmpty().When(x => string.IsNullOrWhiteSpace(x.UserName))
                .WithMessage("用户名或邮箱至少填写一项");
        }
    }
    
    在Program.cs注册服务后即可自动触发模型校验,不需要修改Controller原有逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 06:45:00