Up until recently I hadn't really used any Dependency Injection (DI)frameworks in my ASP.NET projects, largely due to them being rather intimidating to set up the first time. I mean, I barely grasp the concept of DI, so the thought of trying to implement it on my own is worrisome.
Recently, though, my group had a requirement that forced us to consider using a DI framework (which I wrote about in Custom Validation in ASP.NET Web API using FluentValidation) for an ASP.NET Web API project we're in the process of building. We needed a way to pass dependencies to multiple consumers, particularly Web API controller classes and back-end repositories. Further, we needed the solution to be testable, and to be relatively easy to change when necessary.
I started researching different DI setups, and eventually stumbled across one called StructureMap, which after just an hour of exploring, seemed to provide everything our solution needed. The worry I had foreseen was reduced to mere trepidation, and just a little more research showed the kind of power that Dependency Injection really holds.
Of course, if it helps us, it will help someone else out there; hence this post. Let's explore getting started with Dependency Injection in ASP.NET Web API, using StructureMap!
Creating the Project
Let's start at the very beginning (a very good place to start).
Let's create a new Web API project in Visual Studio. I called mine StructureMapWebAPIDemo.
We'll end up with a project that looks a lot like this:
Setting Up the Library and Repositories
Before we do anything else, let's set up our library project. Add a new Class Library to the solution (I called mine StructureMapWebAPIDemo.Lib) with folders called Interfaces, Repositories, and DataModel. The structure will look like this:
We'll need a data model class to interface with, so let's create a class called Movie.
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public int RunningTimeMinutes { get; set; }
public string Director { get; set; }
}
We'll also need a Repository and an Interface to retrieve the movies. Here's those classes.
public interface IMovieRepository
{
List<Movie> GetAllMovies();
Movie GetByID(int id);
}
public class MovieRepository : IMovieRepository
{
public List<Movie> GetAllMovies()
{
return new List<Movie>()
{
new Movie()
{
ID = 1,
Title = "The Incredibles",
ReleaseDate = new DateTime(2004, 11, 5),
RunningTimeMinutes = 116,
Director = "Brad Bird"
},
new Movie()
{
ID = 2,
Title = "Wreck-It Ralph",
ReleaseDate = new DateTime(2012, 11, 2),
RunningTimeMinutes = 120,
Director = "Rich Moore"
},
new Movie()
{
ID = 3,
Title = "Inside Out",
ReleaseDate = new DateTime(2015, 6, 19),
RunningTimeMinutes = 102,
Director = "Pete Doctor & Ronnie Del Carmen"
}
};
}
public Movie GetByID(int id)
{
var allMovies = GetAllMovies();
if (allMovies.Where(x => x.ID == id).Any())
{
return allMovies.First(x => x.ID == id);
}
else return null;
}
}
All the repository does is return either a collection of movies, or an individual movie (or null if given a movie ID that doesn't exist).
Now that we've got our library set up, we can integrate StructureMap.
Adding StructureMap
In the Web API project, let's add a NuGet package calledStructureMap.WebApi2. On doing so, we'll see that the structure of the project has changed:
The new files are what allow StructureMap to operate in our Web API project, and if you'd like more information about them, check out the GitHub documentation. Luckily for us, we don't strictly need to know what these files do in order to use them (though understanding what you are building is always a good idea).
Modifying the Controllers
We have one controller in this project at the moment, HomeController. Let's remove this controller and replace it with our own MovieController and two actions which use the MovieRepository from earlier:
[RoutePrefix("movies")]
public class MovieController : ApiController
{
[HttpGet]
[Route("all")]
public IHttpActionResult All()
{
MovieRepository movieRepo = new MovieRepository();
var allMovies = movieRepo.GetAllMovies();
return Ok(allMovies);
}
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetByID(int id)
{
MovieRepository movieRepo = new MovieRepository();
var movie = movieRepo.GetByID(id);
if(movie == null)
{
return NotFound();
}
return Ok(movie);
}
}
On first glance, this all seems fine and dandy. After all, the
All()
and GetByID()
are pretty descriptive insofar as what they do. The issue arises when the implementation of the MovieRepository needs to change.
Let's say (for the sake of argument) that we need to be flexible about what implementation we want for the MovieRepository. Maybe we need to support different data storage systems, maybe we need the possibility of changing the actual code for different scenarios, what have you. The problem we have right now is that new is glue; that is, creating a dependency using the
new
keyword binds the dependency to the current implementation, whatever that happens to be. If the implementation of the MovieRepository changes, it's likely the code in the MovieController will also need to change.
But what if it didn't have to?
That's the core philosophy behind using a DI container: separate the implementation from the interface. In this way, if the implementation changes, the classes which need that implementation don't actually care, because the interface to the implementation didn't (necessarily) change.
Here's what we need: we need the MovieController to be aware of an interface to the implementation, not the implementation itself. We can do this by creating a private variable in the MovieController class for the repository's interface and assigning the implementation of that interface in the constructor:
[RoutePrefix("movies")]
public class MovieController : ApiController
{
private readonly IMovieRepository _movieRepo;
public MovieController(IMovieRepository movieRepo)
{
_movieRepo = movieRepo;
}
[HttpGet]
[Route("all")]
public IHttpActionResult All()
{
var allMovies = _movieRepo.GetAllMovies();
return Ok(allMovies);
}
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetByID(int id)
{
var movie = _movieRepo.GetByID(id);
if(movie == null)
{
return NotFound();
}
return Ok(movie);
}
}
Now we've got our controller set up perfectly. All we need now are two more steps.
In the DependencyResolution folder's DefaultRegistry.cs file, we need to register the MovieRepository with the DI container:
public class DefaultRegistry : Registry {
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
For<IMovieRepository>().Use<MovieRepository>();
}
}
Finally, in the App_Start/WebApiConfig.cs file, we need to start up the DI container:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
StructuremapWebApi.Start();
....
}
}
This is how we tell StructureMap to inject an instance of MovieRepository into MovieController's constructor for the interface IMovieRepository.
Testing the Controllers
One of the primary benefits of switching to a DI container such as StructureMap is the improved ability to test our code. Unit testing the MovieRepository doesn't change, but unit testing the MovieController does, due to the constructor injection we're performing on it.
Let's set up a stub test class for testing the MovieController:
[TestClass]
public class MoviesControllerTests
{
[TestMethod]
public void Test_GetByID_ValidID()
{
//Arrange
//Act
//Assert
}
[TestMethod]
public void Test_GetByID_InvalidID()
{
//Arrange
//Act
//Assert
}
}
We're going to test two scenarios: when we call the controller's GetByID action with a valid ID, and with an invalid ID.
Valid Movie ID test
Let's write the valid ID test first. The general order of operations for any test is arrange, act, assert.
Arrange
First, we need to arrange the components being tested, as well as their dependencies. Remember that the point of this test is to test thecontroller, not the repository, and so we'll be mocking the MovieRepository using Moq:
//Arrange
var movieID = 2;
var mockMovieRepo = new Mock<IMovieRepository>();
mockMovieRepo.Setup(x => x.GetByID(movieID)).Returns(new Movie() { ID = 2 });
var controller = new MoviesController(mockMovieRepo.Object);
Act
Now, we need to perform the action we are testing. Remember that since we are testing a controller, we will get back an
IHttpActionResult
response, and need to convert that to an appropriate response class (in our case, OkNegotiatedContentResult
). Here's the next part of the method://Act
IHttpActionResult response = controller.GetByID(movieID);
var contentResult = response as OkNegotiatedContentResult<Movie>;
Assert
Finally, we need to assert that three things are true:
- The response itself is not null.
- The content of the response is not null.
- The movie ID we submitted to the controller is the same as the ID of the movie returned.
That code looks like this:
//Assert
Assert.IsNotNull(contentResult);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(movieID, contentResult.Content.ID);
The complete test method looks like this:
[TestMethod]
public void Test_GetByID_ValidID()
{
//Arrange
var mockMovieRepo = new Mock<IMovieRepository>();
var movieID = 2;
mockMovieRepo.Setup(x => x.GetByID(movieID)).Returns(new Movie() { ID = 2 });
var controller = new MoviesController(mockMovieRepo.Object);
//Act
IHttpActionResult response = controller.GetByID(movieID);
var contentResult = response as OkNegotiatedContentResult<Movie>;
//Assert
Assert.IsNotNull(contentResult);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(movieID, contentResult.Content.ID);
}
Invalid Movie ID Test
Now we can complete the other test, where we're testing for the result for an invalid ID. In this scenario, the response should be 404 Not Found.
[TestMethod]
public void Test_GetByID_InvalidID()
{
//Arrange
var mockMovieRepo = new Mock<IMovieRepository>();
var movieID = 6; //This movie does not exist
mockMovieRepo.Setup(x => x.GetByID(movieID)).Returns((Movie)null);
var controller = new MoviesController(mockMovieRepo.Object);
//Act
IHttpActionResult response = controller.GetByID(movieID);
var contentResult = response as NotFoundResult;
//Assert
Assert.IsNotNull(contentResult);
}
Summary
Dependency Injection's primary purpose is to separate the implementation from the interface, and allow the system to supply the implementation at a given time. StructureMap (and it's Web API implementation) provide most of the groundwork to do just that, and all we programmers have to do is:
- Register the dependencies
- Set up the controllers to use injection
- Test
And then we can sick back and sip our margaritas. Or get back to work. Whichever.
I've got a sample project for this post over on GitHub, so go check it out!
Happy Coding!
Beautiful ... I love your blog full of great ideas , in fact I just signed up to your news if you want to do the same, it is a pleasure , good luck !
ReplyDeletevoyance mail serieuse gratuite
Your site is wonderful! I wish you a very good continuation.
ReplyDeletevoyance en ligne gratuite