ASP.NET中Post请求同时使用路由与Body参数的实现问题
我最近接到一个大学项目,使用ASP.NET Core 6 Web API和Entity Framework开发。课程要求实现API时无需考虑其结构与路由规范,特此强调:我无法选择或修改路由规则,哪怕是微小调整也不行。
我有3个模型类,分别为Application、Module和Data。一个Application包含多个子Module,每个Module又包含多个子Data。
问题在于,创建Module或Data实例时,要求同时从URL路由和请求Body中获取数据。
以下是创建Module的示例:
POST: https://ip:port/api/OurProjectName/{application}/
POSTBODY:<?xml version="1.0"?> <Module><!--根节点名称可灵活设置,无需担心DTO问题--> <id>99999</id> <name>some name</name> <creation_dt>{the current timestamp}</creation_dt> </Module>
正确的逻辑应该是:控制器获取路由中的application字符串和Body中的Module/ModuleDTO,从数据库中查询名称匹配的Application实例,再将Body中的模块与路由参数指定的父应用关联,通过EF完成数据持久化。
Data的处理逻辑与Module类似,但仅支持创建和删除操作。创建Data的路由为:
POST: https://ip:port/api/OurProjectName/{application}/{module}
其中application仅用于校验数据库结构相关的历史错误。
我首先尝试如下实现:
[HttpPost("{application}/{module}")] [Produces("application/xml")] [Consumes("application/xml")] public IActionResult Post(string application, string module, [FromBody] Data data) { var mod = _context.Modules.SingleOrDefault(m => m.name == module); if (mod == null) { return NotFound(); } if (mod.parent.name != application) { //... 此处逻辑暂不关注 } data.parent = mod; // TODO: 后续处理解引用问题 mod.datas.Add(data); _context.Add(data); _context.SaveChanges(); return Ok(data); }
但返回了常见的415错误。由于Data包含导航属性,Swagger生成的XML请求体层级极深,类似配置文件。
随后我尝试为路由参数添加[FromRoute]特性,仍返回“unsupported media format”错误。
接着我尝试自定义路由(移除控制器上的特性),相关Startup配置如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); } var option = new RewriteOptions(); option.AddRedirect("^$", "swagger"); app.UseRewriter(option); app.UseCors("AllowAll"); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapControllerRoute( name: "postDataCustom", pattern: "api/OurProjectName/{application}/{module}", new {controller = "DataController", action = "Post"}); }); }
但Swagger中看不到该端点,Postman测试也显示端点不存在。
之后我尝试使用DTO,为字段添加特性:
public IActionResult Post(DataDTO data) { var mod = _context.Modules.SingleOrDefault(m => m.name == data.module); if (mod == null) { return NotFound(); } Console.WriteLine(mod.name); // ...(若能打印则可继续推进) return Ok(); } public class DataDTO { [FromRoute] public string application { get; set; } [FromRoute] public string module { get; set; } [FromBody] public Data data { get; set; } public DataDTO(string application, string module, Data data) { this.application = application; this.module = module; this.data = data; } }
仍返回错误。于是我简化DTO测试:
[HttpPost("{module}")] public IActionResult Post(testeDTO data) { Console.WriteLine(data.module); Console.WriteLine(data.bb); return Ok(); } public class testeDTO { [FromRoute] public string? module { get; set; } [FromBody] public string bb { get; set; } public testeDTO(string bb) { this.bb = bb; } }
请求能返回200,但[FromRoute]修饰的module始终未赋值,似乎Body内容覆盖了路由参数。
目前我已无其他思路,老旧文档也增加了排查难度。我也曾考虑使用x-www-urlencode,但不确定是否符合要求。
简而言之:我无法成功实现同时携带路由参数的Post请求。
调试发现如下代码能正常运行(Swagger测试无错误),检查Git发现仅修改了以下内容:
[HttpPost("{module}")] public IActionResult Post([FromRoute]string module, [FromBody]testeDTO data){ Console.WriteLine(module); Console.WriteLine(data.bb); return Ok(); } ... public class testeDTO{ [FromBody] public string bb{get;set;} public testeDTO(string bb){ this.bb = bb; } }
我将继续调试以找到完整解决方案。
内容的提问来源于stack exchange,提问作者Imeguras

