为什么我的 HTTP Post 在添加 Microsoft.AspNetCore.OData.Versioning 后不再传递正文内容
Why is my HTTP Post no longer passing the body content after adding Microsoft.AspNetCore.OData.Versioning
我正在研究通过 Microsoft.AspNetCore.Odata v7.1.0 NuGet 实现 OData 的 ASP.NET Core 2.2 API。我一切正常,所以我决定通过 Microsoft.AspNetCore.OData.Versioning v3.1.0.
添加 API 版本控制
现在,我的控制器中的 GET 和 GET{id} 方法可以正确处理版本控制。例如,我可以使用 URL
获取 GET 列表端点方法
~/api/v1/addresscompliancecodes
或
~/api/addresscompliancecodes?api-version=1.0
然而,当我尝试创建新记录时,请求路由到控制器中的正确方法,但现在请求正文内容未传递到 POST 控制器方法
我一直在关注 Microsoft.ApsNetCore.OData.Versioning GitHub
中的示例
我的控制器中有 HttpPost 方法;
[HttpPost]
[ODataRoute()]
public async Task<IActionResult> CreateRecord([FromBody] AddressComplianceCode record, ODataQueryOptions<AddressComplianceCode> options)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Add(record);
await _context.SaveChangesAsync();
return Created(record);
}
当我调试时,请求正确路由到控制器方法,但 "record" 变量现在为空,而在添加 API 版本控制的代码更改之前,它已正确填充。
我怀疑这是我使用模型构建器的方式,因为该代码更改为支持 API 版本控制。
在尝试实施 API 版本控制之前,我使用了模型构建器 class,如下所示;
public class AddressComplianceCodeModelBuilder
{
public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.EntitySet<AddressComplianceCode>(nameof(AddressComplianceCode))
.EntityType
.Filter()
.Count()
.Expand()
.OrderBy()
.Page() // Allow for the $top and $skip Commands
.Select();
return builder.GetEdmModel();
}
}
还有一个 Startup.cs --> 配置方法如下面的代码片段所示;
// Support for OData $batch
app.UseODataBatching();
app.UseMvc(routeBuilder =>
{
// Add support for OData to MVC pipeline
routeBuilder
.MapODataServiceRoute("ODataRoutes", "api/v1",
modelBuilder.GetEdmModel(app.ApplicationServices),
new DefaultODataBatchHandler());
});
并且它在控制器的 HttpPost 方法中与 [FromBody] 一起工作。
但是,按照 API 版本控制 OData GitHub 中的示例,我现在使用如下所示的配置 class,而不是之前的模型构建器;
public class AddressComplianceCodeModelConfiguration : IModelConfiguration
{
private static readonly ApiVersion V1 = new ApiVersion(1, 0);
private EntityTypeConfiguration<AddressComplianceCode> ConfigureCurrent(ODataModelBuilder builder)
{
var addressComplianceCode = builder.EntitySet<AddressComplianceCode>("AddressComplianceCodes").EntityType;
addressComplianceCode
.HasKey(p => p.Code)
.Filter()
.Count()
.Expand()
.OrderBy()
.Page() // Allow for the $top and $skip Commands
.Select();
return addressComplianceCode;
}
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
if (apiVersion == V1)
{
ConfigureCurrent(builder);
}
}
}
而我的Startup.cs --> Configure方法更改如下图;
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
VersionedODataModelBuilder modelBuilder)
{
// Support for OData $batch
app.UseODataBatching();
app.UseMvc(routeBuilder =>
{
// Add support for OData to MVC pipeline
var models = modelBuilder.GetEdmModels();
routeBuilder.MapVersionedODataRoutes("odata", "api", models);
routeBuilder.MapVersionedODataRoutes("odata-bypath", "api/v{version:apiVersion}", models);
});
}
如果相关,我的 Startup.cs -> ConfigureServices;
中有以下代码
// Add Microsoft's API versioning
services.AddApiVersioning(options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
});
// Add OData 4.0 Integration
services.AddOData().EnableApiVersioning();
services.AddMvc(options =>
{
options.EnableEndpointRouting = false; // TODO: Remove when OData does not causes exceptions anymore
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
我觉得问题出在模型以某种方式没有正确匹配,但我不明白为什么不正确
2019 年 3 月 18 日更新 - 附加信息
这是我的实体 class;
[Table("AddressComplianceCodes")]
public class AddressComplianceCode : EntityBase
{
[Key]
[Column(TypeName = "char(2)")]
[MaxLength(2)]
public string Code { get; set; }
[Required]
[Column(TypeName = "varchar(150)")]
[MaxLength(150)]
public string Description { get; set; }
}
和 EntityBase class;
public class EntityBase : IEntityDate
{
public bool MarkedForRetirement { get; set; }
public DateTimeOffset? RetirementDate { get; set; }
public DateTimeOffset? LastModifiedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTimeOffset? CreatedDate { get; set; }
public string CreatedBy { get; set; }
public bool Delete { get; set; }
public bool Active { get; set; }
}
这是来自 Postman 的请求正文;
{
"@odata.context": "https://localhost:44331/api/v1/$metadata#AddressComplianceCodes",
"Code": "Z1",
"Description": "Test Label - This is a test for Z1",
"Active": true
}
有什么想法吗?
事实证明,问题是因为我在 Postman 请求正文中没有使用驼峰式大小写作为我的 属性 名称。这不是 Microsoft.AspNetCore.Odata 单独的问题,但是一旦我添加了 Microsoft.AspNetCore.Odata.Versioning NuGet 包,它就失败了 属性 名称的大写起始字符。似乎 Microsoft.AspNetCore.Odata.Versioning 使用它自己的 MediaTypeFormatter 来启用小驼峰大小写。我在下面 GitHub post 中发现了这一点; https://github.com/Microsoft/aspnet-api-versioning/issues/310
没有自定义 MediaTypeFormatter,但行为在 3.0 中确实发生了变化,因为使用驼峰式大小写似乎是大多数基于 JSON 的 API 的默认设置。然而,这很容易恢复。
modelBuilder.ModelBuilderFactory = () => new ODataConventionModelBuilder();
// as opposed to the new default:
// modelBuilder.ModelBuilderFactory = () => new ODataConventionModelBuilder().EnableLowerCamelCase();
这也是您执行或更改与模型构建器相关的任何其他设置的地方。调用工厂方法为每个 API 版本创建一个新的模型构建器。
值得指出的是,您不需要两次映射路线。出于演示目的,配置了 by query string 和 by URL path。您应该选择一个或另一个并删除不使用的那个。
希望对您有所帮助。
我正在研究通过 Microsoft.AspNetCore.Odata v7.1.0 NuGet 实现 OData 的 ASP.NET Core 2.2 API。我一切正常,所以我决定通过 Microsoft.AspNetCore.OData.Versioning v3.1.0.
添加 API 版本控制现在,我的控制器中的 GET 和 GET{id} 方法可以正确处理版本控制。例如,我可以使用 URL
获取 GET 列表端点方法~/api/v1/addresscompliancecodes
或
~/api/addresscompliancecodes?api-version=1.0
然而,当我尝试创建新记录时,请求路由到控制器中的正确方法,但现在请求正文内容未传递到 POST 控制器方法
我一直在关注 Microsoft.ApsNetCore.OData.Versioning GitHub
中的示例我的控制器中有 HttpPost 方法;
[HttpPost]
[ODataRoute()]
public async Task<IActionResult> CreateRecord([FromBody] AddressComplianceCode record, ODataQueryOptions<AddressComplianceCode> options)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Add(record);
await _context.SaveChangesAsync();
return Created(record);
}
当我调试时,请求正确路由到控制器方法,但 "record" 变量现在为空,而在添加 API 版本控制的代码更改之前,它已正确填充。
我怀疑这是我使用模型构建器的方式,因为该代码更改为支持 API 版本控制。
在尝试实施 API 版本控制之前,我使用了模型构建器 class,如下所示;
public class AddressComplianceCodeModelBuilder
{
public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.EntitySet<AddressComplianceCode>(nameof(AddressComplianceCode))
.EntityType
.Filter()
.Count()
.Expand()
.OrderBy()
.Page() // Allow for the $top and $skip Commands
.Select();
return builder.GetEdmModel();
}
}
还有一个 Startup.cs --> 配置方法如下面的代码片段所示;
// Support for OData $batch
app.UseODataBatching();
app.UseMvc(routeBuilder =>
{
// Add support for OData to MVC pipeline
routeBuilder
.MapODataServiceRoute("ODataRoutes", "api/v1",
modelBuilder.GetEdmModel(app.ApplicationServices),
new DefaultODataBatchHandler());
});
并且它在控制器的 HttpPost 方法中与 [FromBody] 一起工作。
但是,按照 API 版本控制 OData GitHub 中的示例,我现在使用如下所示的配置 class,而不是之前的模型构建器;
public class AddressComplianceCodeModelConfiguration : IModelConfiguration
{
private static readonly ApiVersion V1 = new ApiVersion(1, 0);
private EntityTypeConfiguration<AddressComplianceCode> ConfigureCurrent(ODataModelBuilder builder)
{
var addressComplianceCode = builder.EntitySet<AddressComplianceCode>("AddressComplianceCodes").EntityType;
addressComplianceCode
.HasKey(p => p.Code)
.Filter()
.Count()
.Expand()
.OrderBy()
.Page() // Allow for the $top and $skip Commands
.Select();
return addressComplianceCode;
}
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
if (apiVersion == V1)
{
ConfigureCurrent(builder);
}
}
}
而我的Startup.cs --> Configure方法更改如下图;
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
VersionedODataModelBuilder modelBuilder)
{
// Support for OData $batch
app.UseODataBatching();
app.UseMvc(routeBuilder =>
{
// Add support for OData to MVC pipeline
var models = modelBuilder.GetEdmModels();
routeBuilder.MapVersionedODataRoutes("odata", "api", models);
routeBuilder.MapVersionedODataRoutes("odata-bypath", "api/v{version:apiVersion}", models);
});
}
如果相关,我的 Startup.cs -> ConfigureServices;
中有以下代码 // Add Microsoft's API versioning
services.AddApiVersioning(options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
});
// Add OData 4.0 Integration
services.AddOData().EnableApiVersioning();
services.AddMvc(options =>
{
options.EnableEndpointRouting = false; // TODO: Remove when OData does not causes exceptions anymore
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
我觉得问题出在模型以某种方式没有正确匹配,但我不明白为什么不正确
2019 年 3 月 18 日更新 - 附加信息
这是我的实体 class;
[Table("AddressComplianceCodes")]
public class AddressComplianceCode : EntityBase
{
[Key]
[Column(TypeName = "char(2)")]
[MaxLength(2)]
public string Code { get; set; }
[Required]
[Column(TypeName = "varchar(150)")]
[MaxLength(150)]
public string Description { get; set; }
}
和 EntityBase class;
public class EntityBase : IEntityDate
{
public bool MarkedForRetirement { get; set; }
public DateTimeOffset? RetirementDate { get; set; }
public DateTimeOffset? LastModifiedDate { get; set; }
public string LastModifiedBy { get; set; }
public DateTimeOffset? CreatedDate { get; set; }
public string CreatedBy { get; set; }
public bool Delete { get; set; }
public bool Active { get; set; }
}
这是来自 Postman 的请求正文;
{
"@odata.context": "https://localhost:44331/api/v1/$metadata#AddressComplianceCodes",
"Code": "Z1",
"Description": "Test Label - This is a test for Z1",
"Active": true
}
有什么想法吗?
事实证明,问题是因为我在 Postman 请求正文中没有使用驼峰式大小写作为我的 属性 名称。这不是 Microsoft.AspNetCore.Odata 单独的问题,但是一旦我添加了 Microsoft.AspNetCore.Odata.Versioning NuGet 包,它就失败了 属性 名称的大写起始字符。似乎 Microsoft.AspNetCore.Odata.Versioning 使用它自己的 MediaTypeFormatter 来启用小驼峰大小写。我在下面 GitHub post 中发现了这一点; https://github.com/Microsoft/aspnet-api-versioning/issues/310
没有自定义 MediaTypeFormatter,但行为在 3.0 中确实发生了变化,因为使用驼峰式大小写似乎是大多数基于 JSON 的 API 的默认设置。然而,这很容易恢复。
modelBuilder.ModelBuilderFactory = () => new ODataConventionModelBuilder();
// as opposed to the new default:
// modelBuilder.ModelBuilderFactory = () => new ODataConventionModelBuilder().EnableLowerCamelCase();
这也是您执行或更改与模型构建器相关的任何其他设置的地方。调用工厂方法为每个 API 版本创建一个新的模型构建器。
值得指出的是,您不需要两次映射路线。出于演示目的,配置了 by query string 和 by URL path。您应该选择一个或另一个并删除不使用的那个。
希望对您有所帮助。