Почему мой HTTP-пост больше не передает содержимое тела после добавления Microsoft.AspNetCore.OData.Versioning
Я работаю над ASP.NET Core 2.2 API, который реализует OData через NuGet Microsoft.AspNetCore.Odata v7.1.0. У меня все работало нормально, поэтому я решил добавить Версию API через Microsoft.AspNetCore.OData.Versioning v3.1.0.
Теперь мои методы GET и GET{id} в моем контроллере корректно работают с версиями. Например, я могу добраться до метода конечной точки списка GET, используя URL
~/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);
}
Когда я отлаживаюсь, запрос перенаправляется к методу контроллера должным образом, но переменная "запись" теперь пуста, тогда как перед добавлением изменений кода для управления версиями API он был правильно заполнен.
Я подозреваю, что именно так я использую конструктор моделей, поскольку этот код был изменен для поддержки управления версиями API.
Прежде чем пытаться реализовать API Versioning, я использовал класс построителя моделей, как показано ниже;
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 -> Configure, подобный тому, что показан во фрагменте ниже;
// 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());
});
И он работал с [FromBody] в методе HttpPost контроллера.
Однако, следуя примерам в OData GitHub управления версиями API, я теперь использую класс конфигурации, подобный тому, что показан ниже, а не конструктор моделей, представленный ранее;
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();
});
Я чувствую, что проблема с моделью как-то не совпадает правильно, но я не могу точно понять, почему это не так
ОБНОВЛЕНИЕ 3/18/19 - Дополнительная информация
Вот мой класс сущности;
[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;
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; }
}
А вот тело запроса от Почтальона;
{
"@odata.context": "https://localhost:44331/api/v1/$metadata#AddressComplianceCodes",
"Code": "Z1",
"Description": "Test Label - This is a test for Z1",
"Active": true
}
Есть идеи?
2 ответа
Как оказалось, проблема заключалась в том, что я не использовал верблюжий случай в качестве имен своих свойств в теле запроса Postman. Это не было проблемой только с Microsoft.AspNetCore.Odata, но после добавления пакета NuGet для Microsoft.AspNetCore.Odata.Versioning произошел сбой с начальным символом в верхнем регистре имен свойств. Кажется, что Microsoft.AspNetCore.Odata.Versioning использует свой собственный MediaTypeFormatter, который позволяет использовать нижний регистр верблюда. Я обнаружил это в следующем посте GitHub; 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.
Стоит отметить, что вам не нужно наносить на карту маршруты дважды. Для демонстрации настраиваются строка запроса и путь URL. Вы должны выбрать один или другой и удалить тот, который не используется.
Надеюсь, это поможет.