ASP.NET MVC: История одного проекта "Всё ради данных" (часть 2)
После некоторого раздумья, решил переименовать статьи, чтобы название максимально подходило к теме статьи. Я просто расскажу как я создаю себе новый сайт.
Содержание
ASP.NET MVC: История одного проекта "Готовимся к старту" (часть 1)
ASP.NET MVC: История одного проекта "Всё ради данных" (часть 2)
ASP.NET MVC: История одного проекта "Шаблоны и внешний вид" (часть 3)
ASP.NET MVC: История одного проекта "Еще немного классов" (часть 4)
ASP.NET MVC: История одного проекта "UI - всё для пользователя" (часть 5)
ASP.NET MVC: История одного проекта "UI - Добавление экспоната" (часть 6)
ASP.NET MVC: История одного проекта "UI - Редактирование экспоната" (часть 7)
ASP.NET MVC: История одного проекта "Обработка ошибок" (часть 8)
ASP.NET MVC: История одного проекта "Фильтрация" (часть 9)
ASP.NET MVC: История одного проекта "Поиск" (часть 10)
ASP.NET MVC: История одного проекта "Облако тегов" (часть 11)
ASP.NET MVC: История одного проекта "Главная страница" (часть 12)
Итоги прошлой части
Выдалась свободная минутка, продолжу писать свой сайт. Итак, в предыдущей части были решены основные задачи, которые всегда приходится решать в начале разработки. Запустим сайт, проверим что работает авторизация.

Отлично. Давайте перейдем к моделям.
Откуда беруться данные
Не могу сказать за всех разработчиков мира, только сугубо личные предпочтения относительно того, на каком этапе нужно заносить в базу данных "временные" данные, а на каком "реальные". Я сторонник голой, но правды... хреновой, но реальности! На первом этапе я покажу как создать контроллер, представления и репозитории что называется "вручную". Для этого я буду использовать класс Hall. А вот для реализацию контроллера (controller), преставления (view) и репозитория (repository) для класса Exhibit я покажу на примере MvcScaffolding. Сам паттерн MVC для ASP.NET описывать не имеет смысла, потому что лучшие умы человечества уже сделали это, например, в Википедии.
Как пройти в хранилище?
Прежде чем начать работу над первой (нудной) частью оглашенной в предыдущем абзаце, надо бы подумать о контейнере...О UnityContainer конечно же. Подключим через nuget-пакет Unity.Mvc3.
PM> Install-Package Unity.Mvc3 Attempting to resolve dependency 'Unity (? 2.1.505.0)'. Attempting to resolve dependency 'CommonServiceLocator (? 1.0)'. Successfully installed 'CommonServiceLocator 1.0'. You are downloading Unity from Microsoft patterns & practices, the license agreement to which is available at http://www.opensource.org/licenses/ms-pl. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device. Successfully installed 'Unity 2.1.505.0'. Successfully installed 'Unity.Mvc3 1.1'. Successfully added 'CommonServiceLocator 1.0' to Calabonga.Mvc.Humor. Successfully added 'Unity 2.1.505.0' to Calabonga.Mvc.Humor. Successfully added 'Unity.Mvc3 1.1' to Calabonga.Mvc.Humor. PM>
Теперь надо в файле Global.asax в методе Application_Start подключить инициализацию контейнера.
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Bootstrapper.Initialise();
}
Вот теперь всё готово, чтобы начать реализовывать контроллер, представления и репозитории. Я создал в проекте новую папку Engine, в которую и буду складывать файлы с классами описывающие инфраструктуру.
А как же данные?
Создаю новый класс MuseumContext как наследник от DbContext добавляю таблицу для хранение моих залов.
/// <summary>
/// База данных
/// </summary>
public class MuseumContext : DbContext {
/// <summary>
/// Таблица залов
/// </summary>
public DbSet<Hall> Halls { get; set; }
}
Как, наверное вы уже догадались, это и есть подход к реализацию базы данных именуемый "Code First", что означает "сначала код". Я сначала создал классы и специальный контекст. Сейчас в web.config пропишу строку подключения и мой сайт будет готов работать с данными.
Строка подключения
Магические силы EntityFramework 4.3 позаботятся о моих данных, но для того чтобы это произошло надо написать заклинание правильную строку подключения. Правильность соблюдается следующим образом, название моего контекста MuseumContext должно соответствовать названию строки подключения. Это придумал не я, это требование EntityFramework.
<add name="MuseumContext"
connectionString="Data Source=(local);Initial Catalog=museumDb;Integrated Security=True"
providerName="System.Data.SqlClient" />
Одно важное замечание. Даже если сейчас вы и запустите сайт на компиляцию, база данных не появится в списке Microsoft SQL Managment Studio (делее буду назвать SQL MS), но при первом обращении к таблицам MuseumContext сам EF сгенерирует для вас базу данных. А вот чтобы это происходило с максимальной пользой, давайте скажем EF чтобы он не просто генерировал пустые таблицы, а еще и наполнял их некоторыми данными. Раз я завел речь про залы музея, пусть названия залов и наполняюся сразу при генерации. Для этого подключим миграции к EF выполнив в консоле nuget-менеджера простую но очень важную команду (надо сказать, что эта команда появилась только в версии 4.3.):
PM> Enable-Migrations Code First Migrations enabled for project Calabonga.Mvc.Humor. PM>
Миграции (Migrations Code First)
После выполнения команды в проекте появилась папка Migrations, в которой появился файл Configuration.cs (студия его любезно открыла). Я поправил конструктор:
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
Надеюсь, названия параметров говоря сами за себя и комментировать мне их не придется. А в методе Seed я написал инциализацию моей таблицы Hall:
protected override void Seed(MuseumContext context) {
context.Halls.AddOrUpdate(
new Hall { Id = 1, Name = "Анекдоты" },
new Hall { Id = 2, Name = "Истории" },
new Hall { Id = 3, Name = "Афоризмы" },
new Hall { Id = 4, Name = "Стишки" },
new Hall { Id = 5, Name = "Хокку" },
new Hall { Id = 6, Name = "Фразы и изречения" },
new Hall { Id = 7, Name = "Разное" }
);
}
Обратите внимание на то, что идентификаторы тоже проставлены, если их не указывать, то EF будет добавлять вышеуказанные категории при каждом запуске сайта (горький опыт разработки). На этом работа с файлом конфигурации не закончена, надо ее (конфигурации) еще и подключить. Идем в global.asax и в метод Application_Start дописываем код:
protected void Application_Start() {
DbMigrator migrator = new DbMigrator(new Configuration());
migrator.Update();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Bootstrapper.Initialise();
}
Это указан первый способ подключения конфигурации (миграции). Этот способ прост и выполняется автоматически при старте системы. В релизной версии проекта, код рекомендуется убрать. Второй способ - запуск на выполнение из консоли nuget-менеджера метод Update-Database "вручную". Выбирайте сами, какой способ вам нравится больше. Скажу что, и тот и другой способ имеют свои возможности, а также плюсы и минусы, о которых я возможно расскажу по ходу написания статей.
Огласите весь список, пожалуйста, или откуда брать классы
Пришло время увидеть данные, о которых мы так успешно поговорили. Создаем репозиторий HallRepository. Я создал файл, в который поместил класс и интерфейс:
public class HallRepository: IHallRepository {
}
public interface IHallRepository {
IQueryable<Hall> All { get; }
IQueryable<Hall> AllIncluding(params Expression<Func<Hall, object>>[] includeProperties);
Hall Find(int id);
void Insert(Hall district);
void Update(Hall district);
void Delete(int id);
void Save();
}
Теперь, определившись с интерфейсом, начнем его реализацию в классе... Опаньки! Не тут-то было! Для реализации интерфейса мне требуется доступ к базе данных (к MuseumContext)... Запихнем контекст в контейнер, чтобы можно было получить его через Dependency Injection в конструкторе репозитории. Для этого в Bootstrapper в методе инициализации контейнера (BuildUnityContainer) зарегистрируем MuseumContext:
container.RegisterType<MuseumContext>(new HierarchicalLifetimeManager());
Хотелось немного остановиться и оговорить этот способ регистрации, но не буду, ибо он выявлен как самый оптимальный, потому что контекст создается на поток (на один запрос), новый каждый раз, а это как раз то, что нужно (горький опыт разработки). Просто поверьте и всё.
В контрукторе HallRepository попробуем получить экземпляр контекста:
private readonly MuseumContext context;
public HallRepository(MuseumContext context) {
this.context = context;
}
А для того чтобы это сделать надо и IHallRepository поместить в контейнер:
container.RegisterType<IHallRepository, HallRepository>();
Я добавил эту строку после регистрации контекста, чего и вам советую, потому что сначала должен быть зарегистрирован контекст, а потом уже репозитории его использующие.
Чтобы проверить работоспособность контейнера и Dependency Injection надо в конструкторе какого-нибудь контроллера попробовать получить наш новый репозиторий. Я попробую сделать это в HomeController:
public class HomeController : Controller {
private readonly IHallRepository hallRepository;
public HomeController(IHallRepository halls) {
this.hallRepository = halls;
}
public ActionResult Index() {
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About() {
return View();
}
}
Ставлю breakpoint на конструктор репозитория и контроллера и запускаю... За одно посмотрим, будет ли создана база автоматически. УРА!!! Работает! Репозиторий получил контекст:

А контроллер получил репозиторий:

И даже база данных museumDb успешно создана:

То что доктор прописал! Полдела, можно сказать, сделано! Основная рутина почти закончилась. Теперь надо написать реализацию интерфейса в классе HallRepository и можно выводить список залов (Hall) на представление (View) на просмотр!
public IQueryable<Hall> All {
get {
return context.Halls;
}
}
public IQueryable<Hall> AllIncluding(params Expression<Func<Hall, object>>[] includeProperties) {
IQueryable<Hall> query = context.Halls.OrderBy(x => x.Name);
foreach (var includeProperty in includeProperties) {
query = query.Include(includeProperty);
}
return query;
}
public Hall Find(int id) {
return context.Halls.Find(id);
}
public void Insert(Hall hall) {
context.Halls.Add(hall);
}
public void Update(Hall hall) {
Hall h = this.Find(hall.Id);
h.Name = hall.Name;
Save();
}
public void Delete(int id) {
Hall h = this.Find(id);
if (h != null) {c
ontext.Halls.Remove(h);
Save();
}
}
public void Save() {
context.SaveChanges();
}
Теперь я могу в контролле HomeController получить список залов, и даже показать их на Index.

и код в контроллере:
public ActionResult Index() {
return View(hallRepository.All);
}
запускаем...

О, это чудо! Контейнер, репозитории, EF и всё работает! Да еще и как надо!
На этом пока всё. Продолжение, как говориться, следует. Дальше будет еще интереснее. Пишите комментарии.