My team regularly writes ASP.NET Web API projects which have multiple consumers, and oftentimes we also write at least one of said consumers. To accomplish this, we often invoke a project layout and architecture that doesn't seem terribly obvious to newcomers to my group. I hope to better explain myself as to why this project organization works in this post (so I can just say, "Hey, new guy, lookie here!" and point them at this post.)
Sample Architecture
So, let's get the spoilers out of the way first: this architecture works because both the API and the MVC app use a common library of DTO objects. In this way, whenever an object being returned from the API is deserialized from its JSON representation, the MVC app knows what object to deserialize it to. This is directly a benefit of being in charge of both producer (the API) and consumer (the MVC web project).
In the sample app available on GitHub, the project organization looks like this:
The Common project holds what are known as the DTOs (Data Transfer Objects). The DTOs do nothing but move data from one place to another; they will not ever have any methods or logic contained within them.
Once we have a place to put the DTOs, we can begin building the API which uses them.
Building the API
Let's now see how we might build both the DTO objects and the API which serializes them to JSON. First, here's a sample User
class:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Username { get; set; }
}
For our API, let's pretend we have a ridiculously simple scenario: we need an API which returns all Users in our data store, and an MVC web application which displays these users to our end-user.
For this simple demo, let's pretend that our API Controller is returning a list of users, like so:
[RoutePrefix("users")]
public class UsersController : ApiController
{
[HttpGet]
[Route("all")]
public IHttpActionResult GetAll()
{
var users = new List<User>();
users.Add(new User()
{
FirstName = "Marco",
LastName = "Polo",
DateOfBirth = new DateTime(1254, 1, 14),
Username = "greatadventurer"
});
users.Add(new User()
{
FirstName = "Kublai",
LastName = "Khan",
DateOfBirth = new DateTime(1215, 9, 23),
Username = "khanofkhans"
});
users.Add(new User()
{
FirstName = "Kokachin",
LastName = "",
DateOfBirth = new DateTime(1255, 8, 17),
Username = "blueprincess"
});
return Ok(users);
}
}
Now, to prove this works, we can call this action using something like Postman:
As we would expect, calling the path /users/all
results in us getting back the list of users.
Now let's see how we might build an MVC web app to consume this API call.
Building the MVC API Client
The MVC app will use the brilliant NuGet package RestSharp to simplify our calls to the API. Once we've downloaded the package, we can create a class which derives from that package's RestClient
like so:
public class ApiClient : RestClient
{
private static readonly string BaseURL = "http://localhost:55675/";
public ApiClient() : base(BaseURL) { }
public List<User> GetAllUsers()
{
RestRequest request = new RestRequest("users/all");
RestResponse response = base.Execute(request);
return JsonConvert.DeserializeObject<List<User>>(response.Content);
}
}
Let's walk through each of the three lines involved in the method GetAllUsers()
.
The first line is
RestRequest request = new RestRequest("users/all");
This line creates a RestRequest
object which is pointed at the url http://localhost:55675/users/all
(the root of the URL is initialized in the ApiClient
's constructor).
The second line is
var response = base.Execute(request);
This line calls the API at the specified location and returns an object of type RestResponse
.
The third line is the most complex:
return JsonConvert.DeserializeObject<List<User>>(response.Content);
Let's break down the key pieces of this line:
response.Content
is the actual content from the API call; in this case, it's the JSON representation of the list of Users.JsonConvert.DeserializeObject<T>()
deserializes a string into an object (in our particular case, into aList<User>
.- The
List<User>
is then returned to the calling method.
With this step in place, we can finish the MVC app.
Finishing the MVC App
Our MVC controller will need to create an instance of ApiClient
so that it can retrieve the users from the API:
[RoutePrefix("users")]
public class UsersController : Controller
{
[HttpGet]
[Route("index")]
public ActionResult Index()
{
ApiClient client = new ApiClient();
return View(client.GetAllUsers());
}
}
Finally, all we have to do is run the MVC app and we'll see that the users are returned and displayed exactly as we thought they would be:
Summary
This organization allows both producer and consumer to use the same Data Transfer Objects (DTOs) when serializing to or deserializing from JSON objects. Consequently, both "sides" of the relationship can use the same objects without needing to write and maintain their own definitions.
I understand that this organization is only useful in situations where the developers control both sides of the producer/consumer relationship; but in those situations, it's been invaluable for me and my team.
Don't forget to check out the sample project on GitHub, and feel free to submit suggestions for improvements there or in the comments below.
Happy Coding!