我以前的帖子涉及如何將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中啟用主題。為此,您只需要實施兩件事,即:
我們實施的主題系統使用默認主題,並檢查是否需要用自定義主題替換此默認主題的部分。您可以輕鬆支持以下方案之一或將它們組合在一起: