Business Logic Layer

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 20.03.2018

Business Logic Layer или бизнес-уровень инкапсулирует всю бизнес-логику, все необходимые вычисления, получает объекты из уровня доступа к данным и передает их на уровень представления, либо, наоборот, получает данные с уровня представления и передает их на уровень данных.

Итак, добавим в решение новый проект по типу Class Library, который назовем NLayerApp.BLL. Поскольку бизнес-уровень будет использовать классы из уровня доступа к данным, то нам надо добавить на него ссылку:

Уровень представления не может напрямую получать данные из базы данных. В данном случае BLL будет выступать в роли посредника между двумя уровнями. Но также надо учитывать, что напрямую он не может передавать в контроллеры объекты Phone и Order, так как уровень представления не должен иметь доступ к функциональности уровня DAL. Поэтому нам нужны промежуточные сущности.

Итак, добавим в проект BLL папку, которую назовем DTO. Определим в ней новый класс PhoneDTO:

namespace NLayerApp.BLL.DTO
{
    public class PhoneDTO
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Company { get; set; }
        public decimal Price { get; set; }
    }
}

Через этот класс мы будем передавать объекты смартфонов между уровнями. Но хотя данный класс во многом похож по определению на класс Phone, это необязательное условие. Класс PhoneDTO должен содержать только те данные, которые мы собираемся передать на уровень представления или, наоборот, получить с этого уровня. То есть это то, что называется Data Transfer Object - специальная модель для передачи данных.

Подобным образом определим в той же папке класс OrderDTO:

using System;
using NLayerApp.DAL.Entities;

namespace NLayerApp.BLL.DTO
{
    public class OrderDTO
    {
        public int Id { get; set; }
        public string PhoneNumber { get; set; }
        public string Address { get; set; }
        public int PhoneId { get; set; }
        public DateTime? Date { get; set; }
    }
}

Для упрощения сопоставления классов моделей добавим в проект через NuGet библиотеку AutoMapper.

Кроме простых классов типа DTO BLL может содержать классы, которые описывают бизнес-логику. В частности, если мы вернемся к проекту с монолитной архитектурой, то там был небольшой функционал скидки. И в принципе скидку можно выделить в отдельный класс. Но для его хранения добавим в BLL новую папку BusinessModels. И в ней определим класс скидки Discount:

using System;

namespace NLayerApp.BLL.BusinessModels
{
    public class Discount
    {
        public Discount(decimal val)
        {
            _value = val;
        }
        private decimal _value = 0;
        public decimal Value { get { return _value; }}
        public decimal GetDiscountedPrice(decimal sum)
        {
            if(DateTime.Now.Day==1)
                return sum - sum * _value;
            return sum;
        }
    }
}

Это бизнес-модель, которая не только хранит состояние, как классы DTO, но и может описывать некоторые действия. Выделение бизнес-моделей в проекте всецело индивидуально и зависит от конкретной ситуации.

Большую роль в приложении играет валидация данных. По большей части за валидацию отвечает именно BLL. В контроллере мы легко можем провалидировать модель через объект ModelState и при необходимости возвратить в представление сообщения об ошибках. Но на уровне BLL ModelState недоступен. Однако мы все же можем использовать валидацию с передачей ошибок в уровень представления.

Итак, определим в BLL новый каталог Infrastructure и в него добавим новый класс ValidationException:

using System;

namespace NLayerApp.BLL.Infrastructure
{
    public class ValidationException : Exception
    {
        public string Property { get; protected set; }
        public ValidationException(string message, string prop) : base(message)
        {
            Property = prop;
        }
    }
}

Класс ValidationException наследуется от базового класса исключений Exception и определяет свойство Property. Это свойство позволяет сохранить название свойства модели, которое некорректно и не проходит валидацию. И также передавая в конструктор базового класса параметр message, мы определяем сообщение, которое будет выводиться для некорректного свойства в Property.

Взаимодействовать между остальными двумя уровнями мы будем через специальный сервис. Опять же для большей гибкости вначале определим его интерфейс. Для хранения интерфейса добавим в BLL папку Interfaces и в нее положим интерфейс IOrderService:

using NLayerApp.BLL.DTO;
using System.Collections.Generic;
namespace NLayerApp.BLL.Interfaces
{
    public interface IOrderService
    {
        void MakeOrder(OrderDTO orderDto);
        PhoneDTO GetPhone(int? id);
        IEnumerable<PhoneDTO> GetPhones();
        void Dispose();
    }
}

Интерфейс определяет 4 метода: получение всех смартфонов для выбора товара, выбор смартфона для заказа, оформление заказа и метод Dispose.

Для хранения реализаций интерфейса определим в проекте еще одну папку Services. Добавим в нее класс сервиса OrderService:

using System;
using NLayerApp.BLL.DTO;
using NLayerApp.DAL.Entities;
using NLayerApp.BLL.BusinessModels;
using NLayerApp.DAL.Interfaces;
using NLayerApp.BLL.Infrastructure;
using NLayerApp.BLL.Interfaces;
using System.Collections.Generic;
using AutoMapper;

namespace NLayerApp.BLL.Services
{
    public class OrderService : IOrderService
    {
        IUnitOfWork Database { get; set; }

        public OrderService(IUnitOfWork uow)
        {
            Database = uow;
        }
        public void MakeOrder(OrderDTO orderDto)
        {
            Phone phone = Database.Phones.Get(orderDto.PhoneId);

            // валидация
            if (phone == null)
                throw new ValidationException("Телефон не найден","");
            // применяем скидку
            decimal sum = new Discount(0.1m).GetDiscountedPrice(phone.Price);
            Order order = new Order
            {
                Date = DateTime.Now,
                Address = orderDto.Address,
                PhoneId = phone.Id,
                Sum = sum,
                PhoneNumber = orderDto.PhoneNumber
            };
            Database.Orders.Create(order);
            Database.Save();
        }

        public IEnumerable<PhoneDTO> GetPhones()
        {
            // применяем автомаппер для проекции одной коллекции на другую
            var mapper = new MapperConfiguration(cfg => cfg.CreateMap<Phone, PhoneDTO>()).CreateMapper();
            return mapper.Map<IEnumerable<Phone>, List<PhoneDTO>>(Database.Phones.GetAll());
        }

        public PhoneDTO GetPhone(int? id)
        {
            if (id == null)
                throw new ValidationException("Не установлено id телефона","");
            var phone = Database.Phones.Get(id.Value);
            if (phone == null)
                throw new ValidationException("Телефон не найден","");
            
			return new PhoneDTO { Company = phone.Company, Id = phone.Id, Name = phone.Name, Price = phone.Price };
        }

        public void Dispose()
        {
            Database.Dispose();
        }
    }
}

OrderService в конструкторе принимает объект IUnitOfWork, через который идет взаимодействие с уровнем DAL.

Метод MakeOrder() получает объект для сохранения с уровня представления и создает по нему объект Order и сохраняет его в базу данных.

Метод GetPhones() получает все смартфоны и с помощью автомаппера преобразует их в PhoneDTO и передает на уровень представления.

И метод GetPhone() передает отдельный смартфон на уровень представления.

Поскольку в данном случае мы не задаем в конструкторе явно объект IUnitOfWork, то нам надо использовать внедрение зависимостей для передачи конкретной реализации данного интерфейса в OrderService. Для этого добавим в проект через NuGet пакет Ninject:

Dependency Injection in BLL

Затем добавим в проект в ранее созданную папку Infrastructure следующий класс:

using Ninject.Modules;
using NLayerApp.DAL.Interfaces;
using NLayerApp.DAL.Repositories;

namespace NLayerApp.BLL.Infrastructure
{
    public class ServiceModule : NinjectModule
    {
        private string connectionString;
        public ServiceModule(string connection)
        {
            connectionString = connection;
        }
        public override void Load()
        {
            Bind<IUnitOfWork>().To<EFUnitOfWork>().WithConstructorArgument(connectionString);
        }
    }
}

ServiceModule представляет специальный модуль Ninject, который служит для организации сопоставления зависимостей. В частности, он устанавливает использование EFUnitOfWork в качестве объекта IUnitOfWork. Кроме того, здесь через конструктор передается название подключения, которое в итоге будет определяться в файле web.config проекта, представляющего уровень представления.

В итоге структура проекта будет выглядеть следующим образом:

Business Logic Layer in ASP.NET MVC

Теперь создадим уровень представления.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850