Короткая версия
Как создать API, который может использоваться разными сторонами, которые используют разные соглашения для свойств JSON в обоих местах, в запросе и ответе. Другими словами, если клиент # 1 отправляет запрос JSON в случае змеи, он должен получить ответ JSON в случае змеи. Если клиент № 2 отправляет верблюжий чемодан, он должен получить верблюжий чемодан в ответ.
Длинная версия с пояснениями и примерами
1. snake_case { some_id: 1, some_name: "www" }
2. alllowercase { someid: 2, somename: "www" }
3. TitleCase { SomeId: 1, SomeName: "www" }
4. camelCase { someId: 1, someName: "www" }
Связыватель модели по умолчанию - caseнечувствителен и может обрабатывать параметры 2, 3, 4 естественным образом, но если входной JSON содержит «snake_case», механизм связывания модели по умолчанию не извлечет его [FromUri] и не отобразит его в модели.
class SomeModel
{
int SomeId { get; set; }
string SomeName { get; set; }
}
Контроллер
class SomeController()
{
[HttpGet]
public dynamic SomeGetAction([FromUri] SomeModel model) { ... }
[HttpPost]
public dynamic SomePostAction([FromBody] SomeModel model) { ... }
}
Подход № 1: Атрибуты
В этом случае мы сообщаем контроллеру искать свойства случая змеи во входящем JSON. Если атрибут случая змеи не найден, он должен попытаться извлечь свойства без подчеркивания.
class SomeModel
{
[JsonProperty(Name="some_id")]
int SomeId { get; set; }
[JsonProperty(Name="some_name")]
string SomeName { get; set; }
}
Подход № 2. Оболочки Get-Set
Мы можем создать оба типа свойств в нашей модели, чтобы охватить все возможные варианты JSON.
class SomeModel
{
int some_id { get; set; }
string some_name { get; set; }
int SomeId { get { return some_id; } set { some_id = value; } }
string SomeName { get { return some_name; } set { some_name = value; } }
}
Подход № 3: Связыватель пользовательской модели
В этом случае создайте промежуточный слой, который может анализировать заголовок и тело запроса и извлекать свойства любого случая.
class MyModelBinder<SomeModel>()
{
public bool BindModel(HttpActionContext actionCtx, ModelBindingContext modelCtx)
{
var queryGet = actionCtx
.Request
.GetQueryNameValuePairs()
.ToDictionary(o => o.Key, o => o.Value as object);
var queryPost = actionCtx
.Request
.Content
.ReadAsAsync<dynamic>
.Result
.ToObject<Dictionary<string, object>>);
modelContext.model.SomeId = // try to find property in the request
queryGet["some_id"] ??
queryGet["someid"] ??
queryGet["someId"] ??
queryGet["SomeId"];
return true;
}
}
class SomeController()
{
[HttpGet]
public dynamic SomeGetAction([MyModelBinder<typeof(SomeModel)>] SomeModel model) { ... }
[HttpPost]
public dynamic SomePostAction([MyModelBinder<typeof(SomeModel)>] SomeModel model) { ... }
}
Подход № 4: Средство разрешения контрактов
Подходы № 1 и № 2 кажутся слишком многословными и не принимаются некоторыми членами команды. Подход № 3, по-видимому, охватывает все случаи, но по какой-то причине он скрывает все свойства модели от Swagger, поэтому мы решили взглянуть на преобразователь контракта, который можно настроить на уровне контроллера, и изменить входящий и исходящий JSON до илипосле действия. Мы попробовали этот подход https://stackoverflow.com/a/52766426/437393, и он прекрасно работает для исходящего JSON в ответе, но не преобразует входящий JSON в запросе.
Итак, альтернативный вопрос, как убедиться, что контроллер получит JSON определенного формата, используя Contract Resolver, всегда преобразовывать JSON в случай змеи?