NopCommerce源码架构详解-产品列表相关源码分析3

我在前两篇给大家分析了nop产品列表Controller相关的源码,今天我们来看看Nop产品列表mvc视图相关的源码。

NopCommerce源码架构详解概述

文章目录

概述

NopCommerce源码架构详解-产品列表相关源码分析3。

内容

我在前两篇给大家分析了nop产品列表Controller相关的源码,今天我们来看看Nop产品列表mvc视图相关的源码。

首先,我们来看看视图CategoryTemplate.ProductsInGridOrLines.cshtml:

@model CategoryModel
@{
    Layout = "~/Views/Shared/_ColumnsTwo.cshtml";
    Html.AddTitleParts(!String.IsNullOrEmpty(Model.MetaTitle) ? Model.MetaTitle : Model.Name);
    Html.AddMetaDescriptionParts(Model.MetaDescription);
    Html.AddMetaKeywordParts(Model.MetaKeywords);

    var canonicalUrlsEnabled = EngineContext.Current.Resolve<seoSettings>().CanonicalUrlsEnabled;
    if (canonicalUrlsEnabled)
    {
        var categoryUrl = Url.RouteUrl("Category", new { SeName = Model.SeName }, this.Request.Url.Scheme);
        Html.AddCanonicalUrlParts(categoryUrl);
    }

    var breadcrumbDelimiter = EngineContext.Current.Resolve<CommonSettings>().BreadcrumbDelimiter;
}
@using Nop.Core.Domain.Common;
@using Nop.Core.Domain.Seo;
@using Nop.Core.Infrastructure;
@using Nop.Web.Models.Catalog;
@using Nop.Web.Extensions;
@*category breadcrumb*@
@if (Model.DisplayCategoryBreadcrumb)
{
    <div class="breadcrumb">
        <ul>
            <li><a href="@Url.RouteUrl("HomePage")" title="@T("Categories.Breadcrumb.Top")">@T("Categories.Breadcrumb.Top")</a>
                <span class="delimiter">@breadcrumbDelimiter</span> </li>
            @foreach (var cat in Model.CategoryBreadcrumb)
            {
                var isLastCategory = cat.Id == Model.Id;
                <li>
                    @if (isLastCategory)
                    {
                        <strong class="current-item">@cat.Name</strong>
                    }
                    else
                    {
                        <a href="@Url.RouteUrl("Category", new { SeName = cat.SeName })" title="@cat.Name">@cat.Name</a>
                        <span class="delimiter">@breadcrumbDelimiter</span>
                    }
                </li>
            }
        </ul>
    </div>
    @Html.Widget("categorydetails_after_breadcrumb", Model.Id)
}
<div class="page category-page">
    <div class="page-title">
        <h1>@Model.Name</h1>
    </div>
    <div class="page-body">
        @Html.Widget("categorydetails_top", Model.Id)
        @*description*@
        @if (!String.IsNullOrWhiteSpace(Model.Description))
        {
            <div class="category-description">
                @Html.Raw(Model.Description)
            </div>
        }
        @Html.Widget("categorydetails_before_subcategories", Model.Id)
        @*subcategories*@
        @if (Model.SubCategories.Count > 0)
        {
            <div class="sub-category-grid">
                @foreach (var item in Model.SubCategories)
                {
                    <div class="item-box">
                        <div class="sub-category-item">
                            <h2 class="title">
                                <a href="@Url.RouteUrl("Category", new { SeName = item.SeName })" title="@item.PictureModel.Title">
                                    @item.Name</a>
                            </h2>
                            <div class="picture">
                                <a href="@Url.RouteUrl("Category", new { SeName = item.SeName })" title="@item.PictureModel.Title">
                                    <img alt="@item.PictureModel.AlternateText" src="@item.PictureModel.ImageUrl"
                                         title="@item.PictureModel.Title" /></a>
                            </div>
                        </div>
                    </div>
                }
            </div>
        }
        @Html.Widget("categorydetails_before_featured_products", Model.Id)
        @*featured products*@
        @if (Model.FeaturedProducts.Count > 0)
        {
            <div class="product-grid featured-product-grid">
                <div class="title">
                    <strong>@T("Products.FeaturedProducts")</strong>
                </div>
                <div>
                    @foreach (var item in Model.FeaturedProducts)
                    {
                        <div class="item-box">
                            @Html.Partial("_ProductBox", item)
                        </div>
                    }
                </div>
            </div>
        }
        @Html.Widget("categorydetails_after_featured_products", Model.Id)
        <div class="product-selectors">
            @*view mode*@
            @if (Model.PagingFilteringContext.AllowProductViewModeChanging && Model.Products.Count > 0)
            {
                <div class="product-viewmode">
                    <span>@T("Catalog.ViewMode")</span>
                    @Html.DropDownList("products-viewmode", Model.PagingFilteringContext.AvailableViewModes, new { onchange = "setLocation(this.value);" })
                </div>
            }
            @*sorting*@
            @if (Model.PagingFilteringContext.AllowProductSorting && Model.Products.Count > 0)
            {
                <div class="product-sorting">
                    <span>@T("Catalog.OrderBy")</span>
                    @Html.DropDownList("products-orderby", Model.PagingFilteringContext.AvailableSortOptions, new { onchange = "setLocation(this.value);" })
                </div>
            }
            @*page size*@
            @if (Model.PagingFilteringContext.AllowCustomersToSelectPageSize && Model.Products.Count > 0)
            {
                <div class="product-page-size">
                    <span>@T("Catalog.PageSize")</span> 
                    @Html.DropDownList("products-pagesize", Model.PagingFilteringContext.PageSizeOptions, new { onchange = "setLocation(this.value);" })
                    <span>@T("Catalog.PageSize.PerPage")</span>
                </div>
            }
        </div>
        @Html.Widget("categorydetails_before_filters", Model.Id)
        <div class="product-filters-wrapper">
            @*filtering*@
            @if (Model.PagingFilteringContext.PriceRangeFilter.Enabled)
            {
                @Html.Partial("_FilterPriceBox", Model.PagingFilteringContext.PriceRangeFilter, new ViewDataDictionary())
            }
            @*filtering*@
            @if (Model.PagingFilteringContext.SpecificationFilter.Enabled)
            {
                @Html.Partial("_FilterSpecsBox", Model.PagingFilteringContext.SpecificationFilter, new ViewDataDictionary())
            }
        </div>
        @Html.Widget("categorydetails_before_product_list", Model.Id)
        @*product list*@
        @if (Model.Products.Count > 0)
        {
            if (Model.PagingFilteringContext.ViewMode == "list")
            {
            @*list mode*@
            <div class="product-list">
                @foreach (var product in Model.Products)
                {
                    <div class="item-box">
                        @Html.Partial("_ProductBox", product)
                    </div>
                }
            </div>       
            }
            else
            {
            @*grid mode*@
            <div class="product-grid">
                @foreach (var product in Model.Products)
                {
                    <div class="item-box">
                        @Html.Partial("_ProductBox", product)
                    </div>
                }
            </div>
            }
        }
        <div class="pager">
            @Html.Pager(Model.PagingFilteringContext).QueryParam("pagenumber")
        </div>
        @Html.Widget("categorydetails_bottom", Model.Id)
    </div>
</div>

可以看到这个视图使用领域模模型CategoryModel来绑定,同时你也可以注意到这个视图的绑定使用了很多HtmlHelper的扩展方法。下面我们就来针对这两个方面进行分析。

1、领域模模型CategoryModel

using System.Collections.Generic;
using Nop.Web.Framework.Mvc;
using Nop.Web.Models.Media;

namespace Nop.Web.Models.Catalog
{
    public partial class CategoryModel : BaseNopEntityModel
    {
        public CategoryModel()
        {
            PictureModel = new PictureModel();
            FeaturedProducts = new List<ProductOverviewModel>();
            Products = new List<ProductOverviewModel>();
            PagingFilteringContext = new CatalogPagingFilteringModel();
            SubCategories = new List<SubCategoryModel>();
            CategoryBreadcrumb = new List<CategoryModel>();
        }

        public string Name { get; set; }
        public string Description { get; set; }
        public string MetaKeywords { get; set; }
        public string MetaDescription { get; set; }
        public string MetaTitle { get; set; }
        public string SeName { get; set; }

        public PictureModel PictureModel { get; set; }

        public CatalogPagingFilteringModel PagingFilteringContext { get; set; }

        public bool DisplayCategoryBreadcrumb { get; set; }
        public IList<CategoryModel> CategoryBreadcrumb { get; set; }

        public IList<SubCategoryModel> SubCategories { get; set; }

        public IList<ProductOverviewModel> FeaturedProducts { get; set; }
        public IList<ProductOverviewModel> Products { get; set; }

    #region Nested Classes

        public partial class SubCategoryModel : BaseNopEntityModel
        {
            public SubCategoryModel()
            {
                PictureModel = new PictureModel();
            }

            public string Name { get; set; }

            public string SeName { get; set; }

            public PictureModel PictureModel { get; set; }
        }

    #endregion
    }
}
using System.Collections.Generic;
using System.Web.Mvc;

namespace Nop.Web.Framework.Mvc
{
    /// <summary>
    /// Base NopCommerce model
    /// </summary>
    public partial class BaseNopModel
    {
        public BaseNopModel()
        {
            this.CustomProperties = new Dictionary<string, object>();
        }
        public virtual void BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
        }

        /// <summary>
        /// Use this property to store any custom value for your models. 
        /// </summary>
        public Dictionary<string, object> CustomProperties { get; set; }
    }

    /// <summary>
    /// Base nopCommerce entity model
    /// </summary>
    public partial class BaseNopEntityModel : BaseNopModel
    {
        public virtual int Id { get; set; }
    }
}

2、Html.Pager

<div class="pager"> @Html.Pager(Model.PagingFilteringContext).QueryParam("pagenumber")
</div>

Nop分页导航是通过一个分页类Pager和扩展方法实现的。

public static Pager Pager(this HtmlHelper helper, IPageableModel pagination)
{
    return new Pager(pagination, helper.ViewContext);
}

Pager类:

//Contributor : MVCContrib

using System;
using System.Collections.Generic;
using System.linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Nop.Core.Infrastructure;
using Nop.Services.Localization;

namespace Nop.Web.Framework.UI.Paging
{
    /// <summary>
        /// Renders a pager component from an IPageableModel datasource.
    /// </summary>
    public partial class Pager : IHtmlString
    {
        protected readonly IPageableModel model;
        protected readonly ViewContext viewContext;
        protected string pageQueryName = "page";
        protected bool showTotalSummary;
        protected bool showPagerItems = true;
        protected bool showFirst = true;
        protected bool showPrevious = true;
        protected bool showNext = true;
        protected bool showLast = true;
        protected bool showIndividualPages = true;
        protected int individualPagesDisplayedCount = 5;
        protected Func<int, string> urlBuilder;
        protected IList<string> booleanParameterNames;

    public Pager(IPageableModel model, ViewContext context)
    {
            this.model = model;
            this.viewContext = context;
            this.urlBuilder = CreateDefaultUrl;
            this.booleanParameterNames = new List<string>();
    }

    protected ViewContext ViewContext 
    {
        get { return viewContext; }
    }

        public Pager QueryParam(string value)
    {
            this.pageQueryName = value;
        return this;
    }
        public Pager ShowTotalSummary(bool value)
        {
            this.showTotalSummary = value;
            return this;
        }
        public Pager ShowPagerItems(bool value)
        {
            this.showPagerItems = value;
            return this;
        }
        public Pager ShowFirst(bool value)
        {
            this.showFirst = value;
            return this;
        }
        public Pager ShowPrevious(bool value)
        {
            this.showPrevious = value;
            return this;
        }
        public Pager ShowNext(bool value)
        {
            this.showNext = value;
            return this;
        }
        public Pager ShowLast(bool value)
        {
            this.showLast = value;
            return this;
        }
        public Pager ShowIndividualPages(bool value)
        {
            this.showIndividualPages = value;
            return this;
        }
        public Pager IndividualPagesDisplayedCount(int value)
        {
            this.individualPagesDisplayedCount = value;
            return this;
        }
    public Pager Link(Func<int, string> value)
    {
            this.urlBuilder = value;
        return this;
    }
        //little hack here due to ugly MVC implementation
        //find more info here: http://www.mindstorminteractive.com/topics/jquery-fix-asp-net-mvc-checkbox-truefalse-value/
        public Pager BooleanParameterName(string paramName)
        {
            booleanParameterNames.Add(paramName);
            return this;
        }

        public override string ToString()
        {
            return ToHtmlString();
        }
    public virtual string ToHtmlString()
    {
            if (model.TotalItems == 0) 
            return null;
            var localizationService = EngineContext.Current.Resolve<ILocalizationService>();

            var links = new StringBuilder();
            if (showTotalSummary && (model.TotalPages > 0))
            {
                links.Append("<li class=\"total-summary\">");
                links.Append(string.Format(localizationService.GetResource("Pager.CurrentPage"), model.PageIndex + 1, model.TotalPages, model.TotalItems));
                links.Append("</li>");
            }
            if (showPagerItems && (model.TotalPages > 1))
            {
                if (showFirst)
                {
                    //first page
                    if ((model.PageIndex >= 3) && (model.TotalPages > individualPagesDisplayedCount))
                    {
                        links.Append(CreatePageLink(1, localizationService.GetResource("Pager.First"), "first-page"));
                    }
                }
                if (showPrevious)
                {
                    //previous page
                    if (model.PageIndex > 0)
                    {
                        links.Append(CreatePageLink(model.PageIndex, localizationService.GetResource("Pager.Previous"), "previous-page"));
                    }
                }
                if (showIndividualPages)
                {
                    //individual pages
                    int firstIndividualPageIndex = GetFirstIndividualPageIndex();
                    int lastIndividualPageIndex = GetLastIndividualPageIndex();
                    for (int i = firstIndividualPageIndex; i <= lastIndividualPageIndex; i++)
                    {
                        if (model.PageIndex == i)
                        {
                            links.AppendFormat("<li class=\"current-page\"><span>{0}</span></li>", (i + 1));
                        }
                        else
                        {
                            links.Append(CreatePageLink(i + 1, (i + 1).ToString(), "individual-page"));
                        }
                    }
                }
                if (showNext)
                {
                    //next page
                    if ((model.PageIndex + 1) < model.TotalPages)
                    {
                        links.Append(CreatePageLink(model.PageIndex + 2, localizationService.GetResource("Pager.Next"), "next-page"));
                    }
                }
                if (showLast)
                {
                    //last page
                    if (((model.PageIndex + 3) < model.TotalPages) && (model.TotalPages > individualPagesDisplayedCount))
                    {
                        links.Append(CreatePageLink(model.TotalPages, localizationService.GetResource("Pager.Last"), "last-page"));
                    }
                }
            }

            var result = links.ToString();
            if (!String.IsNullOrEmpty(result))
            {
                result = "<ul>" + result + "</ul>";
            }
            return result;
    }

        protected virtual int GetFirstIndividualPageIndex()
        {
            if ((model.TotalPages < individualPagesDisplayedCount) ||
                ((model.PageIndex - (individualPagesDisplayedCount / 2)) < 0))
            {
                return 0;
            }
            if ((model.PageIndex + (individualPagesDisplayedCount / 2)) >= model.TotalPages)
            {
                return (model.TotalPages - individualPagesDisplayedCount);
            }
            return (model.PageIndex - (individualPagesDisplayedCount / 2));
        }

        protected virtual int GetLastIndividualPageIndex()
        {
            int num = individualPagesDisplayedCount / 2;
            if ((individualPagesDisplayedCount % 2) == 0)
            {
                num--;
            }
            if ((model.TotalPages < individualPagesDisplayedCount) ||
                ((model.PageIndex + num) >= model.TotalPages))
            {
                return (model.TotalPages - 1);
            }
            if ((model.PageIndex - (individualPagesDisplayedCount / 2)) < 0)
            {
                return (individualPagesDisplayedCount - 1);
            }
            return (model.PageIndex + num);
        }
    protected virtual string CreatePageLink(int pageNumber, string text, string cssClass)
    {
            var liBuilder = new TagBuilder("li");
            if (!String.IsNullOrWhiteSpace(cssClass))
                liBuilder.AddCssClass(cssClass);

            var aBuilder = new TagBuilder("a");
            aBuilder.SetInnerText(text);
            aBuilder.MergeAttribute("href", urlBuilder(pageNumber));

            liBuilder.InnerHtml += aBuilder;

            return liBuilder.ToString(TagRenderMode.Normal);
    }

        protected virtual string CreateDefaultUrl(int pageNumber)
    {
           var routeValues = new RouteValueDictionary();
           foreach (var key in viewContext.RequestContext.HttpContext.Request.QueryString.AllKeys.Where(key => key != null))
        {
                var value = viewContext.RequestContext.HttpContext.Request.QueryString[key];
                if (booleanParameterNames.Contains(key, StringComparer.InvariantCultureIgnoreCase))
                {
                    //little hack here due to ugly MVC implementation
                    //find more info here: http://www.mindstorminteractive.com/topics/jquery-fix-asp-net-mvc-checkbox-truefalse-value/
                    if (!String.IsNullOrEmpty(value) && value.Equals("true,false", StringComparison.InvariantCultureIgnoreCase))
                    {
                        value = "true";
                    }
                }
            routeValues[key] = value;
        }

            if (pageNumber > 1)
            {
                routeValues[pageQueryName] = pageNumber;
            }
            else
            {
                //SEO. we do not render pageindex query string parameter for the first page
                if (routeValues.ContainsKey(pageQueryName))
                {
                    routeValues.Remove(pageQueryName);
                }
            }

            var url = UrlHelper.GenerateUrl(null, null, null, routeValues, RouteTable.Routes, viewContext.RequestContext, true);
            return url;
    }
    }
}

3、Html.Widget

在Nop中用到很多@Html.Widget这个扩展方法,它其实是返回局部html。下面我们来看看它到底是怎么实现的。

调用方法:

@Html.Widget("categorydetails_after_breadcrumb", Model.Id)

HtmlHelper扩展方法:

public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object additionalData = null)
{
    return helper.Action("WidgetsByZone", "Widget", new { widgetZone = widgetZone, additionalData = additionalData });
}

Nop.Web.Controllers.WidgetController:

[ChildActionOnly]
public ActionResult WidgetsByZone(string widgetZone, object additionalData = null)
{
    var cacheKey = string.Format(ModelCacheEventConsumer.WIDGET_MODEL_KEY, _storeContext.CurrentStore.Id, widgetZone);
    var cacheModel = _cacheManager.Get(cacheKey, () =>
    {
        //model
        var model = new List<RenderWidgetModel>();

        var widgets = _widgetService.LoadActiveWidgetsByWidgetZone(widgetZone, _storeContext.CurrentStore.Id);
        foreach (var widget in widgets)
        {
            var widgetModel = new RenderWidgetModel();

            string actionName;
            string controllerName;
            RouteValueDictionary routeValues;
            widget.GetDisplayWidgetRoute(widgetZone, out actionName, out controllerName, out routeValues);
            widgetModel.ActionName = actionName;
            widgetModel.ControllerName = controllerName;
            widgetModel.RouteValues = routeValues;

            model.Add(widgetModel);
        }
        return model;
    });

    //no data?
    if (cacheModel.Count == 0)
        return Content("");

    //"RouteValues" property of widget models depends on "additionalData".
    //We need to clone the cached model before modifications (the updated one should not be cached)
    var clonedModel = new List<RenderWidgetModel>();
    foreach (var widgetModel in cacheModel)
    {
        var clonedWidgetModel = new RenderWidgetModel();
        clonedWidgetModel.ActionName = widgetModel.ActionName;
        clonedWidgetModel.ControllerName = widgetModel.ControllerName;
        if (widgetModel.RouteValues != null)
            clonedWidgetModel.RouteValues = new RouteValueDictionary(widgetModel.RouteValues);

        if (additionalData != null)
        {
            if (clonedWidgetModel.RouteValues == null)
                clonedWidgetModel.RouteValues = new RouteValueDictionary();
            clonedWidgetModel.RouteValues.Add("additionalData", additionalData);
        }

        clonedModel.Add(clonedWidgetModel);
    }

    return PartialView(clonedModel);
}
原文出处:蓝狐软件工作室【蓝狐】

原文链接:http://m.lanhusoft.com/Article/349.html

本文观点不代表 .Net中文网 立场,转载请联系原作者。

发表评论

登录后才能评论