Your First MVC 6 and EF 7 Application (Dependency Injection) : Part 3
In Part 1 and
Part 2 of this series
you developed a simple database driven application that displays a list of
customers and also allows you to modify the customer details. Although the
application is working as expected, it relies on the local instances of the
NorthwindDbContext to get its job done. In this article we will use the
Dependency Injection (DI) features of MVC 6 to inject the NorthwindDbContext
into the controller class. Later we will also add repository support in the
application.
Let's begin!
Open the same project in the Visual Studio and also open the Startup class in
the IDE. Modify the ConfigureServices() method of the Startup class as shown
below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<NorthwindDbContext>
(options => options.UseSqlServer
(AppSettings.ConnectionString));
}
Notice the code marked in bold letters. We have chained the AddDbContext()
method at the end of t he AddSqlServer() method call. The AddDbContext() is a
generic method that allows you to specify the type of the DbContext that you
wish to inject into the controllers. Moreover, you can also specify the database
connection string while adding the DbContext. In this case the
AppSettings.ConnectionString property holds the database connection string and
is passed to the UseSqlServer() method.
Now open the NorthwindDbContext class and remove the OnConfiguring()
overridden method completely.
public class NorthwindDbContext:DbContext
{
public DbSet<Customer> Customers { get; set; }
// no more OnConfiguring() method
}
We do this because connection string is now being supplied from the
ConfigureServices() method.
Finally, open the HomeController and add a public constructor to it as shown
below:
public class HomeController : Controller
{
private NorthwindDbContext db;
private CustomerRepository repository;
public HomeController(NorthwindDbContext context)
{
this.db = context;
this.repository = repository;
}
...
...
}
The HomeController class declares a NorthwindDbContext variable at the top.
This variable is assigned in the constructor. The constructor takes a parameter
of type NorthwindDbContext. This parameter will be supplied by the DI framework
of MVC 6. Once received we store its reference in the db variable for further
use.
We need to modify the actions to use this class level variable instead of
locally instantiating a DbContext. So, we need remove the using blocks from the
existing code. The modified actions are shown below:
public IActionResult Index()
{
List<Customer> data = db.Customers.ToList();
return View(data);
}
public IActionResult Edit(string id)
{
Customer data = db.Customers.Where(i =>
i.CustomerID == id).SingleOrDefault();
var query = (from c in db.Customers
orderby c.Country ascending
select new SelectListItem()
{ Text = c.Country, Value
= c.Country }).Distinct();
List<SelectListItem> countries = query.ToList();
ViewBag.Countries = countries;
return View(data);
}
public IActionResult Save(Customer obj)
{
var query = (from c in db.Customers
orderby c.Country ascending
select new SelectListItem()
{ Text = c.Country, Value =
c.Country }).Distinct();
List<SelectListItem> countries = query.ToList();
ViewBag.Countries = countries;
if (ModelState.IsValid)
{
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
}
return View("Edit", obj);
}
Run the application by setting a break point in the constructor. You will
find that the DI framework supplies an instance of NorthwindDbContext configured
to use the specified connection string.
So far so good. Now let's go ahead and use a repository for the database
operations.
To do so, add CustomerRepository class in the Classes folder and write five
methods - SelectAll(), SelectByID(), Insert(), Update() and Delete() - in it as
shown below:
public class CustomerRepository
{
public NorthwindDbContext Context {get; private set;}
public CustomerRepository(NorthwindDbContext context)
{
this.Context = context;
}
public List<Customer> SelectAll()
{
return Context.Customers.ToList();
}
public Customer SelectByID(string id)
{
return Context.Customers.Where(i =>
i.CustomerID == id).SingleOrDefault();
}
public void Insert(Customer obj)
{
Context.Entry(obj).State = EntityState.Added;
Context.SaveChanges();
}
public void Update(Customer obj)
{
Context.Entry(obj).State = EntityState.Modified;
Context.SaveChanges();
}
public void Delete(string id)
{
Customer obj = Context.Customers.Where(i =>
i.CustomerID == id).SingleOrDefault();
Context.Entry(obj).State = EntityState.Deleted;
Context.SaveChanges();
}
}
The CustomerRepository class encapsulates all the logic to perform CRUD
operations on the Customers table. Notice that the CustomerRepository doesn't create any local instance of
NorthwindDbContext class. It receives it through the constructor. We do this
because we want the DI framework to inject the DbContext in the repository just
as it did for the controller. The received instance is stored in a public
Context property. The Context property has private setter so that the DbContext
can't be assigned from outside, at the same time allowing access to the DI
supplied instance.
Once the CustomerRepository is ready you need to register it with the DI
framework. This can be done as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<NorthwindDbContext>
(options => options.UseSqlServer
(AppSettings.ConnectionString));
services.AddScoped<CustomerRepository>();
}
Notice the code marked in bold letters. It uses the AddScoped() generic
method to register CustomerRepository class with the DI framework. The AddScope()
creates an object instance whose scope is the current request. So, now
CustomerRepository can also be injected into the controller class.
Although the above class doesn't do that, you can create a repository
interface and then implement that interface on the repository class. If your
repository is based on some interface you can also specify the interface in the
AddScoped() generic method. For example:
services.AddScoped<ICustomerRepository, CustomerRepository>();
The
following code shows how the injected repository instance can be used.
public class HomeController : Controller
{
private CustomerRepository repository;
public HomeController(CustomerRepository repository)
{
this.repository = repository;
}
...
...
}
This code should look familiar to you because you used it while injecting the
NorthwindDbContext. In this case, however, you declare a variable of type
CustomerRepository and assign it in the constructor.
Once injected you can use the CustomerRepository in all the actions instead
of using the DbContext directly. The following code shows the modified actions
of the HomeController.
public IActionResult Index()
{
List<Customer> data = repository.SelectAll();
return View(data);
}
public IActionResult Edit(string id)
{
Customer data = repository.SelectByID(id);
var query = (from c in repository.Context.Customers
orderby c.Country ascending
select new SelectListItem()
{ Text = c.Country, Value = c.Country })
.Distinct();
List<SelectListItem> countries = query.ToList();
ViewBag.Countries = countries;
return View(data);
}
public IActionResult Save(Customer obj)
{
var query = (from c in repository.Context.Customers
orderby c.Country ascending
select new SelectListItem()
{ Text = c.Country, Value = c.Country })
.Distinct();
List<SelectListItem> countries = query.ToList();
ViewBag.Countries = countries;
if (ModelState.IsValid)
{
repository.Update(obj);
}
return View("Edit", obj);
}
For the sake of simplicity the above code creates List of SelectListItem
objects by writing a query using the Context property. You could have added some
method in the repository that returned the desired countries. Run the application again. If all goes well, you should be able to see the
customer list and will be able to modify the customer details.