简体   繁体   中英

What's the best way to properly consume one of two “successful” responses from the same endpoint on a web api?

I have C# client that's making requests to another Web API.

The Web API I'm consuming can respond with 2 "successful" responses, both 200's.

I need to intercept the response before I map it to its corresponding object and am looking for the best practice.

I own both the client and the server, so either can be adjusted to fit best practices.

I haven't tried much, but I've considered the following

  1. Having an understood map of unclaimed status codes to each error.
  2. Try-catching the attempt to map the JSON to either of the two business objects or otherwise parsing and comparing the JSON to the expected format each object expects before mapping it.
response = await PostAsync(
    $"{BaseUrl}/endpoint/{identifier}",
    new StringContent(jsonBody, Encoding.UTF8, "application/json")
);

var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = json.Deserialize<ResponseObject>(responseJson);
businessObject = new BusinessObject(responseObject);```

//These are two example responses
{
 "StartDate": "01/01/01",
 "EndDate": "01/01/01"
 "Object": {
  "Property1": 1,
  "Property2": "someStr"
 }
}
//OR
{
 "message": "Valid reason you are not receiving the JSON above, status 200 btw"
}

I recently had a similar issue but mine was when I was consuming messages from a queue. The way I resolved it was by telling Newtonsoft to add the type when serializing my object. You can do this like so:

JsonConvert.SerializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });

With SerializeObject and DeserializeObject , you can pass some optional serialization settings. Here, we are passing a new instance of the JsonSerializerSettings to this parameter and settings it's TypeNameHandling to the enum value TypeNameHandling.All . This is telling Newtonsoft to embed the types of whatever it's serializing into the JSON itself. For instance, if you had a class that looked like this (based on your example JSON):

public class SuccessOne
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public SuccessOneChild Object { get; set; }
}

public class SuccessOneChild
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

Then the JSON you get will look like this:

{  
   "$type":"Functions.Tests.SuccessOne, Functions.Tests",
   "StartDate":"2019-07-09T09:32:11.0090507+01:00",
   "EndDate":"2019-07-16T09:32:11.0091048+01:00",
   "Object":{  
      "$type":"Functions.Tests.SuccessOneChild, Functions.Tests",
      "Property1":1,
      "Property2":"someStr"
   }
}

Notice the extra $type properties that have been added? These have been added automatically by Newtonsoft because you told it to in the serialization settings.

Then, when you come to deserialising, you can also tell it to use the type handling. It will look at the extra type property in your JSON and deserialize to whatever that type is. When doing this, you don't need to give DeserializeObject a type argument:

var response = JsonConvert.DeserializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });

Note that wherever you're deserializing will need to know about the classes. So it may be useful to have those classes in a shared library that both your API and consumer can reference to avoid duplicating your code.

You can then handle the response based on it's type. I did this by using a switch statement:

switch (response.GetType().Name)
{
    case nameof(SuccessOne):
        var successOneResponse = (SuccessOne)response;
        handleSuccessOne(successOneResponse);
        break;
    case nameof(SuccessTwo):
        var successTwoResponse = (SuccessTwo)response;
        handleSuccessTwo(successTwoResponse);
        break;
    default:
        // Throw exception or whatever you want to do
        break;
}

I've also created a fiddle showing the serialization and deserialization here: https://dotnetfiddle.net/UcugGg

A low-budget solution could be to simply assess the response body before deserializing.

var response = await PostAsync(
    $"{BaseUrl}/endpoint/{identifier}",
    new StringContent(jsonBody, Encoding.UTF8, "application/json")
);

var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = responseJson.Contains("[special string or property name]")
    ? json.Deserialize<ResponseObjectA>(responseJson)
    : json.Deserialize<ResponseObjectB>(responseJson);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM