This library provides a fluent API for building standardized response objects that, optionally, contain payloads and errors.
Built in .NET Core and targetting .NET Standard 1.1, the library can be used in .NET Framework >= 4.5, or cross-platform in .NET Core >= 1.0.
Lets assume our existing code defines an Address
object:
class Address
{
public Name { get; set; }
public Street { get; set; }
public City { get; set; }
// etc...
}
var address = new Address(...) // initialise values
We can use BuildResponse
to build a Response
that contains an Address
instance, and set an Error
on the response...
var response = BuildResponse
.WithPayload(address)
.AndWithErrors(new Error("InvalidName", "The name must be set"))
.Create();
...and then the response serialized to JSON is...
{
"Payload":
{
"Name":"",
"Street":"Buckingham Palace",
"City":"London"
},
"Errors":[
{ "Code":"InvalidName", "Message":"The name must be set" }
]
}
Stable builds are published to Nuget. The package name is BinaryMash.Responses
You'll find everything you need in the Binarymash.Responses
namespace.
using BinaryMash.Responses;
There are two types of response object; the type of response to use will depend on what we want it to contain.
BinaryMash.Responses.Response<T>
is a response that contains a strongly typed payload. We will use this when we want to return a value from a method:
// synchronous example
Response<Address> GetAddress(uint id); // replaces Address GetAddress(uint id)
// asynchronous example
async Task<Response<Address>> GetAddress(uint id); // replaces async Task<Address> GetAddress(unit id)
BinaryMash.Responses.Response
is a response that contains no payload. We will use this when we don't care about a return value:
// synchronous example
Response SetAddress(); // replaces void SetAddress(Address address)
// asynchronous example
async Task<Response> SetAddress(Address address); // replaces async Task SetAddress(Address address)
Now, lets look at how to create these responses.
The simplest example creates an empty response:
var response = BuildResponse
.WithNoPayload()
.Create();
...serialzed into JSON, this response will look like...
{
"Errors":[]
}
Notice that the empty response contains an empty collection of errors. Lets learn how to add some errors to a response.
The following code lets us add errors to the empty response.
var response = BuildResponse
.WithNoPayload()
// errors can be added individually...
.AndWithErrors(new Error("InvalidName", "The name must be set"))
// ...and as a collection...
.AndWithErrors(new []
{
new Error("InvalidStreet", "The street must be set"),
new Error("InvalidCity", "The city must be set")
})
.Create();
Serialized, this looks like...
{
"Errors":[
{ "Code":"InvalidName", "Message":"The name must be set" },
{ "Code":"InvalidStreet", "Message":"The street must be set" },
{ "Code":"InvalidCity", "Message":"The city must be set" }
]
}
Great. We can create a response with errors. But how do we include a payload value?
Lets assume we have an address object...
class Address
{
public Name { get; set; }
public Street { get; set; }
public City { get; set; }
// etc...
}
This code builds a response that contains an instance of Address
.
Address address;
// some code here to set value of address (not shown)
var response = BuildResponse
.WithPayload<Address>(address)
.Create();
In most cases the compiler will infer the type of the payload from the value you supply, so we can omit the type definition from the code.
// ...
.WithPayload(address)
// ...
The serialized response is...
{
"Payload":
{
"Name":"The Queen of England",
"Street":"Buckingham Palace",
"City":"London"
},
"Errors":[]
}
Of course, we can add errors to the payload response in just the same way we did on the empty response:
var response = BuildResponse
.WithPayload(address)
.AndWithErrors(new Error("InvalidName", "The name must be set"))
.Create();
If we are setting errors on a payload response, we might decide it is not meaningful to also set a value in the payload. However, will likely be constrained at compile time to return a Response<T>
rather than a Response
.
In this case, rather than having to always explicitly specify a value for the payload, we can choose to implicitly return the default value of our payload type. We can do this by using the parameterless version of .WithPayload<T>()
...
var response = BuildResponse
.WithPayload<Address>()
.AndWithErrors(new Error("InvalidName", "The name must be set"))
.Create();
This is exactly the same as explicitly setting the default value:
// ...
.WithPayload<Address>(default(Address))
// ...
In most cases it will be the same as setting the payload to null:
// ...
.WithPayload((Address)null)
// ...
The serialized output will look something like this:
{
"Payload":null,
"Errors":[
{"Code":"InvalidName","Message":"The name must be set"}
]
}
In some cases it might not be possible to know at compile time the exact type of response you want to create without using reflection. In the example below, we don't know at compile time if TResponse
is a payload-less Response
or if it is a Response<TPayload>
. Even if it does have a payload, we can't be sure of its type.
In this situation, we cannot easily use .WithNoPayload()
or .WithPayload<TPayload>()
. However, the library provides another method to create a response: .WithType<TResponse>()
.
public TResponse Handle(TRequest request)
{
// The code that calls this method specifies exactly what type TResponse is.
var response = BuildResponse
.WithType<TResponse>()
.AndWithPayload(new Address()) // dangerous! Do we know for sure that the TResponse has a payload of this type?
.AndWithErrors(new Error("InvalidName", "The name must be set"))
.Create();
return response;
}
Note that this mechanism is not as strongly typed as the other methods; you will get exceptions at runtime if you try to use mismatching types. So, you should only use this option if you really have no alternative.
The repository is built on AppVeyor.
Development builds are published to https://www.myget.org/F/binarymash-unstable/api/v3/index.json