Основы. Часть 5 – Создаем Модель

Самое время перейти к разработке демонстрационного проекта. Для этого поставим задачу – создать каталог книг. Несмотря на простоту, он послужит хорошим примером для изучения ASP.NET MVC 3.

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

Согласно идеологии MVC в Модели содержится вся бизнес-логика будущего веб-приложения: от описания используемых данных до алгоритмов, ответственных за представляемую пользователю функциональность. Поэтому и начнем разработку с её классов.

Проектируем Модель

Давайте определим набор данных, который будет соответствовать описанию книги в базе данных создаваемого веб-приложения:

  • уникальный код в локальной базе данных (Id);
  • название (Title);
  • автор или авторы (Author);
  • язык, на котором она написана (Language);
  • издатель (Publisher);
  • дата выхода (PublishedAt);
  • ссылка на сайт книги (для бесплатных изданий) или на онлайн-магазин (для коммерческих) (Url);
  • краткое описание (Description);
  • ключевые слова (Tags);
  • рейтинг (Rating);
  • признак того, что книга доступна бесплатно (IsFree);
  • признак того, что запись о книге может быть отображена в каталоге (IsVisible).

Здесь можно отметить три свойства, которые желательно задавать из готового перечня значений: Language, Publisher и Tags.  Для хранения из значений разработаем дополнительные классы с соответствующими именами.

В папке Models создадим следующие файлы:

  • Tag.cs – класс для хранения ключевых слов:
namespace BookCatalog.Models
{
    using System.Collections.Generic;

    public class Tag
    {
        public int Id { get; set; }

        public string Text { get; set; }

        public virtual ICollection<BookDetails> Books { get; set; }
    }
}

В данном классе свойство Books предназначено для получения списка книг, которым было присвоено выбранное ключевое слово.

  • Language.cs – класс для хранения названий языков:
namespace BookCatalog.Models
{
    using System.Collections.Generic;

    public class Language
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public virtual ICollection<BookDetails> Books { get; set; }
    }
}
  • Publisher.cs – класс содержащий информацию об издательстве:
namespace BookCatalog.Models
{
    using System.Collections.Generic;

    public class Publisher
    {
        public int Id { get; set; }

        public string Title { get; set; }

        public string Homepage { get; set; }

        public virtual ICollection<BookDetails> Books { get; set; }
    }
}

В данном случае свойство Books, которое возвращает коллекцию книг, выпущенных издательством.

  • BookDetails.cs – класс, содержащий информацию о книге:
namespace BookCatalog.Models
{
    using System;
    using System.Collections.Generic;

    public class BookDetails
    {
        public int Id { get; set; }

        public string Title { get; set; }

        public string Author { get; set; }

        public int LanguageId { get; set; }

        public int? PublisherId { get; set; }

        public DateTime PublishedAt { get; set; }

        public string Url { get; set; }

        public string Description { get; set; }

        public int? Rating { get; set; }

        /// <summary>Gets or sets a value indicating whether 
        /// the book is free (true) or not (false).</summary>
        public bool IsFree { get; set; }

        /// <summary>Gets or sets a value indicating whether 
        /// the book is visible in the catalog (true) or not (false).</summary>
        public bool IsVisible { get; set; }

        public virtual ICollection<Tag> Tags { get; set; }

        public virtual Language Language { get; set; }

        public virtual Publisher Publisher { get; set; }
    }
}

Давайте рассмотрим некоторые свойства отдельно:

  • Для указания языка и издателя используются свойства LanguageId и PublisherId, которые будут содержать идентификаторы из соответствующих списков. Кроме того, для них добавлены "спутники" Language и Publisher указывающие на соответствующие экземпляры данных классов.
  • Свойство PublisherId может принимать значение null, что будет обозначать отсутствие издателя. Это может потребоваться, например, если это книга опубликованная на сайте автора.

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

А что дальше?

Следующим шагом в разработке должно стать создание Контроллеров и Представлений. Для этого можно поступить аналогично рассмотренному в прошлой части варианту и самостоятельно написать весь код. Но это достаточно трудоемкий способ для относительно однообразной работы.

Creating Controller with EF supportДругим вариантом будет использование готовых шаблонов, поставляемых с ASP.NET MVC 3. Для этого необходимо в диалоге создания Контроллера в качестве Template выбрать: Controller with read/write actions and views, using Entity Framework.

В этом случае помощник ASP.NET MVC 3 создаст Представления и Контроллер. В последний будет добавлен исходный код для взаимодействия с базой данных с применением Entity Framework. Рассмотрим работу этой библиотеки позже, а пока просто взглянем на методы полученного в результате класса Контроллера:

//
// GET: /Catalog/Edit/5
 
public ActionResult Edit(int id)
{
    BookDetails bookdetails = db.BookDetails.Find(id);
    ViewBag.LanguageId = new SelectList(db.Languages, "Id", "Name", bookdetails.LanguageId);
    ViewBag.PublisherId = new SelectList(db.Publishers, "Id", "Title", bookdetails.PublisherId);
    return View(bookdetails);
}

//
// POST: /Catalog/Edit/5

[HttpPost]
public ActionResult Edit(BookDetails bookdetails)
{
    if (ModelState.IsValid)
    {
        db.Entry(bookdetails).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    ViewBag.LanguageId = new SelectList(db.Languages, "Id", "Name", bookdetails.LanguageId);
    ViewBag.PublisherId = new SelectList(db.Publishers, "Id", "Title", bookdetails.PublisherId);
    return View(bookdetails);
}

В примере выше представлены два метода, созданные помощником ASP.NET MVC 3 и относящиеся к Действию Edit. Как можно легко заметить, в коде раскрываются детали работы с базой данных и библиотекой Entity Framework. Такой подход может в дальнейшем способствовать переносу логики приложения в Контроллер, что противоречит сути шаблона MVC.

Вполне логично было бы использовать шаблон Repository, подразумевающий более абстрактную работу с источником данных. Конечно, можно все сделать вручную. К счастью, этом случае есть более удобное решение – давайте сократим срок начальной разработки и применим заготовки (scaffolding).


Исходный код проекта (C#, Visual Studio 2010): mvc3-in-depth-basics-05.zip

Комментарии (27) -

Очень много статей и обзоров опубликовано уже на эту тему, и в каждом примере рассматривают только связь "One-To-Many", было бы замечательно если бы было рассмотрен пример CodeFirst именно со связью "Many-To-Many", точнее как её реализовать/переделать/дополнить(т.к. CodeFirst пока её не поддерживает я так понимаю).

Ошибаетесь про EF CodeFirst. И в данной Моделе как раз есть основа для такого примера.

Т.е. Вы хотите сказать что EF CodeFirst поддерживает связь Many-To-Many? Я не спорю, что основа есть в данном примере, хотелось бы на реализацию посмотреть. Если это у Вас появится в следующих статьях, я бы с удовольствием прочитал. Спасибо большое за статью!

@ psyb00t:

Вот пример реализации:
blogs.msdn.com/.../...ny-to-many-relationship.aspx

Ссылка на исходный код проекта не работает!

Спасибо что заметили. Поправил.

При создании проекта Web Developer ругается на строки в CatalogController

ViewBag.LanguageId = new SelectList(db.Languages, "Id", "Name", bookdetails.LanguageId);
ViewBag.PublisherId = new SelectList(db.Publishers, "Id", "Title", bookdetails.PublisherId);

Пишет что BookCatalog.Models.CatalogContext не содержит определения для Languages и Publishers

Почему ?

@ Serge: А вы взяли готовый проект (приложенный к этой части) или создавали по описанным шагам? Кроме того, установлена ли последняя версия ASP.NET MVC 3? Так же обратите внимание, что при создании контроллера создается контекст. На этот момент классы модели должны быть в сборке (необходимо скомпилировать проект).

Андрей 03.12.2011 1:05:20

Очень прошу помощи!!!! При выполнении проекта студия дает предупреждение на строке
var bookdetails = db.BookDetails.Include(b => b.Language).Include(b => b.Publisher);

а после нажатия Продолжить браузер выдает ошибку:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Андрей 03.12.2011 1:13:06

Да забыл предупреждение вот какое падает:
The provider did not return a ProviderManifestToken string.

а строка эта расположение в контроллере, методе:
public ViewResult Index()
        {
var bookdetails = db.BookDetails.Include(b => b.Language).Include(b => b.Publisher);
            return View(bookdetails.ToList());
        }

@ Андрей: Не установлен или не запущен SQL сервер. В демонстрационном проекте используется SQL Compact 4.0
www.microsoft.com/.../details.aspx

Хотя вы можете использовать и другие версии по желанию.

Игорь 05.12.2011 19:40:53

    ViewBag.LanguageId = new SelectList(db.Languages, "Id", "Name", bookdetails.LanguageId);
    ViewBag.PublisherId = new SelectList(db.Publishers, "Id", "Title", bookdetails.PublisherId);

Вот такие строки создаются в контроллере, а у нас определены классы Language и Publisher, без буквы "S" в конце.
У меня тоже возникла такая-же проблема как и у Serge, поправил руками, заработало.

@ Игорь: Cтранно. Это не имена классов Модели. db.Languages это свойство класса контекста и оно совпадает с именем таблицы в базе данных. В EF по умолчанию при создании имен таблиц происходит преобразование названий. В частности, они генерируются во множественной форме от имени класса. Отсюда и "s" в окончании слова: Language -> Languages.

Посмотрите – в классе контекста базы данных какие свойства? Без "s"?

Димка 27.01.2012 12:18:11

на gotdotnet.ru/forums/ пропиарил твой сайт как хороший источник изучения mvc  =)

Здравствуйте, Андрей!
не могли бы вы залить на какой-нибудь файло-обменник полное готовое приложение BookCatalog для большей наглядности.
Заранее спасибо.

@ Лия: А чем не устраивает ссылка в конце статьи?

Извините пожалуйста, не заметила сразу

Евгений 29.04.2012 22:36:48

А где можно посмотретьтолковое описание конфигурации подключения к SQL Server?
У меня установлен 2008, не экспресс.
Как я понял, у Вас подключение настраивается в Web.Debug.Config
    <connectionStrings>
      <add name="MyDB"
        connectionString="Data Source=ReleaseSQLServer;
                        Initial Catalog=MyReleaseDB;
                        Integrated Security=True"
        xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    </connectionStrings>

У меня при запуске упорно выдается сообщение об ошибке при подключении к серверу. Мол, сервер не найден. Пробовал уже и явно пароль sa задавать - не помогает.
Простите за чайницкий вопрос, но из-за этой мелочи не получается идти дальше.

Да, подключание указывается в web.config. А имя сервеа правильно указано у вас, инстанс тоже в нем верный?

Евгений Липес 17.07.2012 20:35:25

@ Andrey:

Имя сервера у меня ставилось неправильное - исходя из каких-то умолчаний, там был (local)/SQLExpress, вместо (local)/SQL2008.

Евгений Липес 17.07.2012 20:36:49

Андрей, а как можно пересоздать представления, не используя scaffolding - не подскажете навскидку ?
Т.е. контроллер есть, а вот представления я хочу пересоздать ?

Евгений Липес 18.07.2012 12:59:55

@ Евгений Липес:

Уже нашел )

@ Евгений Липес: Ну вот - пришел, а проблема уже решена Smile

спасибо наконец-то понял немного как модели создавать.

Скажите пж это:
public int? PublisherId { get; set; }

опечатка? Зачем "int?"   вопросительный знак?

@ Димка:
int? это сокращенная форма записи Nullable<int>, т.е. переменная может принимать не только int значения, но и null.

msdn.microsoft.com/en-us/library/b3h38hb0.aspx

Ну а в данном случае это говорит о том, что издателя может и не быть.

@ Andrey:

спасибо за ответ

Павел 11.06.2013 17:54:19

"уникальное код в локальной базе данных", правильно "уникальный код".

Добавить комментарий