Текст книги "Asp.net mvc framework"
Автор книги: Гайдар Магдануров
сообщить о нарушении
Текущая страница: 15 (всего у книги 17 страниц)
Подмена реализации может быть использована во всех случаях, требующих привязки к конкретным сервисам или функциональности, использование которой в тестовых условиях не требуется для проведения тестов.
Для поддержки возможности замены конкретной реализации необходимо использовать интерфейсы для основных зависимостей действий контроллеров. Например, если в приложении есть выделенный объект, отвечающий за восстановление и сохранение объектов данных DataManager, то в контроллерах следует использовать интерфейс iDataManager, который реализует конкретный класс DataManager. Таким образом, во время проведения тестов объект DataManager может быть подменен объектом DataManagerMock, не содержащим полного функционала работы с данными и, возможно, даже не работающим с базой данных. Этот распространенный подход к тестированию называется имитацией (англ. moking).
Для реализации имитации при тестировании во время разработки удобно использовать фабрику контроллеров и инъекцию зависимостей, когда необходимые контроллеру объекты передаются через вызов конструктора, таким образом контроллер не инициирует создание объектов, а получает все объекты как данность. Другим подходом является реализация нескольких отдельных конструкторов для контроллеров для использования в тестовом окружении.
Рассмотрим создание более сложного теста на примере тестового сценария для контроллера Accountcontroller, входящего в шаблон MVC-приложения. В листинге 8.2 приведен фрагмент кода Accountcontroller, демонстрирующий использование интерфейсов в конструкторах класса.
Листинг 8.2. Фрагмент кода класса Accountcontroller
public class Accountcontroller : Controller
{
public Accountcontroller() : this(null, null)
{
}
public AccountController(IFormsAuthentication formsAuth,
IMembershipService service)
{
FormsAuth = formsAuth ?? new FormsAuthenticationService();
MembershipService = service ?? new AccountMembershipService();
}
public IFormsAuthentication FormsAuth
{
get;
private set;
}
public IMembershipService MembershipService
{
get;
private set;
}
}
Поскольку в контроллере Accountcontroller используются компоненты инфраструктуры ASP.NET, отвечающие за аутентификацию пользователей и управление учетными записями, для них созданы специальные обертки, приведенные в листинге 8.3, реализующие интерфейсы IFormsAuthentication и IMembershipService.
Листинг 8.3. Интерфейсы и реализация поддержки базовых служб
public interface IFormsAuthentication
{
void SignIn(string userName, bool createPersistentcookie);
void SignOut();
}
public class FormsAuthenticationService : IFormsAuthentication
{
public void SignIn(string userName, bool createPersistentCookie)
{
FormsAuthentication.SetAuthCookie(userName,
createPersistentCookie);
}
public void SignOut()
{
FormsAuthentication.SignOut();
}
}
public interface IMembershipService
{
int MinPasswordLength { get; }
bool ValidateUser(string userName, string password);
MembershipCreateStatus CreateUser(string userName,
string password, string email);
bool ChangePassword(string userName,
string oldPassword, string newPassword);
}
public class AccountMembershipService : IMembershipService
{
private MembershipProvider _provider;
public AccountMembershipService()
: this(null)
{
}
public AccountMembershipService(MembershipProvider provider)
{
_provider = provider ?? Membership.Provider;
}
public int MinPasswordLength
{
get
{
return _provider.MinRequiredPasswordLength;
}
}
public bool ValidateUser(string userName, string password)
{
return _provider.ValidateUser(userName, password);
}
public MembershipCreateStatus CreateUser(string userName,
string password, string email)
{
MembershipCreateStatus status;
_provider.CreateUser(userName, password,
email, null, null, true, null, out status);
return status;
}
public bool ChangePassword(string userName,
string oldPassword, string newPassword)
{
MembershipUser currentUser = _provider.GetUser(
userName, true);
return currentUser.ChangePassword(oldPassword, newPassword);
}
}
Выделение обертки над службами ASP.NET позволяет не только получить возможность простого тестирования, но и заменять реализацию в процессе развития приложения. Например, при замене стандартного провайдера Membership на собственную реализацию или изменение провайдера авторизации для поддержки сертификатов, или Windows-аутентификация. Однако в целях тестирования этот подход просто незаменим.
Во время работы веб-приложения, созданного из шаблона, используется стандартная фабрика контроллеров, не передающая дополнительных параметров конструктору класса Accountcontroller, в тестовом же окружении используется имитация – код классов, имитирующих бурную деятельность. Пример классов, реализующих подход имитации, приведен в листинге 8.4.
Листинг 8.4. Классы для имитации в условиях тестирования
public class MockFormsAuthenticationService :
IFormsAuthentication
{
public void SignIn(string userName,
bool createPersistentcookie) { }
public void SignOut() { }
}
public class MockHttpContext : HttpContextBase
{
private IPrincipal _user;
public override IPrincipal User
{
get
{
if (_user == null)
{
_user = new MockPrincipal();
}
return _user;
}
set
{
_user = value;
}
}
}
Аналогично коду, приведенному в листинге 8.4, строятся остальные классы, используемые для тестирования. Основной принцип их создания состоит в том, чтобы развязать процесс тестирования с реальным функционалом – вместо работы с базами данных не делать ничего, либо возвращать стандартные значения, используемые для тестирования, и т. п.
Пример использования имитирующих классов в целях тестирования приведен в листинге 8.5. Процесс подготовки объектов выделен в отдельный метод GetAccountController() .
Листинг 8.5. Несколько методов для тестирования AccountController
private static AccountController GetAccountController()
{
IFormsAuthentication formsAuth = new
MockFormsAuthenticationService();
MembershipProvider membershipProvider = new MockMembershipProvider();
AccountMembershipService membershipService = new
AccountMembershipService(membershipProvider);
AccountController controller = new
AccountController(formsAuth, membershipService);
ControllerContext controllerContext = new
ControllerContext(new MockHttpContext(),
new RouteData(),
controller);
controller.ControllerContext = controllerContext;
return controller;
}
[Test]
public void RegisterPostReturnsViewIfPasswordIsNull()
{
AccountController controller = GetAccountController();
ViewResult result = (ViewResult)controller.Register("username",
"email", null, null);
Assert.AreEqual(6, result.ViewData["PasswordLength"]);
Assert.AreEqual(
"You must specify a password of 6 or more characters.",
result.ViewData.ModelState["password"].Errors[0].ErrorMessage);
}
[Test]
public void
RegisterPostReturnsViewIfNewPasswordDoesNotMatchConfirmPassword()
{
AccountController controller = GetAccountController();
ViewResult result = (ViewResult)controller.Register("username",
"email", "password", "password2");
Assert.AreEqual (6, result.ViewData["PassworcLLength"] );
Assert.AreEqual(
"The new password and confirmation password do not match.",
result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage);
}
[Test]
public void RegisterPostReturnsViewIfPasswordIsTooShort()
{
AccountController controller = GetAccountController();
ViewResult result = (ViewResult)controller.Register("username",
"email", "12345", "12345");
Assert.AreEqual(6, result.ViewData["PasswordLength"]);
Assert.AreEqual(
"You must specify a password of 6 or more characters.",
result.ViewData.ModelState["password"].Errors[0].ErrorMessage);
}
[Test]
public void RegisterPostReturnsViewIfRegistrationFails()
{
AccountController controller = GetAccountController();
ViewResult result = (ViewResult)controller.Register("someUser",
"DuplicateUserName" /* error */, "badPass", "badPass");
Assert.AreEqual(6, result.ViewData["PasswordLength"]);
Assert.AreEqual(
"Username already exists. Please enter a different user name.",
result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage);
}
[Test]
public void RegisterPostReturnsViewIfUsernameNotSpecified()
{
AccountController controller = GetAccountController();
ViewResult result = (ViewResult)controller.Register("", "email",
"password", "password");
Assert.AreEqual(6, result.ViewData["PasswordLength"]);
Assert.AreEqual("You must specify a username.",
result.ViewData.ModelState["username"].Errors[0].ErrorMessage);
}
Основная концепция, продемонстрированная в листинге 8.5, заключается в использовании «подставных» реализаций везде, где применяются реализации, не зависящие от кода, описанного в самом контроллере, а также в создании отдельных тестовых методов для разных сценариев выполнения одного и того же действия контроллера.
Заключение
Необходимость автоматического тестирования становится очевидной при увеличении сложности проекта и необходимости его постоянного развития, привлечении новых разработчиков и проведения рефакторинга существующего кода. Введение практики создания модульных тестов при разработке функциональности приложения позволяет в будущем значительно сэкономить на ресурсах поддержки проекта. MV C Framework предлагает архитектуру, позволяющую реализовать автоматическое тестирование приложений без необходимости создания сложных тестовых сред, и создание тестов перестает быть головной болью разработчиков.
ГЛАВА 9
ASP.NET MVC 2 и Visual Studio 2010
Пока писалась эта книга, процесс разработки ASP.NET MVC Framework не стоял на месте. Сначала вышла предварительная версия ASP.NET MVC Preview 1, а затем и ASP.NET MVC Preview 2. Некоторое время спустя была выпущена и новая версия инструмента разработчиков Visual Studio 2010 Beta 2. На момент написания этой части книги Visual Studio 2010 Beta 2 и ASP.NET MVC Preview 2 – последние версии, доступные широкой аудитории разработчиков.
Новая вторая версия ASP.NET MVC принесла во Framework значительную порцию нового функционала, новых концепций, новых удобных инструментов и API. В этой части книги мы бы хотели кратко осветить то, что предлагается разработчиками ASP.NET в предварительных версиях Framework.
Области
Понятие областей (areas) для ASP.NET MVC – новая концепция. Суть областей сводится к тому, чтобы предоставить возможность разделить чрезмерно большой проект на несколько групп, которые продолжали бы взаимодействовать друг с другом. Например, в компании работает несколько групп разработчиков, которые создают большой проект, включающий в себя: основной сайт, магазин, обзоры товаров, форум, внутренний сайт по администрированию и пр. Для создания такого проекта ASP.NET MVC 2 предлагает выделить каждую отдельную часть проекта в область, вместе эти области продолжали бы представлять собой один взаимосвязанный проект. Выделение из большого проекта отдельных проектов-областей позволит разным группам разработчиков более независимо разрабатывать свои части приложения.
Предположим, что в крупной компании разрабатывается большой проект на базе ASP.NET MVC. Обычно группы, работающие над разными частями проекта, независимы друг от друга и связаны только спецификацией. Поэтому очень важно не допустить возможных конфликтов в программном коде, которые могут возникнуть, когда несколько частей большого проекта сливаются в одно целое. Так, например, группы, разрабатывающие части, отвечающие за магазин и за блог сайта, могут независимо друг от друга создать одноименные контроллеры AdminController. В случае, когда проект не основан на областях, слияние двух независимых частей с одноименным контроллером может быть сильно затруднено или невозможно. С использованием областей данная проблема решается, т. к. каждая область может иметь свой собственный независимый набор контроллеров.
Области предлагают механизм, схожий с пространствами имен в языках программирования. Они разделяют функционал частей и подсистем проекта на независимые составляющие, которые не могут конфликтовать друг с другом. Таким образом, области – это инструмент, помогающий разделять большой проект на малые независимые части для слаженной командной разработки.
Концепция областей появилась в ASP.NET MVC 2 Preview 1 в виде областей в решении с несколькими проектами. После выхода ASP.NET MVC 2 Preview 2 применение областей было расширено возможностью создавать области в одном проекте. Рассмотрим оба варианта.
Области для нескольких проектов
При создании решения с использованием областей, работающих для нескольких проектов, необходимо следовать следующим шагам:
1. Создать проект, который будет родительским (или главным) по отношению к проектам-областям.
2. Создать проекты-области, например, для форума, блога, магазина и т. д. В этих проектах требуется удалить файлы Global.asax, кроме того, можно удалить созданные по умолчанию файлы в папку Scripts, поскольку все скрипты будут браться из родительского проекта.
3. Добавить ссылки на проекты-области в родительском проекте.
4. Создать для каждого из проектов класс, наследующий от класса AreaRegistration, в котором перегрузить два элемента: метод RegisterArea и свойство AreaName. RegisterArea должен реализовать логику по регистрированию области в контексте приложения, свойство AreaName должно вернуть название области.
5. В родительском проекте в Global.asax необходимо вызвать метод AreaRegistration.RegisterAllAreas(); для регистрации областей в контексте приложения во время запуска.
6. Заключительным шагом, который требуется для успешного запуска проекта с областями, является модификация файлов проекта *.csproj (*.vbproj для проектов на Visual Basic) каждого из проектов. В каждого таком файле необходимо раскомментировать относящиеся к областям строки кода (будет показано далее).
Рассмотрим шаги немного подробнее. На рис. 9.1 показана структура решения, в котором содержится три проекта: родительский проект AreasProject и два проекта-области – Forum и Store, которые реализуют логику форума и магазина соответственно.

Рис. 9.1. Структура проекта ASP.NET MVC с областями
В проекте-области Forum создан контроллер ForumController, который реализует три действия: Index, AddPost, EditPost. Точно так же в проекте Store создан контроллер ProductsController с действиями List, AddReview и Details. Для отображения результатов действий созданы соответствующие представления. После создания проектов-областей в проект AreasProject были добавлены ссылки на эти проекты.
На рис. 9.1 вы можете заметить в проектах Store и Forum файл Routes.cs. Этот файл добавлен в проекты для того, чтобы реализовать класс AreaRegistration. Рассмотрим содержимое файла Routes.cs в проекте Store в листинге 9.1.
Листинг 9.1. Содержимое файла Routes.cs проекта Store
namespace Store
{
using System.Web.Mvc;
using System.Web.Routing;
public class Routes : AreaRegistration
{
public override void RegisterArea(
AreaRegistrationContext context)
{
context.MapRoute(
«Store_Default»,
«Store/{controller}/{action}/{id}»,
new {controller = «Products»,
action = «List», id = ""},
new string[] { «Store.Controllers» }
);
}
public override string AreaName
{
get { return «Store»; }
}
}
}
Обратите внимание, класс Routes наследуется от AreaRegistration, что позволит подсистеме ASP.NET MVC в дальнейшем проинициализировать с помощью него подсистему областей. В классе Routes содержится два перегруженных члена: метод RegisterArea и свойство AreaName.
В RegisterArea производится регистрация маршрутов в контексте приложения с областями. С помощью context.MapRoute создается маршрут под названием store_Default. Последнее значение параметра new string[] { "Store.Controllers" } определяет пространство имен, в котором находятся контроллеры, сопоставленные данному маршруту.
Перегруженное свойство AreaName возвращает название области. Это имя в дальнейшем используется как ASP.NET MVC, так и разработчиком для указания области, из которой необходимо вызвать требуемый функционал.
Классы, подобные Routes для проекта Store, необходимо создать для каждого проекта в решении с областями.
Следующим шагом будет добавление в родительском проекте AreasProject в Global.asax следующей строки кода:
protected void Application_Start()
{
AreaReg±stration.Reg±sterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
Вызов AreaRegistration.RegisterAllAreas() при запуске приложения позволит ASP.NET MVC инициализировать все имеющиеся области и их маршруты в приложении.
Последним шагом, необходимым для запуска проекта с областями, является модификация файлов проекта *.csproj (или *.vbproj, если ваши проекты написаны на Visual Basic) для каждого из проектов в решении. Для того чтобы получить доступ к этим файлам, последовательно выгрузите проекты из решения с помощью команды UnloadProject. В контекстном меню на выгруженном проекте выберите Edit***.csproj (рис. 9.2), где *** – название проекта.

Рис. 9.2. Контекстное меню, позволяющее редактировать файл проекта
В файлах проектов содержится часть закомментированных определений, которые требуется раскомментировать для использования решения на базе областей. Для проектов-областей это куски кода с комментариями:
и
Для родительского проекта необходимо раскомментировать куски кода, помеченные:
и
Примечание
Возможно, что в следующих релизах ASP.NET MVC 2 действие с редактированием файлов проекта будет излишним. Ожидается, что работа с областями будет поддерживаться на уровне Solution Explorer, что облегчит создание новых областей и проектов на их базе.
*****************************
Теперь, когда проект подготовлен полностью, можно использовать проекты-области в родительском проекте следующим образом. Видоизменим в файл Site.Master в родительском проекте блок кода списка вкладок так, чтобы он выглядел, как во фрагменте:
Обратите внимание, что построение ссылок с помощью Html.ActionLink теперь требует указания области, в которой нужно найти контроллер и действие. Если мы запустим проект, то увидим следующее (рис. 9.3).
Новые вкладки Store и Forum будут работать согласно заданным правилам и отображать представления из проектов-областей.

Области в одном проекте
Если в решении предыдущего примера областей для нескольких проектов включить опцию показа скрытых файлов для родительского проекта, то мы увидим следующую картину (рис. 9.4).
В родительском проекте AreasProject в папке Views/Areas создаются скрытые элементы из проектов-областей. Это требуется для того, чтобы родительский проект самостоятельно отображал представления проектов-областей.
Однако эта структура скрытых файлов может быть использована и напрямую. В ASP.NET MVC 2 Preview 2 появилась возможность создания решений с одиночными проектами на базе областей. Такие решения содержат только один проект, в котором реализованы все области. На рис. 9.5 представлена структура одиночного проекта, где реализованы те же самые области, которые были реализованы в предыдущем разделе с помощью нескольких проектов-областей.
Чтобы создать такой проект, по аналогии с предыдущим примером, мы должны создать вместо нескольких проектов иерархию областей в одном проекте. Для этого в корне проекта необходимо создать папку Areas, в которой каждая созданная папка будет являться отдельной областью. На рис. 9.5 такими папками являются Forum и Store. В них следует создать папку Controllers и Views для контроллеров и представлений и реализовать их подобно тому, как мы сделали это в предыдущем разделе. Обратите внимание, что для каждой папки-области также нужно создать файл Routes.cs с реализацией класса AreaRegistration. Необходимо обновить и Global.asax, добавив в него вызов AreaRegistration.RegisterAllAreas ().
Приятное отличие разработки единичного проекта на базе областей от решения с несколькими проектами-областями состоит в том, что при создании одиночного проекта ASP.NET MVC на базе областей нет необходимости изменять его файл проекта, т. к. нет нужды обрабатывать зависимости между проектами. В целом, это несколько упрощает создание решения с областями.


Функционал областей – это полезный механизм, который позволяет разделить в большом решении логику различных частей на составные объекты – области. Вы можете выбрать, как области будут представлены в вашем решении: на базе нескольких проектов или же на базе только одного проекта. В любом случае, создание областей не составит труда и позволит вам удобно разделить разнородные функции в приложении.
Шаблонные помощники
Шаблонные помощники (templated helpers) – это новое средство в MVC 2, которое призвано сократить время разработки представлений. Шаблонные помощники строго типизированы и основаны на моделях приложения, кроме того, для вывода информации и валидации модели используется механизм DataAnnotations. В общем, шаблонных помощников можно представить себе как генератор разметки на базе определенной модели и правил, указанных с помощью атрибутов DataAnnotations.
Значительное преимущество шаблонных помощников состоит в том, что строгая типизация при их использовании позволяет еще на этапе написания кода и позже при компиляции избегать ошибок, связанных с неверными значениями параметров. Вместо указания параметров в виде текстовой строки, при написании которой можно допустить ошибки или опечатки, шаблонные помощники предлагают использовать элементы модели данных, что исключает подобные ошибки.
Для примера рассмотрим следующую модель данных:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public DateTime UpdatedAt { get; set; }
}
Класс Product представляет собой модель продукта для некого магазина. Модель содержит идентификатор, название, описание, стоимость и дату обновления.
Механизм шаблонных помощников устроен так, что позволяет отображать разметку для модели всего одной строчкой кода. Например, создадим контроллер StoreController и строготипизированное представление Info, основанное на Product, которое отображает информацию о продукте (листинг 9.2).
Листинг 9.2. Представление с использованием шаблонных помощников
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage
runat="server"> Информация о продукте
runat="server"> Info
<%= Html.DisplayFor(x => Model)%>
Обратите внимание на выделенный участок кода, который представляет собой одну строчку шаблонного помощника, предназначенную для вывода информации о переданном экземпляре модели.
Код контроллера StoreController для примера будет таким, как в листинге 9.3.
Листинг 9.3. Контроллер StoreController
namespace TemplatedHelpersProject.Controllers
{
using System;
using System.Web.Mvc;
using TemplatedHelpersProj ect.Models;
public class StoreController : Controller
{
public ActionResult Info()
{
var product = new Product() { Cost = 1,
Description = "Описание",
Id = 1, Name = "Товар",
UpdatedAt = DateTime.Now };
return View(product);
}
}
}
Запустим приложение на выполнение и перейдем на представление Store/Info. Результатом будет полная информация, заведенная нами в экземпляре Product (рис. 9.6).

Как вы можете увидеть, написав всего одну строчку кода в представлении, мы сформировали разметку для целой комплексной модели данных.
Шаблоны по умолчанию
Для работы с шаблонами по умолчанию ASP.NET MVC предлагает несколько стандартных методов расширения:
□ Html.LabelFor – производит рендеринг описательной информации для данных модели. По умолчанию выводит название поля модели;
□ Html.DisplayFor – производит рендеринг значений данных модели в соответствии с их типом;
□ Html.EditorFor – производит рендеринг элементов управления с данными модели для возможности редактирования.
Примечание
Кроме перечисленных стандартных методов расширения, в будущем планируется добавить методы: ValidationMessageFor, TextAreaFor, TextBoxFor, HiddenFor, DropDownListFor для соответствующей поддержки валидации и элементов управления в различных представлениях.
************************
Для всех этих методов расширения есть дополнительные методы LabelForModel, DisplayForModel и EditorForModel, которые являются сокращенным вариантом своих аналогов. Эти методы предназначены для рендеринга всей модели данных целиком без указания параметров.
Рассмотрим вариант рендеринга модели с помощью стандартных методов расширения. Для этого сформируем следующее представление (листинг 9.4).
Листинг 9.4. Модифицированное представление
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage
runat="server"> Info
runat="server">
Результат работы данного кода представлен на рис. 9.7.
Обратите внимание, что с использованием методов Html.LabelFor и Html.EditorFor для каждого элемента модели мы получили возможность осуществлять рендеринг данных в таком виде, в котором нам бы этого хотелось. Вместо стандартной разметки с использованием
- для представления только той ее части, которая нужна нам.

Создание шаблонов
Существует большая вероятность того, что один и тот же рендеринг одного и того же типа данных вам предстоит выполнить в разных представлениях. Для того чтобы поддержать такой сценарий, ASP.NET MVC предлагает разработчику создать один-единственный шаблон рендеринга типа данных, а затем использовать его во всех необходимых местах.
Для того чтобы создать шаблон представления типа данных, необходимо в папке нужного контроллера в папке представлений Views создать одну из папок: DisplayTemplates или EditorTemplates. DisplayTemplates должна содержать шаблоны для рендеринга представлений для просмотра, а EditorTemplates – для редактирования. Папки DisplayTemplates и EditorTemplates содержат частичные представления *.ascx, наименование которых соответствуют типам данных, с которыми необходимо работать.
Например, на рис. 9.8 в папке Views/Store представлена папка DisplayTemplates, которая содержит частичное представление Product.ascx. Это означает, что для рендеринга данных типа Product при работе в контроллере storeController будет использоваться шаблон Product.ascx.
Содержимое Product.ascx представлено в листинге 9.5.

Листинг 9.5. Содержимое Product.ascx
<%@ Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl
%>
Как вы можете убедиться, шаблон Product.ascx соответствует той разметке, которую мы создавали для рендеринга в листинге 9.4. Однако если теперь модифицировать код листинга 9.4 до простого варианта <%= Html.DisplayFor (x => Model) %>, то результат останется неизменным, т. к. механизм ASP.NET MVC, обнаружив имеющийся шаблон Product.ascx, использует его для рендеринга представления модели с типом Product.
Аннотация данных и валидация
ASP.NET MVC 2 имеет поддержку валидации данных на основании аннотации с помощью атрибутов из пространства имен System.ComponentModel.DataAnnotations.
Давайте улучшим наше представление, внеся изменение в модель так, как показано в листинге 9.6.
Листинг 9.6. Модель данных с аннотациями
namespace TemplatedHelpersProj ect.Models
{
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class Product
{
[DisplayName("Идентификатор")]
[HiddenInput]
public int Id { get; set; }
[DisplayName(«Наименование товара»)]
Популярные книги за неделю
-
Недостает только 3 репортажа.Перед вами — сборник репортажей из Освенцима, Заксенхаузена,…
-
Пройдя все мыслимые горячие точки, я честно поймал свою пулю и приготовился к вечному…
-
Соблазнить злодея в планы не входило…
Муж изменил, подруга предала, еще и очутилась внутри романа в роли неудачницы-злодейки.… -
В мире Пекатум появилось Зло, ползущее из разлома, прозванного Прядью. И Зло побеждало,…
-
Ненужная жена дракона. Колокольня его…
Меня вызвали в Зал брачных клятв, где муж-дракон должен был защитить моё имя.Вместо этого… -
Я Ника Соболева и точно знаю, как нас разводят романтические шоу. Монтаж, подставные…
-
Поддавшись уговорам подруг, согласилась погадать на суженого, но что-то пошло не так. Я…
-
Единственная. Попаданка к оборотням…
НЕЗАКОННОЕ ПОТРЕБЛЕНИЕ НАРКОТИЧЕСКИХ СРЕДСТВ, ПСИХОТРОПНЫХ ВЕЩЕСТВ, ИХ АНАЛОГОВ ПРИЧИНЯЕТ… -
Создано с помощью нейросети. Ещё вчера он был обычным молодым парнем, что по вечерам с…
-
Она хотела только одного: тишины. Отцовского дома. Философских и военных свитков и…
-
Я нахмурилась, осмотрелась по сторонам, а затем поняла, что звук идет из ящика, который…
-
Это точно он. Тот, кто пару лет назад на ночном пляже лишил меня невинности. Сидит за…
-
Создано с помощью нейросети.Сорок третий успешно давит врагов своей новой страны, ведь…
-
Провинция Хэйлунцзян, далёкая глубинка.Жениться на Сяо Ши, первой красавице района - идея…
-
Тайна Стихий и дракон в придачу (СИ)
Незаконное потребление наркотических средств, психотропных веществ, их аналогов причиняет… -
Отбор невест для принца, Неправильная…
Мечтала ли я попасть на отбор невест наследника престола? Нет. Никогда.Но украдена… -
Незаконное потребление наркотических средств, психотропных веществ, их аналогов причиняет…
-
Как говорить с детьми об искусстве.
Первая книга об искусстве для детей, адресованная взрослым -
После развода с предателем (СИ)
— У меня давно другая, — голос мужа звучит ровно, сухо, словно он рассказывает о прогнозе… -
Развод в 35. Я больше не твоя (СИ)
Десять лет брака я была хорошей женой.Сидела дома с детьми, потому что муж сказал:— Зачем…




























