Reputation: 6649
I was wondering what the easiest way would be to return clean json from the result of a discriminated union in WebApi? This use case is for business logic only and not used for http errors, etc - those get handled in the pipeline and get returned to user as usual (404, 500, etc)
For example:
type ServiceResult<'a> = { Message:string; Payload:'a }
type ServiceResponse<'a> =
| Success of ServiceResult<'a>
| Fail of ServiceResult<string>
Returns Either:
{
"Case": "Fail",
"Fields": [
{
"Message": "Error performing business logic, your x is not in the y.",
"Payload": "I just couldnt do it"
}
]
}
...or...
{
"Case": "Success",
"Fields": [
{
"Message": "",
"Payload": { "FirstName": "Johnny", "LastName":"Smith" }
}
]
}
Where I would like to have only the service result returned like:
{
"Message": "",
"Payload": { "FirstName": "Johnny", "LastName":"Smith" }
}
... or ...
{
"Message": "Error during operation due to spite.",
"Payload": "I just couldnt do it"
}
I have tried IdiomaticDuConverter: https://gist.github.com/isaacabraham/ba679f285bfd15d2f53e
but it didnt work. The closest i have seen is Microsoft.FSharpLu.Json but it doesnt have a MediaTypeFormatter to plug into the pipeline.
I know i can use Lu and create my own MediaTypeFormatter but I feel like there has to be an easier way (like some Json.Net option I am missing).
Can you point me in the right direction?
thanks :-)
Upvotes: 4
Views: 682
Reputation: 233150
While there may be corner cases where the return representation you're asking about is the correct design, in general this isn't good HTTP API design. ASP.NET Web API returns HTTP responses, so the implication of returning
{
"Success": false,
"Message": "Error during operation due to spite.",
"Payload": "I just couldnt do it"
}
is that this value is being returned as part of a 200 OK
response. This puts an extra burden on clients, because they now have to handle not only HTTP errors (which can still happen), but also can't trust that 200 OK
actually meant success.
Instead, if there's an error, return the appropriate HTTP status code.
This also solves the problem, and makes your F# code easy to write. In a Controller, you'd basically do something like this:
member this.Get() =
let result = // produce the result somehow...
match result with
| Success x -> this.Ok x :> IHttpActionResult
| Fail msg -> this.BadRequest msg :> IHttpActionResult
For simplicity's sake, this example returns 400 Bad Request
on a Fail
case, but if this case represents an internal error, perhaps 500 Internal Server Error
would be more appropriate.
Upvotes: 3
Reputation: 13577
You can write a custom JSON.NET serializer for your ServiceResponse
type that would simply skip the case label and delegate serialization to the nested ServiceResult
.
The real question here is why do you even have this problem? You already carry the success/failure status in the record, the DU doesn't offer any additional information. What's more, with the setup you have, you can easily represent seemingly undesirable states like this:
Success <| { Success = false; Message = "necktie"; Payload = "bacon" }
Why not just drop it and pass around the record only, avoiding the serialization problem altogether?
Upvotes: 1