Skip to content

Developing Web Layer

Mehmet Özkaya edited this page Mar 25, 2019 · 1 revision

Developing Web Layer

The last layer is UI layer, so in this layer we consumed all other layers. Lets start with the ViewModel classes. This classes you can think that imagine of Page components and what is the required data of page.

public class ProductViewModel : BaseViewModel
    {
        public string ProductName { get; set; }
        public string QuantityPerUnit { get; set; }
        public decimal? UnitPrice { get; set; }
        public short? UnitsInStock { get; set; }
        public short? UnitsOnOrder { get; set; }
        public short? ReorderLevel { get; set; }
        public bool Discontinued { get; set; }
        public int? CategoryId { get; set; }
        public CategoryViewModel Category { get; set; }
    }
 public class CategoryViewModel : BaseViewModel
    {
        public string CategoryName { get; set; }
        public string Description { get; set; }
    }

Developing Page Services

Page Services provide to support Razor pages in order to implement screen logics. Its the same way we create interface and also implementation classes.

Interfaces ;

 public interface IProductPageService
    {
        Task<IEnumerable<ProductViewModel>> GetProducts(string productName);
        Task<ProductViewModel> GetProductById(int productId);
        Task<IEnumerable<ProductViewModel>> GetProductByCategory(int categoryId);
        Task<IEnumerable<CategoryViewModel>> GetCategories();
        Task<ProductViewModel> CreateProduct(ProductViewModel productViewModel);
        Task UpdateProduct(ProductViewModel productViewModel);
        Task DeleteProduct(ProductViewModel productViewModel);
    }
public interface ICategoryPageService
    {
        Task<IEnumerable<CategoryViewModel>> GetCategories();
    }

Implementations;

 public class ProductPageService : IProductPageService
    {
        private readonly IProductAppService _productAppService;
        private readonly ICategoryAppService _categoryAppService;
        private readonly IMapper _mapper;
        private readonly ILogger<ProductPageService> _logger;

        public ProductPageService(IProductAppService productAppService, ICategoryAppService categoryAppService, IMapper mapper, ILogger<ProductPageService> logger)
        {
            _productAppService = productAppService ?? throw new ArgumentNullException(nameof(productAppService));
            _categoryAppService = categoryAppService ?? throw new ArgumentNullException(nameof(categoryAppService));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task<IEnumerable<ProductViewModel>> GetProducts(string productName)
        {
            if (string.IsNullOrWhiteSpace(productName))
            {
                var list = await _productAppService.GetProductList();
                var mapped = _mapper.Map<IEnumerable<ProductViewModel>>(list);
                return mapped;
            }

            var listByName = await _productAppService.GetProductByName(productName);
            var mappedByName = _mapper.Map<IEnumerable<ProductViewModel>>(listByName);
            return mappedByName;
        }

        public async Task<ProductViewModel> GetProductById(int productId)
        {
            var product = await _productAppService.GetProductById(productId);
            var mapped = _mapper.Map<ProductViewModel>(product);
            return mapped;
        }

        public async Task<IEnumerable<ProductViewModel>> GetProductByCategory(int categoryId)
        {
            var list = await _productAppService.GetProductByCategory(categoryId);
            var mapped = _mapper.Map<IEnumerable<ProductViewModel>>(list);
            return mapped;
        }

        public async Task<IEnumerable<CategoryViewModel>> GetCategories()
        {
            var list = await _categoryAppService.GetCategoryList();
            var mapped = _mapper.Map<IEnumerable<CategoryViewModel>>(list);
            return mapped;
        }

        public async Task<ProductViewModel> CreateProduct(ProductViewModel productViewModel)
        {
            var mapped = _mapper.Map<ProductDto>(productViewModel);
            if (mapped == null)
                throw new Exception($"Entity could not be mapped.");

            var entityDto = await _productAppService.Create(mapped);
            _logger.LogInformation($"Entity successfully added - IndexPageService");

            var mappedViewModel = _mapper.Map<ProductViewModel>(entityDto);
            return mappedViewModel;
        }

        public async Task UpdateProduct(ProductViewModel productViewModel)
        {
            var mapped = _mapper.Map<ProductDto>(productViewModel);
            if (mapped == null)
                throw new Exception($"Entity could not be mapped.");

            await _productAppService.Update(mapped);
            _logger.LogInformation($"Entity successfully added - IndexPageService");
        }

        public async Task DeleteProduct(ProductViewModel productViewModel)
        {
            var mapped = _mapper.Map<ProductDto>(productViewModel);
            if (mapped == null)
                throw new Exception($"Entity could not be mapped.");

            await _productAppService.Delete(mapped);
            _logger.LogInformation($"Entity successfully added - IndexPageService");
        }
    }
public class CategoryPageService : ICategoryPageService
    {        
        private readonly ICategoryAppService _categoryAppService;
        private readonly IMapper _mapper;

        public CategoryPageService(ICategoryAppService categoryAppService, IMapper mapper)
        {
            _categoryAppService = categoryAppService ?? throw new ArgumentNullException(nameof(categoryAppService));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
        }

        public async Task<IEnumerable<CategoryViewModel>> GetCategories()
        {
            var list = await _categoryAppService.GetCategoryList();
            var mapped = _mapper.Map<IEnumerable<CategoryViewModel>>(list);
            return mapped;
        }
    }

Developing Razor Pages

The final part of UI implementation is Razor Pages. So we should create a Product and Category folder into Pages folder. And in these folder, create cshtml razor pages with Index-Create-Edit-Delete pages in order to building web pages.

Lets create with Index page;

Html Part;

@page
@model AspnetRun.Web.Pages.Product.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Product List</h1>

<form method="get">
    <div class="form-group">
        <div class="input-group">
            <input type="search" class="form-control" asp-for="SearchTerm" />
            <span class="input-group-btn">
                <button class="btn btn-default">
                    Search
                </button>
            </span>
        </div>
    </div>
</form>

<p>
    <a asp-page="Create">Create New</a>
</p>

<table class="table table-hover">
    <thead>
        <tr>
            <th scope="col">Id</th>
            <th scope="col">Name</th>
            <th scope="col">UnitPrice</th>
            <th scope="col">Category</th>
            <th scope="col">Action</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var product in Model.ProductList)
        {
            <tr>
                <th scope="row">@product.Id</th>
                <td>@product.ProductName</td>
                <td>@product.UnitPrice</td>
                <td>@product.Category.CategoryName</td>
                <td>
                    <a class="btn"
                       asp-page="./Details"
                       asp-route-productId="@product.Id">
                        Details
                    </a>
                    <a class="btn"
                       asp-page="./Edit"
                       asp-route-productId="@product.Id">
                        Edit
                    </a>
                    <a class="btn"
                       asp-page="./Delete"
                       asp-route-productId="@product.Id">
                        Delete
                    </a>
                </td>
            </tr>
        }
    </tbody>
</table>

IndexModel.cs

 public class IndexModel : PageModel
    {
        private readonly IProductPageService _productPageService;

        public IndexModel(IProductPageService productPageService)
        {
            _productPageService = productPageService ?? throw new ArgumentNullException(nameof(productPageService));
        }

        public IEnumerable<ProductViewModel> ProductList { get; set; } = new List<ProductViewModel>();

        [BindProperty(SupportsGet = true)]
        public string SearchTerm { get; set; }

        public async Task<IActionResult> OnGetAsync()
        {
            ProductList = await _productPageService.GetProducts(SearchTerm);
            return Page();
        }
    }

Lets continue with Create Page;

Html Part ;

@page
@model AspnetRun.Web.Pages.Product.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Product.ProductName" class="control-label"></label>
                <input asp-for="Product.ProductName" class="form-control" />
                <span asp-validation-for="Product.ProductName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.QuantityPerUnit" class="control-label"></label>
                <input asp-for="Product.QuantityPerUnit" class="form-control" />
                <span asp-validation-for="Product.QuantityPerUnit" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.UnitPrice" class="control-label"></label>
                <input asp-for="Product.UnitPrice" class="form-control" />
                <span asp-validation-for="Product.UnitPrice" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.UnitsInStock" class="control-label"></label>
                <input asp-for="Product.UnitsInStock" class="form-control" />
                <span asp-validation-for="Product.UnitsInStock" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.UnitsOnOrder" class="control-label"></label>
                <input asp-for="Product.UnitsOnOrder" class="form-control" />
                <span asp-validation-for="Product.UnitsOnOrder" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Product.ReorderLevel" class="control-label"></label>
                <input asp-for="Product.ReorderLevel" class="form-control" />
                <span asp-validation-for="Product.ReorderLevel" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="Product.Discontinued" /> @Html.DisplayNameFor(model => model.Product.Discontinued))
                </label>
            </div>
            <div class="form-group">
                <label asp-for="Product.CategoryId" class="control-label"></label>
                <select asp-for="Product.CategoryId" class ="form-control" asp-items="ViewBag.CategoryId"></select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

CreateModel.cs;

public class CreateModel : PageModel
    {
        private readonly IProductPageService _productPageService;

        public CreateModel(IProductPageService productPageService)
        {
            _productPageService = productPageService ?? throw new ArgumentNullException(nameof(productPageService));
        }

        public async Task<IActionResult> OnGetAsync()
        {
            var categories = await _productPageService.GetCategories();
            ViewData["CategoryId"] = new SelectList(categories, "Id", "CategoryName");
            return Page();
        }

        [BindProperty]
        public ProductViewModel Product { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            Product = await _productPageService.CreateProduct(Product);
            return RedirectToPage("./Index");
        }
    }

Add Depency Injections into Startup.cs

In order to use all implementations properly, we should add dependecies with using ASP.NET Core Default DI classes. So for this implementation we should write all dependencies in Startup.cs ConfigureAspnetRunServices method;

private void ConfigureAspnetRunServices(IServiceCollection services)
        {
            // Add Core Layer
            services.Configure<AspnetRunSettings>(Configuration);

            // Add Infrastructure Layer
            ConfigureDatabases(services);
            services.AddScoped(typeof(IAsyncRepository<>), typeof(AspnetRunRepository<>));
            services.AddScoped<IProductRepository, ProductRepository>();
            services.AddScoped<ICategoryRepository, CategoryRepository>();
            services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));

            // Add Application Layer
            services.AddScoped<IProductAppService, ProductAppService>();
            services.AddScoped<ICategoryAppService, CategoryAppService>();

            // Add Web Layer
            services.AddAutoMapper(); // Add AutoMapper
            services.AddScoped<IIndexPageService, IndexPageService>();
            services.AddScoped<IProductPageService, ProductPageService>();
            services.AddScoped<ICategoryPageService, CategoryPageService>();

            // Add Miscellaneous
            services.AddHttpContextAccessor();
            services.AddHealthChecks()
                .AddCheck<IndexPageHealthCheck>("home_page_health_check");
        }

This step is very important in order to run application. Without giving implementations Asp.Net Core can not find implemented class and will raised the error.

Clone this wiki locally