January 2018 : Instructor-led Online Course in ASP.NET Core 2.0. Conducted by Bipin Joshi. Read more...
Registration for January 2018 batch of ASP.NET Core 2.0 instructor-led online course has already started. Conducted by Bipin Joshi. Register today ! Click here for more details.

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.


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 16 November 2015


Tags : ASP.NET Data Access MVC C#