Source Code on the GitHub
Recently I gave a talk on the Onion Architecture in MVC applications to the audience of 300 at the C-Sharpcorner conference 2015. The talk was well received and I had many requests to write a step-by-step article on it. So here I am writing that. This article is written in the simplest words possible and will help you in implementing the Onion Architecture in ASP.NET MVC applications. Even though the theme of this article is ASP.NET MVC, you can use the core, infrastructure and the test project with other kinds of applications as well like WCF or WPF.
You may want to read: Refactoring the ASP.NET MVC Application to the Onion Architecture
Source Code on the GitHub
What the Onion Architecture is
The Onion Architecture is the preferred way of architecting applications for better testability, maintainability and dependability on the infrastructures like databases and services. This term was first coined by Jeffery Palermo in his blog back in 2008.
Advantages of Onion Architecture
- In the Onion Architecture layers talk to each other using the interfaces. Any concrete implantation would be provided to the application at run time
- Any external dependency like database access and the web service call are part of the external layers
- The UI is part of the external layers
- Objects representing domains are part of the internal layers or they are the centers
- External layers can depend on the layers internal to it or central to it
- The internal layer should not depend on the external layers
- The domain object that is at the core or centre can have access to both the UI and the database layers
- All the coupling are towards the centre
- Code that may change often should be part of the external layers
Project structure
Let us start with creating the solution structure. We will work with the following four projects.
- Core project (Class library )
- Infrastructure project (Class library)
- Test project (Unit Test project)
- Web project (MVC project)
The Core project will contain the domain entities and the repositories interfaces. The infrastructure project will contain the classes (in this case EF data context) that will work with the database. The Test project will have the tests and the MVC project will contain the MVC controllers and the view.
The solution structure should look such as listed in the following image.
Core project
The core project is the inner-most layer of the architecture. All the external layers, like infrastructure, web and so on will use and depend on the core layer. However the core project does not depend on any other layers.
Usually the core project should contain:
- Domain entities
- Repositories interfaces
It should not have any external dependencies. For example we must not have the following references in the core project:
- Reference of the ORM like LINQ to SQL or EF
- Reference of ADO.NET libraries
- Reference of Entity Framework and so on
Create Entity
Let us proceed to creating the BloodDonor entity class.
- public class BloodDonor
- {
- public string BloodDonorID { get; set; }
- public string Name { get; set; }
- public DateTime Dob { get; set; }
- public string BloodGroup { get; set; }
- public string City { get; set; }
- public string Country { get; set; }
- public int PinCode { get; set; }
- public string PhoneNumber { get; set; }
- public string Email { get; set; }
- public bool IsActive { get; set; }
- public bool IsPrivate { get; set; }
- public bool IsVerified { get; set; }
- }
We may have a requirement to have some restrictions on the entity properties. For example, the maximum length, required and so on. We do have the following two choices to do this:
- Using System.ComponentModel.DataAnnotations
- Use Entity Framework fluent API.
Both of the preceding approaches have their own advantages. However to keep the core project without any external dependencies like EntityFramework, I prefer to use DataAnnotations. To use the DataAnnotations add a System.ComponentModel.DataAnnotations reference to the core project. We can modify the entity class as shown in the listing below:
- using System;
- using System.ComponentModel.DataAnnotations;
-
- namespace LifeLine.Core
- {
- public class BloodDonor
- {
- [Required]
- public string BloodDonorID { get; set; }
- [Required]
- [MaxLength(50)]
- public string Name { get; set; }
- [Required]
- public string BloodGroup { get; set; }
- [Required]
- public string City { get; set; }
-
- public string Country { get; set; }
- [Required]
- public int PinCode { get; set; }
- [Required]
- public string PhoneNumber { get; set; }
- public string Email { get; set; }
- public bool IsActive { get; set; }
- public bool IsPrivate { get; set; }
- public bool IsVerified { get; set; }
- }
- }
As of now we have created the BloodDonor entity class with the data annotations. Next we need to add the repository interface.
Create Repository interface We will follow the repository pattern. Roughly, the repository pattern allows us to replace the database access codes (class) without affecting the other layers. In the repository interface we will put the definition of all the database operations to be performed.
- using System.Collections.Generic;
- namespace LifeLine.Core.Interfaces
- {
- public interface IBloodDonorRepository
- {
- void Add(BloodDonor b);
- void Edit(BloodDonor b);
- void Remove(string BloodDonorID);
- IEnumerable < BloodDonor > GetBloodDonors();
- BloodDonor FindById(string BloodDonorID);
-
- }
- }
As of now we have created the domain entity and the repository interface. Next let us proceed to create the infrastructure project.
Infrastructure project In the infrastructure project we perform operations related to outside the application. For example:
- Database operation
- Accessing outside service
- Accessing File systems
The preceding operations should be part of the infrastructure project. We will use the Entity Framework to do the database operations. We are working with the Entity Framework code first approach, so we need to perform the following procedure:
- Create the data context class
- Implement the repository class
- Create the database initializer class
Before we proceed to create the data context class, let us go ahead and add a reference of the Entity Framework. To add the reference right-click on the infrastructure project and select Manage Nuget Package. From the Nuget Package Manager select the Entity Framework package to install into the project.
DataContext class
Let us create the data context class. In the EF code first approach, we create the data context class that will represent the database. Since we have only one business entity, we will create one table, BloodDonors.
- using System.Data.Entity;
- using LifeLine.Core;
-
- namespace LifeLine.Infrastructure
- {
- public class BloodDonorContext: DbContext
- {
- public BloodDonorContext(): base("name=BloodDonorContextConnectionString")
- {
- var a = Database.Connection.ConnectionString;
- }
-
- public DbSet < BloodDonor > BloodDonors{get;set;}
- }
- }
Connection string
Optionally, you can either pass the connection string or rely on the Entity Framework to create the database. We will set up the connection string in app.config of the infrastructure project. Let us proceed to set the connection string as shown in the following listing:
- < connectionStrings >
- < add name = "BloodDonorContextConnectionString" connectionString = "Data Source= LocalDb)\v11.0;Initial Catalog=BloodDonors;Integrated Security=True;MultipleActiveResultSets=true" providerName = "System.Data.SqlClient" / >
- < /connectionStrings>
Database initialize class
We want the database to be initialized with some initial values. It can be done as shown in the following listing:
- using System;
- using System.Data.Entity;
- using LifeLine.Core;
-
- namespace LifeLine.Infrastructure
- {
- public class BloodDonorInitalizeDb: DropCreateDatabaseIfModelChanges < BloodDonorContext > {
-
- protected override void Seed(BloodDonorContext context)
- {
- context.BloodDonors.Add(
- new BloodDonor
- {
- Name = "Rahul Kapoor",
- City = "Gurgaon",
- BloodGroup = "A+",
- BloodDonorID = "BD1",
- Country = "India",
- IsActive = true,
- IsPrivate = false,
- IsVerified = true,
- PhoneNumber = "91+7378388383",
- PinCode = 122002,
- Email = "[email protected]"
-
- });
- context.BloodDonors.Add(
- new BloodDonor
- {
- Name = "Salman Khan",
- City = "Mumbai",
- BloodGroup = "A-",
- BloodDonorID = "BD2",
- Country = "India",
- IsActive = true,
- IsPrivate = false,
- IsVerified = true,
- PhoneNumber = "91+84848484",
- PinCode = 25678,
- Email = "[email protected]"
- });
- base.Seed(context);
- }
- }
- }
We are setting the value that, if the model changes, recreate the database. We can explore the other options of the Entity Framework also.
Repository class implementationNext we need to implement the repository class. The repository class will access the database using the LINQ to Entity. To create the BloodDonorRepository class let us proceed to create a class that will implement the IBloodDonorRepository interface.
- using System.Collections.Generic;
- using System.Linq;
- using LifeLine.Core;
- using LifeLine.Core.Interfaces;
-
- namespace LifeLine.Infrastructure
- {
- public class BloodDonorRepository: IBloodDonorRepository {
- BloodDonorContext context = new BloodDonorContext();
- public void Add(BloodDonor b)
- {
- context.BloodDonors.Add(b);
- context.SaveChanges();
- }
-
- public void Edit(BloodDonor b)
- {
- context.Entry(b).State = System.Data.Entity.EntityState.Modified;
- }
-
- public void Remove(string BloodDonorID)
- {
- BloodDonor b = context.BloodDonors.Find(BloodDonorID);
- context.BloodDonors.Remove(b);
- context.SaveChanges();
- }
-
- public IEnumerable < BloodDonor > GetBloodDonors()
- {
- return context.BloodDonors;
- }
-
- public BloodDonor FindById(string BloodDonorID)
- {
- var bloodDonor = (from r in context.BloodDonors where r.BloodDonorID == BloodDonorID select r).FirstOrDefault();
- return bloodDonor;
- }
- }
- }
To implement the repository class, we are using the LINQ to Entity for the database operations. For example to add a blood donor:
- Create object of context class
- Use context.entiy.Add(entity)
- Use context.SaveChnages()
As of now we have implemented the infrastructure project. Be sure to build the project to confirm everything is fine.
Whether to directly do something to the MVC project or write a test
After creation of core project and the infrastructure project, we need to take a decision that whether we want to directly create the MVC project or write the unit test for the repository class. Better approach would be to write the Unit tests for the repository class such that we would be sure that database related code has been implemented correctly.
Test Project
We have created a test project by selecting the Unit Test project template from the Test project tab. To start writing the test, very first in the unit test project we need to add the following references:
- Reference of the core project
- Reference of the infrastructure project
- Entity Framework package
- Reference of the System.LINQ
In the test project, I have created a class called BloodDonorRepositoryTest. Inside the unit test class initialize the test as shown in the listing below:
- BloodDonorRepository repo;
- [TestInitialize]
- public void TestSetUp()
- {
-
- BloodDonorInitalizeDb db = new BloodDonorInitalizeDb();
- System.Data.Entity.Database.SetInitializer(db);
- repo = new BloodDonorRepository();
- }
In the test setup, we are creating an instance of the BloodDonorInitalizeDb class and then setting up the database with the initial values. Also in the test setup, an instance of the repository class is created. Next let us create a test to validate whether or not the database is initialized with the right number of data.
- [TestMethod]
- public void IsRepositoryInitalizeWithValidNumberOfData()
- {
- var result = repo.GetBloodDonors();
- Assert.IsNotNull(result);
- var numberOfRecords = result.ToList().Count;
- Assert.AreEqual(2, numberOfRecords);
- }
In the test above we are calling the GetBloodDonors method of the repository class and then verifying the number of records. In an ideal condition the preceding test should be passed. You can verify the test results in the Test-Window-Test Explorer.
We can create tests for add, edit and delete also. As of now we have written a test to verify that the database is being created along with the initialized data. We can be sure about the implementation of the repository class after passing the test. Now let us proceed to implement the web project.
Web Project
We will create a MVC project that will use the core project and the infrastructure project. Add the following reference to the MVC project:
- Reference of infrastructure project
- Reference of core project
- Entity Framework package
After adding all the references, build the web project. If everything is fine then we should get a successful build. Next let us right-click on the Controller folder and add a new controller. Select create controller with Entity Framework with view option. Provide the name of the controller as BloodDonorsController.
Also we need to select the following options:
- BloodDonor class from the core project as the Model class
- BloodDonorContext class as the data context class from the Infrastructure project
We have created the controller along with the view using the scaffolding. At this point if we notice the web project, we will find the BloodDonors controller and a BloodControllers subfolder in the view folder.
As of now we have created the controller using the model and data context class. Next we need to create an instance of the BloodDonorInitalizeDb class and set the database. We need to write this code inside the global.asax.
- BloodDonorInitalizeDb db = new BloodDonorInitalizeDb();
- System.Data.Entity.Database.SetInitializer(db);
Last we need to copy the connection string from the app.config of infrastructure project to web.config of the web project. So let us go ahead and copy the connection string in the web.config
- <connectionStrings>
- <add name="BloodDonorContextConnectionString" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=BloodDonors;Integrated Security=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient"/>
- </connectionStrings>
At this point if we run the application we should be able to perform the CRUD operations.
Dependency Injection using Unity
We have a running application but there is a problem. On noticing the controller class closely, we will find that the object of the data context class is directly created inside the controller. We used scaffolding to create the controller using the Entity Framework and it causes the creation of a data context object inside the controller. Right now the controller and the data context class are tightly coupled to each other and if we change the database access program then the controller will be affected also. We should not have the BloodDonorContext object inside the controller.
We have already created the repository class inside the infrastructure project. We should use the repository class inside the controller class. We have the following two options to use the repository class.
- Directly create the object of the repository class in the controller class
- Use dependency injection to resolve the repository interface to the concrete repository class object at run time using the Unity container.
We will use the second approach. To use the Unity container add the Unity.Mvc5 package to the web project using Manage Nuget Package.
After installing the Unity package, we need to register the type. To register the type open the UnityConfig class inside the App_Start folder. As highlighted in the following image, add the register.
And inside the global.asax register all the components of the UnityConfig as shown in the following image.
So far we have added the Unity container reference and registered the type. Now let us proceed to refactor the controller class to use the repository class. To start with:
- Create a global variable of the type IBloodDonorRepository
- Create controller class constructor with a parameter as shown in the following image:
Once this is done we need to refactor the controller class to use functions of the repository class instead of the data context class. For example, the Index method can be refactored as shown below.
After refactoring, the BloodDonorController class will look such as shown in the following listing:
- using System.Linq;
- using System.Net;
- using System.Web.Mvc;
- using LifeLine.Core;
- using LifeLine.Core.Interfaces;
- namespace LifeLine.Web.Controllers
- {
- public class BloodDonorsController: Controller
- {
- IBloodDonorRepository db;
-
- public BloodDonorsController(IBloodDonorRepository db)
- {
- this.db = db;
- }
-
-
- public ActionResult Index()
- {
- return View(db.GetBloodDonors().ToList());
- }
-
-
- public ActionResult Details(string id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- BloodDonor bloodDonor = db.FindById(id);
- if (bloodDonor == null)
- {
- return HttpNotFound();
- }
- return View(bloodDonor);
- }
-
-
- public ActionResult Create()
- {
- return View();
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Create([Bind(Include = "BloodDonorID,Name,BloodGroup,City,Country,PinCode,PhoneNumber,Email,IsActive,IsPrivate,IsVerified")] BloodDonor bloodDonor)
- {
- if (ModelState.IsValid)
- {
- db.Add(bloodDonor);
- return RedirectToAction("Index");
- }
-
- return View(bloodDonor);
- }
-
-
- public ActionResult Edit(string id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- BloodDonor bloodDonor = db.FindById(id);
- if (bloodDonor == null)
- {
- return HttpNotFound();
- }
- return View(bloodDonor);
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Edit([Bind(Include = "BloodDonorID,Name,BloodGroup,City,Country,PinCode,PhoneNumber,Email,IsActive,IsPrivate,IsVerified")] BloodDonor bloodDonor)
- {
- if (ModelState.IsValid)
- {
- db.Edit(bloodDonor);
- return RedirectToAction("Index");
- }
- return View(bloodDonor);
- }
-
-
- public ActionResult Delete(string id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- BloodDonor bloodDonor = db.FindById(id);
- if (bloodDonor == null)
- {
- return HttpNotFound();
- }
- return View(bloodDonor);
- }
-
-
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public ActionResult DeleteConfirmed(string id)
- {
- BloodDonor bloodDonor = db.FindById(id);
- db.Remove(bloodDonor.BloodDonorID);
- return RedirectToAction("Index");
- }
- }
- }
Conclusion
This is it. This is the procedure we need to follow to create a MVC application following the Onion Architecture.
Source Code on the GitHub
Have something to add? Please add in the comments section.