Создание интернет-магазина на ASP.NET MVC 3, язык C#. Часть 16. Помещение товаров в категории, загрузка картинок к товарам

Итак, мы подготовили разделы «Категории» и «Коллекции», и выглядят они у нас вполне прилично. Теперь наша задача разместить в них товары. Начнём с раздела «Категории». Соотношение между категориями и товарами будет «много-ко-многим». Это означает, что мы должны создать для них промежуточную таблицу.

Но сначала, давайте добавим загрузку картинок к товарам.

В папке Content создадим папку ProductImages, а внутри неё – папку Mini. В папке ProductImages будут храниться полноразмерные картинки товаров, а в папке Mini – их уменьшенные варианты для списков. Поместим в папку Mini картинку-заглушку 0.jpg.

ProductImages

Добавим в класс 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 товара, как было и в случае с категориями и коллекциями.

ImageExt

Откроем теперь объект Entity Framework, и обновим его (правая кнопка -> Update Model fr om Database...). В объекте Product должно появиться свойство ImageExt.

Entity Framework

В контроллер товаров 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"] })&nbsp;&nbsp;&nbsp;@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.

CategoryProduct

Создадим связи для этой таблицы: с таблицей Category по внешнему ключу CategoryID и с таблицей Product по внешнему ключу ProductID.

Связи таблицы CategoryProduct

Добавим таблицу CategoryProduct в объект Entity Framework. В нём должен появиться класс CategoryProduct, а в этом классе – свойства Product и Category. В таблице Product и в таблице Category должно, соответственно, появиться свойство CategoryProducts. Проверьте внимательно: если у вас не так, значит были неправильно созданы связи таблиц. В этом случае надо удалить новую таблицу из Entity Framework (просто выделить её и нажать клавишу Delete), затем открыть диаграмму, и в ней удалить связи между таблицами CategoryProduct и Category, CategoryProduct и Product (выделить каждую связь и нажать клавишу Delete), и создать их заново. Затем снова импортировать таблицу в Entity Framework.

Класс CategoryProduct

В папке Models создаём файл расширения класса CategoryProduct. Поскольку в классе CategoryProduct имеется свойство Sequence, следовательно объекты этого класса можно будет менять местами. А значит, в классе CategoryProduct вполне можно реализовать интерфейс IOrdered.

CategoryProduct.cs

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

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

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

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"] })&nbsp;&nbsp;&nbsp;@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 })&nbsp;&nbsp;&nbsp;@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 += "&nbsp;&nbsp;&nbsp;";
                    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 += "&nbsp;&nbsp;&nbsp;";
                    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>
    }
}

Создайте несколько товаров, и добавьте их все в одну и туже категорию. Затем откройте страницу этой категории. Она должна выглядеть так.

Wardrobes

Теперь мы должны обеспечить возможность перейти со страницы категории на страницу товара. При этом, в 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"] })&nbsp;&nbsp;&nbsp;@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, так и хлебные крошки отображают наш путь на это страницу.

Product

Наша следующая тема: помещение товаров в коллекции. До следующей встречи!


Оглавление

Для того чтобы оставить свой комментарий - пожалуйста авторизуйтесь.
Если Вы еще не зарегистрированы на портале, Вы можете это сделать здесь.

Интернет-журнал MSDeveloper.RU Интернет-журнал MSDeveloper.RU Twitter @MSDeveloperRU Интернет-журнал Маковое Поле Twitter @MacovoePoleRu Интернет-журнал ITNewsPRO Twitter @ITNewsPRO Интернет-журнал Едем Заграницу Twitter @EdemZagranicy Станислав Горнаков Twitter @Gornakov Интернет-журнал Веб-Аналитик.РФ