Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Создание проекта для примера

Мы собираемся начать с создания простого проекта для примера, который мы будем использовать в этой главе. Мы создали новый Visual Studio проект, используя шаблон ASP.NET MVC 4 Web Application и выбрав опцию Empty для создания MVC проекта без начального содержания. Это такой же проект, с каким мы до сих пор работали, и мы назвали проект для этого примера

EssentialTools.

Создание классов модели

Затем добавьте файл для класса Product.cs в папку проекта Models и установите контент в соответствии с листингом 6-1. Это тот же самый класс модели, который использовался в предыдущих главах, и единственное изменение заключается в том, чтобы пространство имен соответствовало проекту EssentialTools.

Листинг 6-1: Класс Product

namespace EssentialTools.Models

{

public class Product

{

public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { set; get; }

}

}

Мы также хотим добавить класс, который будет рассчитывать общую стоимость объектов Product (коллекция объектов). Добавьте новый файл класса LinqValueCalculator.cs в папку Models и задайте ему содержание в соответствии с листингом 6-2.

Листинг 6-2: Класс LinqValueCalculator

using System.Collections.Generic; using System.Linq;

namespace EssentialTools.Models

{

public class LinqValueCalculator

{

public decimal ValueProducts(IEnumerable<Product> products)

{

return products.Sum(p => p.Price);

}

}

}

Класс LinqValueCalculator определяет единственный метод ValueProducts, использующий LINQ метод Sum, чтобы сложить значения свойства Price каждого объекта Product, который передается методу (приятная функция LINQ, которой мы часто пользуемся).

Наш последний класс модели называется ShoppingCart, он представляет собой коллекцию объектов Product и использует LinqValueCalculator для определения их общей стоимости. Создайте новый файл класса ShoppingCart.cs и убедитесь, что его содержимое соответствуют листингу 6-3.

121

Листинг 6-3: Класс ShoppingCart

using System.Collections.Generic; namespace EssentialTools.Models

{

public class ShoppingCart

{

private LinqValueCalculator calc;

public ShoppingCart(LinqValueCalculator calcParam)

{

calc = calcParam;

}

public IEnumerable<Product> Products { get; set; } public decimal CalculateProductTotal()

{

return calc.ValueProducts(Products);

}

}

}

Добавление контроллера

Добавьте новый контроллер HomeController в папку Controllers и установите его контент в соответствии с листингом 6-4. Метод действия Index создает массив объектов Product и использует класс LinqValueCalculator для получения общей стоимости, которая передается методу View. Мы не указываем представление, когда вызываем метод View, поэтому будет использоваться представление по умолчанию.

Листинг 6-4: Контроллер HomeController

using EssentialTools.Models; using System.Web.Mvc;

using System.Linq;

namespace EssentialTools.Controllers

{

public class HomeController : Controller

{

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

};

public ActionResult Index()

{

LinqValueCalculator calc = new LinqValueCalculator();

ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal();

return View(totalValue);

}

}

}

Добавление представления

Последним дополнением к проекту является представление. Щелкните правой кнопкой мыши по методу действия Index в контроллере Home, а затем выберите Add View. Создайте представление Index, которое является строго типизированным с объектом модели представления decimal. Как только вы создадите файл, поменяйте его содержимое, чтобы оно соответствовало листингу 6-5.

122

Листинг 6-5: Файл Index.cshtml

@model decimal @{

Layout = null;

}

<!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" /> <title>Value</title>

</head>

<body>

<div>

Total value is $@Model

</div>

</body>

</html>

Это очень простое представление, которое использует выражение @Model для отображения значения decimal, которое передается методом действия. Если вы запустите проект, вы увидите общую стоимость, подсчитанную классом LinqValueCalculator, что показано на рисунке 6-1.

Рисунок 6-1: Запуск приложения из примера

Это очень простой проект, но это закладывает базу для изучения различных инструментов и методов, которые мы описываем в этой главе.

Использование Ninject

Мы ввели понятие DI в главе 3. Напомним, что идея состоит в том, чтобы разделять компоненты в наших MVC приложениях, и мы делаем это, сочетая интерфейсы и DI. В следующих разделах мы разъясним проблему, которую мы сознательно создали в приложении из примера, и покажем, как использовать Ninject, наш любимый DI пакет, который может быть использован для ее решения.

Вчем же проблема?

Вприложении мы создали одну проблему, которую может решить DI. Наш простой проект

опирается на сильно связанные классы: класс ShoppingCart тесно связан с классом

LinqValueCalculator, а класс HomeController тесно связан с обоими классами ShoppingCart и LinqValueCalculator. Это обозначает, что если мы захотим заменить класс LinqValueCalculator, нам нужно будет найти и заменить все ссылки в классах, которые тесно с ним связаны. В принципе, это не проблема для такого простого проекта, но это может принести много ошибок в реальный проект, особенно если мы хотим реализовать разные виды расчетов, а не просто заменить один класс другим.

123

Применение интерфейса

Мы можем частично решить эту проблему, используя C# интерфейс для того, чтобы отделить определение функционала калькулятора от его реализации. Чтобы показать это, мы добавили файл IValueCalculator.cs в папку Models и создали интерфейс, показанный в листинге 6-6.

Листинг 6-6: Интерфейс IValueCalculator

using System.Collections.Generic; namespace EssentialTools.Models

{

public interface IValueCalculator

{

decimal ValueProducts(IEnumerable<Product> products);

}

}

Затем мы можем реализовать этот интерфейс в классе LinqValueCalculator, как показано в листинге 6-7.

Листинг 6-7: Применение интерфейса к классу LinqValueCalculator

using System.Collections.Generic; using System.Linq;

namespace EssentialTools.Models

{

public class LinqValueCalculator : IValueCalculator

{

public decimal ValueProducts(IEnumerable<Product> products)

{

return products.Sum(p => p.Price);

}

}

}

Интерфейс позволяет нам сломать сильную связь между классами ShoppingCart и LinqValueCalculator, как показано в листинге 6-8.

Листинг 6-8: Применение интерфейса к классу ShoppingCart

using System.Collections.Generic; namespace EssentialTools.Models

{

public class ShoppingCart

{

private IValueCalculator calc;

public ShoppingCart(IValueCalculator calcParam)

{

calc = calcParam;

}

public IEnumerable<Product> Products { get; set; } public decimal CalculateProductTotal()

{

return calc.ValueProducts(Products);

}

}

}

Мы добились определенного прогресса, но C# требует от нас указать класс реализации для интерфейса при создании экземпляра, что достаточно справедливо, ведь ему необходимо знать, какой именно класс реализации мы хотим использовать. Это обозначает, что у нас до сих пор есть проблема в контроллере Home, когда мы создаем объект LinqValueCalculator:

124

...

public ActionResult Index() {

IValueCalculator calc = new LinqValueCalculator();

ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal();

return View(totalValue);

}

...

Мы хотим использовать Ninject, чтобы достичь того этапа, когда мы указываем, что мы хотим создать экземпляр реализации интерфейса IValueCalculator, но детали о том, какая требуется реализация, не являлись бы частью кода в контроллере Home.

Добавление Ninject в проект Visual Studio

Самый простой способ добавить Ninject в MVC проект – это использовать интегрированную поддержку Visual Studio для NuGet, что облегчает установку большого набора пакетов и поддерживает их в актуальном состоянии. Выберите Tools Library Package Manager Manage NuGet Packages for Solution, чтобы открыть диалоговое NuGet пакетов. Нажмите Online на левой панели и введите Ninject в поле поиска в правом верхнем углу диалогового окна. Вы увидите ряд пакетов Ninject, похожих на те, что показаны на рисунке 6-2. (Вы можете увидеть другие результаты поиска, которые отображают обновления, выпущенные после того, как мы написали эту главу).

Рисунок 6-2: Выбор Ninject из пакетов NuGet

Нажмите кнопку Install для библиотеки Ninject, и Visual Studio загрузит библиотеку и установит ее в вашем проекте: вы увидите, как Ninject появится в разделе References проекта.

NuGet является отличным инструментом для получения и поддержания пакетов, используемых в проектах Visual Studio, и мы рекомендуем вам использовать его. В этой книге, однако, нам нужно применять другой подход, потому что использование NuGet добавляет в проект много логов и дополнительной информации, чтобы была возможность синхронизировать и обновлять пакеты. Эти

125

дополнительные данные удвоили бы размер нашего простого проекта. И поэтому исходной код, доступный для скачивания на Apress.com, стал бы слишком большим для многих читателей.

Вместо этого мы загрузили последнюю версию библиотеки с веб сайта Ninject (www.ninject.org) и установили его вручную, выбрав Add Reference из меню Project в Visual Studio, нажали на кнопку Browse, перешли к файлу и распаковали zip файл Ninject. Мы выбрали файл Ninject.dll и добавили его в проект вручную. Мы получили такой же результат, как если бы использовали NuGet, но, конечно, мы не сможем получить выгоду от простого обновления и управления пакетами.

Примечание

Внесем ясность: единственная причина, почему мы добавили библиотеку Ninject вручную, заключается в том, чтобы сохранить довольно небольшой размер исходного кода, прилагаемого к этой книге. В наших собственных проектах, где несколько дополнительных мегабайт данных не имеют никакого значения, мы используем NuGet. Мы рекомендуем вам также использовать NuGet.

Приступим к работе с Ninject

Есть три стадии начала работы с основным функционалом Ninject, и все они представлены в листинге 6-9. Тут выделены изменения, которые мы внесли в контроллер Home.

Листинг 6-9: Добавление базового функционала Ninject в метод действия Index

using EssentialTools.Models; using System.Web.Mvc;

using System.Linq; using Ninject;

namespace EssentialTools.Controllers

{

public class HomeController : Controller

{

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

};

public ActionResult Index()

{

IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();

ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal();

return View(totalValue);

}

}

}

Первый этап заключается в подготовке Ninject для использования. Нам нужно создать экземпляр Ninject ядра – это объект, который мы будем использовать для связи с Ninject и запросом реализаций интерфейса. Вот выражение из листинга, которое создает ядро:

...

IKernel ninjectKernel = new StandardKernel();

...

126

Нам нужно создать реализацию интерфейса Ninject.IKernel, что мы делаем, создав новый экземпляр класса StandardKernel. Это позволит нам выполнить второй этап, который заключается в создании связи между интерфейсами в нашем приложении и реализациями классов, с которыми мы хотим работать. Вот выражение из листинга, которое это делает:

...

ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

...

Ninject использует параметры C# типов, чтобы создать связь: мы устанавливаем интерфейс, с которым мы хотим работать, в качестве параметра типа для метода Bind и вызываем метод To для результата, который он возвращает. Мы устанавливаем класс реализации, для которого мы хотим создать экземпляр, в качестве параметра типа для метода To. Это выражение говорит Ninject, что когда его попросят реализовать интерфейс IValueCalculator, он должен выполнить запрос, создав новый экземпляр класса LinqValueCalculator.

Последний этап заключается в реальном использовании Ninject, что мы делаем при помощи метода

Get:

...

IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();

...

Параметр типа, используемый методом Get, говорит Ninject, в каком интерфейсе мы заинтересованы, а результаты этого метода является экземпляром типа реализации, который мы только что определили для метода To.

Установка MVC DI

Результатом этих трех этапов, которые мы показали вам в предыдущем листинге, является то, что мы знаем, какой класс реализации должен быть использован для выполнения запросов для интерфейса IValueCalculator, который был установлен в Ninject. Но, конечно, мы еще не улучшили наше приложение, потому что все это по-прежнему определяется в контроллере Home: это означает, что контроллер Home по-прежнему тесно связан с классом LinqValueCalculator.

В следующих разделах мы покажем вам, как вставить Ninject в сердце MVC приложения, что позволит нам упростить контроллер, расширить влияние Ninject, чтобы он работал по всему приложению и использовал свои дополнительные возможности.

Создание DR (Dependency Resolver)

Первое изменение, которое мы собираемся сделать, заключается в создании DR (dependency resolver: функциональная возможность, которая отвечает за создание зависимостей). MVC использует DR для создания экземпляров классов, для которых он должен обрабатывать запросы. Создавая DR, мы гарантируем, что Ninject будет использоваться всякий раз, когда будет создан объект.

Добавьте в наш проект новую папку Infrastructure и добавьте новый файл класса NinjectDependencyResolver.cs. Убедитесь, что содержимое этого файла соответствует листингу 6- 10.

Листинг 6-10: Содержимое файла NinjectDependencyResolver.cs

using System;

using System.Collections.Generic; using System.Web.Mvc;

127

using Ninject;

using Ninject.Parameters; using Ninject.Syntax;

using System.Configuration; using EssentialTools.Models;

namespace EssentialTools.Infrastructure

{

public class NinjectDependencyResolver : IDependencyResolver

{

private IKernel kernel;

public NinjectDependencyResolver()

{

kernel = new StandardKernel(); AddBindings();

}

public object GetService(Type serviceType)

{

return kernel.TryGet(serviceType);

}

public IEnumerable<object> GetServices(Type serviceType)

{

return kernel.GetAll(serviceType);

}

private void AddBindings()

{

kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

}

}

}

MVC фреймворк вызовет методы GetService или GetServices, когда ему будет нужен экземпляр класса для обработки входящих запросов. Работа DR заключается в создании этого экземпляра: задача, которую мы выполняем при помощи методов Ninject TryGet и GetAll. Метод TryGet работает как метод Get, который мы использовали ранее, но он возвращает null, если нет подходящей связки, а не выбрасывает исключение. Метод GetAll поддерживает несколько связок для одного типа.

Наш класс DR также находится там, где мы установили наши Ninject связки. В методе AddBindings мы используем методы Bind и To, чтобы установить связь между интерфейсом IValueCalculator и

классом LinqValueCalculator.

Регистрация DR

Нам нужно сказать MVC, что мы хотим использовать наш собственный DR, мы это можем сделать, изменив файл Global.asax.cs. Вы можете увидеть дополнения, которые мы внесли в класс MVCApplication, содержащийся в этом файле, в листинге 6-11.

Листинг 6-11: Регистрация DR

using EssentialTools.Infrastructure; using System.Web.Http;

using System.Web.Mvc; using System.Web.Routing; namespace EssentialTools

{

public class MvcApplication : System.Web.HttpApplication

{

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

DependencyResolver.SetResolver(new NinjectDependencyResolver());

128

WebApiConfig.Register(GlobalConfiguration.Configuration);

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

RouteConfig.RegisterRoutes(RouteTable.Routes);

}

}

}

С этим дополнением Ninject будет предложена возможность создать любой экземпляр объекта, который требует MVC фреймворк, разместив DI прямо в ядро нашего MVC приложения.

Рефакторинг контроллера Home

Последним шагом является рефакторинг контроллера Home, чтобы он использовал возможности, которые мы создали в предыдущих разделах. Изменения, которые мы сделали, представлены в листинге 6-12.

Листинг 6-12: Рефакторинг контроллера Home

using EssentialTools.Models; using System.Web.Mvc;

using System.Linq;

namespace EssentialTools.Controllers

{

public class HomeController : Controller

{

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

};

private IValueCalculator calc;

public HomeController(IValueCalculator calcParam)

{

calc = calcParam;

}

public ActionResult Index()

{

ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal();

return View(totalValue);

}

}

}

Основное изменение, которое мы здесь сделали, заключается в том, что мы добавили конструктор, который принимает реализацию интерфейса IValueCalculator. Мы не указали, с какой реализацией мы хотим работать, и мы добавили переменную экземпляра calc, которую мы можем использовать для обращения к IValueCalculator, который мы получаем в конструктор через класс контроллера.

Другое изменение, которые мы сделали, заключается в том, чтобы удалить любые упоминания о Ninject коде или классе LinqValueCalculator: наконец, мы разбили сильную связь между HomeController и классом LinqValueCalculator. Если вы запустите пример, вы увидите результат, показанный на рисунке 6-3. Естественно, мы получили тот же результат, что и когда мы создавали экземпляр класса LinqValueCalculator непосредственно в контроллере.

129

Рисунок 6-3: Результат запуска приложения

То, что мы создали, является примером внедрения через конструктор, это одна из форм внедрения зависимостей. Вот то, что произошло, когда вы запустили пример приложения и Internet Explorer сделал запрос на корневой URL приложения:

1.MVC фреймворк получил запрос и выяснил, что запрос предназначен для контроллера Home (мы объясним, как MVC фрейморк выясняет это, в главе 15).

2.MVC фреймворк попросил наш пользовательский DR класс создать новый экземпляр класса HomeController, указав класс, используя параметр Type метода GetService.

3.Наш DR попросил Ninject создать новый класс HomeController при помощи передачи объекта

Type методу TryGet.

4.Ninject изучил конструктор HomeController и обнаружил, что он требует реализацию

IValueCalculator.

5.Ninject создает экземпляр класса LinqValueCalculator и использует его для создания нового экземпляра класса HomeController.

6.Ninject передает вновь созданный экземпляр HomeController пользовательскому DR, который возвращает его MVC фреймоворку. MVC фреймворк использует экземпляр контроллера для обработки запроса.

Мы все это объяснили, потому что DI может показаться немного запутанным, если вы впервые с ним сталкиваетесь. Одно из преимуществ такого подхода заключается в том, что любой контроллер может объявить, что он требует в своем конструкторе IValueCalculator и что будет использоваться Ninject. Наш пользовательский DR создает экземпляр реализации, которую мы указали в методе

AddBindings.

Самое приятное то, что нам нужно изменить только наш класс DR, если мы хотим заменить LinqValueCalculator другой реализацией, потому что это единственное место, где требуется определить реализацию, которая используется для выполнения запросов для интерфейса

IValueCalculator.

Создание цепочек зависимостей

Когда вы просите Ninject создать тип, он проверяет связи между этим типом и другими типами. Если есть дополнительные зависимости, Ninject автоматически схватывает их и создает экземпляры всех требуемых классов. Чтобы показать эту функцию, мы добавили файл Discount.cs в папку Models проекта и определили новый интерфейс и класс, реализующий его, как представлено в листинге 6-13.

Листинг 6-13: Определение нового интерфейса и реализации

namespace EssentialTools.Models

{

public interface IDiscountHelper

{

decimal ApplyDiscount(decimal totalParam);

130

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]