Getting Started With ASP.NET Core 5.0 MVC Web Application Using Dapper ORM And SQL Server
Hi All,
2. Next is to create a new ASP.NET Core 5.0 MVC Web Application using Visual Studio 2019.
3. Then add a new folder called Data Access Layer with two subfolders specifically Repository and UnitOfWork. The project structure is identical with my previous tutorials on RepoDB and Entity Framework Core. 4. Add Dapper and System.Data.SqlClient NuGet packages in your project. At the time of writing, the version of Dapper installed is 2.0.90. 5. Install Font-Awesome 5.x version via Library Manager in Visual Studio. 6. In appsettings.json of the project, add a connection string to the database that holds the customer information. Replace the server and database name with the actual server and db names in your computer.
2. Create a class called GenericRepository that implements the IRepository interface. This class has two variables which is an IConfiguration that retrieve the connection string value from appsettings.json and a connection string. This file also contains several properties for the query statements and SQL parameters used by Dapper. In the constructor method, set the value for the IConfiguration variable using constructor injection. Once initialized, use the GetConnectionString() method to retrieve the connection string value and store it to the class level variable. This class also implements the methods defined in IRepository interface utilizing Dapper ORM built-in functions for manipulating database information.
3. Add a ICustomerRepository interface that inherits the IRepository interface. This interface has two function definitions. First function definition called SetInsertParams will set the save parameters and SetUpdateParams for the update parameters.
4. Create a CustomerRepository class that inherits the GenericRepository class and implements the ICustomerRepository interface SetInsertParams and SetUpdateParams methods.
5. Inside your UnitOfWork folder, create an IUnitOfWork interface with a CustomerRepository property of type ICustomerRepository without a setter.
6. Define a UnitOfWork class that implements the IUnitOfWork interface and has a constructor that will assign the CustomerRepository property. The constructor injection mechanism will be handled by the ConfigureServices method in Startup.cs file through the AddTransient() method.
7. In Startup.cs, create multiple registrations through Dependency Injection by calling the AddTransient() method for the service type which is the first parameter and it's implementation respectively.
2. Index.cshtml - This page has a table that will be populated with records retrieved from the Customer table. Each row has a delete and edit button that redirects to delete and edit pages. The page also has a new button that opens up the add new customer record.
3. Create.cshtml - This view oversee's the adding of new customer to the database.
4. Edit.cshtml - This page manage the updating of existing customer from the database. The form layout is identical to the Create page except that it has a hidden field for the Customer ID.
5. Delete.cshtml - This page will handle deletion of a customer record. When the form is submitted, it will call the Delete method in the controller and remove that record from the database based from the Customer ID.
6. Customer.css - This file contains custom css styles for the views.
Output
In this tutorial, I will demonstrate on how to create an ASP.NET Core 5.0 MVC CRUD Web Application using Dapper ORM which is considered as the King of Micro ORM and an alternative to both Entity Framework and RepoDB ORM. I have published ASP.NET MVC and ASP.NET Webforms articles before using Dapper ORM in this blog and have used this ORM in an internal application before. With the birth of .NET Core framework, it's time to join the bandwagon of promoting these type of tools that helped maximize the productivity of the developers by creating articles as guide or reference. Enough with the chitchat and lets start coding. :-)
I. Project Setup
1. Create a Customer table in a SQL Server database using the script below.USE [Your_Database] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Customer]( [CustomerID] [numeric](18, 0) IDENTITY(1,1) NOT NULL, [CompanyName] [varchar](40) NULL, [Address] [varchar](35) NULL, [City] [varchar](35) NULL, [State] [varchar](35) NULL, [IntroDate] [datetime] NULL, [CreditLimit] [decimal](18, 2) NULL, CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ( [CustomerID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
3. Then add a new folder called Data Access Layer with two subfolders specifically Repository and UnitOfWork. The project structure is identical with my previous tutorials on RepoDB and Entity Framework Core. 4. Add Dapper and System.Data.SqlClient NuGet packages in your project. At the time of writing, the version of Dapper installed is 2.0.90. 5. Install Font-Awesome 5.x version via Library Manager in Visual Studio. 6. In appsettings.json of the project, add a connection string to the database that holds the customer information. Replace the server and database name with the actual server and db names in your computer.
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "ASPCoreRepoDB": "Server=Your_server;Database=Your_DB;Integrated Security=True" } }
II. Coding The Model classes
1. In the Models folder of the project created, add a new class called Customer that has property names similar to the table fields you just created using the script in Project Setup section. The properties will be used for storing information which is bound to the controls in the page.public class Customer { public int CustomerID { get; set; } [Display(Name = "Company Name")] public string CompanyName { get; set; } [Display(Name = "Address")] public string Address { get; set; } [Display(Name = "City")] public string City { get; set; } [Display(Name = "State")] public string State { get; set; } [Display(Name = "Intro Date")] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime IntroDate { get; set; } [Display(Name = "Credit Limit")] [DisplayFormat(DataFormatString = "{0:n2}")] public decimal CreditLimit { get; set; } }
III. Coding The Data Access Classes And Interfaces (Repository and Unit Of Work)
In this section, we will create inferaces and classes using the Repository Design Pattern and Unit Of Work. The repository classes will contain queries and methods that will utilize Dapper methods for handling CRUD (Create/Update/Delete) operations. For further explanation, please refer to this page Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application provided by Microsoft.
1. Add an IRepository interface with a generic type and common function definitions for CRUD operations such as FindByID(), GetAll(), Add(), Delete() and Update().
public interface IRepository<T> where T : class{ T FindByID(int ID); IEnumerable<T> GetAll(); int Add(); int Update(); int Delete(int ID); }
public class GenericRepository<T> : IRepository<T> where T : class{ private readonly IConfiguration _configuration; private readonly string connectionString; public string InsertQuery { get; set; } public string UpdateQuery { get; set; } public string GetByIDQuery { get; set; } public string DeleteQuery { get; set; } public string GetAllQuery { get; set; } public List<SqlParameter> InsertParams { get; set; } public List<SqlParameter> UpdateParams { get; set; } public GenericRepository(IConfiguration configuration) { _configuration = configuration; connectionString = _configuration.GetConnectionString("ASPCoreRepoDB"); InsertQuery = string.Empty; UpdateQuery = string.Empty; GetByIDQuery = string.Empty; DeleteQuery = string.Empty; GetAllQuery = string.Empty; InsertParams = new List<SqlParameter>(); UpdateParams = new List<SqlParameter>(); } public int Add() { int affectedRows = 0; try { var args = new DynamicParameters(new { }); InsertParams.ForEach(p => args.Add(p.ParameterName, p.Value)); using (var connection = new SqlConnection(this.connectionString)) { affectedRows = connection.Execute(InsertQuery, args); } } catch (Exception ex) { return affectedRows; } return affectedRows; } public int Update() { int affectedRows = 0; try { var args = new DynamicParameters(new { }); UpdateParams.ForEach(p => args.Add(p.ParameterName, p.Value)); using (var connection = new SqlConnection(this.connectionString)) { affectedRows = connection.Execute(UpdateQuery, args); } } catch (Exception ex) { return affectedRows; } return affectedRows; } public int Delete(int ID) { int affectedRows = 0; try { using (var connection = new SqlConnection(this.connectionString)) { affectedRows = connection.Execute(DeleteQuery, new { ID = ID }); } } catch (Exception ex) { return affectedRows; } return affectedRows; } public T FindByID(int ID) { T result; try { using (var connection = new SqlConnection(this.connectionString)) { result = connection.Query<T>(GetByIDQuery, new { ID = ID }).FirstOrDefault(); } } catch (Exception ex) { return null; } return result; } public IEnumerable<T> GetAll() { List<T> lstRecords = new List<T>(); try { using (var connection = new SqlConnection(this.connectionString)) { lstRecords = connection.Query<T>(GetAllQuery).ToList(); } } catch (Exception ex) { return null; } return lstRecords; } }
public interface ICustomerRepository : IRepository<Customer> { public void SetInsertParams(Customer customer); public void SetUpdateParams(Customer customer); }
public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository { public CustomerRepository(IConfiguration _configuration) : base(_configuration) { SetQueries(); } private void SetQueries() { GetAllQuery = "SELECT* From Customer;"; GetByIDQuery = "SELECT * FROM Customer WHERE CustomerID=@ID"; DeleteQuery = "DELETE FROM Customer WHERE CustomerID = @ID"; InsertQuery = "INSERT INTO Customer(CompanyName,Address,City,State,IntroDate,CreditLimit)" + " Values(@CompanyName,@Address,@City,@State,@IntroDate,@CreditLimit)"; UpdateQuery = " UPDATE Customer SET CompanyName = @CompanyName,Address = @Address, " + " City = @City,State = @State,IntroDate = @IntroDate,CreditLimit = @CreditLimit" + " WHERE CustomerID = @CustomerID"; } public void SetInsertParams(Customer customer) { InsertParams = new List<SqlParameter>() { new SqlParameter("@CompanyName", customer.CompanyName), new SqlParameter("@Address",customer.Address), new SqlParameter("@City",customer.City), new SqlParameter("@State",customer.State), new SqlParameter("@IntroDate",customer.IntroDate), new SqlParameter("@CreditLimit",customer.CreditLimit) }; } public void SetUpdateParams(Customer customer) { UpdateParams = new List<SqlParameter>() { new SqlParameter("@CustomerID",customer.CustomerID), new SqlParameter("@CompanyName",customer.CompanyName), new SqlParameter("@Address",customer.Address), new SqlParameter("@City",customer.City), new SqlParameter("@State",customer.State), new SqlParameter("@IntroDate",customer.IntroDate), new SqlParameter("@CreditLimit",customer.CreditLimit) }; } }
public interface IUnitOfWork { ICustomerRepository CustomerRepository { get; } }
public class UnitOfWork : IUnitOfWork { public ICustomerRepository CustomerRepository { get; private set; } public UnitOfWork(ICustomerRepository customerRepository) { CustomerRepository = customerRepository; } }
public void ConfigureServices(IServiceCollection services) { services.AddTransient(typeof(IRepository<>), typeof(GenericRepository<>)); services.AddTransient<ICustomerRepository, CustomerRepository>(); services.AddTransient<IUnitOfWork, UnitOfWork>(); services.AddControllersWithViews(); }
IV. Coding The Controller
In the HomeController.cs class, create a class level Unit Of Work variable which will then be assigned a value through the HomeController constructor. For this to work, you already have mapped the service type and it's implementation in Startup.cs file's ConfigureServices method. Next is to generate ActionResult methods that will show all customers to the view, add a new customer to the database, update customer information and delete a specific record from the database. Each ActionResult method will call the CRUD (Create/Update/Delete) methods of the repository class utilizing some of Dapper ORM's built-in functions through the Unit Of Work variable.
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; private IUnitOfWork _unitOfWork; public HomeController(ILogger<HomeController> logger, IUnitOfWork unitOfWork) { _logger = logger; _unitOfWork = unitOfWork; } public IActionResult Index() { List<Customer> model; model = new List<Customer>(); model = _unitOfWork.CustomerRepository.GetAll().ToList(); return View(model); } [HttpGet] public IActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(Customer customer) { if (ModelState.IsValid) { _unitOfWork.CustomerRepository.SetInsertParams(customer); _unitOfWork.CustomerRepository.Add(); return RedirectToAction("Index"); } return View("Index"); } [HttpGet] public IActionResult Edit(int? id) { Customer model; model = new Customer(); if (id == null) return NotFound(); model = _unitOfWork.CustomerRepository.FindByID(Convert.ToInt32(id)); if (model == null) return NotFound(); return View(model); } [HttpPost] public ActionResult Edit(Customer customer) { try { _unitOfWork.CustomerRepository.SetUpdateParams(customer); _unitOfWork.CustomerRepository.Update(); return RedirectToAction("Index"); } catch { return View(); } } [HttpGet] public IActionResult Delete(int? id) { Customer model; model = new Customer(); if (id == null) return NotFound(); model = _unitOfWork.CustomerRepository.FindByID(Convert.ToInt32(id)); if (model == null) return NotFound(); return View(model); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(Customer customer) { try { _unitOfWork.CustomerRepository.Delete(customer.CustomerID); return RedirectToAction("Index"); } catch { return View(); } } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
V. Coding The Views
1. Layout.cshtml - Reference the jQuery libraries and fontawesome css files specifically fontawesome.css, regular.min.css and solid.min.css.<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - XYZ Customer Information System</title> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" /> </head> <body> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> <link rel="stylesheet" href="~/lib/font-awesome/css/fontawesome.css" /> <link rel="stylesheet" href="~/lib/Font-Awesome/css/regular.min.css"> <link rel="stylesheet" href="~/lib/Font-Awesome/css/solid.min.css"> @await RenderSectionAsync("Scripts", required: false) </body> </html>
@model IEnumerable<ASPCoreMVCDapper.Models.Customer> @{ ViewData["Title"] = "Index"; } <div id="divIndexHeader"> <h3>Athletes Record Monitoring</h3> </div> <div id="divIndexCreate" class="row"> <a asp-action="Create" class="btn btn-primary"> <i class="fas fa-plus"><span> Add Customer</span></i> </a> </div> <div class="row"> <table id="tblRecords" class="table table-bordered table-striped"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.CustomerID) </th> <th> @Html.DisplayNameFor(model => model.CompanyName) </th> <th> @Html.DisplayNameFor(model => model.Address) </th> <th> @Html.DisplayNameFor(model => model.City) </th> <th> @Html.DisplayNameFor(model => model.State) </th> <th> @Html.DisplayNameFor(model => model.IntroDate) </th> <th> @Html.DisplayNameFor(model => model.CreditLimit) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.CustomerID) </td> <td> @Html.DisplayFor(modelItem => item.CompanyName) </td> <td> @Html.DisplayFor(modelItem => item.Address) </td> <td> @Html.DisplayFor(modelItem => item.City) </td> <td> @Html.DisplayFor(modelItem => item.State) </td> <td> @Html.DisplayFor(modelItem => item.IntroDate) </td> <td> @Html.DisplayFor(modelItem => item.CreditLimit) </td> <td> <a asp-action="Edit" title="Edit Customer" asp-route-id="@item.CustomerID"> <i class="fas fa-edit"></i> </a> <a asp-action="Delete" title="Delete Customer" asp-route-id="@item.CustomerID"> <i class="fas fa-trash"></i> </a> </td> </tr> } </tbody> </table> </div> @section scripts{ <link href="~/css/Customer.css" rel="stylesheet" /> }
@model ASPCoreMVCDapper.Models.Customer @{ ViewData["Title"] = "Create"; } <div> <h3>Add New Customer</h3> </div> <hr /> <div class="row"> <div class="col-6"> <form asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group row"> <label asp-for="CompanyName" class="control-label col-4"></label> <div class="col-8"> <input asp-for="CompanyName" class="form-control" /> <span asp-validation-for="CompanyName" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="Address" class="control-label col-4"></label> <div class="col-8"> <input asp-for="Address" class="form-control" /> <span asp-validation-for="Address" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="City" class="control-label col-4"></label> <div class="col-8"> <input asp-for="City" class="form-control" /> <span asp-validation-for="City" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="State" class="control-label col-4"></label> <div class="col-8"> <input asp-for="State" class="form-control" /> <span asp-validation-for="State" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="IntroDate" class="control-label col-4"></label> <div class="col-8"> <input asp-for="IntroDate" type="date" class="form-control" /> <span asp-validation-for="IntroDate" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="CreditLimit" class="control-label col-4"></label> <div class="col-8"> <input asp-for="CreditLimit" class="form-control" /> <span asp-validation-for="CreditLimit" class="text-danger"></span> </div> </div> <div class="form-group row"> <div class="col-4"></div> <div class="col-8"> <input type="submit" value="Save" class="btn btn-primary" /> <a asp-action="Index" class="btn btn-info">Back to List</a> </div> </div> </form> </div> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
@model ASPCoreMVCDapper.Models.Customer @{ ViewData["Title"] = "Edit"; } <div> <h3>Edit Customer</h3> </div> <hr /> <div class="row"> <div class="col-md-6"> <form asp-action="Edit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="CustomerID" /> <div class="form-group row"> <label asp-for="CompanyName" class="control-label col-4"></label> <div class="col-8"> <input asp-for="CompanyName" class="form-control" /> <span asp-validation-for="CompanyName" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="Address" class="control-label col-4"></label> <div class="col-8"> <input asp-for="Address" class="form-control" /> <span asp-validation-for="Address" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="City" class="control-label col-4"></label> <div class="col-8"> <input asp-for="City" class="form-control" /> <span asp-validation-for="City" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="State" class="control-label col-4"></label> <div class="col-8"> <input asp-for="State" class="form-control" /> <span asp-validation-for="State" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="IntroDate" class="control-label col-4"></label> <div class="col-8"> <input asp-for="IntroDate" type="date" class="form-control" /> <span asp-validation-for="IntroDate" class="text-danger"></span> </div> </div> <div class="form-group row"> <label asp-for="CreditLimit" class="control-label col-4"></label> <div class="col-8"> <input asp-for="CreditLimit" class="form-control" /> <span asp-validation-for="CreditLimit" class="text-danger"></span> </div> </div> <div class="form-group row"> <div class="col-4"></div> <div class="col-8"> <input type="submit" value="Save" class="btn btn-primary" /> <a asp-action="Index" class="btn btn-info">Back to List</a> </div> </div> </form> </div> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
@model ASPCoreMVCDapper.Models.Customer @{ ViewData["Title"] = "Delete"; } <div> <h3>Are you sure you want to delete this record?</h3> </div> <hr /> <div> <dl class="row"> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.CustomerID) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.CustomerID) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.CompanyName) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.CompanyName) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.Address) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.Address) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.City) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.City) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.State) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.State) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.IntroDate) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.IntroDate) </dd> <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.CreditLimit) </dt> <dd class="col-sm-10"> @Html.DisplayFor(model => model.CreditLimit) </dd> </dl> <form asp-action="Delete"> <input type="hidden" asp-for="CustomerID" /> <input type="submit" value="Delete" class="btn btn-danger" /> <a asp-action="Index" class="btn btn-info">Back to List</a> </form> </div>
#divIndexCreate { margin-top: 20px; margin-bottom: 20px; } #divIndexHeader { margin-top: 50px; margin-bottom: 50px; text-align:center; } #tblRecords{ border-radius: 5px; }
VI. Wrapping Up
This is the third article that I have created with respect to existing ORM's widely used by the .NET community. First is Microsoft's Entity Framework Core and second is Michael Camara Pendon's RepoDB ORM. Each has it's own limitations and strong points. It's up to you as the architect or developer to decide which ORM is feasible to your project.
Output
Comments
Post a Comment