Унифицировать JSON разных форматов - C # WebApi - PullRequest
0 голосов
/ 13 октября 2019

Короткая версия

Как создать 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 в случай змеи?

...