原始代码(ASP.NET MVC 5 / .NET Framework 4.x)

// 引用:System.Web.Mvc
public static class ModelStateExtension
{
    public static string GetErrorMessage(this ModelStateDictionary msDictionary)
    {
        if (msDictionary.IsValid || !msDictionary.Any()) return "";
        foreach (string key in msDictionary.Keys)
        {
            ModelState tempModelState = msDictionary[key];
            if (tempModelState.Errors.Any())
            {
                var firstOrDefault = tempModelState.Errors.FirstOrDefault();
                if (firstOrDefault != null)
                    return firstOrDefault.ErrorMessage;
            }
        }
        return "";
    }
}

用起来像:

if (!ModelState.IsValid)
{
    var msg = ModelState.GetErrorMessage();
    return Content(msg);
}
同名陷阱:ASP.NET Core 里 ModelStateDictionary 类名保留,但命名空间是 Microsoft.AspNetCore.Mvc.ModelBinding,且 ModelState(单词)的实现细节有差异—— 例如不再暴露 Errors 属性,而是 Errors 集合通过 ModelStateEntry.Errors 访问。

直接迁移到 ASP.NET Core 8

如果你的项目只是把旧代码搬过来用,最小改动版:

using Microsoft.AspNetCore.Mvc.ModelBinding;

public static class ModelStateExtension
{
    public static string GetErrorMessage(this ModelStateDictionary msDictionary)
    {
        if (msDictionary.IsValid || msDictionary.Count == 0) return "";

        foreach (var entry in msDictionary)
        {
            if (entry.Value?.Errors.Count > 0)
            {
                var firstError = entry.Value.Errors.FirstOrDefault();
                if (firstError != null)
                    return firstError.ErrorMessage;
            }
        }
        return "";
    }
}

差异点:

  • Keys → 直接迭代 ModelStateDictionary 本身(实现 IEnumerable<KeyValuePair<string, ModelStateEntry>>)。
  • msDictionary[key] → entry.Value(ModelStateEntry,不再叫 ModelState)。
  • Errors 仍是集合,ErrorMessage 仍在。

现代替代方案

替代 1:直接遍历,封装为 ASP.NET Core 过滤器

ASP.NET Core 通常配合全局异常过滤器与 ProblemDetails 使用。返回首个错误:

// Program.cs
builder.Services.AddProblemDetails();

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var firstError = context.ModelState
                .Where(kv => kv.Value?.Errors.Count > 0)
                .Select(kv => kv.Value!.Errors.First().ErrorMessage)
                .FirstOrDefault() ?? "参数校验失败";

            return new BadRequestObjectResult(new
            {
                type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                title = "Validation failed",
                status = StatusCodes.Status400BadRequest,
                detail = firstError
            });
        };
    });

注意:context.ModelState 就是 ASP.NET Core 的 ModelStateDictionary,可以直接用上面写的扩展方法。

替代 2:FluentValidation(推荐用于复杂业务)

DataAnnotations 在简单场景够用,业务规则复杂时换成 FluentValidation:

// 安装 NuGet 包 FluentValidation.AspNetCore
public class OrderRequestValidator : AbstractValidator<OrderRequest>
{
    public OrderRequestValidator()
    {
        RuleFor(x => x.CustomerName)
            .NotEmpty().WithMessage("客户名称必填")
            .MaximumLength(50);

        RuleFor(x => x.Amount)
            .GreaterThan(0).WithMessage("金额必须大于 0");
    }
}

// Program.cs
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddScoped<IValidator<OrderRequest>, OrderRequestValidator>();

优势:规则与模型分离、易单元测试、支持条件分支(When / Unless)。

替代 3:MiniValidation(轻量替代)

ASP.NET Core 内置的轻量方案,仅需 DataAnnotations:

// Program.cs
builder.Services.AddControllersWithViews();

// 在 Controller / Razor Page 里:
if (!ModelState.IsValid)
{
    var errors = ModelState
        .SelectMany(kv => kv.Value!.Errors.Select(e => $"{kv.Key}: {e.ErrorMessage}"))
        .ToList();

    return BadRequest(new { Errors = errors });
}

适合:没有大型项目、想避免额外 NuGet 依赖。

完整迁移清单

.NET Framework 原代码ASP.NET Core 8 迁移
using System.Web.Mvc;using Microsoft.AspNetCore.Mvc.ModelBinding;
ModelStateDictionary.Keys直接迭代 ModelStateDictionary
msDictionary[key] 返回 ModelState返回 ModelStateEntry,访问 .Errors
System.Web.Mvc.ControllerMicrosoft.AspNetCore.Mvc.ControllerBase
return View(model)同名,未变
Request.QueryString未变
HttpContext.Current.Server.MapPath()IWebHostEnvironment.WebRootPath + Path.Combine
global.asax 启动Program.cs 最小 API

使用准则

  1. 不要把 ModelState 当成"错误信息聚合器"——它只用于"模型绑定/校验失败"。业务校验放进领域层。
  2. 错误消息保持中文且对用户友好;开发者可以看 ModelStateEntry.Exception。
  3. 生产环境不要把 ErrorMessage 原样返回——可能泄漏内部信息,统一走 ProblemDetails。
  4. ASP.NET Core 默认就会在 [ApiController] 自动返回 400 + ProblemDetails,无需手动判空。
参考资料