Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
Presentation Layer или уровень представления отвечает за взаимодействие с пользователем. Для этого в нашем решении уже есть проект NLayerApp.WEB. Правда, фактически всю основную работу и весь основной функционал мы создали, осталось только его применить на уровне представления. Для этого вначале добавим в проект ссылку на проект BLL:
Также добавим в проект через NuGet пакеты AutoMapper, EntityFramewok и Ninject.MVC5, которые нам потом понадобятся.
Для представления данных в представлениях определим две модели представления в папке Models. Класс PhoneViewModel, представляющий смартфон:
namespace NLayerApp.WEB.Models { public class PhoneViewModel { public int Id { get; set; } public string Name { get; set; } public string Company { get; set; } public decimal Price { get; set; } } }
И класс OrderViewModel, представляющий заказ:
namespace NLayerApp.WEB.Models { public class OrderViewModel { public int PhoneId { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } } }
И тогда контроллер HomeController будет выглядеть следующим образом:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using NLayerApp.BLL.Interfaces; using NLayerApp.BLL.DTO; using NLayerApp.WEB.Models; using AutoMapper; using NLayerApp.BLL.Infrastructure; namespace NLayerApp.WEB.Controllers { public class HomeController : Controller { IOrderService orderService; public HomeController(IOrderService serv) { orderService = serv; } public ActionResult Index() { IEnumerable<PhoneDTO> phoneDtos = orderService.GetPhones(); var mapper = new MapperConfiguration(cfg => cfg.CreateMap<PhoneDTO, PhoneViewModel>()).CreateMapper(); var phones = mapper.Map<IEnumerable<PhoneDTO>, List<PhoneViewModel>>(phoneDtos); return View(phones); } public ActionResult MakeOrder(int? id) { try { PhoneDTO phone = orderService.GetPhone(id); var order = new OrderViewModel { PhoneId = phone.Id }; return View(order); } catch (ValidationException ex) { return Content(ex.Message); } } [HttpPost] public ActionResult MakeOrder(OrderViewModel order) { try { var orderDto = new OrderDTO { PhoneId = order.PhoneId, Address = order.Address, PhoneNumber = order.PhoneNumber }; orderService.MakeOrder(orderDto); return Content("<h2>Ваш заказ успешно оформлен</h2>"); } catch (ValidationException ex) { ModelState.AddModelError(ex.Property, ex.Message); } return View(order); } protected override void Dispose(bool disposing) { orderService.Dispose(); base.Dispose(disposing); } } }
Контроллер производит все те действия, что и производил аналогичный контроллер в проекте с монолитной архитектурой.
Чтобы перехватить ошибки валидации, которые возникают на уровне BLL, здесь используется конструкция try..catch
, в которой
перехватываются исключения ValidationException
, а соответствующие сообщения об ошибках передаются в представление.
В папке Views/Home определим для действий этого контроллера представления. Представление Index.cshtml:
@model IEnumerable<NLayerApp.WEB.Models.PhoneViewModel> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Name) </th> <th> @Html.DisplayNameFor(model => model.Company) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Company) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Html.ActionLink("Order", "MakeOrder", new { id=item.Id }) </td> </tr> } </table>
Представление MakeOrder.cshtml:
@model NLayerApp.WEB.Models.OrderViewModel @{ ViewBag.Title = "MakeOrder"; } <h2>Make Order</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <input type="hidden" name="PhoneId" value="@Model.PhoneId" /> <div class="form-group"> @Html.LabelFor(model => model.Address, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Address, new { htmlAttributes = new { @class = "form-control" } }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.PhoneNumber, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.PhoneNumber, new { htmlAttributes = new { @class = "form-control" } }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Make Order" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
И финальный момент - внедрение зависимостей. Для установки зависимостей добавим в проект папку Util, а в нее класс сопоставителя зависимостей:
using Ninject.Modules; using NLayerApp.BLL.Services; using NLayerApp.BLL.Interfaces; namespace NLayerApp.WEB.Util { public class OrderModule : NinjectModule { public override void Load() { Bind<IOrderService>().To<OrderService>(); } } }
В данном случае интерфейс IOrderService
сопоставляется с классом OrderService
.
Теперь перейдем к файлу Global.asax и изменим его следующим образом:
using Ninject; using Ninject.Modules; using Ninject.Web.Mvc; using NLayerApp.BLL.Infrastructure; using NLayerApp.WEB.Util; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace NLayerApp.WEB { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // внедрение зависимостей NinjectModule orderModule = new OrderModule(); NinjectModule serviceModule = new ServiceModule("DefaultConnection"); var kernel = new StandardKernel(orderModule, serviceModule); DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); } } }
В данном случае мы регистрируем свой сопоставитель зависимостей. Причем будут использоваться сразу два модуля Ninject, которые устанавливают сопоставление зависимостей.
Первый модуль - это тот модуль, который создан выше и который сопоставляет зависимость IOrderService с сервисом OrderService.
А второй модуль взят из проекта BLL, который сопоставлял IUnitOfWork с EFUnitOfWork и передавал им название подключения.
В данном случае устанавливается строка подключения DefaultConnection
. Соответственно это подключение должно быть определено в файле
web.config. При желании затем мы сможем изменить строку подключения.
Проект уровня представления будет иметь следующую структуру:
Таким образом, мы создали приложение, реализующее трехуровневую архитектуру, которое выполняет все те же действия, что и проект с монолитной архитектурой. Однако теперь нам легче его тестировать, разрабатывать в команде распределено, развивать и расширять.