TRUNGTQ

Think Big, Act Small, Fail Fast and Learn Rapidly

NAVIGATION - SEARCH

Onion Architecture In ASP.NET Core MVC Detail

 

Introduction

The Onion Architecture term was coined by Jeffrey Palermo in 2008. This architecture provides a better way to build applications for better testability, maintainability, and dependability on the infrastructures like databases and services. This architecture's main aim is to address the challenges faced with 3-tier architecture or n-tier architecture, and to provide a solution for common problems, like coupling and separation of concerns. There are two types of coupling - tight coupling and loose coupling.

Tight Coupling

When a class is dependent on a concrete dependency, it is said to be tightly coupled to that class. A tightly coupled object is dependent on another object; that means changing one object in a tightly coupled application, often requires changes to a number of other objects. It is not difficult when an application is small but in an enterprise level application, it is too difficult to make the changes.

Loose Coupling

It means two objects are independent and an object can use another object without being dependent on it. It is a design goal that seeks to reduce the inter-dependencies among components of a system with the goal of reducing the risk that changes in one component will require changes in any other component.


Advantages of Onion Architecture

There are several advantages of the Onion Architecture, as listed below.

  1. It provides better maintainability as all the codes depend on layers or the center.
  2. It provides better testability as the unit test can be created for separate layers without an effect of other modules of the application.
  3. It develops a loosely coupled application as the outer layer of the application always communicates with inner layer via interfaces.
  4. Any concrete implantation would be provided to the application at run time
  5. Domain entities are core and center part. It can have access to both database and UI layers.
  6. The internal layers never depend on external layer. The code that may have changed should be part of an external layer.

Why Onion Architecture

There are several traditional architectures, like 3-tier architecture and n-tier architecture, all having their own pros and cons. All these traditional architectures have some fundamental issues, such as - tight coupling and separation of concerns. The Model-View-Controller is the most commonly used web application architecture, these days. It solves the problem of separation of concern as there is a separation between UI, business logic, and data access logic. The View is used to design the user interface. The Model is used to pass the data between View and Controller on which the business logic performs any operations. The Controller is used to handle the web request by action methods and returns View accordingly. Hence, it solves the problem of separation of concern while the Controller is still used to database access logic. In essence, MVC solves the separation of concern issue but the tight coupling issue still remains.

On the other hand, Onion Architecture addresses both the separation of concern and tight coupling issues. The overall philosophy of the Onion Architecture is to keep the business logic, data access logic, and model in the middle of the application and push the dependencies as far outward as possible means all coupling towards to center.


Onion Architecture Layers

This architecture relies heavily on the Dependency Inversion Principle. The UI communicates to business logic through interfaces. It has four layers, as shown in figure 1.

  1. Domain Entities Layer
  2. Repository Layer
  3. Service Layer
  4. UI (Web/Unit Test) Layer

Figure 1: Onion Architecture Layers

These layers are towards to center. The center part is the Domain entities which represent the business and behavior objects. These layers can vary but the domain entities layer is always part of the center. The other layer defines more behavior of an object. Let’s see each layer one by one.

Domain Entities Layer

It is the center part of the architecture. It holds all application domain objects. If an application is developed with ORM entity framework then this layer holds POCO classes (Code First) or Edmx (Database First) with entities. These domain entities don't have any dependencies.

Repository Layer

The layer is intended to create an Abstraction layer between the Domain entities layer and Business Logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access. We create a generic repository, which queries the data source for the data, maps the data from the data source to a business entity and persists changes in the business entity to the data source.

Service Layer

The layer holds interfaces which are used to communicate between the UI layer and repository layer. It holds business logic for an entity so it’s called business logic layer as well.

UI Layer

It’s the most external layer. It could be the web application, Web API or Unit Test project. This layer has an implementation of the Dependency Inversion Principle so that application builds a loosely coupled application. It communicates to internal layer via interfaces.


Onion Architecture Project Structure

To implement the Onion architecture, we develop an ASP.NET Core application. This application performs CRUD operations on entities. The application holds four projects as per figure 2. Each project represents a layer in onion architecture.

Figure 2: Application projects structure

There are four projects in which three are class library projects and one is web application project. Let’s see each project mapping with onion architecture layers.

OA.Data

It is a class library project. It holds POCO classes along with configuration classes. It represents the Domain Entities layer of the onion architecture. These classes are used to create database tables. It’s a core and central part of the application.

OA.Repo

It is a second class library project. It holds generic repository class with its interface implementation. It also holds DbContext class. The Entity Framework Code First data access approach needs to create a data access context class that inherits from the DbContext class. This project represents the Repository layer of the onion architecture.

OA.Service

It is a third class library project. It holds business logic and interfaces. These interfaces communicate between UI and data access logic. As it communicates via interfaces, it builds applications that are loosely coupled. This project represents the Service layer of the onion architecture.

OA.Web

It is an ASP.NET Core Web application in this sample but it could be Unit Test or Web API project. It is the most external part of an application by which the end user can interact with the application. It builds loosely coupled applications with in-built dependency injection in ASP.NET Core. It represents the UI layer of the onion architecture.


Implement Onion Architecture

To implement the Onion Architecture in the ASP.NET Core application, create four projects as described in the above section. These four projects represent four layers of the onion architecture. Let’s see each one by one.

Domain Entities Layer

The Entities Domain layer is a core and central part of the architecture. So first, we create "OA.Data" project to implement this layer. This project holds POCO class and fluent API configuration for this POCO classes.

There is an unsupported issue of EF Core 1.0.0-preview2-final with "NETStandard.Library": "1.6.0". Thus, we have changed the target framework to netstandard1.6 > netcoreapp1.0. We modify the project.json file of OA.Data project to implement the Entity Framework Core in this class library project. Thus, the code snippet, mentioned below, is used for the project.json file after modification.

{
  "dependencies": {
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [ "dotnet5.6", "portable-net45+win8" ]
    }
  },
  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },
  "version": "1.0.0-*"
}

This Application uses the Entity Framework Code First approach, so the project OA.Data contains entities that are required in the application's database. The OA.Data project holds three entities, one is the BaseEntity class that has common properties that will be inherited by each entity. The code snippet, mentioned below is the BaseEntity class.

using System;
 
namespace OA.Data
{
    public class BaseEntity
    {
        public Int64 Id { get; set; }
        public DateTime AddedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string IPAddress { get; set; }
    }
}

There are two more entities, one is User and the another one is UserProfile. Both entities have a one to one relationship, as shown below.

Figure 3: One to One User-UserProfile relationship

Now, we create an User entity, which is inherited from BaseEntity class. The code snippet, mentioned below is for the User entity.

namespace OA.Data
{
    public class User:BaseEntity
    {
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public virtual UserProfile UserProfile { get; set; }
    }
}

Now, we define the configuration for the User entity that will be used when the database table will be created by the entity. The following is a code snippet for the User mapping entity (UserMap.cs).

using Microsoft.EntityFrameworkCore.Metadata.Builders;
 
namespace OA.Data
{
    public class UserMap
    {
        public UserMap(EntityTypeBuilder<User> entityBuilder)
        {
            entityBuilder.HasKey(t => t.Id);
            entityBuilder.Property(t => t.Email).IsRequired();
            entityBuilder.Property(t => t.Password).IsRequired();
            entityBuilder.Property(t => t.Email).IsRequired();
            entityBuilder.HasOne(t => t.UserProfile).WithOne(u => u.User).HasForeignKey<UserProfile>(x => x.Id);
        }
    }
}

Now, we create a UserProfile entity, which inherits from the BaseEntity class. The code snippet, mentioned below is the UserProfile entity.

namespace OA.Data
{
    public class UserProfile:BaseEntity
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public virtual User User { get; set; }
    }
}

Now, we define the configuration for the UserProfile entity that will be used when the database table will be created by the entity. The code snippet is mentioned below for the UserProfile mapping entity (UserProfileMap.cs).

using Microsoft.EntityFrameworkCore.Metadata.Builders;
 
namespace OA.Data
{
    public class UserProfileMap
    {
        public UserProfileMap(EntityTypeBuilder<UserProfile> entityBuilder)
        {
            entityBuilder.HasKey(t => t.Id);
            entityBuilder.Property(t => t.FirstName).IsRequired();
            entityBuilder.Property(t => t.LastName).IsRequired();
            entityBuilder.Property(t => t.Address); 
        }
    }
}

Repository Layer

Now we create a second layer of the onion architecture which is repository layer. To build this layer, we create one more class library project named OA.Repo. This project holds both the repository and data context classes.

The OA.Repo project contains DataContext. The ADO.NET Entity Framework Code First data access approach needs to create a data access context class that inherits from the DbContext class, so we create a context class ApplicationContext (ApplicationContext.cs) class.

In this class, we override the OnModelCreating() method. This method is called when the model for a context class (ApplicationContext) has been initialized, but before the model has been locked down and used to initialize the context such that the model can be further configured before it is locked down. The following is the code snippet for the context class.

using Microsoft.EntityFrameworkCore;
using OA.Data;
 
namespace OA.Repo
{
    public class ApplicationContext : DbContext
    {
        public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            new UserMap(modelBuilder.Entity<User>());
            new UserProfileMap(modelBuilder.Entity<UserProfile>());
        }
    }
}

The DbContext must have an instance of DbContextOptions in order to execute. We will use dependency injection, so we pass options via constructor dependency injection. ASP.NET Core is designed from the ground to support and leverage dependency injection. Thus, we create generic repository interface for the entity operations, so that we can develop loosely coupled application. The code snippet, mentioned below is the IRepository interface.

using OA.Data;
using System.Collections.Generic;
 
namespace OA.Repo
{
    public interface IRepository<T> where T : BaseEntity
    {
        IEnumerable<T> GetAll();
        T Get(long id);
        void Insert(T entity);
        void Update(T entity);
        void Delete(T entity);
        void Remove(T entity);
        void SaveChanges();
    }
}

Now, let's create a repository class to perform database operations on the entity, which implements IRepository. This repository contains a parameterized constructor with a parameter as Context, so when we create an instance of the repository, we pass a context so that the entity has the same context. The code snippet is mentioned below for the Repository class under OA.Repo project.

using Microsoft.EntityFrameworkCore;
using OA.Data;
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace OA.Repo
{
    public class Repository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly ApplicationContext context;
        private DbSet<T> entities;
        string errorMessage = string.Empty;
 
        public Repository(ApplicationContext context)
        {
            this.context = context;
            entities = context.Set<T>();
        }
        public IEnumerable<T> GetAll()
        {
            return entities.AsEnumerable();
        }
 
        public T Get(long id)
        {
            return entities.SingleOrDefault(s => s.Id == id);
        }
        public void Insert(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            entities.Add(entity);
            context.SaveChanges();
        }
 
        public void Update(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            context.SaveChanges();
        }
 
        public void Delete(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            entities.Remove(entity);
            context.SaveChanges();
        }
        public void Remove(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            entities.Remove(entity);           
        }
 
        public void SaveChanges()
        {
            context.SaveChanges();
        }
    }
}

We developed entity and context which are required to create a database but we will come back to this after creating the two more projects.

Service Layer

Now we create the third layer of the onion architecture which is service layer. To build this layer, we create one more class library project named OA.Service. This project holds interfaces and classes which have an implementation of interfaces. This layer is intended to build loosely coupled applications. This layer communicates with both Web applications and repository projects.

We create an interface named IUserService. This interface holds all methods signature which access by external layer for the User entity. The following code snippet is for the same (IUserService.cs).

using OA.Data;
using System.Collections.Generic;
 
namespace OA.Service
{
    public  interface IUserService
    {
        IEnumerable<User> GetUsers();
        User GetUser(long id);
        void InsertUser(User user);
        void UpdateUser(User user);
        void DeleteUser(long id);
    }
}

Now, this IUserService interface implements on a class named UserService. This UserService class holds all the operations for User entity. The following code snippet is for the same(UserService.cs).

using OA.Data;
using OA.Repo;
using System.Collections.Generic;
 
namespace OA.Service
{
    public class UserService:IUserService
    {
        private IRepository<User> userRepository;
        private IRepository<UserProfile> userProfileRepository;
 
        public UserService(IRepository<User> userRepository, IRepository<UserProfile> userProfileRepository)
        {
            this.userRepository = userRepository;
            this.userProfileRepository = userProfileRepository;
        }
 
        public IEnumerable<User> GetUsers()
        {
            return userRepository.GetAll();
        }
 
        public User GetUser(long id)
        {
            return userRepository.Get(id);
        }
 
        public void InsertUser(User user)
        {
            userRepository.Insert(user);
        }
        public void UpdateUser(User user)
        {
            userRepository.Update(user);
        }
 
        public void DeleteUser(long id)
        {           
            UserProfile userProfile = userProfileRepository.Get(id);
            userProfileRepository.Remove(userProfile);
            User user = GetUser(id);
            userRepository.Remove(user);
            userRepository.SaveChanges();
        }
    }
}

We create one more interface named IUserProfileService. This interface holds method signature which is accessed by the external layer for the UserProfile entity. The following code snippet is for the same (IUserProfileService.cs).

using OA.Data;
 
namespace OA.Service
{
    public interface IUserProfileService
    {
        UserProfile GetUserProfile(long id);
    }
}

Now, this IUserProfileService interface implements on a class named UserProfileService. This UserProfileService class holds the operation for the UserProfile entity. The following code snippet is for the same(UserProfileService.cs).

using OA.Data;
using OA.Repo;
 
namespace OA.Service
{
    public class UserProfileService: IUserProfileService
    {
        private IRepository<UserProfile> userProfileRepository;
 
        public UserProfileService(IRepository<UserProfile> userProfileRepository)
        {          
            this.userProfileRepository = userProfileRepository;
        }
 
        public UserProfile GetUserProfile(long id)
        {
            return userProfileRepository.Get(id);
        }
    }
}

UI Layer

Now, we create the external layer of the onion architecture which is UI layer. The end user interacts with the application by this layer. To build this layer, we create an ASP.NET Core MVC web application named OA.Web. This layer communicates to service layer projects. This project contains the user interface for both user and user profile entities database operations and the controller to do these operations.

As the concept of dependency injection is central to the ASP.NET Core application, we register context, repository, and service to the dependency injection during the application start up. Thus, we register these as a Service in the ConfigureServices method in the StartUp class as per following code snippet.

public void ConfigureServices(IServiceCollection services)
     {          
         services.AddMvc();
         services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
         services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
         services.AddTransient<IUserService, UserService>();
         services.AddTransient<IUserProfileService, UserProfileService>();
     }

Here, the DefaultConnection is connection string which defined in appsettings.json file as per following code snippet.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP-RG33QHE;Initial Catalog=OADb;User ID=sa; Password=****"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Now, we have configured settings to create database, so we have time to create a database, using migration. We must choose the OA.Repo project in the Package Manager console during the performance of the steps, mentioned below.

  1. Tools -> NuGet Package Manager -> Package Manager Console
  2. Run PM> Add-Migration MyFirstMigration to scaffold a migration to create the initial set of tables for our model. If we receive an error stating the term `add-migration' is not recognized as the name of a cmdlet, then close and reopen Visual Studio.
  3. Run PM> Update-Database to apply the new migration to the database. Because our database doesn't exist yet, it will be created for us before the migration is applied.

Create Application User Interface

Now, we proceed to the controller. We create controller named UserController under the Controllers folder of the application. It has all ActionResult methods for end user interface of operations. We create both IUserService and IUserProfile interface instances; then we inject these in the controller's constructor to get its object. The following is a partial code snippet for the UserController in which service interfaces are injected, using constructor dependency injection.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using OA.Service;
using OA.Web.Models;
using OA.Data;
using Microsoft.AspNetCore.Http;
 
namespace OA.Web.Controllers
{
    public class UserController : Controller
    {
        private readonly IUserService userService;
        private readonly IUserProfileService userProfileService;
 
        public UserController(IUserService userService, IUserProfileService userProfileService)
        {
            this.userService = userService;
            this.userProfileService = userProfileService;
        }
    }
}

We can notice that Controller takes both IUserService and IUserProfileService as a constructor parameters. The ASP.NET Core dependency injection will take care of passing an instance of these services into UserController. The controller is developed to handle operations requests for both User and UserProfile entities. Now, let's develop the user interface for the User Listing, Add User, Edit User and Delete User. Let's see each one by one.

User List View

This is the first view when the application is accessed or the entry point of the application is executed. It shows the author listing as in Figure 4. The user data is displayed in a tabular format and on this view, it has linked to add a new user, edit a user and delete a user.

To pass data from controller to view, create named UserViewModel view model, as per the code snippet, mentioned below. This view model is also used for adding or editing a user.

using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
 
namespace OA.Web.Models
{
    public class UserViewModel
    {
        [HiddenInput]
        public Int64 Id { get; set; }
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        [Display(Name = "User Name")]
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        [Display(Name = "Added Date")]
        public DateTime AddedDate { get; set; }
    }
}

Now, we create action method, which returns an index view with the data. The code snippet of Index action method in UserController is mentioned below.

[HttpGet]
        public IActionResult Index()
        {
            List<UserViewModel> model = new List<UserViewModel>();
            userService.GetUsers().ToList().ForEach(u =>
            {
                UserProfile userProfile = userProfileService.GetUserProfile(u.Id);
                UserViewModel user = new UserViewModel
                {
                    Id = u.Id,
                    Name = $"{userProfile.FirstName} {userProfile.LastName}",
                    Email = u.Email,
                    Address = userProfile.Address
                };
                model.Add(user);
            });
 
            return View(model);
        }

Now, we create an index view, as per the code snippet, mentioned below under the User folder of views.

@model IEnumerable<UserViewModel>
@using OA.Web.Models
@using OA.Web.Code
 
<div class="top-buffer"></div>
<div class="panel panel-primary">
    <div class="panel-heading panel-head">Users</div>
    <div class="panel-body">
        <div class="btn-group">
            <a id="createEditUserModal" data-toggle="modal" asp-action="AddUser" data-target="#modal-action-user"class="btn btn-primary">
                <i class="glyphicon glyphicon-plus"></i>  Add User
            </a>
        </div>
        <div class="top-buffer"></div>
        <table class="table table-bordered table-striped table-condensed">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Address</th>
                    <th>Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>@Html.DisplayFor(modelItem => item.Name)</td>
                        <td>@Html.DisplayFor(modelItem => item.Email)</td>
                        <td>@Html.DisplayFor(modelItem => item.Address)</td>
                        <td>
                            <a id="editUserModal" data-toggle="modal" asp-action="EditUser" asp-route-id="@item.Id"data-target="#modal-action-user"
                               class="btn btn-info">
                                <i class="glyphicon glyphicon-pencil"></i>  Edit
                            </a>                        
                            <a id="deleteUserModal" data-toggle="modal" asp-action="DeleteUser" asp-route-id="@item.Id"data-target="#modal-action-user" class="btn btn-danger">
                                <i class="glyphicon glyphicon-trash"></i>  Delete
                            </a>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</div>
 
@Html.Partial("_Modal", new BootstrapModel { ID = "modal-action-user", AreaLabeledId = "modal-action-user-label", Size = ModalSize.Large })
 
@section scripts
{
    <script src="~/js/user-index.js" asp-append-version="true"></script>
}

It shows all forms in the Bootstrap model popup so create the user - index.js file as per the following code snippet.

(function ($) {
    function User() {
        var $this = this;
 
        function initilizeModel() {
            $("#modal-action-user").on('loaded.bs.modal', function (e) {
 
            }).on('hidden.bs.modal', function (e) {
                $(this).removeData('bs.modal');
            });
        }
        $this.init = function () {
            initilizeModel();
        }
    }
    $(function () {
        var self = new User();
        self.init();
    })
}(jQuery))

When the application runs and calls the index() action method from UserController with a HttpGet request, it gets all the users listed in the UI, as shown in Figure 4.

Figure 4: User listing

Add User

To pass the data from UI to a controller to add a user, use same view model named UserViewModel. The AuthorController has an action method named AddUser which returns the view to add a user. The code snippet mentioned below is for same action method for both GET and Post requests.

[HttpGet]
        public ActionResult AddUser()
        {
            UserViewModel model = new UserViewModel();
 
            return PartialView("_AddUser", model);
        }
 
        [HttpPost]
        public ActionResult AddUser(UserViewModel model)
        {
            User userEntity = new User
            {
                UserName = model.UserName,
                Email = model.Email,
                Password = model.Password,
                AddedDate = DateTime.UtcNow,
                ModifiedDate = DateTime.UtcNow,
                IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString(),
                UserProfile = new UserProfile
                {
                    FirstName = model.FirstName,
                    LastName = model.LastName,
                    Address = model.Address,
                    AddedDate = DateTime.UtcNow,
                    ModifiedDate = DateTime.UtcNow,
                    IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString()
                }
            };
            userService.InsertUser(userEntity);
            if (userEntity.Id > 0)
            {
                return RedirectToAction("index");
            }
            return View(model);
        }

The GET request for the AddUser action method returns _AddUser partial view; the code snippet follows under the User folder of views.

@model UserViewModel
@using OA.Web.Models
 
<form asp-action="AddUser" role="form">
    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Add User" })
    <div class="modal-body form-horizontal">
        <div class="row">
            <div class="col-lg-6">
                <div class="form-group">
                    <label asp-for="FirstName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="FirstName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="LastName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="LastName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Email" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="Email" class="form-control" />
                    </div>
                </div>
            </div>
            <div class="col-lg-6">
                <div class="form-group">
                    <label asp-for="UserName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="UserName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Password" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input type="password" asp-for="Password" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Address" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="Address" class="form-control" />
                    </div>
                </div>
            </div>
        </div>
    </div>
    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })
</form>

When the application runs and you click on the Add User button, it makes a GET request for the AddUser() action; add a user screen, as shown in Figure 5.

Figure 5: Add User screen

Edit User

To pass the data from UI to controller to edit a user, use same view model named UserViewModel. The UserController has an action method named EditUser, which returns view to edit a user. The code snippet mentioned below is for the same action method for both GET and Post requests.

public ActionResult EditUser(int? id)
        {
            UserViewModel model = new UserViewModel();
            if (id.HasValue && id != 0)
            {
                User userEntity = userService.GetUser(id.Value);
                UserProfile userProfileEntity = userProfileService.GetUserProfile(id.Value);
                model.FirstName = userProfileEntity.FirstName;
                model.LastName = userProfileEntity.LastName;
                model.Address = userProfileEntity.Address;
                model.Email = userEntity.Email;
            }
            return PartialView("_EditUser", model);
        }
 
        [HttpPost]
        public ActionResult EditUser(UserViewModel model)
        {
            User userEntity = userService.GetUser(model.Id);
            userEntity.Email = model.Email;
            userEntity.ModifiedDate = DateTime.UtcNow;
            userEntity.IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
            UserProfile userProfileEntity = userProfileService.GetUserProfile(model.Id);
            userProfileEntity.FirstName = model.FirstName;
            userProfileEntity.LastName = model.LastName;
            userProfileEntity.Address = model.Address;
            userProfileEntity.ModifiedDate = DateTime.UtcNow;
            userProfileEntity.IPAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString();
            userEntity.UserProfile = userProfileEntity;
            userService.UpdateUser(userEntity);
            if (userEntity.Id > 0)
            {
                return RedirectToAction("index");
            }
            return View(model);
        }

The GET request for the EditUser action method returns _EditUser partial view, where code snippet follows under the User folder of views.

@model UserViewModel
@using OA.Web.Models
 
<form asp-action="EditUser" role="form">
    @await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Edit User" })
    <div class="modal-body form-horizontal">
        <div class="row">           
            <input asp-for="Id" />
                <div class="form-group">
                    <label asp-for="FirstName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="FirstName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="LastName" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="LastName" class="form-control" />
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Email" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="Email" class="form-control" />
                    </div>
                </div>        
           
                <div class="form-group">
                    <label asp-for="Address" class="col-lg-3 col-sm-3 control-label"></label>
                    <div class="col-lg-6">
                        <input asp-for="Address" class="form-control" />
                    </div>
                </div>
             
        </div>
    </div>
    @await Html.PartialAsync("_ModalFooter", new ModalFooter { })
</form>

When the application runs and you click on the Edit button in the User listing, it makes a GET request for the EditUser() action, then the edit user screen is shown in Figure 6.

Figure 6: Edit User

Delete User

The UserController has an action method named DeleteUser, which returns the view to delete a user. The code snippet mentioned below is for the same action method for both GET and Post requests.

[HttpGet]
        public PartialViewResult DeleteUser(int id)
        {
            UserProfile userProfile = userProfileService.GetUserProfile(id);
            string name = $"{userProfile.FirstName} {userProfile.LastName}";
            return PartialView("_DeleteUser", name);
        }
 
        [HttpPost]
        public ActionResult DeleteUser(long id, FormCollection form)
        {
            userService.DeleteUser(id);         
            return RedirectToAction("Index");
        }

The GET request for the DeleteUser action method returns _DeleteUser partial View. The code snippet mentioned below is under the User folder of Views.

@model string
@using OA.Web.Models
 
<form asp-action="DeleteUser" role="form">
    @Html.Partial("_ModalHeader", new ModalHeader { Heading = "Delete User" })
 
    <div class="modal-body form-horizontal">
        Are you want to delete @Model?
    </div>
    @Html.Partial("_ModalFooter", new ModalFooter { SubmitButtonText = "Delete" })
</form>

When the application runs and a user clicks on the "Delete" button in the user listing, it makes a GET request for the DeleteUser() action, then the delete user screen is shown, as below.

Figure 7: Delete User
 

 

Onion Architecture In ASP.NET Core MVC

Bài viết khá hay về ASP.NET Core, có thể tham khảo về kiến trúc

Introduction

A Visual Studio 2015 project which shows how to perform the create, read, update and delete operations in the ASP.NET Core MVC application using onion architecture with Entity Framework Core Code First approach.

The code illustrates the following topics:

  1. Listings, create, update and delete operations.
  2. One to one relationship in Entity Framework Core.
  3. Create, Update and Delete operations perform in bootstrap modal pop up with tag helpers.
  4. Database design using entity framework core code first approach with fluent API.
  5. Entity classes and repository in separate class library project and use migration to create database from it.
  6. Dependency injection for DbContext, Repository and service.

Article: Onion Architecture In ASP.NET Core MVC

Getting Started

To build and run this sample as-is, you must have Visual Studio 2015 installed. In most cases you can run the application by following these steps:

  1. Download and extract the .zip file.
  2. Open the solution file in Visual Studio.
  3. Change connection string in the appsettings.json file of the web project.
  4. Choose OA.Repo project in package manager console and run the following command for migration and create database.
    • Tools –> NuGet Package Manager –> Package Manager Console
    • PM> Add-Migration MyFirstMigration
    • PM> Update-Database
  5. Run the application.

Running the Sample

To run the sample, hit F5 or choose the Debug | Start Debugging menu command. You will see the user list screen. From this screen you have user listing screen as shown in below figure.

Figure 1: User listing

Now click on “Add User” button to add new user in the application as per following screen.

Figure 2: Add User

As per figure 1, Edit button uses to edit individual user as per following figure.

Figure 3: Edit User

As per figure 1, Delete button uses to delete individual user as per following figure.

Figure 4: Delete User

Source Code Overview

Most of folders play same role as in MVC application but there are following more folder and files.

  1. wwwroot: It holds static js and css files.
  2. OA.Data: It’s separate class library project. It holds Entities and table configuration classes which create database.
  3. OA.Repo: It’s a class libraray project which holds DataContext and Repository.
  4. OA.Service: It’s a class libraray project which holds service interface which communicates to UI.
  5. appsettings.json:It holds database connection string.
  6. Migrations: It holds database migration files.

LINK: https://code.msdn.microsoft.com/Onion-Architecture-In-9c58c06d#content

ASP.NET MVC Solution Architecture – Best Practices

Bài viết hơi cũ, nhưng kiến trúc dạng này thuộc vào dạng ngon, đáng để tham khảo.

SourceCode: mvcarchitecture

  1. Entity Framework Code First development
  2. Generic Repository Pattern
  3. Dependency Injection using Autofac framework
  4. Automapper

mvc-architecture-01

Choosing the right architecture for Web Applications is a must, especially for large scale ones. Using the default Visual Studio ASP.NET MVC Web Application project templates, adding controllers with Scaffolding options, just to bootstrap your application and create pages and data in just a few minutes, sounds awesome for sure, but let’s be honest it’s not always the right choise. Peeking all the default options, keeping business, data and presentation logic in the same project will impact several factors in your solutions, such as scalability, usability or testability. In this post, will see how to keep things clean, creating a highly loosely coupled ASP.NET MVC Solution, where Data Access, Business and Presentation layers are defined in the right manner. To do this, we ‘ll make use of several patterns and frameworks, some of those are presented below.

  1. Entity Framework Code First development
  2. Generic Repository Pattern
  3. Dependency Injection using Autofac framework
  4. Automapper

The architecture we will try to build in this post is summarized in the following diagram.
Let’s start. Assuming we want to built an e-shop Web Application called “Store”, create a blank solution with the same name.

Models

Add a class library project to the solution, named Store.Model. This library is where we ‘ll keep all of our domain objects. Entity Framework will count on them in order to build the database but we are not going to configure Code First using DataAnnotations attributes on this project. Instead, we are going to put all the Code First configuration in specific Configuration classes using the Fluent API. Add a folder named Models and add the following two simple classes.

Gadget.cs
1
2
3
4
5
6
7
8
9
10
11
public class Gadget
    {
        public int GadgetID {get;set; }
        public string Name {get;set; }
        public string Description {get;set; }
        public decimal Price {get;set; }
        public string Image {get;set; }
 
        public int CategoryID {get;set; }
        public Category Category {get;set; }
    }
Category.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Category
    {
        public int CategoryID {get;set; }
        public string Name {get;set; }
        public DateTime? DateCreated {get;set; }
        public DateTime? DateUpdated {get;set; }
 
        public virtual List<Gadget> Gadgets {get;set; }
 
        public Category()
        {
            DateCreated = DateTime.Now;
        }
    }

I preffered to keep the namespace Store.Model instead of the namespace Store.Model.Models

Data Access Layer and Repositories

mvc-architecture-02
The purpose of this layer, is the direct access to the database. It’s the only layer responsible to communicate with the database. If some other layer wants to access the database, then this will be done through some of the classes (repositories) we will define in this project. This will be strictly the only way Add a new class library project named Store.Data and make sure you add a reference to the previous created project, Store.Model. Install Entity Framework using the Nuget Package Manager. First thing we wanna do, is to define the Entity Type Configurations for our domain objecs. Add a folder named Configuration with the following two classes that inherits the EntityTypeConfiguration class.

GadgetConfiguration.cs
1
2
3
4
5
6
7
8
9
10
public class GadgetConfiguration: EntityTypeConfiguration<Gadget>
    {
        public GadgetConfiguration()
        {
            ToTable("Gadgets");
            Property(g => g.Name).IsRequired().HasMaxLength(50);
            Property(g => g.Price).IsRequired().HasPrecision(8, 2);
            Property(g => g.CategoryID).IsRequired();
        }
    }
CategoryConfiguration.cs
1
2
3
4
5
6
7
8
public class CategoryConfiguration : EntityTypeConfiguration<Category>
    {
        public CategoryConfiguration()
        {
            ToTable("Categories");
            Property(c => c.Name).IsRequired().HasMaxLength(50);
        }
    }

There isn’t any difficult configuration to explain, the point is just to understand where to put the right objects. The next thing we will do is to create the DbContext class that will be responsible to access the database. Add the following class under the root of the current project.

StoreEntities.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StoreEntities : DbContext
    {
        public StoreEntities() :base("StoreEntities") { }
 
        public DbSet<Gadget> Gadgets {get;set; }
        public DbSet<Category> Categories {get;set; }
 
        public virtual void Commit()
        {
            base.SaveChanges();
        }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new GadgetConfiguration());
            modelBuilder.Configurations.Add(new CategoryConfiguration());
        }
    }

We want to seed the database when our application fire up for the first time, so add the following class to the root of the project as well.

StoreSeedData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class StoreSeedData : DropCreateDatabaseIfModelChanges<StoreEntities>
    {
        protected override void Seed(StoreEntities context)
        {
            GetCategories().ForEach(c => context.Categories.Add(c));
            GetGadgets().ForEach(g => context.Gadgets.Add(g));
 
            context.Commit();
        }
 
        private static List<Category> GetCategories()
        {
            return new List<Category>
            {
                new Category {
                    Name ="Tablets"
                },
                new Category {
                    Name ="Laptops"
                },
                new Category {
                    Name ="Mobiles"
                }
            };
        }
 
        private static List<Gadget> GetGadgets()
        {
            return new List<Gadget>
            {
                new Gadget {
                    Name ="ProntoTec 7",
                    Description ="Android 4.4 KitKat Tablet PC, Cortex A8 1.2 GHz Dual Core Processor,512MB / 4GB,Dual Camera,G-Sensor (Black)",
                    CategoryID = 1,
                    Price = 46.99m,
                    Image ="prontotec.jpg"
                },
                new Gadget {
                    Name ="Samsung Galaxy",
                    Description ="Android 4.4 Kit Kat OS, 1.2 GHz quad-core processor",
                    CategoryID = 1,
                    Price = 120.95m,
                    Image="samsung-galaxy.jpg"
                },
                new Gadget {
                    Name ="NeuTab® N7 Pro 7",
                    Description ="NeuTab N7 Pro tablet features the amazing powerful, Quad Core processor performs approximately Double multitasking running speed, and is more reliable than ever",
                    CategoryID = 1,
                    Price = 59.99m,
                    Image="neutab.jpg"
                },
                new Gadget {
                    Name ="Dragon Touch® Y88X 7",
                    Description ="Dragon Touch Y88X tablet featuring the incredible powerful Allwinner Quad Core A33, up to four times faster CPU, ensures faster multitasking speed than ever. With the super-portable size, you get a robust power in a device that can be taken everywhere",
                    CategoryID = 1,
                    Price = 54.99m,
                    Image="dragon-touch.jpg"
                },
                new Gadget {
                    Name ="Alldaymall A88X 7",
                    Description ="This Alldaymall tablet featuring the incredible powerful Allwinner Quad Core A33, up to four times faster CPU, ensures faster multitasking speed than ever. With the super-portable size, you get a robust power in a device that can be taken everywhere",
                    CategoryID = 1,
                    Price = 47.99m,
                    Image="Alldaymall.jpg"
                },
                new Gadget {
                    Name ="ASUS MeMO",
                    Description ="Pad 7 ME170CX-A1-BK 7-Inch 16GB Tablet. Dual-Core Intel Atom Z2520 1.2GHz CPU",
                    CategoryID = 1,
                    Price = 94.99m,
                    Image="asus-memo.jpg"
                },
                // Code ommitted
            };
        }
    }

I have ommitted some of the Gadges objects for brevity but you can always download the solution project at the bottom of this post. Now let’s create the Heart of this project. Add a folder named Infrastructure. In order to use the Repository Pattern in the right manner, we need to define a well designed infrastructure. From the bottom to the top all instances will be available through injected interfaces. And the first instance we will require, guess what.. will be an instance of the StoreEntities. So let’s create a factory Interface responsible to initialize instances of this class. Add an interface named IDbFactory into the Infrastructure folder.

1
2
3
4
public interface IDbFactory : IDisposable
    {
        StoreEntities Init();
    }

You can see that this interface inherits the IDisposable one, so the Concrete class that will implement the IDbFactory interface, must also implement the IDisposable one. To do this in a clean way, add a Disposable class that will implement the IDisposable interface. Then any class that will implement the IDbFactory interface, will just want to inherit this very class.

Disposable.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Disposable : IDisposable
    {
        private bool isDisposed;
 
        ~Disposable()
        {
            Dispose(false);
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if (!isDisposed && disposing)
            {
                DisposeCore();
            }
 
            isDisposed =true;
        }
 
        // Ovveride this to dispose custom objects
        protected virtual void DisposeCore()
        {
        }
    }

I have highlighted the DisposeCore virtual method cause this method will make others classes inherit this one, to dispose their own objects in the way the need to. Now add the implementation class of the IDbFactory interface.

DbFactory.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DbFactory : Disposable, IDbFactory
    {
        StoreEntities dbContext;
 
        public StoreEntities Init()
        {
            return dbContext ?? (dbContext =new StoreEntities());
        }
 
        protected override void DisposeCore()
        {
            if (dbContext !=null)
                dbContext.Dispose();
        }
    }

It’s time to create a generic IRepository interface where we will declare the default operations that our repositories will support. Here I added some that i thought are the most used ones, but you can extend those operations as you wish.

IRepository.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IRepository<T>where T :class
    {
        // Marks an entity as new
        void Add(T entity);
        // Marks an entity as modified
        void Update(T entity);
        // Marks an entity to be removed
        void Delete(T entity);
        void Delete(Expression<Func<T,bool>>where);
        // Get an entity by int id
        T GetById(int id);
        // Get an entity using delegate
        T Get(Expression<Func<T,bool>>where);
        // Gets all entities of type T
        IEnumerable<T> GetAll();
        // Gets entities using delegate
        IEnumerable<T> GetMany(Expression<Func<T,bool>>where);
    }

Notice that the CRUD operations are commented as Mark to do something... This means that when a repository implentation adds, updates or removes an entity, does not send the command to the database at that very moment. Instead, the caller (service layer) will be responsible to send a Commit command to the database through a IUnitOfWork injected instance. For this to be done will use a pattern called UnitOfWork. Add the following two files into the Infrastructure folder.

IUnitOfWork.cs
1
2
3
4
public interface IUnitOfWork
    {
        void Commit();
    }
UnitOfWork.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UnitOfWork : IUnitOfWork
    {
        private readonly IDbFactory dbFactory;
        private StoreEntities dbContext;
 
        public UnitOfWork(IDbFactory dbFactory)
        {
            this.dbFactory = dbFactory;
        }
 
        public StoreEntities DbContext
        {
            get {return dbContext ?? (dbContext = dbFactory.Init()); }
        }
 
        public void Commit()
        {
            DbContext.Commit();
        }
    }

In the same way we used the Disposable class we are going to use an abstract class that has virtual implementations of the IRepository interface. This base class will be inherited from all specific repositories and hence will implement the IRepository interface. Add the following class.

RepositoryBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public abstract class RepositoryBase<T>where T :class
    {
        #region Properties
        private StoreEntities dataContext;
        private readonly IDbSet<T> dbSet;
 
        protected IDbFactory DbFactory
        {
            get;
            private set;
        }
 
        protected StoreEntities DbContext
        {
            get {return dataContext ?? (dataContext = DbFactory.Init()); }
        }
        #endregion
 
        protected RepositoryBase(IDbFactory dbFactory)
        {
            DbFactory = dbFactory;
            dbSet = DbContext.Set<T>();
        }
 
        #region Implementation
        public virtual void Add(T entity)
        {
            dbSet.Add(entity);
        }
 
        public virtual void Update(T entity)
        {
            dbSet.Attach(entity);
            dataContext.Entry(entity).State = EntityState.Modified;
        }
 
        public virtual void Delete(T entity)
        {
            dbSet.Remove(entity);
        }
 
        public virtual void Delete(Expression<Func<T,bool>>where)
        {
            IEnumerable<T> objects = dbSet.Where<T>(where).AsEnumerable();
            foreach (T objin objects)
                dbSet.Remove(obj);
        }
 
        public virtual T GetById(int id)
        {
            return dbSet.Find(id);
        }
 
        public virtual IEnumerable<T> GetAll()
        {
            return dbSet.ToList();
        }
 
        public virtual IEnumerable<T> GetMany(Expression<Func<T,bool>>where)
        {
            return dbSet.Where(where).ToList();
        }
 
        public T Get(Expression<Func<T,bool>>where)
        {
            return dbSet.Where(where).FirstOrDefault<T>();
        }
 
        #endregion
     
    }

Since the implementations marked as virtual, any repository can ovveride a specific operation as required. And now the Concrete repositories: Add a new folder named Repositories and add the following two classes:

GadgetRepository.cs
1
2
3
4
5
6
7
8
9
10
public class GadgetRepository : RepositoryBase<Gadget>, IGadgetRepository
    {
        public GadgetRepository(IDbFactory dbFactory)
            :base(dbFactory) { }
    }
 
    public interface IGadgetRepository : IRepository<Gadget>
    {
 
    }
CategoryRepository.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CategoryRepository : RepositoryBase<Category>, ICategoryRepository
    {
        public CategoryRepository(IDbFactory dbFactory)
            :base(dbFactory) { }
 
        public Category GetCategoryByName(string categoryName)
        {
            var category =this.DbContext.Categories.Where(c => c.Name == categoryName).FirstOrDefault();
 
            return category;
        }
 
        public override void Update(Category entity)
        {
            entity.DateUpdated = DateTime.Now;
            base.Update(entity);
        }
    }
 
    public interface ICategoryRepository : IRepository<Category>
    {
        Category GetCategoryByName(string categoryName);
    }

You can see that the GadgetRepository supports the default operations using the default behavior and of course that’s OK. On the other hand, you can see an example where a specific repository requires to either extend it’s operations (GetCategoryByName) or overried the default ones (Update). Usually you add a repository for each of your Model classes, hence each repository of type T, is responsible to manipulate a specific DbSet through the DbContext.Set<T>. We are done implementing the Data Access layer so we can procceed to the next one.

Service Layer


What operations do you want to expose your MVC Controllers? Where is the business logic is going to be implemented? Yeap.. you have guessed right, in this very layer. Add a new class library project named Store.Service. You will have to add references to the two previous created projects, Store.Model and Store.Data. Notice I haven’t told you yet to install Entity Framework to this project.. and I am not going to, cause any database operation required will be done through the injected repositories we created before. Add the first Service file to this project.

GadgetService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// operations you want to expose
    public interface IGadgetService
    {
        IEnumerable<Gadget> GetGadgets();
        IEnumerable<Gadget> GetCategoryGadgets(string categoryName,string gadgetName =null);
        Gadget GetGadget(int id);
        void CreateGadget(Gadget gadget);
        void SaveGadget();
    }
 
    public class GadgetService : IGadgetService
    {
        private readonly IGadgetRepository gadgetsRepository;
        private readonly ICategoryRepository categoryRepository;
        private readonly IUnitOfWork unitOfWork;
 
        public GadgetService(IGadgetRepository gadgetsRepository, ICategoryRepository categoryRepository, IUnitOfWork unitOfWork)
        {
            this.gadgetsRepository = gadgetsRepository;
            this.categoryRepository = categoryRepository;
            this.unitOfWork = unitOfWork;
        }
 
        #region IGadgetService Members
 
        public IEnumerable<Gadget> GetGadgets()
        {
            var gadgets = gadgetsRepository.GetAll();
            return gadgets;
        }
 
        public IEnumerable<Gadget> GetCategoryGadgets(string categoryName,string gadgetName =null)
        {
            var category = categoryRepository.GetCategoryByName(categoryName);
            return category.Gadgets.Where(g => g.Name.ToLower().Contains(gadgetName.ToLower().Trim()));
        }
 
        public Gadget GetGadget(int id)
        {
            var gadget = gadgetsRepository.GetById(id);
            return gadget;
        }
 
        public void CreateGadget(Gadget gadget)
        {
            gadgetsRepository.Add(gadget);
        }
 
        public void SaveGadget()
        {
            unitOfWork.Commit();
        }
 
        #endregion
     
    }

The first and the last highlighted code lines reminds you why we created the IUnitOfWork interface. If we wanted to create a gadget object though this service class, we would write something like this..

1
2
3
// init a gadget object..
gadgetService.CreateGadget(gadget);
gadgetService.SaveGadget();

The other highlighted code lines denotes that any required repository for this service, will be injected through it’s constructor. This will be done through a Dependency Container we will setup in the MVC project’s start up class, using the Autofac framework. In the same way I created the GadgetService.cs file.

CategoryService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// operations you want to expose
   public interface ICategoryService
   {
       IEnumerable<Category> GetCategories(string name =null);
       Category GetCategory(int id);
       Category GetCategory(string name);
       void CreateCategory(Category category);
       void SaveCategory();
   }
 
   public class CategoryService : ICategoryService
   {
       private readonly ICategoryRepository categorysRepository;
       private readonly IUnitOfWork unitOfWork;
 
       public CategoryService(ICategoryRepository categorysRepository, IUnitOfWork unitOfWork)
       {
           this.categorysRepository = categorysRepository;
           this.unitOfWork = unitOfWork;
       }
 
       #region ICategoryService Members
 
       public IEnumerable<Category> GetCategories(string name =null)
       {
           if (string.IsNullOrEmpty(name))
               return categorysRepository.GetAll();
           else
               return categorysRepository.GetAll().Where(c => c.Name == name);
       }
 
       public Category GetCategory(int id)
       {
           var category = categorysRepository.GetById(id);
           return category;
       }
 
       public Category GetCategory(string name)
       {
           var category = categorysRepository.GetCategoryByName(name);
           return category;
       }
 
       public void CreateCategory(Category category)
       {
           categorysRepository.Add(category);
       }
 
       public void SaveCategory()
       {
           unitOfWork.Commit();
       }
 
       #endregion
   }

And we are done with the service layer as well. Let’s procceed with the last one, the ASP.NET MVC Web Application.

Presentation Layer


Add a new ASP.NET Web Application named Store.Web choosing the empty template with the MVC option checked. We need to add references to all of the previous class library projects and an Entity Framework installation via Nuget Packages as well. You may be wondering, are we going to write any Entity Framework related queries in this project? Not at all, we need though some of it’s namespaces so we can setup the database configurations for our application, such as the database initializer. And since we started with this, open Global.asax.cs file and add the following line of code to setup the seed initializer we created in the Store.Data project.

Glbal.asax.cs
1
2
3
4
5
6
7
8
protected void Application_Start()
        {
            // Init database
            System.Data.Entity.Database.SetInitializer(new StoreSeedData());
 
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }

You will also need to create a connection string element to define where you want the database to be created. Add the following element in the Web.config file and changed it accoarding to your development enviroment requirements.

Web.config
1
2
3
<connectionStrings>
  <add name="StoreEntities" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=StoreDb;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

We have made such an effort in the previous steps to create repositories and services but now it’s the time to make them work all together. If you recall, all services contructors have repositories interfaces that must be injected to. The services themeselfs will later be injected in to the controllers conustructors and this is how our application will work. To achieve this, we need to setup Dependancy Injection and for thish reason I decided to use Autofac. Make sure you install Autofac ASP.NET MVC 5 Integration through Nuget Packages.

Create a Bootstrapper.cs file under the Start_App folder and paste the following code.

Bootstrapper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void Run()
        {
            SetAutofacContainer();
        }
 
        private static void SetAutofacContainer()
        {
            var builder =new ContainerBuilder();
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
            builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();
 
            // Repositories
            builder.RegisterAssemblyTypes(typeof(GadgetRepository).Assembly)
                .Where(t => t.Name.EndsWith("Repository"))
                .AsImplementedInterfaces().InstancePerRequest();
            // Services
            builder.RegisterAssemblyTypes(typeof(GadgetService).Assembly)
               .Where(t => t.Name.EndsWith("Service"))
               .AsImplementedInterfaces().InstancePerRequest();
 
            IContainer container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }

The code itself is self explanatory. I hope you have followed along with me and you have named your repository and service classes as I did, cause otherwise, this is not gonna work. There are two importants things left to complete the tutotiral. The first one to define ViewModel classes and set Automapper to map domain entities to viewmodels and backwards. The second one is to see how to setup CSS Bootstrap in our web application. I suppose most of you, install bootstrap from Nuget Packages and start adding css and script references to your project. Here though we will follow a different approach.

CSS Bootstrap

First of all download Boostrap distribution from the official site. Add three folders to your application named css, fonts and js respectively. In the css folder paste the bootstrap.css file from what you have downloaded, in the fonts folder paste everything is inside the respective fonts folder and in the js folder, just paste the bootstrap.js file. We are going to use Bundling and Minification for bootstrap and to achieve that you need to install Microsoft ASP.NET Web Optimazation Framework through Nuget Packages.

When you finish installing this, add a new class named BundleConfig into the App_Start folder as follow:

BundleConfig.cs
1
2
3
4
5
6
7
8
9
10
public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bootstrap/js").Include("~/js/bootstrap.js","~/js/site.js"));
            bundles.Add(new StyleBundle("~/bootstrap/css").Include("~/css/bootstrap.css","~/css/site.css"));
 
            BundleTable.EnableOptimizations =true;
        }
    }

As you can see I have also referenced site.js and site.css javascript and css files. Those files can host any bootstrap css customazations you wanna do or any javascript related code. Feel free to add the respective files and leave them empty. Now we need to declare that we want MVC to use bundling and minication, so add the following line into the Global.asax.cs file.

Global.asax.cs
1
2
3
4
5
6
7
8
9
10
11
12
protected void Application_Start()
        {
            // Init database
            System.Data.Entity.Database.SetInitializer(new StoreSeedData());
 
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
 
            // Autofac and Automapper configurations
            Bootstrapper.Run();
        }

Notice that I have also called the Bootstrapper.Run() function that will setup the Autofac’s configuration we made before. This function will also configure Automapper, something we gonna see in a bit. Let’s finish with Bootrap for now. We will need a Layout to use for our application, so go and create a Shared folder under the Views folder and add a new item of type MVC 5 Layout Page (Razor) named _Layout.cshtml.

_Layout.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@ViewBag.Title</title>
    <!-- Bootstrap -->
    @Styles.Render("~/bootstrap/css")
    <!--[if lt IE 9]>
    html5shiv.js"></script>
    respond.min.js"></script>
    <![endif]-->
</head>
<body>
    <nav id="myNavbar" class="navbar navbar-default navbar-inverse navbar-fixed-top" role="navigation">
        <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbarCollapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Store", "Index", "Home", new { }, new { @class = "navbar-brand" })
            </div>
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="navbarCollapse">
                <ul class="nav navbar-nav">
                    <li class="active">
                        @Html.ActionLink("Tablets", "Index", "Home", new { category = "Tablets" }, null)
                    </li>
                    <li class="active">
                        @Html.ActionLink("Laptops", "Index", "Home", new { category = "Laptops" }, null)
                    </li>
                    <li class="active">
                        @Html.ActionLink("Mobiles", "Index", "Home", new { category = "Mobiles" }, null)
                    </li>
                </ul>
            </div>
    </nav>
    @RenderBody()
    @Scripts.Render("~/bootstrap/js")
</body>
</html>

The page will probably complain that cannot resolve Razor syntax so you have to add the following using statement in the web.config file which is under the Views folder (not application’s web.config). Following is part of that file..

web.config
1
2
3
4
5
6
7
8
<namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="Store.Web" />
        <add namespace="System.Web.Optimization" />
      </namespaces>

Automapper

In a real application your domain objects will probably have a lot of properties but you only want to display some of them in the browser. More over when posting back to server, for example when creating objects though a form element, you also want to post only a few of the domain object’s properties. For this reason you define ViewModel objects and use them instead of the real domain ones. Make sure you install Automapper from Nuget Packages.

Add a new folder named ViewModels with the following classes.

GadgetViewModel.cs
1
2
3
4
5
6
7
8
9
10
public class GadgetViewModel
    {
        public int GadgetID {get;set; }
        public string Name {get;set; }
        public string Description {get;set; }
        public decimal Price {get;set; }
        public string Image {get;set; }
 
        public int CategoryID {get;set; }
    }
CategoryViewModel.cs
1
2
3
4
5
6
7
public class CategoryViewModel
    {
        public int CategoryID {get;set; }
        public string Name {get;set; }
 
        public List<GadgetViewModel> Gadgets {get;set; }
    }
GadgetFormViewModel.cs
1
2
3
4
5
6
7
8
public class GadgetFormViewModel
    {
        public HttpPostedFileBase File {get;set; }
        public string GadgetTitle {get;set; }
        public string GadgetDescription {get;set; }
        public decimal GadgetPrice {get;set; }
        public int GadgetCategory {get;set; }
    }

When your ViewModel classes have properties named as the respective domain objects, Automapper is smart enough to make the mapping through default conventions. Otherwise you have to set the mapping manualy by yourself. Notice the last class I have added, the GadgetFormViewModel. We can make a convetion to add a “Form” word before “ViewModel” so that we know that this type of view model, is posted back to server through a form element. Let’s now configure the mappings. Add a new folder Mappings and add the following class file.

AutoMapperConfiguration.cs
1
2
3
4
5
6
7
8
9
10
11
public class AutoMapperConfiguration
    {
        public static void Configure()
        {
            Mapper.Initialize(x =>
            {
                x.AddProfile<DomainToViewModelMappingProfile>();
                x.AddProfile<ViewModelToDomainMappingProfile>();
            });
        }
    }

We haven’t created the required profiles yet but we will in a bit. What I wanted to show you is that you can create as many Automapper profiles you want and then add them into Mapper.Initialize function. Here we will define two profiles, one to map domain models to ViewModels and another one for backwards. Add the following classes in the same folder as the previous.

DomainToViewModelMappingProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DomainToViewModelMappingProfile : Profile
    {
        public override string ProfileName
        {
            get {return "DomainToViewModelMappings"; }
        }
 
        protected override void Configure()
        {
            Mapper.CreateMap<Category,CategoryViewModel>();
            Mapper.CreateMap<Gadget, GadgetViewModel>();
        }
    }
ViewModelToDomainMappingProfile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ViewModelToDomainMappingProfile : Profile
    {
        public override string ProfileName
        {
            get {return "ViewModelToDomainMappings"; }
        }
 
        protected override void Configure()
        {
            Mapper.CreateMap<GadgetFormViewModel, Gadget>()
                .ForMember(g => g.Name, map => map.MapFrom(vm => vm.GadgetTitle))
                .ForMember(g => g.Description, map => map.MapFrom(vm => vm.GadgetDescription))
                .ForMember(g => g.Price, map => map.MapFrom(vm => vm.GadgetPrice))
                .ForMember(g => g.Image, map => map.MapFrom(vm => vm.File.FileName))
                .ForMember(g => g.CategoryID, map => map.MapFrom(vm => vm.GadgetCategory));
        }
    }

For the Domain -> ViewModels mapping we didn’t need to setup anything. Automapper will use the default conventions and that’s fine. For our GadgetFormViewModel -> Gadget mapping though, we set manually the configuration as shown above. The last thing remained to finish with Automapper is to add the following line in the Bootstrapper class.

Bootsrapper.cs
1
2
3
4
5
6
7
8
9
public static class Bootstrapper
    {
        public static void Run()
        {
            SetAutofacContainer();
            //Configure AutoMapper
            AutoMapperConfiguration.Configure();
        }
// Code ommitted

Controllers and Views

We are almost finished. Add a new MVC Controller named HomeController and paste the following code.

HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HomeController : Controller
    {
        private readonly ICategoryService categoryService;
        private readonly IGadgetService gadgetService;
 
        public HomeController(ICategoryService categoryService, IGadgetService gadgetService)
        {
            this.categoryService = categoryService;
            this.gadgetService = gadgetService;
        }
 
        // GET: Home
        public ActionResult Index(string category =null)
        {
            IEnumerable<CategoryViewModel> viewModelGadgets;
            IEnumerable<Category> categories;
 
            categories = categoryService.GetCategories(category).ToList();
 
            viewModelGadgets = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryViewModel>>(categories);
            return View(viewModelGadgets);
        }
    }

Now you can see in action why we have made such an effort to setup Repositories, Services, Autofac and Automapper. Services will be injected in the controller for each request and their data will be mapped to ViewModels before send to the Client. Right click in the Index action and add a View named Index with the following code. I must mention here, that the gadgets objects we use, have image references to a folder named images in the Web Application project. You can use your images or just download this project at the end of this post.

Views/Home/Index.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@model IEnumerable<Store.Web.ViewModels.CategoryViewModel>
 
@{
    ViewBag.Title ="Store";
    Layout ="~/Views/Shared/_Layout.cshtml";
}
 
<p>
 
</p>
<divclass="container">
    <divclass="jumbotron">
 
        @foreach (var itemin Model)
        {
            <divclass="panel panel-default">
                <divclass="panel-heading">
                    @*@Html.DisplayFor(modelItem => item.Name)*@
                    @Html.ActionLink("View all " + item.Name,"Index",new { category = item.Name },new { @class ="pull-right" })
                    @using (Html.BeginForm("Filter","Home",new { category = item.Name }, FormMethod.Post,new { @class ="navbar-form" }))
                    {
                        @Html.TextBox("gadgetName",null,new { @class ="form-control", placeholder ="Search in " + item.Name })
                    }
 
 
                </div>
                @foreach (var gadgetin item.Gadgets)
                {
                    @Html.Partial("Gadget", gadget)
                }
                <divclass="panel-footer">
                    @using (Html.BeginForm("Create","Home", FormMethod.Post,
                            new { enctype ="multipart/form-data", @class ="form-inline" }))
                    {
                        @Html.Hidden("GadgetCategory", item.CategoryID)
                        <divclass="form-group">
                            <labelclass="sr-only" for="file">File</label>
                            <input type="file" class="form-control" name="file" placeholder="Select picture..">
                        </div>
                        <divclass="form-group">
                            <labelclass="sr-only" for="gadgetTitle">Title</label>
                            <input type="text" class="form-control" name="gadgetTitle" placeholder="Title">
                        </div>
                        <divclass="form-group">
                            <labelclass="sr-only" for="gadgetName">Price</label>
                            <input type="number" class="form-control" name="gadgetPrice" placeholder="Price">
                        </div>
                        <divclass="form-group">
                            <labelclass="sr-only" for="gadgetName">Description</label>
                            <input type="text" class="form-control" name="gadgetDescription" placeholder="Description">
                        </div>
                        <button type="submit" class="btn btn-primary">Upload</button>
                    }
                </div>
            </div>
        }
 
    </div>
 
</div>

Two things to notice here. The first one is that we need to create a Partial view to diplay a GadgetViewModelobject and the second one is the Form’s control element’s names. You can see that they much our GadgetFormViewModel properties. Under the Shared folder create the following Partial view for displaying a GadgetViewModel object.

Views/Shared/Gadget.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@model Store.Web.ViewModels.GadgetViewModel
 
<div class="panel-body">
    <div class="media">
        <a class="pull-left" href="#">
            <img class="media-object" src="../../images/@Model.Image" />
        </a>
        <div class="media-body">
            <h3 class="media-heading">
                @Model.Name
            </h3>
            <p>@Model.Description</p>
        </div>
    </div>
</div>

In the Index.cshtml page I have added search and filter functionality and Create gadget as well. To achieve that you need to add the following Action methods to the HomeController.

HomeController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public ActionResult Filter(string category,string gadgetName)
        {
            IEnumerable<GadgetViewModel> viewModelGadgets;
            IEnumerable<Gadget> gadgets;
 
            gadgets = gadgetService.GetCategoryGadgets(category, gadgetName);
 
            viewModelGadgets = Mapper.Map<IEnumerable<Gadget>, IEnumerable<GadgetViewModel>>(gadgets);
 
            return View(viewModelGadgets);
        }
 
        [HttpPost]
        public ActionResult Create(GadgetFormViewModel newGadget)
        {
            if (newGadget !=null && newGadget.File !=null)
            {
                var gadget = Mapper.Map<GadgetFormViewModel, Gadget>(newGadget);
                gadgetService.CreateGadget(gadget);
 
                string gadgetPicture = System.IO.Path.GetFileName(newGadget.File.FileName);
                string path = System.IO.Path.Combine(Server.MapPath("~/images/"), gadgetPicture);
                newGadget.File.SaveAs(path);
 
                gadgetService.SaveGadget();
            }
 
            var category = categoryService.GetCategory(newGadget.GadgetCategory);
            return RedirectToAction("Index",new { category = category.Name });
        }

I am sure that at this point you understand the purpose of all the above code so I won’t explain anything. You need to add a Filter page so right click in the Filter action and create the following View.

Home/Views/Filter.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@model IEnumerable<Store.Web.ViewModels.GadgetViewModel>
 
@{
    ViewBag.Title = "Filter";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<div class="container">
    <div class="jumbotron">
 
        @foreach (var item in Model)
        {
            <div class="panel panel-default">
                <div class="panel-heading">
                    @Html.Label(item.Name)
                </div>
                @Html.Partial("Gadget", item)
            </div>
        }
 
    </div>
</div>


You can filter gadgets by category or search gadgets in a specific category. That’s it, we have finished creating a highly decoupled architecture that could support large scale MVC applications.

Github

I have decided to move all of my code to my Github account so you can download most of the projects that we have seen on this blog from there. You can download this project from here. I hope you enjoyed this post as much as I did.

In case you find my blog’s content interesting, register your email to receive notifications of new posts and follow chsakell’s Blog on its Facebook or Twitter accounts.

LINK: https://chsakell.com/2015/02/15/asp-net-mvc-solution-architecture-best-practices/