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

如何让控制器Action同时支持表单与JSON请求?

问题原因

ASP.NET Core 不允许在单个 Action 中同时使用 [FromForm] 和 [FromBody] 特性。[FromBody] 会强制框架使用对应 JSON 格式的输入格式化器处理请求,而表单提交的 Content-Type(如 application/x-www-form-urlencoded)与 JSON 请求的 Content-Type(application/json)不匹配,导致表单请求触发 415 媒体类型错误。

解决方案

以下两种方案均可在保留同一路由的前提下解决问题:

方案一:拆分 Action + [Consumes] 特性区分请求类型

这是最简洁易维护的方案,通过两个同名 Action,结合 [Consumes] 特性指定各自支持的媒体类型,框架会自动根据请求的 Content-Type 匹配对应的 Action:

// 处理表单提交请求(支持application/x-www-form-urlencoded和multipart/form-data)
[HttpPost]
[Consumes("application/x-www-form-urlencoded", "multipart/form-data")]
public async Task<ActionResult<Project>> PostProject([FromForm] string name = "")
{
    GenericResponse result = new GenericResponse();
    try
    {
        if (!string.IsNullOrEmpty(name))
        {
            result = Utils.Helpers.Post(name, "projects");          
        }
        else
        {
            result.message = "未提供有效的name参数";
        }
    }
    catch (Exception ex)
    {
        result.message = ex.Message;
    }

    return new JsonResult(result);
}

// 处理JSON对象请求(支持application/json)
[HttpPost]
[Consumes("application/json")]
public async Task<ActionResult<Project>> PostProject([FromBody] Project aProject)
{
    GenericResponse result = new GenericResponse();
    try
    {
        if (aProject != null)
        {
            // 处理完整Project对象的业务逻辑
        }
        else
        {
            result.message = "未提供有效的Project对象";
        }
    }
    catch (Exception ex)
    {
        result.message = ex.Message;
    }

    return new JsonResult(result);
}

方案二:自定义模型绑定器(适合逻辑高度复用场景)

如果两个请求的业务逻辑高度重叠,可以自定义模型绑定器,让单个 Action 同时支持两种输入类型:

1. 定义统一请求模型

public class ProjectRequest
{
    public string Name { get; set; }
    public Project Project { get; set; }
}

2. 实现自定义模型绑定器

public class ProjectRequestModelBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var request = bindingContext.HttpContext.Request;
        var contentType = request.ContentType ?? string.Empty;
        var result = new ProjectRequest();

        if (contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // 解析JSON请求
            result.Project = await request.ReadFromJsonAsync<Project>();
        }
        else if (contentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) || 
                 contentType.StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase))
        {
            // 解析表单请求
            result.Name = bindingContext.ValueProvider.GetValue("name").FirstValue;
        }
        else
        {
            bindingContext.ModelState.AddModelError(string.Empty, "不支持的媒体类型");
            return;
        }

        bindingContext.Result = ModelBindingResult.Success(result);
    }
}

// 注册绑定器提供器
public class ProjectRequestModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        return context.Metadata.ModelType == typeof(ProjectRequest) 
            ? new ProjectRequestModelBinder() 
            : null;
    }
}

3. 注册绑定器(Program.cs/Startup.cs)

// 在Program.cs中配置控制器服务时添加绑定器
builder.Services.AddControllers(options =>
{
    options.ModelBinderProviders.Insert(0, new ProjectRequestModelBinderProvider());
});

4. 修改Action使用统一模型

[HttpPost]
public async Task<ActionResult<Project>> PostProject(ProjectRequest request)
{
    GenericResponse result = new GenericResponse();
    try
    {
        if (!string.IsNullOrEmpty(request.Name))
        {
            result = Utils.Helpers.Post(request.Name, "projects");          
        }
        else if (request.Project != null)
        {
            // 处理完整Project对象的业务逻辑
        }
        else
        {
            result.message = "未提供有效参数";
        }
    }
    catch (Exception ex)
    {
        result.message = ex.Message;
    }

    return new JsonResult(result);
}
方案选择
  • 优先选择方案一:代码拆分清晰,无需额外自定义组件,维护成本更低。
  • 当两种请求的业务逻辑高度一致时,再考虑方案二。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 22:07:39