Async Asp.Net MVC Solution Architecture with Repository Pattern by Unity via Service Layer
Introduction
Asp.Net MVC Solution Architecture project presents a flexible layered architecture is inspired from some of the best solutions structure's available incorporating the best practices like Async Repository Pattern, Dependency Injection in ASP.NET MVC using Unity IoC Container, Entity Framework, POCO classes and Application Service layer for BAL.
It re-uses the Entity models in various layers. At the same time other View Models for UI and Business Models can be introduced as when required for complex operations.
Background
ASP.Net comes up with the default solution template with separation of concerns. But at times when we prepare solution architecture we need to have the flexibility built in the DAL, BAL and Presentation layers to take care of dynamic scenarios. What if apart from the ASP.Net MVC one also needs a Web API. What if the Entity Framework needs to be replaced with other object-relational mapper. How easy it would be to modify one layer without touching other layers?
Using the code
First the database was created as database first approach was used. The detail code can be found and further contribution can be made in GitHub
The demo uses a simple table created in SQL Server Express:
StarDesc Table in Database
CREATE TABLE [dbo].[StarDesc] (
[StarName] NVARCHAR (150) NULL,
[StarSize] NVARCHAR (50) NULL,
[StarDistanceFromSun] NVARCHAR (50) NULL,
[StarGalaxyName] NVARCHAR (50) NULL,
[StarBrightness] NVARCHAR (50) NULL,
[SpectralType] NVARCHAR (50) NULL,
[Id] INT IDENTITY (1, 1) NOT NULL,
CONSTRAINT [PK_StarDesc] PRIMARY KEY CLUSTERED ([Id] ASC)
);
SET IDENTITY_INSERT [dbo].[StarDesc] ON
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'10 Lacertae', N'8.27', N'2300', N'Lacerta OB1', N'-4.40', N'O9V', 1)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Rigel', N'79', N'863', N'Orion', N'-7.84', N'B8Ia', 2)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Sirius ', N'1.71', N'8.6', N'Canis Major', N'1.42', N'A1V', 3)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Canopus', N'71', N'320', N'Carina', N'-5.71', N'A9II', 4)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Sun', N'1', N'0', N'Milky Way', N'4.83', N'G2V', 5)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Arcturus', N'25.4', N'36.66', N'Bootes', N'-0.32', N'K0III', 6)
INSERT INTO [dbo].[StarDesc] ([StarName], [StarSize], [StarDistanceFromSun], [StarGalaxyName], [StarBrightness], [SpectralType], [Id]) VALUES (N'Betelgeuse', N'1180', N'642.5', N'Orion', N'-5.85', N'M1Ia', 7)
SET IDENTITY_INSERT [dbo].[StarDesc] OFF
The entire Solution structure is shown in the snapshots below
Infrastructure
Domain
Presentation
Using the 'Entity Framework Power Tools Beta 4 (Visual Studios Extension)' we auto-generate the entity classes, mapper classes and the database context class, namely 'StarDesc.cs', 'StarDescMap.cs' and 'StarSystemContext.cs'.
StarDesc.cs
namespace CG.StarSystem.Data.Models
{
public partial class StarDesc
{
public int Id { get; set; }
public string StarName { get; set; }
public string StarSize { get; set; }
public string StarDistanceFromSun { get; set; }
public string StarGalaxyName { get; set; }
public string StarBrightness { get; set; }
public string SpectralType { get; set; }
}
}
StarDescMap.cs
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
namespace CG.StarSystem.Data.Models.Mapping
{
public class StarDescMap : EntityTypeConfiguration<StarDesc>
{
public StarDescMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(t => t.StarName)
.HasMaxLength(150);
this.Property(t => t.StarSize)
.HasMaxLength(50);
this.Property(t => t.StarDistanceFromSun)
.HasMaxLength(50);
this.Property(t => t.StarGalaxyName)
.HasMaxLength(50);
this.Property(t => t.StarBrightness)
.HasMaxLength(50);
this.Property(t => t.SpectralType)
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("StarDesc");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.StarName).HasColumnName("StarName");
this.Property(t => t.StarSize).HasColumnName("StarSize");
this.Property(t => t.StarDistanceFromSun).HasColumnName("StarDistanceFromSun");
this.Property(t => t.StarGalaxyName).HasColumnName("StarGalaxyName");
this.Property(t => t.StarBrightness).HasColumnName("StarBrightness");
this.Property(t => t.SpectralType).HasColumnName("SpectralType");
}
}
}
StarSystemContext.cs
using System.Data.Entity;
using CG.StarSystem.Data.Models.Mapping;
namespace CG.StarSystem.Data.Models
{
public partial class StarSystemContext : DbContext
{
static StarSystemContext()
{
Database.SetInitializer<StarSystemContext>(null);
}
public StarSystemContext()
: base("Name=StarSystemContext")
{
// the terrible hack
var ensureDLLIsCopied =
System.Data.Entity.SqlServer.SqlProviderServices.Instance;
}
public DbSet<SpectralClassesSubType> SpectralClassesSubTypes { get; set; }
public DbSet<StarDesc> StarDescs { get; set; }
public DbSet<StarTypeByLuminosityClass> StarTypeByLuminosityClasses { get; set; }
public DbSet<StarTypeBySpectralClass> StarTypeBySpectralClasses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SpectralClassesSubTypeMap());
modelBuilder.Configurations.Add(new StarDescMap());
modelBuilder.Configurations.Add(new StarTypeByLuminosityClassMap());
modelBuilder.Configurations.Add(new StarTypeBySpectralClassMap());
}
}
}
IStarDescRepository.cs
using CG.StarSystem.Data.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CG.StarSystem.Repository.StarDescs
{
public interface IStarDescRepository: IDisposable
{
Task<List<StarDesc>> GetStarDescsAsync();
Task<StarDesc> GetStarDescByIdAsync(int? id);
Task CreateStarAsync(StarDesc stardesc);
Task DeleteStarAsync(int? id);
Task EditStarDescAsync(StarDesc stardesc);
}
}
StarDescRepository.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CG.StarSystem.Data.Models;
using System.Data.Entity;
namespace CG.StarSystem.Repository.StarDescs
{
public class StarDescRepository : IStarDescRepository, IDisposable
{
private StarSystemContext _context;
/// <summary>
/// Create a new instance of <see cref="StarDescRepository" />.
/// </summary>
/// <param name="transaction">Active transaction</param>
/// <exception cref="ArgumentNullException">transaction</exception>
public StarDescRepository()
{
_context = new StarSystemContext();
}
public async Task<List<StarDesc>> GetStarDescsAsync()
{
return await _context.StarDescs.ToListAsync();
}
public async Task<StarDesc> GetStarDescByIdAsync(int? id)
{
return await _context.StarDescs.FindAsync(id);
}
public async Task CreateStarAsync(StarDesc stardesc)
{
_context.StarDescs.Add(stardesc);
await _context.SaveChangesAsync();
}
public async Task EditStarDescAsync(StarDesc stardesc)
{
_context.Entry(stardesc).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
public async Task DeleteStarAsync(int? id)
{
StarDesc stardesc = await _context.StarDescs.FindAsync(id);
_context.StarDescs.Remove(stardesc);
await _context.SaveChangesAsync();
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_context.Dispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
IStarDescService.cs
using CG.StarSystem.Data.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CG.StarSystem.ApplicationServices
{
public interface IStarDescService : IDisposable
{
Task<List<StarDesc>> GetAllStarsAsync();
Task<StarDesc> GetStarDescriptionByIdAsync(int? id);
Task AddStarAsync(StarDesc stardesc);
Task DeleteStarAsync(int? id);
Task EditStarDescAsync(StarDesc stardesc);
}
}
StarDescService.cs
using CG.StarSystem.Data.Models;
using CG.StarSystem.Repository.StarDescs;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CG.StarSystem.ApplicationServices
{
public class StarDescService : IStarDescService, IDisposable
{
private readonly IStarDescRepository _StarDescRepository;
//public StarDescService(IStarDescRepository IStarDescRepository) {
// _IStarDescRepository = IStarDescRepository;
//}
public StarDescService()
{
_StarDescRepository = new StarDescRepository();
}
public async Task<List<StarDesc>> GetAllStarsAsync()
{
return await _StarDescRepository.GetStarDescsAsync();
}
public async Task<StarDesc> GetStarDescriptionByIdAsync(int? id)
{
return await _StarDescRepository.GetStarDescByIdAsync(id);
}
public async Task AddStarAsync(StarDesc stardesc)
{
await _StarDescRepository.CreateStarAsync(stardesc);
}
public async Task DeleteStarAsync(int? id)
{
await _StarDescRepository.DeleteStarAsync(id);
}
public async Task EditStarDescAsync(StarDesc stardesc)
{
await _StarDescRepository.EditStarDescAsync(stardesc);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_StarDescRepository.Dispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
StarDescController.cs
using System.Collections.Generic;
using System.Web.Mvc;
using CG.StarSystem.ApplicationServices;
using CG.StarSystem.Data.Models;
using System.Threading.Tasks;
using System.Net;
namespace CG.StarSystem.Web.Controllers
{
public class StarDescController : Controller
{
private readonly IStarDescService _service;
public StarDescController(IStarDescService service) {
_service = service;
}
// GET: StarDesc
public async Task<ActionResult> Index()
{
IList<StarDesc> stars = await _service.GetAllStarsAsync();
return View(stars);
}
public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
StarDesc star = await _service.GetStarDescriptionByIdAsync(id);
if (star == null)
{
return HttpNotFound();
}
return View(star);
}
// GET: Notes/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "StarName,StarSize,StarDistanceFromSun,StarGalaxyName,StarBrightness,SpectralType")] StarDesc stardesc)
{
if (ModelState.IsValid)
{
await _service.AddStarAsync(stardesc);
return RedirectToAction("Index");
}
return View(stardesc);
}
// GET: Notes/Edit/5
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
StarDesc stardesc = await _service.GetStarDescriptionByIdAsync(id);
if (stardesc == null)
{
return HttpNotFound();
}
return View(stardesc);
}
// POST: Notes/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,StarName,StarSize,StarDistanceFromSun,StarGalaxyName,StarBrightness,SpectralType")] StarDesc stardesc)
{
if (ModelState.IsValid)
{
await _service.EditStarDescAsync(stardesc);
return RedirectToAction("Index");
}
return View(stardesc);
}
// GET: Notes/Delete/5
public async Task<ActionResult> Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
StarDesc stardesc = await _service.GetStarDescriptionByIdAsync(id);
if (stardesc == null)
{
return HttpNotFound();
}
return View(stardesc);
}
// POST: Notes/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
await _service.DeleteStarAsync(id);
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_service.Dispose();
}
base.Dispose(disposing);
}
}
}
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>
Index.cshtml
@using CG.StarSystem.Data.Models;
@model IEnumerable<StarDesc>
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
ViewBag.Title = "Star System";
}
<h2>Star List</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table border="1">
<tr>
<th>
@Html.DisplayNameFor(model => model.Id)
</th>
<th>
@Html.DisplayNameFor(model => model.StarName)
</th>
<th>
@Html.DisplayNameFor(model => model.StarSize)
</th>
<th>
@Html.DisplayNameFor(model => model.StarDistanceFromSun)
</th>
<th>
@Html.DisplayNameFor(model => model.StarGalaxyName)
</th>
<th>
@Html.DisplayNameFor(model => model.StarBrightness)
</th>
<th>
@Html.DisplayNameFor(model => model.SpectralType)
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.StarName)
</td>
<td>
@Html.DisplayFor(modelItem => item.StarSize)
</td>
<td>
@Html.DisplayFor(modelItem => item.StarDistanceFromSun)
</td>
<td>
@Html.DisplayFor(modelItem => item.StarGalaxyName)
</td>
<td>
@Html.DisplayFor(modelItem => item.StarBrightness)
</td>
<td>
@Html.DisplayFor(modelItem => item.SpectralType)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
@Html.ActionLink("Details", "Details", new { id = item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
Details.cshtml
@using CG.StarSystem.Data.Models;
@model StarDesc
@{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
}
</style>
<h2>Details</h2>
<div>
<h4>Star Description</h4>
<hr />
<dl class="dl-horizontal" border="1">
<dt>
@Html.DisplayNameFor(model => model.StarName)
</dt>
<dd>
@Html.DisplayFor(model => model.StarName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StarSize)
</dt>
<dd>
@Html.DisplayFor(model => model.StarSize)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StarDistanceFromSun)
</dt>
<dd>
@Html.DisplayFor(model => model.StarDistanceFromSun)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StarGalaxyName)
</dt>
<dd>
@Html.DisplayFor(model => model.StarGalaxyName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StarBrightness)
</dt>
<dd>
@Html.DisplayFor(model => model.StarBrightness)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SpectralType)
</dt>
<dd>
@Html.DisplayFor(model => model.SpectralType)
</dd>
</dl>
</div>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Back to List", "Index")
</p>
Create.cshtml, Delete.cshtml & Edit.cshtml were generated by scaffolding the views from controller action methods.
The configuration files codes of various project are given below
In the project CG.StarSystem.Data
App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration>
In the project CG.StarSystem.Repository
App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
</configuration>
In the project CG.StarSystem.Web
Web.config
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=301880
-->
<configuration>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5.2" />
<httpRuntime targetFramework="4.5.2" />
</system.web>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
<connectionStrings>
<add name="StarSystemContext" connectionString="Data Source=pgcg\sqlexpress;Initial Catalog=StarSystem;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Finally the Unity Config file in the project CG.StarSystem.Web
UnityConfig.cs
using System;
using Microsoft.Practices.Unity;
//using CG.StarSystem.Repository;
using CG.StarSystem.ApplicationServices;
//using CG.StarSystem.Repository.StarDescs;
namespace CG.StarSystem.Web.App_Start
{
/// <summary>
/// Specifies the Unity configuration for the main container.
/// </summary>
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IUnityContainer, UnityContainer >(new PerRequestLifetimeManager());
container.RegisterType<IStarDescService, StarDescService>(new PerRequestLifetimeManager());
//container.RegisterType<IStarDescRepository, StarDescRepository>(new PerRequestLifetimeManager());
}
}
}
Points of Interest
1. Project made in Visual Studios Community Edition 2015.
2. The 'Entity Framework Power Tools Beta 4 Extension's file named 'extension.vsixmanifest' had to be modified. It was used for 'Reverese Engineer Code First' for generating POCO classes.
3. Almost ended up by using 'Grifin DAL Repo Generator' but deleted the codes as it uses ADO.Net transactions and not entity framework.
4. Please check the video in point 2 below if the code looks over whelming. The entire project can be made in less than an hour by using visual studios extensions and tools. Also community edition has 'Resharper' in built.
Bibliography
1. https://stackoverflow.com/questions/37724049/using-repository-patern-when-using-async-await-methods-asp-net-mvc5-ef
2. URF (Unit of Work & Repository Framework) in ASP.NET MVC 5 with Entity Framework 6 & Unity 3 - v2
https://www.youtube.com/watch?v=QwwfTWMrM9k
3. http://www.itworld.com/article/2700950/development/a-generic-repository-for--net-entity-framework-6-with-async-operations.html
4. http://www.dotnetcurry.com/ShowArticle.aspx?ID=617
5. https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/async-and-stored-procedures-with-the-entity-framework-in-an-asp-net-mvc-application
6. http://thedatafarm.com/data-access/installing-ef-power-tools-into-vs2015/
7. http://hannesdorfmann.com/android/evolution-of-the-repository-pattern
History
1. Made the method asynchronous across the layers like View, Controller, Services and Repository
2. Initially had the repository class creation from the UnityConfig.cs, which was changed. Now the Repository class instances are created from the Service layer so that there would be no dependency of repository later with the Presentation Layer.
3. Had to add the lines below in StarSystemContext.cs
// the terrible hack
var ensureDLLIsCopied =
System.Data.Entity.SqlServer.SqlProviderServices.Instance;