我以前的帖子涉及如何将Magento的API与WCF一起使用。我遇到的Magento的另一个方面是,它对主题的支持极为灵活。
您可以设计一个与默认主题完全不同的新主题。您不仅可以更改级联样式表中的图像和颜色,还可以重新定义组成页面的区域(标题,内容,页脚...)。根据区域,您可以指定将哪些HTML注入其中。这为您的网站为主题提供了最大的自定义功能。
ASP.NET中的主题受到封装的支持。使用app_themes文件夹,您可以自定义网站的外观和感觉,但是该系统可能很麻烦。
Magento是使用MVC模式建造的,就像ASP.NET MVC框架一样。这就是本文的重点。我们如何在ASP.NET MVC中实施主题?
让我们开始...
在开始编码之前,让我们总结要实现的目标。假设我们拥有一家在中国,越南,南库里亚...等的公司。我们不是直接卖给最终用户,而是向转售者出售。
我们想设计一个ASP.NET MVC驱动的Web应用程序,我们所有经销商都可以用作电子商务网站。每个经销商都有自己的域名,并希望在线出售自己的商品。这些域中的每个域都链接到我们的单个Web应用程序。
Web应用程序提供的功能对于每个经销商都相同,但是每个转售商都希望通过应用自定义主题来自定义他的在线商店。我们将支持以下情况:
因此,我们必须弄清楚如何动态替换样式表(CSS),主页,视图和部分视图。
在开始之前,我们需要奠定基础。让我们从为简单的演示应用程序构建基础知识开始。
注释:在本节中,我简要概述了设置演示应用程序,以便可以演示主题功能。这只是一种快速简便的方法。本文的重点不是如何设计域模型,设计您的业务逻辑层...等。因此,我尽可能短。随意改进它。
数据库
使用SQL Server Express(2005或2008)创建一个新数据库。受到西北数据库的启发,我将数据库命名为[WindDiriend]。
该数据库完全包含一个名为[转售者]的表。设计表如下图所示。
图1- [经销商]表
![转售商表 [Reseller] Table Design](https://images.downcodes.com/uploads/20250616/img_68500176a3ae030.png)
如您所见,ID列是主键,它使用身份规范(=自动启动)。还要在域列上添加独特的约束,因为每个转售商都有自己的独特域。
备注:本文随附的源代码包含一个脚本(DDL.SQL),如果您不想手工设计它,该脚本允许您快速生成此表。
设置数据库的最后一部分是输入[转售者]表的一些虚拟记录。请输入以下记录:
图2-转售者

我们有四个转售商。第一个经销商没有自定义主题,并且落在默认的主题上。所有其他人都有自己的自定义主题定义。
启动Visual Studio 2008,并创建一个名为“ MvCapplication”的新空白解决方案。添加一个新的代码库,并将其称为“ cgeers.winddirection.database”。删除自动生成的class1.css文件。
接下来,将新的LINQ添加到SQL类项目中,并将其命名为“ Dataclasses”。将新的DataContext重命名为“ WindDirectionDataContext”。现在,将[转售者]表从“服务器资源管理器”选项卡拖到LINQ到SQL Designer Surface。
图3-经销商实体

将DataContext的连接属性设置为“无”,然后删除连接字符串应用程序设置和应用程序配置文件(app.config)。我不喜欢Visual Studio为我注入连接字符串的事实。我喜欢自己做。
这就是为什么我将以下部分类添加到此汇编中,该类别用连接字符串处理DataContext的初始化。我们唯一要同意的部分是连接字符串称为“风向”。
清单1- winddirectiondatacontext类
public partial class WindDirectionDataContext
{
private static readonly string ConnectionString ;
static WindDirectionDataContext ( )
{
ConnectionStringSettings settings = ConfigurationManager . ConnectionStrings [ "WindDirection" ] ;
ConnectionString = settings != null ? settings . ConnectionString : String . Empty ;
}
public WindDirectionDataContext ( ) : base ( ConnectionString ) { }
}不要忘记添加对系统的参考。配置组件。只要您将在引用此组件的应用程序中包括一个称为“ WindDiriondection”的连接字符串,就可以正常工作。
我们快到了。只要坚持下去,我们将通过。现在,将一个新的代码库添加到称为“ cgeers.winddirection.managers”的解决方案。删除自动生成的class1.cs文件,然后将引用添加到system.data.linq assembly。
添加一个名为Manager的新类,并向其添加以下代码:
清单2-抽象经理
public abstract class Manager
{
protected Manager ( )
{
Context = new WindDirectionDataContext ( ) ;
}
public WindDirectionDataContext Context { get ; set ; }
}这个非常简单的类创建了一个新的DataContext,以后我们可以在其上释放LINQ查询。
接下来,将一个名为“ ResellerManager”的类添加到项目中,并添加列表3中显示的代码。
列出3个ResellerManager
public class ResellerManager : Manager
{
public string GetThemeForDomain ( string domain )
{
var q = from r in Context . Resellers
where r . Domain == domain
select r . Theme ;
string theme = q . SingleOrDefault ( ) ;
return ! String . IsNullOrEmpty ( theme ) ? theme : "Default" ;
}
}该管理器类来自我们的抽象管理器类,并添加了一种称为getThemeFordomain(...)的方法。此方法根据给定的域名查找经销商主题。由于每个域与一个转售商唯一绑定,这毫无问题。
瞧,这就是我们的演示应用程序所需的所有数据访问。我们需要根据传入请求的域名弄清楚经销商的主题,然后我们必须应用它。
备注:当心在ASP.NET供电应用程序中使用LINQ与SQL上下文。尽管本文没有证明,因为它会分散我们的主要努力,但建议仅根据请求创建一个上下文。将上下文存储在请求的httpcontext中,以便您可以在请求期间始终访问它。
不久前,我写了一篇有关此文章的文章,请在此处查看“实体框架objectwork”文章。尽管它处理实体框架,而不是LINQ到SQL,但仍然适用。
完成我们的基本演示应用程序的最后一步是将新的网站项目添加到解决方案中。基于ASP.NET MVC Web应用程序项目模板将新项目添加到解决方案中,并将其命名为“ MVCapplication”。您是否还需要为此应用程序创建一个单元测试项目。选择“否”来跳过此内容,因为我们不需要本文。
Visual Studio将生成一个“ Hello,World!” - 键入包含许多默认页面的ASP.NET MVC应用程序(主页,大约登录...等)。将您的连接字符串添加到web.config文件中,然后将引用添加到cgeers.winddirection.database和cgeers.winddirection.managers组件。
备注:Web.config包含许多涉及ASP.NET成员资格,配置文件,角色...提供商的配置设置。您可以继续删除这些内容,因为我们不需要它们。
您的解决方案资源管理器应类似于图4。
图4-解决方案资源管理器

备注:在撰写本文时,我正在使用ASP.NET MVC版本1.0。但是,2.0版将在不久的将来发布。
运行Web应用程序时,第一件事需要弄清楚的是需要应用的主题。每个请求都需要完成此操作。因此,在Request-Pipeline中插入自定义的HTTP模块似乎合适。
将新类添加到MVCapplication项目中,并将其称为themhttpModule。让类实现IHTTPMODULE接口。该类的整个代码在清单4中显示。
本文不是编写HTTP模块的入门,因此,如果您需要更多信息,请查看“演练:创建和注册自定义HTTP模块”文章。
列表4- themehttpModule
public class ThemeHttpModule : IHttpModule
{
public void Init ( HttpApplication application )
{
application . BeginRequest += application_BeginRequest ;
}
private void application_BeginRequest ( object sender , EventArgs e )
{
HttpApplication application = ( HttpApplication ) sender ;
HttpContext context = application . Context ;
if ( context . Cache == null )
{
return ;
}
string domain = context . Request . Url . GetDomain ( ) ;
string cacheKey = String . Format ( CultureInfo . InvariantCulture , "theme_for_{0}" , domain ) ;
if ( context . Cache [ cacheKey ] == null )
{
ResellerManager manager = new ResellerManager ( ) ;
string theme = manager . GetThemeForDomain ( domain ) ;
context . Cache [ cacheKey ] = theme ;
}
}
public void Dispose ( ) { }
}此HTTP模块为BeginRequest事件添加了事件处理程序。当ASP.NET响应请求时,此事件是HTTP管道链中的第一个事件。
在这里,我们从传入请求中提取域名。接下来,我们使用ResellerManager的GetThemeFordomain(...)方法来检索该域的主题。然后将结果缓存。下次触发该域请求时,将从缓存中检索主题,并且不会触发数据库查询。
getDomain()方法是URI类的扩展方法。查看本文的源代码,以了解其工作原理。以类似的方式,您可以选择从请求中提取子域(例如:www,admin ...等)。然后,您可以将主题引擎扩展到为域的每个子域应用不同的主题。
最后但并非最不重要的一点是通过在Web.config文件中创建条目来注册themeHttpModule。为了将HTTP模块订阅到请求Pipeline通知,这是必需的。
列表5-注册themehttpModule
< httpModules >
< add name = " ThemeHttpModule " type = " MvcApplication.ThemeHttpModule " />
<!-- ... -->
</ httpModules >启动Web应用程序时,您会收到如图5所示的默认外观和感觉。Visual Studio将生成一些默认页面(主页,大约在,登录...等),包括主页和样式表。我们将使用这些文件来弥补我们的默认主题。
图5- ASP.NET MVC应用程序默认主题

默认情况下,所有文件都保存在内容和视图文件夹中。我们需要实现自己的目录结构,以便我们可以从逻辑上分组主题。因此,创建一个名为主题的新文件夹。为主题目录创建子文件夹,并将其称为默认值。在此默认目录下移动内容和视图目录。
移动内容和视图文件夹后,您需要为每个视图调整页面指令的MasterPageFile属性!旧值引用了不再存在的位置。将MasterPageFile =“〜/views/views/shared/site.master”更改为MasterPageFile =“〜/everes/default/views/views/shared/site.master” !
图6-默认主题

瞧,我们的默认主题已设置。如果要创建一个新主题,则只需创建一个新文件夹即可将其放在主题文件夹下。如您在上一个屏幕截图中所看到的,我们将在以后创建其他一些主题(绿色,橙色,红色)。
我们只是移动了主页,样式表,视图等。到另一个目录。如果我们立即启动Web应用程序,我们将收到以下例外:
图7-无效的Exception

找不到“索引”或其主人的视图。搜索以下位置:
MVC试图找到默认启动页面的视图,但在搜索的默认位置找不到它,因此您会收到异常。我们将这些文件移至默认主题文件夹中,很快我们将创建其他主题。我们需要一种方法来将MVC告知查看视图,主页,部分视图等地点。这些位置在转销商的主题上有所不同。
因此,基本上,我们需要做的所有以支持ASP.NET MVC中的主题是:
为此,我们需要编写自己的视图引擎。 MVC使用视图引擎渲染页面以进行响应。此视图引擎负责找到主页,视图和部分视图。默认情况下,使用WebFormViewEngine。
我们需要自己替换此默认视图引擎。为此,将一个名为themedviewEngine的新类添加到MVCapplication项目中,并将其从WebFormViewEngine类中添加。
列表6-主题ViewEngine
public class ThemedViewEngine : WebFormViewEngine
{
#region Constructor(s)
// Replace the default search paths by our own.
public ThemedViewEngine ( )
{
// Search paths for the master pages
base . MasterLocationFormats = new [ ]
{
"~/Themes/{2}/Views/{1}/{0}.master" ,
"~/Themes/{2}/Views/Shared/{0}.master"
} ;
// Search paths for the views
base . ViewLocationFormats = new [ ]
{
"~/Themes/{2}/Views/{1}/{0}.aspx" ,
"~/Themes/{2}/Views/{1}/{0}.ascx" ,
"~/Themes/{2}/Views/Shared/{0}.aspx" ,
"~/Themes/{2}/Views/Shared/{0}.ascx" ,
} ;
// Search parts for the partial views
// The search parts for the partial views are the same as the regular views
base . PartialViewLocationFormats = base . ViewLocationFormats ;
}
#endregion
}在此新视图引擎的构造函数中,我们将MasterLocationFormats,ViewLocationFormats和PartialViewLocationFormats设置为新位置,例如:〜/themes/{2}/{2}/view/{1}/{0}/{0} .aspx。
每个路径包含3个动态确定的部分。
为了使用新视图引擎,您需要注册它。通过将以下代码添加到位于global.asax.cs文件中的application_start事件处理程序中来执行此操作。
列表7-注册主题ViewEngine
protected void Application_Start ( )
{
ViewEngines . Engines . Clear ( ) ;
ViewEngines . Engines . Add ( new ThemedViewEngine ( ) ) ;
RegisterRoutes ( RouteTable . Routes ) ;
}在这里,您可以清除任何可能已加载并注入自己的视图引擎。现在剩下的就是指示视图引擎如何格式化新的搜索路径,以便正确地找到所请求的文件。为此,您需要覆盖以下两种方法:
清单8- findpartialview(...)&findview(...)方法
public override ViewEngineResult FindPartialView ( ControllerContext controllerContext , string partialViewName , bool useCache )
public override ViewEngineResult FindView ( ControllerContext controllerContext , string viewName , string masterName , bool useCache )我不会在此处包含这两个功能的代码,因为它很漫长,并且有一些引用私人助手方法。基本上,这两种方法遵循相同的模式:
因此,我们的新视图引擎基本上搜索了我们的主题文件夹,如果找不到请求的主页,视图或部分视图,则使用默认主题的视图。当然,默认主题需要完成,并且不能有任何丢失的文件。
这使您能够创建仅包含主页的主题,该主题又引用了不同的样式表或包含视图和 /或部分视图的主题,而您只想以不同方式样式的那些部分。
通过遵循此模式,您可以创建仅覆盖某些视图的主题,并在没有提供自定义视图的情况下依靠默认主题的视图。
我将视图引擎基于Chris Pietschmann在ASP.NET MVC中主题的出色文章的工作。我建议您查看他的文章,因为它包含有关内部视图引擎如何工作的更多信息。
有了新的视图引擎,我们可以在没有任何例外的情况下再次运行Web应用程序,因为它现在可以解决主页,视图和部分视图的请求。
备注:我对代码进行了一些更改,以便当视图引擎无法解析某个主页面的请求时,视图或部分视图将归还默认主题中的内容。因此,请务必查看本文的源代码。
让我们快速创建一个新主题。在主题文件夹下方添加一个名为“红色”的新文件夹。从默认主题中复制site.master和site.css.css,如下图所示。
图8-红色主题

打开红色主题的样式表并更改身体元素的背景色属性。将其设置为红色。现在,打开[转售者]表,并将主题字段设置为“红色”,以供将域设置为Localhost的转售商。重新启动Web应用程序,现在应该使用红色主题的主页和样式表。
图9-行动中的红色主题

同样,您可以创建一个橙色主题,该主题不仅包含主页,而且还包含主页的不同视图。
图10-橙色主题

橙色主题将渲染主页的新视图,而不是默认视图。如果要替换部分视图,则可以以相同的方式进行。只需将默认部分视图复制到新主题文件夹下的同一位置,然后根据需要对其进行调整。
对于每个主题,您现在都可以提供不同的主页,视图和部分视图。我希望支持的剩余场景。对默认视图感到满意但只想调整徽标,一些颜色...等的转售者。通过将不同的样式表应用于默认主题,可以轻松满足。
在主题目录下方添加一个新的主题文件夹,并将其称为“绿色”。如下图所示,将默认主题的样式表复制到绿色主题。
图11-绿色主题

打开绿色主题的样式表,并将身体元素的背景色属性调整为绿色。如果将域Localhost的转售商设置为绿色,并启动应用程序,您会注意到它仍在使用默认主题的样式表。
这是由于绿色主题没有自己的主页而引起的。它使用默认主题的主页,此主页引用其自己的样式表。
打开默认主题的主页并替换行:
< link href =" ../../Content/Site.css " rel =" stylesheet " type =" text/css " />和
< link href =" <% " ="Html.GetThemedStyleSheet()" % /> rel="stylesheet"
type="text/css" / >GetThemedStyleSheet()方法是HTML实用程序类的扩展方法。将一个名为htmlhelperextensensions的新类添加到项目中,并向其添加以下代码。
清单9- htmlhelperextensions
public static class HtmlHelperExtensions
{
public static string GetThemedStyleSheet ( this HtmlHelper html )
{
HttpContext context = HttpContext . Current ;
if ( context == null )
{
throw new InvalidOperationException ( "Http Context cannot be null." ) ;
}
string defaultStyleSheet = context . Server . MapPath ( "~/Themes/Default/Content/Site.css" ) ;
string domain = context . Request . Url . GetDomain ( ) ;
string cacheKey = String . Format ( CultureInfo . InvariantCulture , "theme_for_{0}" , domain ) ;
string theme = ( string ) context . Cache [ cacheKey ] ;
if ( String . IsNullOrEmpty ( theme ) || theme == "Default" )
{
return defaultStyleSheet ;
}
string styleSheet = context . Server . MapPath ( String . Format ( CultureInfo . InvariantCulture ,
"~/Themes/{0}/Content/Site.css" , theme ) ) ;
if ( ! File . Exists ( styleSheet ) )
{
styleSheet = defaultStyleSheet ;
}
return String . Format ( CultureInfo . InvariantCulture , "'{0}'" , styleSheet ) ;
}
}GetThemedStyleSheet()方法从HTTPApplication的缓存中加载主题,并检查此主题是否具有自己的样式表。如果没有,它将落在默认主题的样式表上。该代码包含一些硬编码的字符串,尽管不是最佳的,但它可以解决问题。随意改进这种方法。
如果您现在启动Web应用程序,您将获得不错的绿色背景。
本文向您展示了如何在ASP.NET MVC中启用主题。为此,您只需要实施两件事,即:
我们实施的主题系统使用默认主题,并检查是否需要用自定义主题替换此默认主题的部分。您可以轻松支持以下方案之一或将它们组合在一起: