Udostępnij za pośrednictwem



May 2016

Volume 31 Number 5

[ASP.NET]

Writing Clean Code in ASP.NET Core with Dependency Injection

By Steve Smith

ASP.NET Core 1.0 is a complete rewrite of ASP.NET, and one of the main goals for this new framework is a more modular design. That is, apps should be able to leverage only those parts of the framework they need, with the framework providing dependencies as they’re requested. Further, developers building apps using ASP.NET Core should be able to leverage this same functionality to keep their apps loosely coupled and modular. With ASP.NET MVC, the ASP.NET team greatly improved the framework’s support for writing loosely coupled code, but it was still very easy to fall into the trap of tight coupling, especially in controller classes.

Tight Coupling

Tight coupling is fine for demo software. If you look at the typical sample application showing how to build ASP.NET MVC (versions 3 through 5) sites, you’ll most likely find code like this (from the NerdDinner MVC 4 sample’s DinnersController class):

private NerdDinnerContext db = new NerdDinnerContext();
private const int PageSize = 25;
public ActionResult Index(int? page)
{
  int pageIndex = page ?? 1;
  var dinners = db.Dinners
    .Where(d => d.EventDate >= DateTime.Now).OrderBy(d => d.EventDate);
  return View(dinners.ToPagedList(pageIndex, PageSize));
}

This kind of code is very difficult to unit test, because the NerdDinnerContext is created as part of the class’s construction, and requires a database to which to connect. Not surprisingly, such demo applications don’t often include any unit tests. However, your application might benefit from a few unit tests, even if you’re not test-driving your development, so it would be better to write the code so it could be tested. What’s more, this code violates the Don’t Repeat Yourself (DRY) principle, because every controller class that performs any data access has the same code in it to create an Entity Framework (EF) database context. This makes future changes more expensive and error-prone, especially as the application grows over time.

When looking at code to evaluate its coupling, remember the phrase “new is glue.” That is, anywhere you see the “new” keyword instantiating a class, realize you’re gluing your implementation to that specific implementation code. The Dependency Inversion Principle (bit.ly/DI-Principle) states: “Abstractions should not depend on details; details should depend on abstractions.” In this example, the details of how the controller pulls together the data to pass to the view depend on the details of how to get that data—namely, EF.

In addition to the new keyword, “static cling” is another source of tight coupling that makes applications more difficult to test and maintain. In the preceding example, there’s a dependency on the executing machine’s system clock, in the form of a call to DateTime.Now. This coupling would make creating a set of test Dinners to use in some unit tests difficult, because their EventDate properties would need to be set relative to the current clock’s setting. This coupling could be removed from this method in several ways, the simplest of which is to let whatever new abstraction returns the Dinners worry about it, so it’s no longer a part of this method. Alternatively, I could make the value a parameter, so the method might return all Dinners after a provided DateTime parameter, rather than always using DateTime.Now. Last, I could create an abstraction for the current time, and reference the current time through that abstraction. This can be a good approach if the application references DateTime.Now frequently. (It’s also worth noting that because these dinners presumably happen in various time zones, the DateTimeOffset type might be a better choice in a real application).

Be Honest

Another problem with the maintainability of code like this is that it isn’t honest with its collaborators. You should avoid writing classes that can be instantiated in invalid states, as these are frequent sources of errors. Thus, anything your class needs in order to perform its tasks should be supplied through its constructor. As the Explicit Dependencies Principle (bit.ly/ED-Principle) states, “Methods and classes should explicitly require any collaborating objects they need in order to function correctly.” The DinnersController class has only a default constructor, which implies that it shouldn’t need any collaborators in order to function properly. But what happens if you put that to the test? What will this code do, if you run it from a new console application that references the MVC project?

var controller = new DinnersController();
var result = controller.Index(1);

The first thing that fails in this case is the attempt to instantiate the EF context. The code throws an InvalidOperationException: “No connection string named ‘NerdDinnerContext’ could be found in the application config file.” I’ve been deceived! This class needs more to function than what its constructor claims! If the class needs a way to access collections of Dinner instances, it should request that through its constructor (or, alternatively, as parameters on its methods).

Dependency Injection

Dependency injection (DI) refers to the technique of passing in a class’s or method’s dependencies as parameters, rather than hardcoding these relationships via new or static calls. It’s an increasingly common technique in .NET development, because of the decoupling it affords to applications that employ it. Early versions of ASP.NET didn’t make use of DI, and although ASP.NET MVC and Web API made progress toward supporting it, neither went so far as to build full support, including a container for managing the dependencies and their object lifecycles, into the product. With ASP.NET Core 1.0, DI isn’t just supported out of the box, it’s used extensively by the product itself.

ASP.NET Core not only supports DI, it also includes a DI container—also referred to as an Inversion of Control (IoC) container or a services container. Every ASP.NET Core app configures its dependencies using this container in the Startup class’s ConfigureServices method. This container provides the basic support required, but it can be replaced with a custom implementation if desired. What’s more, EF Core also has built-in support for DI, so configuring it within an ASP.NET Core application is as simple as calling an extension method. I’ve created a spinoff of NerdDinner, called GeekDinner, for this article. EF Core is configured as shown here:

public void ConfigureServices(IServiceCollection services)
{
  services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<GeekDinnerDbContext>(options =>
      options.UseSqlServer(ConnectionString));
  services.AddMvc();
}

With this in place, it’s quite simple to use DI to request an instance of GeekDinnerDbContext from a controller class like DinnersController:

public class DinnersController : Controller
{
  private readonly GeekDinnerDbContext _dbContext;
  public DinnersController(GeekDinnerDbContext dbContext)
  {
    _dbContext = dbContext;
  }
  public IActionResult Index()
  {
    return View(_dbContext.Dinners.ToList());
  }
}

Notice there’s not a single instance of the new keyword; the dependencies the controller needs are all passed in via its constructor, and the ASP.NET DI container takes care of this for me. While I’m focused on writing the application, I don’t need to worry about the plumbing involved in fulfilling the dependencies my classes request through their constructors. Of course, if I want to, I can customize this behavior, even replacing the default container with another implementation entirely. Because my controller class now follows the explicit dependencies principle, I know that for it to function I need to provide it with an instance of a GeekDinnerDbContext. I can, with a bit of setup for the DbContext, instantiate the controller in isolation, as this Console application demonstrates:

var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(Startup.ConnectionString);
var dbContext = new GeekDinnerDbContext(optionsBuilder.Options);
var controller = new DinnersController(dbContext);
var result = (ViewResult) controller.Index();

There’s a little bit more work involved in constructing an EF Core DbContext than an EF6 one, which just took a connection string. This is because, just like ASP.NET Core, EF Core has been designed to be more modular. Normally, you won’t need to deal with DbContextOptionsBuilder directly, because it’s used behind the scenes when you configure EF through extension methods like AddEntityFramework and AddSqlServer.

But Can You Test It?

Testing your application manually is important—you want to be able to run it and see that it actually runs and produces the expected output. But having to do that every time you make a change is a waste of time. One of the great benefits of loosely coupled applications is that they tend to be more amenable to unit testing than tightly coupled apps. Better still, ASP.NET Core and EF Core are both much easier to test than their predecessors were. To get started, I’ll write a simple test directly against the controller by passing in a DbContext that has been configured to use an in-memory store. I’ll configure the GeekDinnerDbContext using the DbContextOptions parameter it exposes via its constructor as part of my test’s setup code:

var optionsBuilder = new DbContextOptionsBuilder<GeekDinnerDbContext>();
optionsBuilder.UseInMemoryDatabase();
_dbContext = new GeekDinnerDbContext(optionsBuilder.Options);
// Add sample data
_dbContext.Dinners.Add(new Dinner() { Title = "Title 1" });
_dbContext.Dinners.Add(new Dinner() { Title = "Title 2" });
_dbContext.Dinners.Add(new Dinner() { Title = "Title 3" });
_dbContext.SaveChanges();

With this configured in my test class, it’s easy to write a test showing that the correct data is returned in the ViewResult’s Model:

[Fact]
public void ReturnsDinnersInViewModel()
{
  var controller = new OriginalDinnersController(_dbContext);
  var result = controller.Index();
  var viewResult = Assert.IsType<ViewResult>(result);
  var viewModel = Assert.IsType<IEnumerable<Dinner>>(
    viewResult.ViewData.Model).ToList();
  Assert.Equal(1, viewModel.Count(d => d.Title == "Title 1"));
  Assert.Equal(3, viewModel.Count);
}

Of course, there’s not a lot of logic to test here yet, so this test isn’t really testing that much. Critics will argue that this isn’t a terribly valuable test, and I would agree with them. However, it’s a starting point for when there’s more logic in place, as there soon will be. But first, although EF Core can support unit testing with its in-memory option, I’ll still avoid direct coupling to EF in my controller. There’s no reason to couple UI concerns with data access infrastructure concerns—in fact, it violates another principle, Separation of Concerns.

Don’t Depend on What You Don’t Use

The Interface Segregation Principle (bit.ly/LS-Principle) states that classes should depend only on functionality they actually use. In the case of the new DI-enabled DinnersController, it’s still depending on the entire DbContext. Instead of gluing the controller implementation to EF, an abstraction that provided the necessary functionality (and little or nothing more) could be used.

What does this action method really need in order to function? Certainly not the entire DbContext. It doesn’t even need access to the full Dinners property of the context. All it needs is the ability to display the appropriate page’s Dinner instances. The simplest .NET abstraction representing this is IEnumerable<Dinner>. So, I’ll define an interface that simply returns an IEnumerable<Dinner>, and that will satisfy (most of) the requirements of the Index method:

public interface IDinnerRepository
{
  IEnumerable<Dinner> List();
}

I’m calling this a repository because it follows that pattern: It abstracts data access behind a collection-like interface. If for some reason you don’t like the repository pattern or name, you can call it IGetDinners or IDinnerService or whatever name you prefer (my tech reviewer suggests ICanHasDinner). Regardless of how you name the type, it will serve the same purpose.

With this in place, I now adjust DinnersController to accept an IDinnerRepository as a constructor parameter, instead of a GeekDinnerDbContext, and call the List method instead of accessing the Dinners DbSet directly:

private readonly IDinnerRepository _dinnerRepository;
public DinnersController(IDinnerRepository dinnerRepository)
{
  _dinnerRepository = dinnerRepository;
}
public IActionResult Index()
{
  return View(_dinnerRepository.List());
}

At this point, you can build and run your Web application, but you’ll encounter an exception if you navigate to /Dinners: Invalid­OperationException: Unable to resolve service for type ‘Geek­Dinner.Core.Interfaces.IdinnerRepository’ while attempting to activate GeekDinner.Controllers.DinnersController. I haven’t yet implemented the interface, and once I do, I’ll also need to configure my implementation to be used when DI fulfills requests for IDinnerRepository. Implementing the interface is trivial:

public class DinnerRepository : IDinnerRepository
{
  private readonly GeekDinnerDbContext _dbContext;
  public DinnerRepository(GeekDinnerDbContext dbContext)
  {
    _dbContext = dbContext;
  }
  public IEnumerable<Dinner> List()
  {
    return _dbContext.Dinners;
  }
}

Note that it’s perfectly fine to couple a repository implementation to EF directly. If I need to swap out EF, I’ll just create a new implementation of this interface. This implementation class is a part of my application’s infrastructure, which is the one place in the application where my classes depend on specific implementations.

To configure ASP.NET Core to inject the correct implemen­tation when classes request an IDinnerRepository, I need to add the following line of code to the end of the ConfigureServices method shown earlier:

services.AddScoped<IDinnerRepository, DinnerRepository>();

This statement instructs the ASP.NET Core DI container to use a DinnerRepository instance whenever the container is resolving a type that depends on an IDinnerRepository instance. Scoped means that one instance will be used for each Web request ASP.NET handles. Services can also be added using Transient or Singleton lifetimes. In this case, Scoped is appropriate because my DinnerRepository depends on a DbContext, which also uses the Scoped lifetime. Here’s a summary of the available object lifetimes:

  • Transient: A new instance of the type is used every time the type is requested.
  • Scoped: A new instance of the type is created the first time it’s requested within a given HTTP request, and then re-used for all subsequent types resolved during that HTTP request.
  • Singleton: A single instance of the type is created once, and used by all subsequent requests for that type.

The built-in container supports several ways of constructing the types it will provide. The most typical case is to simply provide the container with a type, and it will attempt to instantiate that type, providing any dependencies that type requires as it goes. You can also provide a lambda expression for constructing the type, or, for a Singleton lifetime, you can provide the fully constructed instance in ConfigureServices when you register it.

With dependency injection wired up, the application runs just as before. Now, as Figure 1 shows, I can test it with this new abstraction in place, using a fake or mock implementation of the IDinner­Repository interface, instead of relying on EF directly in my test code.

Figure 1 Testing DinnersController Using a Mock Object

public class DinnersControllerIndex
{
  private List<Dinner> GetTestDinnerCollection()
  {
    return new List<Dinner>()
    {
      new Dinner() {Title = "Test Dinner 1" },
      new Dinner() {Title = "Test Dinner 2" },
    };
  }
  [Fact]
  public void ReturnsDinnersInViewModel()
  {
    var mockRepository = new Mock<IDinnerRepository>();
    mockRepository.Setup(r =>
      r.List()).Returns(GetTestDinnerCollection());
    var controller = new DinnersController(mockRepository.Object, null);
    var result = controller.Index();
    var viewResult = Assert.IsType<ViewResult>(result);
    var viewModel = Assert.IsType<IEnumerable<Dinner>>(
      viewResult.ViewData.Model).ToList();
    Assert.Equal("Test Dinner 1", viewModel.First().Title);
    Assert.Equal(2, viewModel.Count);
  }
}

This test works regardless of where the list of Dinner instances comes from. You could rewrite the data access code to use another database, Azure Table Storage, or XML files, and the controller would work the same. Of course, in this case it’s not doing a whole lot, so you might be wondering …

What About Real Logic?

So far I haven’t really implemented any real business logic—it’s just been simple methods returning simple collections of data. The real value of testing comes when you have logic and special cases you need to have confidence will behave as intended. To demonstrate this, I’m going to add some requirements to my GeekDinner site. The site will expose an API that will allow anybody to RSVP for a dinner. However, dinners will have an optional maximum capacity and RSVPs should not exceed this capacity. Users requesting RSVPs beyond the maximum capacity should be added to a waitlist. Finally, dinners can specify a deadline by which RSVPs must be received, relative to their start time, after which they stop accepting RSVPs.

I could code all of this logic into an action, but I believe that’s way too much responsibility to put into one method, especially a UI method that should be focused on UI concerns, not business logic. The controller should verify that the inputs it receives are valid, and it should ensure the responses it returns are appropriate to the client. Decisions beyond that, and especially business logic, don’t belong in controllers.

The best place to keep business logic is in the application’s domain model, which shouldn’t depend on infrastructure concerns (like databases or UIs). The Dinner class makes the most sense to manage the RSVP concerns described in the requirements, because it will store the maximum capacity for the dinner and it will know how many RSVPs have been made so far. However, part of the logic also depends on when the RSVP occurs—whether or not it’s past the deadline—so the method also needs access to the current time.

I could just use DateTime.Now, but this would make the logic difficult to test and would couple my domain model to the system clock. Another option is to use an IDateTime abstraction and inject this into the Dinner entity. However, in my experience it’s best to keep entities like Dinner free of dependencies, especially if you plan on using an O/RM tool like EF to pull them from a persistence layer. I don’t want to have to populate dependencies of the entity as part of that process, and EF certainly won’t be able to do it without additional code on my part. A common approach at this point is to pull the logic out of the Dinner entity and put it in some kind of service (such as DinnerService or RsvpService) that can have dependencies injected easily. This tends to lead to the anemic domain model antipattern (bit.ly/anemic-model), though, in which entities have little or no behavior and are just bags of state. No, in this case the solution is straightforward—the method can simply take in the current time as a parameter and let the calling code pass this in.

With this approach, the logic for adding an RSVP is straightforward (see Figure 2). This method has a number of tests that demonstrate it behaves as expected; the tests are available in the sample project associated with this article.

Figure 2 Business Logic in the Domain Model

public RsvpResult AddRsvp(string name, string email, DateTime currentDateTime)
{
  if (currentDateTime > RsvpDeadlineDateTime())
  {
    return new RsvpResult("Failed - Past deadline.");
  }
  var rsvp = new Rsvp()
  {
    DateCreated = currentDateTime,
    EmailAddress = email,
    Name = name
  };
  if (MaxAttendees.HasValue)
  {
    if (Rsvps.Count(r => !r.IsWaitlist) >= MaxAttendees.Value)
    {
      rsvp.IsWaitlist = true;
      Rsvps.Add(rsvp);
      return new RsvpResult("Waitlist");
    }
  }
  Rsvps.Add(rsvp);
  return new RsvpResult("Success");
}

By shifting this logic to the domain model, I’ve ensured that my controller’s API method remains small and focused on its own concerns. As a result, it’s easy to test that the controller does what it should, as there are relatively few paths through the method.

Controller Responsibilities

Part of the controller’s responsibility is to check ModelState and ensure it’s valid. I’m doing this in the action method for clarity, but in a larger application I would eliminate this repetitive code within each action by using an Action Filter:

[HttpPost]
public IActionResult AddRsvp([FromBody]RsvpRequest rsvpRequest)
{
  if (!ModelState.IsValid)
  {
    return HttpBadRequest(ModelState);
  }

Assuming the ModelState is valid, the action must next fetch the appropriate Dinner instance using the identifier provided in the request. If the action can’t find a Dinner instance matching that Id, it should return a Not Found result:

var dinner = _dinnerRepository.GetById(rsvpRequest.DinnerId);
if (dinner == null)
{
  return HttpNotFound("Dinner not found.");
}

Once these checks have been completed, the action is free to delegate the business operation represented by the request to the domain model, calling the AddRsvp method on the Dinner class that you saw previously, and saving the updated state of the domain model (in this case, the dinner instance and its collection of RSVPs) before returning an OK response:

var result = dinner.AddRsvp(rsvpRequest.Name,
    rsvpRequest.Email,
    _systemClock.Now);
  _dinnerRepository.Update(dinner);
  return Ok(result);
}

Remember that I decided the Dinner class shouldn’t have a dependency on the system clock, opting instead to have the current time passed into the method. In the controller, I’m passing in _systemClock.Now for the currentDateTime parameter. This is a local field that’s populated via DI, which keeps the controller from being tightly coupled to the system clock, too. It’s appropriate to use DI on the controller, as opposed to a domain entity, because controllers are always created by ASP.NET services containers; this will fulfill any dependencies the controller declares in its constructor. _systemClock is a field of type IDateTime, which is defined and implemented in just a few lines of code:

public interface IDateTime
{
  DateTime Now { get; }
}
public class MachineClockDateTime : IDateTime
{
  public DateTime Now { get { return System.DateTime.Now; } }
}

Of course, I also need to ensure the ASP.NET container is configured to use MachineClockDateTime whenever a class needs an instance of IDateTime. This is done in ConfigureServices in the Startup class, and in this case, although any object lifetime will work, I choose to use a Singleton because one instance of MachineClockDateTime will work for the whole application:

services.AddSingleton<IDateTime, MachineClockDateTime>();

With this simple abstraction in place, I’m able to test the controller’s behavior based on whether the RSVP deadline has passed, and ensure the correct result is returned. Because I already have tests around the Dinner.AddRsvp method that verify it behaves as expected, I won’t need very many tests of that same behavior through the controller to give me confidence that, when working together, the controller and domain model are working correctly.

Next Steps

Download the associated sample project to see the unit tests for Dinner and DinnersController. Remember that loosely coupled code is typically much easier to unit test than tightly coupled code riddled with “new” or static method calls that depend on infrastructure concerns. “New is glue” and the new keyword should be used intentionally, not accidentally, in your application. Learn more about ASP.NET Core and its support for dependency injection at docs.asp.net.


Steve Smith is an independent trainer, mentor and consultant, as well as an ASP.NET MVP. He has contributed dozens of articles to the official ASP.NET Core documentation (docs.asp.net), and works with teams learning this technology. Contact him at ardalis.com or follow him on Twitter: @ardalis.

Thanks to the following Microsoft technical expert for reviewing this article: Doug Bunting
Doug Bunting is a developer working on the MVC team at Microsoft. He’s been doing this for a while and loves the new DI paradigm in the MVC Core rewrite.


Discuss this article in the MSDN Magazine forum