Итак, мы подготовили разделы «Категории» и «Коллекции», и выглядят они у нас вполне прилично. Теперь наша задача разместить в них товары. Начнём с раздела «Категории». Соотношение между категориями и товарами будет «много-ко-многим». Это означает, что мы должны создать для них промежуточную таблицу.
Но сначала, давайте добавим загрузку картинок к товарам.
В папке Content создадим папку ProductImages, а внутри неё – папку Mini. В папке ProductImages будут храниться полноразмерные картинки товаров, а в папке Mini – их уменьшенные варианты для списков. Поместим в папку Mini картинку-заглушку 0.jpg.
Добавим в класс Constants константы, содержащие адреса этих папок, размеры картинок и количество картинок в одной строке списка.
Фрагмент Constants.cs
...
public const string TITLE = "Интернет-магазин мебели '21 век'";
public const string BREADCRUMBS_SEPARATOR = " > ";
public const int PAGER_LINKS_PER_PAGE = 15;
public const int PAGER_NUMBER_OF_VISIBLE_LINKS = 5;
public const string ROLE_ADMIN = "admin";
public const string ROLE_CONTENT_MANAGER = "contentmanager";
public const string ROLES_ADMIN_CONTENT_MANAGER = "admin,contentmanager";
public const string PROFILE_FIRST_NAME = "FirstName";
public const string PROFILE_MIDDLE_NAME = "MiddleName";
public const string PROFILE_LAST_NAME = "LastName";
public const string CATEGORY_MINI_IMAGES_FOLDER = "CategoryMiniImages";
public const int CATEGORY_NUMBER_PER_ROW = 3;
public const int CATEGORY_MINI_IMAGE_HEIGHT = 168;
public const int CATEGORY_MINI_IMAGE_WIDTH = 238;
public const string COLLECTION_MINI_IMAGES_FOLDER = "CollectionMiniImages";
public const int COLLECTION_NUMBER_PER_ROW = 3;
public const int COLLECTION_MINI_IMAGE_HEIGHT = 168;
public const int COLLECTION_MINI_IMAGE_WIDTH = 238;
// Папки для загрузки картинок для фотогалереи раздела Коллекции
public const string COLLECTION_IMAGE_FOLDER = "CollectionImages";
public const string COLLECTION_IMAGE_PREVIEW_FOLDER = "Preview";
// Размеры уменьшенной и увеличенной картинки для фотогалереи раздела Коллекции
public const int COLLECTION_IMAGE_HEIGHT = 0;
public const int COLLECTION_IMAGE_WIDTH = 750;
public const int COLLECTION_IMAGE_PREVIEW_HEIGHT = 100;
public const int COLLECTION_IMAGE_PREVIEW_WIDTH = 0;
public const int COLLECTION_IMAGE_PREVIEW_COUNT = 5;
// Папки для загрузки картинок для раздела Товары
public const string PRODUCT_IMAGES_FOLDER = "ProductImages";
public const string PRODUCT_IMAGES_MINI_FOLDER = "Mini";
// Размеры картинок для раздела Товары
public const int PRODUCT_IMAGE_HEIGHT = 0;
public const int PRODUCT_IMAGE_WIDTH = 750;
public const int PRODUCT_IMAGE_MINI_HEIGHT = 168;
public const int PRODUCT_IMAGE_MINI_WIDTH = 238;
// Количество мини-изображений товара в одной строке на странице списка
public const int PRODUCT_NUMBER_IN_ROW = 3;
...
Перейдём в базу данных. Добавим в таблицу Product столбец ImageExt. Он будет содержать расширение картинки для товара. Название файла картинки будет таким же, как ID товара, как было и в случае с категориями и коллекциями.
Откроем теперь объект Entity Framework, и обновим его (правая кнопка -> Update Model fr om Database...). В объекте Product должно появиться свойство ImageExt.
В контроллер товаров ProductController добавим действие UploadImage, которое будет обрабатывать загрузку картинок.
ProductController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using _21century.Models;
using _21century.Service.Interface;
using _21century.Service.Factory;
using _21century.Controllers.Abstract;
namespace _21century.Controllers
{
public class ProductController : UrlFriendlyController<Product>
{
public ProductController(IUrlFriendlyService<Product> _service) : base(_service) { }
public ProductController() : this(ProductServiceFactory.Create()) { }
// Включаем постраничный вывод
protected override bool IsPageable { get { return true; } }
#region Overridden virtual methods
// На страницу списка переходим через действие GetByShortName без параметров
protected override ActionResult ReturnToList(Product obj)
{
return RedirectToAction("GetByShortName", new { shortname = string.Empty, page = Request.QueryString["page"] });
}
// На страницу объекта переходим через действие GetByShortName с текстовым параметром shortName
protected override ActionResult ReturnToObject(Product obj)
{
return RedirectToAction("GetByShortName", new { shortname = obj.ShortName, page = Request.QueryString["page"] });
}
// Свойство ShortName будет автогенерироваться из свойства Name
protected override string GetShortNameSource(FormCollection collection)
{
return collection["Name"];
}
// Приводим разделитель десятичной дроби к стандартному, и удаляем пробелы
protected override void ChangeFormCollectionValues(Product obj, FormCollection collection)
{
base.ChangeFormCollectionValues(obj, collection);
collection["Price"] = collection["Price"].Replace(" ", "");
collection["Price"] = collection["Price"].Replace(",", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
collection["Price"] = collection["Price"].Replace(".", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
}
#endregion
// Переход на страницу товара со страницы производителя
public ActionResult GetProductInManufacturer(string manufacturer, string product)
{
Product obj = (service as IUrlFriendlyService<Product>).Get(product);
if (obj == null) return View("NotFound");
return View("Details", obj);
}
// Обработка загрузки картинки
public ActionResult UploadImage(HttpPostedFileBase imagefile, int objID)
{
// Получаем объкут, для которого загружаем картинку
Product obj = service.Get(objID);
if (obj == null) return View("NotFound");
try
{
if (imagefile != null)
{
// Определяем название и полный путь полноразмерной картинки и миниатюры
string strExtension = System.IO.Path.GetExtension(imagefile.FileName);
string strSaveFileName = objID + strExtension;
string strSaveFullPath = System.IO.Path.Combine(Server.MapPath(Url.Content("~/Content")), Constants.PRODUCT_IMAGES_FOLDER, strSaveFileName);
string strSaveMiniFullPath = System.IO.Path.Combine(Server.MapPath(Url.Content("~/Content")), Constants.PRODUCT_IMAGES_FOLDER, Constants.PRODUCT_IMAGES_MINI_FOLDER, strSaveFileName);
// Если файлы с такими названиями уже имеются, удаляем их
if (System.IO.File.Exists(strSaveFullPath))
System.IO.File.Delete(strSaveFullPath);
if (System.IO.File.Exists(strSaveMiniFullPath))
System.IO.File.Delete(strSaveMiniFullPath);
// Сохраняем полную картинку и миниатюру
imagefile.ResizeAndSave(Constants.PRODUCT_IMAGE_HEIGHT, Constants.PRODUCT_IMAGE_WIDTH, strSaveFullPath);
imagefile.ResizeAndSave(Constants.PRODUCT_IMAGE_MINI_HEIGHT, Constants.PRODUCT_IMAGE_MINI_WIDTH, strSaveMiniFullPath);
// Расширение файла записываем в базу данных в поле ImageExt
obj.ImageExt = strExtension;
service.Save();
}
}
catch (Exception ex)
{
string strErrorMessage = ex.Message;
if (ex.InnerException != null) strErrorMessage = string.Format("{0} --- {1}", strErrorMessage, ex.InnerException.Message);
ViewBag.ErrorMessage = strErrorMessage;
return View("Error");
}
return ReturnToObject(obj);
}
}
}
Поместим в файл представления Details.cshtml раздела Product код для отображения полной версии картинки и код для формы загрузки картинки.
Views\Product\Details.cshtml
@model _21century.Models.Product
@{
ViewBag.Title = string.Format("{0} | Товары | {1}", Model.Name, _21century.Constants.TITLE);
}
<p>
@{
List<string> path=_21century.Helper.GetUrlPath(Request);
if (path.Count == 2)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Товары", "GetByShortName", new { shortname = string.Empty, page = Request.QueryString["page"] })
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
else if (path.Count == 3)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Производители", "GetByShortName", "Manufacturer", new { shortname = string.Empty }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink(_21century.Service.Factory.ManufacturerServiceFactory.Create().Get(path[1]).Name, "GetByShortName", "Manufacturer", new { shortname = path[1], page = Request.QueryString["page"] }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
}
</p>
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<p>@Html.ActionLink("Редактировать", "Edit", new { id = Model.ID, page = Request.QueryString["page"] }) @Html.ActionLink("Удалить", "Delete", new { id = Model.ID, page = Request.QueryString["page"] })</p>
}
<h2>@Model.Name</h2>
<p><span st yle="font-weight:bold">Цена: </span>@Model.Price руб.</p>
<p><span st yle="font-weight:bold">Производитель: </span>@Model.Manufacturer.Name</p>
<p>@Model.Description</p>
@if (!string.IsNullOrWhiteSpace(Model.ImageExt))
{
<div st yle="width:100%; text-align:center">
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@Model.ID@Model.ImageExt.TrimEnd()?@DateTime.Now" alt="@Model.Name" />
</div>
}
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<h2>Загрузить картинку</h2>
using (Html.BeginForm("UploadImage", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.Hidden("objID", Model.ID)
<table>
<tr>
<td>
@{
string filename = string.Empty;
if(string.IsNullOrWhiteSpace(Model.ImageExt)) { filename="0.jpg"; }
else { filename = string.Format("{0}{1}?{2}", Model.ID, Model.ImageExt.TrimEnd(), DateTime.Now); }
}
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@(_21century.Constants.PRODUCT_IMAGES_MINI_FOLDER)/@filename" alt="@Model.Name" />
</td>
<td st yle="padding-left:20px">
<input type="file" name="imagefile" />
<br />
<input type="submit" value="Загрузить" />
</td>
</tr>
</table>
}
}
Запустим наш сайт, залогинимся под администратором, и зайдём на страницу какого-нибудь товара. Мы увидим, что большой картинки нет (мы её и не загружали), а снизу появилась форма для загрузки картинки.
Загрузим картинку. Большая версия должна сразу появиться на странице под текстом, а малая – на форме загрузки.
Итак, картинки к товарам у нас загружаются. Теперь мы должны поместить товары в категории.
Соотношение товаров и категорий у нас будет «много-ко-многим». То есть, в каждой категории может быть много товаров, а каждый товар может относиться к нескольким категориям.
Чтобы реализовать такое отношение между объектами, нам понадобится новая таблица. Каждая запись в этой таблице будет хранить пару ссылок: на категорию, и на принадлежащий ей товар. Создадим новую таблицу CategoryProduct со следующими полями: ID int, первичный ключ, (IsIdentity) Yes; CategoryID int; ProductID int; Sequence int.
Создадим связи для этой таблицы: с таблицей Category по внешнему ключу CategoryID и с таблицей Product по внешнему ключу ProductID.
Добавим таблицу CategoryProduct в объект Entity Framework. В нём должен появиться класс CategoryProduct, а в этом классе – свойства Product и Category. В таблице Product и в таблице Category должно, соответственно, появиться свойство CategoryProducts. Проверьте внимательно: если у вас не так, значит были неправильно созданы связи таблиц. В этом случае надо удалить новую таблицу из Entity Framework (просто выделить её и нажать клавишу Delete), затем открыть диаграмму, и в ней удалить связи между таблицами CategoryProduct и Category, CategoryProduct и Product (выделить каждую связь и нажать клавишу Delete), и создать их заново. Затем снова импортировать таблицу в Entity Framework.
В папке Models создаём файл расширения класса CategoryProduct. Поскольку в классе CategoryProduct имеется свойство Sequence, следовательно объекты этого класса можно будет менять местами. А значит, в классе CategoryProduct вполне можно реализовать интерфейс IOrdered.
CategoryProduct.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using _21century.Models.Interface;
namespace _21century.Models
{
public partial class CategoryProduct : IOrdered
{
public bool CanBeDeleted()
{
return true;
}
}
}
Создадим класс сервиса для объектов CategoryProduct. Здесь нам придётся переопределить методы GetPrevious и GetNext. Поскольку мы будем перемещать объекты внутри одной категории, эти методы должны возвращать предыдущий или следующий объект только из числа принадлежащих этой категории.
CategoryProductEntityService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using _21century.Service.Abstract;
using _21century.Models;
namespace _21century.Service
{
public class CategoryProductEntityService : OrderedEntityService<CategoryProduct>
{
Entities entities = new Entities();
protected override System.Data.Objects.ObjectSet<CategoryProduct> EntitySet { get { return entities.CategoryProducts; } }
public override CategoryProduct GetPrevious(CategoryProduct dataObject)
{
return (fr om obj in EntitySet wh ere obj.Sequence < dataObject.Sequence && obj.CategoryID == dataObject.CategoryID orderby obj.Sequence descending sel ect obj).FirstOrDefault();
}
public override CategoryProduct GetNext(CategoryProduct dataObject)
{
return (fr om obj in EntitySet wh ere obj.Sequence > dataObject.Sequence && obj.CategoryID == dataObject.CategoryID orderby obj.Sequence ascending select obj).FirstOrDefault();
}
}
}
Далее создаём фабричный класс для объектов класса сервиса.
CategoryProductServiceFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using _21century.Models;
using _21century.Service.Interface;
namespace _21century.Service.Factory
{
public static class CategoryProductServiceFactory
{
public static IOrderedService<CategoryProduct> Create()
{
return new CategoryProductEntityService();
}
}
}
И, наконец, создаём класс контроллера.
В этом контроллере мы должны поместить действие Add, обрабатывающее форму добавления категории к товару. Кроме того, этот контроллер будет иметь две интересных особенности.
Во-первых, мы не собираемся создавать файлы представления для объектов CategoryProduct. Добавление и удаление этих объектов будет осуществляться со страницы товара. Значит, мы должны в контроллере переопределить методы ReturnToList и ReturnToObject, чтобы они перенаправляли нас на страницу товара.
Во-вторых, перемещение «Вверх» и «Вниз» будет осуществляться со страницы категории. Значит, мы должны переопределить методы OnUp и OnDown, чтобы они перенаправляли нас на страницу категории.
CategoryProductController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using _21century.Models;
using _21century.Service.Interface;
using _21century.Service.Factory;
using _21century.Controllers.Abstract;
namespace _21century.Controllers
{
public class CategoryProductController : OrderedController<CategoryProduct>
{
public CategoryProductController(IOrderedService<CategoryProduct> _service) : base(_service) { }
public CategoryProductController() : this(CategoryProductServiceFactory.Create()) { }
#region Actions
// Обрабатываем форму добавления категории к товару
[Authorize(Roles = Constants.ROLES_ADMIN_CONTENT_MANAGER)]
public ActionResult Add(int? CategoryID, int ProductID)
{
Product product = _21century.Service.Factory.ProductServiceFactory.Create().Get(ProductID);
if (CategoryID.HasValue && product.CategoryProducts.Where(item => item.CategoryID == CategoryID.Value).Count() == 0)
{
CategoryProduct obj = new CategoryProduct();
obj.CategoryID = CategoryID.Value;
obj.ProductID = ProductID;
AddValuesOnCreate(obj);
service.Add(obj);
service.Save();
}
return RedirectToAction("GetProductInManufacturer", "Product", new { manufacturer = product.Manufacturer.ShortName, product = product.ShortName });
}
#endregion
#region Overridden virtual methods
// После перемещения вверх перенаправляем на страницу категории
protected override ActionResult OnUp(CategoryProduct obj)
{
return RedirectToAction("GetByShortName", "Category", new { shortname = obj.Category.ShortName });
}
// После перемещения вних перенаправляем на страницу категории
protected override ActionResult OnDown(CategoryProduct obj)
{
return RedirectToAction("GetByShortName", "Category", new { shortname = obj.Category.ShortName });
}
// Перенаправляем на страницу товара
protected override ActionResult ReturnToList(CategoryProduct obj)
{
return RedirectToAction("GetProductInManufacturer", "Product", new { manufacturer = obj.Product.Manufacturer.ShortName, product = obj.Product.ShortName });
}
// Перенаправляем на страницу товара
protected override ActionResult ReturnToObject(CategoryProduct obj)
{
return RedirectToAction("GetProductInManufacturer", "Product", new { manufacturer = obj.Product.Manufacturer.ShortName, product = obj.Product.ShortName });
}
#endregion
}
}
Теперь в файле представления Details.cshtml раздела Product создадим список категорий этого товара, а также форму добавления категории к данному товару.
Views\Product\Details.cshtml
@model _21century.Models.Product
@{
ViewBag.Title = string.Format("{0} | Товары | {1}", Model.Name, _21century.Constants.TITLE);
}
<p>
@{
List<string> path=_21century.Helper.GetUrlPath(Request);
if (path.Count == 2)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Товары", "GetByShortName", new { shortname = string.Empty, page = Request.QueryString["page"] })
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
else if (path.Count == 3)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Производители", "GetByShortName", "Manufacturer", new { shortname = string.Empty }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink(_21century.Service.Factory.ManufacturerServiceFactory.Create().Get(path[1]).Name, "GetByShortName", "Manufacturer", new { shortname = path[1], page = Request.QueryString["page"] }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
}
</p>
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<p>@Html.ActionLink("Редактировать", "Edit", new { id = Model.ID, page = Request.QueryString["page"] }) @Html.ActionLink("Удалить", "Delete", new { id = Model.ID, page = Request.QueryString["page"] })</p>
}
<h2>@Model.Name</h2>
<p><span st yle="font-weight:bold">Цена: </span>@Model.Price руб.</p>
<p><span st yle="font-weight:bold">Производитель: </span>@Model.Manufacturer.Name</p>
<p>@Model.Description</p>
@if (!string.IsNullOrWhiteSpace(Model.ImageExt))
{
<div st yle="width:100%; text-align:center">
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@Model.ID@Model.ImageExt.TrimEnd()?@DateTime.Now" alt="@Model.Name" />
</div>
}
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<h2>Загрузить картинку</h2>
using (Html.BeginForm("UploadImage", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.Hidden("objID", Model.ID)
<table>
<tr>
<td>
@{
string filename = string.Empty;
if(string.IsNullOrWhiteSpace(Model.ImageExt)) { filename="0.jpg"; }
else { filename = string.Format("{0}{1}?{2}", Model.ID, Model.ImageExt.TrimEnd(), DateTime.Now); }
}
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@(_21century.Constants.PRODUCT_IMAGES_MINI_FOLDER)/@filename" alt="@Model.Name" />
</td>
<td st yle="padding-left:20px">
<input type="file" name="imagefile" />
<br />
<input type="submit" value="Загрузить" />
</td>
</tr>
</table>
}
<h2>Категории</h2>
if (Model.CategoryProducts.Count > 0)
{
<table>
@foreach(var item in Model.CategoryProducts.OrderBy(item => item.Category.Name))
{
<tr>
<td>@item.Category.Name</td>
<td st yle="padding-left:20px">@Html.ActionLink("Удалить", "DeleteExpress", "CategoryProduct", new { id = item.ID }, new { onc lick = "jav * ascript:return confirm('Вы уверены?');" })</td>
</tr>
}
</table>
}
<h2>Добавить категорию</h2>
using (Html.BeginForm("Add", "CategoryProduct", FormMethod.Post))
{
@Html.Hidden("ProductID", Model.ID)
var queryCategories = (_21century.Service.Factory.CategoryServiceFactory.Create()).Get().Select(item => new { ID = item.ID, Name = item.Name }).ToList();
var listCategories = queryCategories.Select(item => new SelectListItem() { Text = item.Name, Value = item.ID.ToString(), Selected = false }).ToList();
@Html.DropDownList("CategoryID", listCategories, "...")
<input type="submit" value="Добавить" />
}
}
Зайдём на сайт под логином администратора, перейдём на страницу товара. Внизу мы увидим форму добавления категории. Добавим несколько категорий к товару. При добавлении, категория должна появляться в списке над формой добавления. Проверьте, чтобы функция удаления также работала.
Теперь мы должны на странице категории отобразить список товаров, принадлежащих этой категории.
Внесём следующие изменения в файл представления Details.cshtml раздела Category.
Views\Category\Details.cshtml
@model _21century.Models.Category
@{
ViewBag.Title = string.Format("{0} | {1}", Model.Name, _21century.Constants.TITLE);
}
<p>
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
</p>
<h2>@Model.Name</h2>
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<p>@Html.ActionLink("Редактировать", "Edit", new { id = Model.ID }) @Html.ActionLink("Удалить", "Delete", new { id = Model.ID })</p>
}
@functions
{
public string Row(ref int index)
{
string strResult = "<tr>";
for(int innerIndex=0; innerIndex < _21century.Constants.COLLECTION_NUMBER_PER_ROW; innerIndex++)
{
strResult += "<td st yle='text-align:center; padding-bottom:20px;'>";
if(index < Model.Collections.Count())
{
var obj = Model.Collections.OrderByDescending(item => item.Sequence).Skip(index).Take(1).First();
string strImage = "0.jpg";
if(!string.IsNullOrWhiteSpace(obj.ImageExt)) { strImage = obj.ID.ToString() + obj.ImageExt.TrimEnd() + "?" + DateTime.Now.ToString(); }
strResult += string.Format("<a href='{0}'><img src='{1}/{2}/{3}' alt='{4}' /></a>", Url.Action("GetCollectionInCategory", "Collection", new { category = obj.Category.ShortName, collection = obj.ShortName }), Url.Content("~/Content"), _21century.Constants.COLLECTION_MINI_IMAGES_FOLDER, strImage, obj.Name);
strResult += "<br />";
strResult += Html.ActionLink(obj.Name, "GetCollectionInCategory", "Collection", new { category = obj.Category.ShortName, collection = obj.ShortName }, null);
if(Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN)||User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
strResult += "<br />";
strResult += Html.ActionLink("<- Вверх", "Up", "Collection", new { id = obj.ID }, null);
strResult += " ";
strResult += Html.ActionLink("Вниз ->", "Down", "Collection", new { id = obj.ID }, null);
}
}
strResult += "</td>";
index = index + 1;
}
strResult += "</tr>";
return strResult;
}
public string RowProducts(ref int index)
{
string strResult = "<tr>";
for(int innerIndex=0; innerIndex<_21century.Constants.PRODUCT_NUMBER_IN_ROW; innerIndex++)
{
strResult += "<td st yle='text-align:center; padding-bottom:20px;'>";
if(index < Model.CategoryProducts.Count())
{
var obj = Model.CategoryProducts.OrderByDescending(item => item.Sequence).Skip(index).Take(1).First();
string strImage = "0.jpg";
if(!string.IsNullOrWhiteSpace(obj.Product.ImageExt)) { strImage = obj.Product.ID.ToString() + obj.Product.ImageExt.TrimEnd() + "?" + DateTime.Now.ToString(); }
strResult += string.Format("<a href='{0}'><img src='{1}/{2}/{3}/{4}' alt='{5}' /></a>", Url.Action("GetProductInCategory", "Product", new { category = obj.Category.ShortName, product = obj.Product.ShortName }), Url.Content("~/Content"), _21century.Constants.PRODUCT_IMAGES_FOLDER, _21century.Constants.PRODUCT_IMAGES_MINI_FOLDER, strImage, obj.Product.Name);
strResult += "<br />";
strResult += Html.ActionLink(obj.Product.Name, "GetProductInCategory", "Product", new { category = obj.Category.ShortName, product = obj.Product.ShortName }, null);
if(Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN)||User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
strResult += "<br />";
strResult += Html.ActionLink("<- Вверх", "Up", "CategoryProduct", new { id = obj.ID }, null);
strResult += " ";
strResult += Html.ActionLink("Вниз ->", "Down", "CategoryProduct", new { id = obj.ID }, null);
}
}
strResult += "</td>";
index = index + 1;
}
strResult += "</tr>";
return strResult;
}
}
<table st yle="width:100%">
@{
int iCount = Model.Collections.Count;
int i = 0;
while(i < iCount)
{
@MvcHtmlString.Create(Row(ref i))
}
}
</table>
<table st yle="width:100%">
@{
int iCategoryProductsCount = Model.CategoryProducts.Count;
int iCategoryProductIndex = 0;
while (iCategoryProductIndex < iCategoryProductsCount)
{
@MvcHtmlString.Create(RowProducts(ref iCategoryProductIndex))
}
}
</table>
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<h2>Мини-изображение на главной странице</h2>
using (Html.BeginForm("UploadCatalogImage", "Category", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.Hidden("objID", Model.ID)
<table>
<tr>
<td>
@{
string filename = string.Empty;
if(string.IsNullOrWhiteSpace(Model.ImageExt)) { filename = "0.jpg"; }
else { filename = string.Format("{0}{1}?{2}", Model.ID, Model.ImageExt.TrimEnd(), DateTime.Now); }
}
<img src='@(Url.Content("~/Content"))/@(_21century.Constants.CATEGORY_MINI_IMAGES_FOLDER)/@(filename)' alt='@Model.Name' />
</td>
<td st yle="padding-left:20px">
<input type="file" name="imagefile" />
<br />
<input type="submit" value="Загрузить" />
</td>
</tr>
</table>
}
}
Создайте несколько товаров, и добавьте их все в одну и туже категорию. Затем откройте страницу этой категории. Она должна выглядеть так.
Теперь мы должны обеспечить возможность перейти со страницы категории на страницу товара. При этом, в URL-адресе должен быть показан путь перехода: сначала название категории, потом название товара.
Для этого нам опять понадобится создать новое правило маршрутизации. Внесём изменения в файл Global.aspx.cs.
Global.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
namespace _21century
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Manufacturers",
"manufacturers/{shortname}",
new { controller = "Manufacturer", action = "GetByShortName", shortname = UrlParameter.Optional }
);
routes.MapRoute(
"ProductInManufacturer",
"manufacturers/{manufacturer}/{product}",
new { controller = "Product", action = "GetProductInManufacturer" }
);
routes.MapRoute(
"Products",
"products/{shortname}",
new { controller = "Product", action = "GetByShortName", shortname = UrlParameter.Optional }
);
routes.MapRoute(
"Categories",
"catalog/{shortname}",
new { controller = "Category", action = "GetByShortName", shortname = UrlParameter.Optional }
);
routes.MapRoute(
"Collections",
"catalog/{category}/{collection}",
new { controller = "Collection", action = "GetCollectionInCategory" }
);
routes.MapRoute(
"ProductInCategory",
"catalog/{category}/products/{product}",
new { controller = "Product", action = "GetProductInCategory" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
public static void AddAdmin()
{
// Если нет в системе роли admin, создаём её
if (!Roles.RoleExists(Constants.ROLE_ADMIN))
Roles.CreateRole(Constants.ROLE_ADMIN);
// Если нет в системе пользователя admin, создаём его
if (Membership.GetUs er("admin") == null)
{
MembershipCreateStatus status = MembershipCreateStatus.Success;
Membership.CreateUs er("admin", "temp_pass", "info@21century.com", "My favourite music", "Disco 80-es", true, out status);
}
// Если у пользователя admin нет роли admin, присваиваем ему эту роль
if (!Roles.IsUserInRole("admin", Constants.ROLE_ADMIN))
Roles.AddUserToRole("admin", Constants.ROLE_ADMIN);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
AddAdmin();
}
}
}
Итак, если на наш сайт обратятся по URL http://21century.ru/catalog/shkafy/products/shkafkupe/, то обрабатывать его будет действие GetProductInCategory в контроллере ProductController, и этому действию будут переданы следующие параметры: category со значением shkafy и product со значением shkafkupe.
Следовательно, мы должны добавить действие GetProductInCategory в контроллер ProductController.
ProductController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using _21century.Models;
using _21century.Service.Interface;
using _21century.Service.Factory;
using _21century.Controllers.Abstract;
namespace _21century.Controllers
{
public class ProductController : UrlFriendlyController<Product>
{
public ProductController(IUrlFriendlyService<Product> _service) : base(_service) { }
public ProductController() : this(ProductServiceFactory.Create()) { }
// Включаем постраничный вывод
protected override bool IsPageable { get { return true; } }
#region Overridden virtual methods
// На страницу списка переходим через действие GetByShortName без параметров
protected override ActionResult ReturnToList(Product obj)
{
return RedirectToAction("GetByShortName", new { shortname = string.Empty, page = Request.QueryString["page"] });
}
// На страницу объекта переходим через действие GetByShortName с текстовым параметром shortName
protected override ActionResult ReturnToObject(Product obj)
{
return RedirectToAction("GetByShortName", new { shortname = obj.ShortName, page = Request.QueryString["page"] });
}
// Свойство ShortName будет автогенерироваться из свойства Name
protected override string GetShortNameSource(FormCollection collection)
{
return collection["Name"];
}
// Приводим разделитель десятичной дроби к стандартному, и удаляем пробелы
protected override void ChangeFormCollectionValues(Product obj, FormCollection collection)
{
base.ChangeFormCollectionValues(obj, collection);
collection["Price"] = collection["Price"].Replace(" ", "");
collection["Price"] = collection["Price"].Replace(",", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
collection["Price"] = collection["Price"].Replace(".", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
}
#endregion
// Переход на страницу товара со страницы производителя
public ActionResult GetProductInManufacturer(string manufacturer, string product)
{
Product obj = (service as IUrlFriendlyService<Product>).Get(product);
if (obj == null) return View("NotFound");
return View("Details", obj);
}
// Переход на страницу товара со страницы категории
public ActionResult GetProductInCategory(string category, string product)
{
Product obj = (service as IUrlFriendlyService<Product>).Get(product);
if (obj == null) return View("NotFound");
return View("Details", obj);
}
// Обработка загрузки картинки
public ActionResult UploadImage(HttpPostedFileBase imagefile, int objID)
{
// Получаем объект, для которого загружаем картинку
Product obj = service.Get(objID);
if (obj == null) return View("NotFound");
try
{
if (imagefile != null)
{
// Определяем название и полный путь полноразмерной картинки и миниатюры
string strExtension = System.IO.Path.GetExtension(imagefile.FileName);
string strSaveFileName = objID + strExtension;
string strSaveFullPath = System.IO.Path.Combine(Server.MapPath(Url.Content("~/Content")), Constants.PRODUCT_IMAGES_FOLDER, strSaveFileName);
string strSaveMiniFullPath = System.IO.Path.Combine(Server.MapPath(Url.Content("~/Content")), Constants.PRODUCT_IMAGES_FOLDER, Constants.PRODUCT_IMAGES_MINI_FOLDER, strSaveFileName);
// Если файлы с такими названиями уже имеются, удаляем их
if (System.IO.File.Exists(strSaveFullPath))
System.IO.File.Delete(strSaveFullPath);
if (System.IO.File.Exists(strSaveMiniFullPath))
System.IO.File.Delete(strSaveMiniFullPath);
// Сохраняем полную картинку и миниатюру
imagefile.ResizeAndSave(Constants.PRODUCT_IMAGE_HEIGHT, Constants.PRODUCT_IMAGE_WIDTH, strSaveFullPath);
imagefile.ResizeAndSave(Constants.PRODUCT_IMAGE_MINI_HEIGHT, Constants.PRODUCT_IMAGE_MINI_WIDTH, strSaveMiniFullPath);
// Расширение файла записываем в базу данных в поле ImageExt
obj.ImageExt = strExtension;
service.Save();
}
}
catch (Exception ex)
{
string strErrorMessage = ex.Message;
if (ex.InnerException != null) strErrorMessage = string.Format("{0} --- {1}", strErrorMessage, ex.InnerException.Message);
ViewBag.ErrorMessage = strErrorMessage;
return View("Error");
}
return ReturnToObject(obj);
}
}
}
Теперь мы можем перейти с раздела «Категории» сразу в раздел «Товары», и URL будет отображать весь наш путь перехода. Но мы хотим, чтобы этот путь отображался ещё и в хлебных крошках. Поэтому внесём ещё одно небольшое дополнение в файл представления Details.cshtml раздела Product.
Views\Product\Details.cshtml
@model _21century.Models.Product
@{
ViewBag.Title = string.Format("{0} | Товары | {1}", Model.Name, _21century.Constants.TITLE);
}
<p>
@{
List<string> path=_21century.Helper.GetUrlPath(Request);
if (path.Count == 2)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Товары", "GetByShortName", new { shortname = string.Empty, page = Request.QueryString["page"] })
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
else if (path.Count == 3)
{
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink("Производители", "GetByShortName", "Manufacturer", new { shortname = string.Empty }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Html.ActionLink(_21century.Service.Factory.ManufacturerServiceFactory.Create().Get(path[1]).Name, "GetByShortName", "Manufacturer", new { shortname = path[1], page = Request.QueryString["page"] }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
@Model.Name
}
else if(path.Count == 4 && path[0] == "catalog" && path[2] == "products")
{
_21century.Models.Category category = Model.CategoryProducts.Where(item => item.Category.ShortName == path[1]).Select(item => item.Category).FirstOrDefault();
@Html.ActionLink("На главную", "Index", "Home")
@_21century.Constants.BREADCRUMBS_SEPARATOR
if(category != null)
{
@Html.ActionLink(category.Name, "GetByShortName", "Category", new { shortname = category.ShortName }, null)
@_21century.Constants.BREADCRUMBS_SEPARATOR
}
@Model.Name
}
}
</p>
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<p>@Html.ActionLink("Редактировать", "Edit", new { id = Model.ID, page = Request.QueryString["page"] }) @Html.ActionLink("Удалить", "Delete", new { id = Model.ID, page = Request.QueryString["page"] })</p>
}
<h2>@Model.Name</h2>
<p><span st yle="font-weight:bold">Цена: </span>@Model.Price руб.</p>
<p><span st yle="font-weight:bold">Производитель: </span>@Model.Manufacturer.Name</p>
<p>@Model.Description</p>
@if (!string.IsNullOrWhiteSpace(Model.ImageExt))
{
<div st yle="width:100%; text-align:center">
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@Model.ID@Model.ImageExt.TrimEnd()?@DateTime.Now" alt="@Model.Name" />
</div>
}
@if (Request.IsAuthenticated && (User.IsInRole(_21century.Constants.ROLE_ADMIN) || User.IsInRole(_21century.Constants.ROLE_CONTENT_MANAGER)))
{
<h2>Загрузить картинку</h2>
using (Html.BeginForm("UploadImage", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.Hidden("objID", Model.ID)
<table>
<tr>
<td>
@{
string filename = string.Empty;
if(string.IsNullOrWhiteSpace(Model.ImageExt)) { filename="0.jpg"; }
else { filename = string.Format("{0}{1}?{2}", Model.ID, Model.ImageExt.TrimEnd(), DateTime.Now); }
}
<img src="@Url.Content("~/Content")/@(_21century.Constants.PRODUCT_IMAGES_FOLDER)/@(_21century.Constants.PRODUCT_IMAGES_MINI_FOLDER)/@filename" alt="@Model.Name" />
</td>
<td st yle="padding-left:20px">
<input type="file" name="imagefile" />
<br />
<input type="submit" value="Загрузить" />
</td>
</tr>
</table>
}
<h2>Категории</h2>
if (Model.CategoryProducts.Count > 0)
{
<table>
@foreach(var item in Model.CategoryProducts.OrderBy(item => item.Category.Name))
{
<tr>
<td>@item.Category.Name</td>
<td st yle="padding-left:20px">@Html.ActionLink("Удалить", "DeleteExpress", "CategoryProduct", new { id = item.ID }, new { onc lick = "jav * ascript:return confirm('Вы уверены?');" })</td>
</tr>
}
</table>
}
<h2>Добавить категорию</h2>
using (Html.BeginForm("Add", "CategoryProduct", FormMethod.Post))
{
@Html.Hidden("ProductID", Model.ID)
var queryCategories = (_21century.Service.Factory.CategoryServiceFactory.Create()).Get().Select(item => new { ID = item.ID, Name = item.Name }).ToList();
var listCategories = queryCategories.Select(item => new SelectListItem() { Text = item.Name, Value = item.ID.ToString(), Selected = false }).ToList();
@Html.DropDownList("CategoryID", listCategories, "...")
<input type="submit" value="Добавить" />
}
}
Теперь мы можем со страницы категории зайти на страницу товара, и увидеть, что как URL, так и хлебные крошки отображают наш путь на это страницу.
Наша следующая тема: помещение товаров в коллекции. До следующей встречи!
Оглавление