Asp.net

如何使用 StylePlaceHolder 和 Style 控制項控制 ASP.NET 主題中的樣式表

  • December 3, 2015

**更新:**這變成了一篇部落格文章,帶有更新的連結和程式碼,在我的部落格上:https ://egilhansen.com/2008/12/01/how-to-take-control-of-style-sheets-in- asp-net-themes-with-the-styleplaceholder-and-style-control/


問題很簡單。使用 ASP.NET 主題時,您對如何將樣式表呈現到頁面沒有太多發言權。

渲染引擎使用 <link href=”…” 符號按字母順序添加您在主題文件夾中的所有樣式表。

我們都知道樣式表的順序很重要,幸運的是 asp.nets 的缺點可以通過在樣式表前加上 01、02、…、99 來規避,從而強制您想要的順序(參見 Rusty Swayne部落格文章更多資訊的技術)。

如果您使用我強烈推薦的重置樣式表,這一點尤其重要;它使跨瀏覽器以一致的形式對站點進行樣式設置變得更加容易(請查看來自 Eric Meyer 的 Reset Reloaded)。

您也錯過了指定媒體類型(例如螢幕、印刷、投影、盲文、語音)的可能性。如果您更喜歡使用 @import 方法包含樣式表,那麼您也會被冷落。

另一個缺少的選項是條件註釋,如果您使用“ie-fix.css”樣式表,它特別有用。

在我解釋 StylePlaceholder 和 Style 控制項如何解決上述問題之前,我的解決方案受到Per Zimmerman關於該主題的部落格文章的啟發。

StylePlaceHolder 控制項放置在母版頁或頁面的頁眉部分。它可以承載一個或多個 Style 控制項,並且預設會移除渲染引擎添加的樣式,並添加自己的樣式(它只會移除從目前活動主題中添加的樣式)。

Style 控制項既可以在其開始和結束標記之間託管內聯樣式,也可以通過其 CssUrl 屬性對外部樣式表文件的引用。使用其他屬性,您可以控製樣式表呈現給頁面的方式。

讓我舉個例子。考慮一個簡單的網站項目,它有一個母版頁和一個包含三個樣式表的主題——01reset.css、02style.css、99iefix.cs。注意:我使用前面描述的前綴技術命名它們,因為它可以提供更好的設計時體驗。此外,自定義控制項的標籤前綴是“ass:”。

在母版頁的標題部分中,添加:

&lt;ass:StylePlaceHolder ID="StylePlaceHolder1" runat="server" SkinID="ThemeStyles" /&gt;

在您的主題目錄中,添加一個皮膚文件(例如 Styles.skin)並添加以下內容:

&lt;ass:StylePlaceHolder1runat="server" SkinId="ThemeStyles"&gt;
   &lt;ass:Style CssUrl="~/App_Themes/Default/01reset.css" /&gt;
   &lt;ass:Style CssUrl="~/App_Themes/Default/02style.css" /&gt;
   &lt;ass:Style CssUrl="~/App_Themes/Default/99iefix.css" ConditionCommentExpression="[if IE]" /&gt;
&lt;/ass:StylePlaceHolder1&gt;

基本上就是這樣。Style 控制項上有更多屬性可用於控制渲染,但這是基本設置。有了它,您可以輕鬆添加另一個主題並替換所有樣式,因為您只需要包含不同的皮膚文件。

現在到使這一切發生的程式碼。我必須承認設計時體驗有一些怪癖。可能是因為我寫自定義控制項不是很熟練(其實這兩個是我第一次嘗試),所以很想在下面輸入。在我正在開發的目前基於 WCAB/WCSF 的項目中,我在 Visual Studios 設計視圖中看到了這樣的錯誤,我不知道為什麼。該網站編譯,一切都線上執行。

Visual Studio 中的設計時錯誤範例 http://www.egil.dk/wp-content/styleplaceholder-error.jpg

以下是 StylePlaceHolder 控制項的程式碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;

[assembly: TagPrefix("Assimilated.Extensions.Web.Controls", "ass")]
namespace Assimilated.WebControls.Stylesheet
{
   [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
   [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
   [DefaultProperty("SkinID")]
   [ToolboxData("&lt;{0}:StylePlaceHolder runat=\"server\" SkinID=\"ThemeStyles\"&gt;&lt;/{0}:StylePlaceHolder&gt;")]
   [ParseChildren(true, "Styles")]
   [Themeable(true)]
   [PersistChildren(false)]
   public class StylePlaceHolder : Control
   {
       private List&lt;Style&gt; _styles;

       [Browsable(true)]
       [Category("Behavior")]
       [DefaultValue("ThemeStyles")]
       public override string SkinID { get; set; }

       [Browsable(false)]
       public List&lt;Style&gt; Styles
       {
           get
           {
               if (_styles == null)
                   _styles = new List&lt;Style&gt;();
               return _styles;
           }
       }

       protected override void CreateChildControls()
       {
           if (_styles == null)
               return;

           // add child controls
           Styles.ForEach(Controls.Add);
       }

       protected override void OnLoad(EventArgs e)
       {
           base.OnLoad(e);

           // get notified when page has finished its load stage
           Page.LoadComplete += Page_LoadComplete;
       }

       void Page_LoadComplete(object sender, EventArgs e)
       {
           // only remove if the page is actually using themes
           if (!string.IsNullOrEmpty(Page.StyleSheetTheme) || !string.IsNullOrEmpty(Page.Theme))
           {
               // Make sure only to remove style sheets from the added by
               // the runtime form the current theme.
               var themePath = string.Format("~/App_Themes/{0}",
                                             !string.IsNullOrEmpty(Page.StyleSheetTheme)
                                                 ? Page.StyleSheetTheme
                                                 : Page.Theme);

               // find all existing stylesheets in header
               var removeCandidate = Page.Header.Controls.OfType&lt;HtmlLink&gt;()
                   .Where(link =&gt; link.Href.StartsWith(themePath)).ToList();

               // remove the automatically added style sheets
               removeCandidate.ForEach(Page.Header.Controls.Remove);
           }
       }

       protected override void AddParsedSubObject(object obj)
       {
           // only add Style controls
           if (obj is Style)
               base.AddParsedSubObject(obj);
       }

   }
}

以及 Style 控制項的程式碼:

using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;

[assembly: TagPrefix("Assimilated.Extensions.Web.Controls", "ass")]
namespace Assimilated.WebControls.Stylesheet
{
   [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
   [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
   [DefaultProperty("CssUrl")]
   [ParseChildren(true, "InlineStyle")]
   [PersistChildren(false)]
   [ToolboxData("&lt;{0}:Style runat=\"server\"&gt;&lt;/{0}:Style&gt;")]
   [Themeable(true)]
   public class Style : Control
   {
       public Style()
       {
           // set default value... for some reason the DefaultValue attribute do
           // not set this as I would have expected.
           TargetMedia = "All";
       }

       #region Properties

       [Browsable(true)]
       [Category("Style sheet")]
       [DefaultValue("")]
       [Description("The url to the style sheet.")]
       [UrlProperty("*.css")]
       public string CssUrl
       {
           get; set;
       }

       [Browsable(true)]
       [Category("Style sheet")]
       [DefaultValue("All")]
       [Description("The target media(s) of the style sheet. See http://www.w3.org/TR/REC-CSS2/media.html for more information.")]
       public string TargetMedia
       {
           get; set;
       }

       [Browsable(true)]
       [Category("Style sheet")]
       [DefaultValue(EmbedType.Link)]
       [Description("Specify how to embed the style sheet on the page.")]
       public EmbedType Type
       {
           get; set;
       }

       [Browsable(false)]
       [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
       public string InlineStyle
       {
           get; set;
       }

       [Browsable(true)]
       [Category("Conditional comment")]
       [DefaultValue("")]
       [Description("Specifies a conditional comment expression to wrap the style sheet in. See http://msdn.microsoft.com/en-us/library/ms537512.aspx")]
       public string ConditionalCommentExpression
       {
           get; set;
       }

       [Browsable(true)]
       [Category("Conditional comment")]
       [DefaultValue(CommentType.DownlevelHidden)]
       [Description("Whether to reveal the conditional comment expression to downlevel browsers. Default is to hide. See http://msdn.microsoft.com/en-us/library/ms537512.aspx")]
       public CommentType ConditionalCommentType
       {
           get; set;
       }

       [Browsable(true)]
       [Category("Behavior")]
       public override string SkinID { get; set; }

       #endregion

       protected override void Render(HtmlTextWriter writer)
       {            
           // add empty line to make output pretty
           writer.WriteLine();

           // prints out begin condition comment tag
           if (!string.IsNullOrEmpty(ConditionalCommentExpression))
               writer.WriteLine(ConditionalCommentType == CommentType.DownlevelRevealed ? "&lt;!{0}&gt;" : "&lt;!--{0}&gt;",
                                ConditionalCommentExpression);

           if (!string.IsNullOrEmpty(CssUrl))
           {               
               // add shared attribute
               writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css");

               // render either import or link tag
               if (Type == EmbedType.Link)
               {
                   // &lt;link href=\"{0}\" type=\"text/css\" rel=\"stylesheet\" media=\"{1}\" /&gt;
                   writer.AddAttribute(HtmlTextWriterAttribute.Href, ResolveUrl(CssUrl));
                   writer.AddAttribute(HtmlTextWriterAttribute.Rel, "stylesheet");
                   writer.AddAttribute("media", TargetMedia);
                   writer.RenderBeginTag(HtmlTextWriterTag.Link);
                   writer.RenderEndTag();
               }
               else
               {
                   // &lt;style type="text/css"&gt;@import "modern.css" screen;&lt;/style&gt;
                   writer.RenderBeginTag(HtmlTextWriterTag.Style);
                   writer.Write("@import \"{0}\" {1};", ResolveUrl(CssUrl), TargetMedia);
                   writer.RenderEndTag();
               }
           }

           if(!string.IsNullOrEmpty(InlineStyle))
           {
               // &lt;style type="text/css"&gt;... inline style ... &lt;/style&gt;
               writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css");
               writer.RenderBeginTag(HtmlTextWriterTag.Style);
               writer.Write(InlineStyle);
               writer.RenderEndTag();
           }

           // prints out end condition comment tag
           if (!string.IsNullOrEmpty(ConditionalCommentExpression))
           {
               // add empty line to make output pretty
               writer.WriteLine();
               writer.WriteLine(ConditionalCommentType == CommentType.DownlevelRevealed ? "&lt;![endif]&gt;" : "&lt;![endif]--&gt;");
           }
       }
   }

   public enum EmbedType
   {        
       Link = 0,
       Import = 1,
   }

   public enum CommentType
   {
       DownlevelHidden = 0,
       DownlevelRevealed = 1
   }
}

那你們怎麼看?這是解決 asp.net 主題問題的好方法嗎?那程式碼呢?我真的很想對此進行一些輸入,尤其是在設計時體驗方面。

我上傳了包含該項目的 Visual Studio 解決方案的壓縮版本,以防有人感興趣。

最好的問候,埃吉爾。

找到了我自己的問題的答案。

我在設計模式下出現渲染錯誤的原因是 Visual Studio SP1 中的一個明顯錯誤,微軟尚未修復

因此,上面的程式碼在設計模式下也能按預期工作,只要您只將自定義控制項包含在預編譯的程序集中,而不是通過同一解決方案中的另一個項目。

有關如何以及為什麼更詳細的說明,請參閱上面的連結。

工作非常順利。

對於像我這樣從不記得 <% 標記語法的人來說,這是您需要添加到母版頁定義和外觀文件頂部以註冊命名空間的內容。

&lt;%@ Register TagPrefix="ass" Namespace="Assimilated.WebControls.Stylesheet" %&gt;

我不確定我的程式碼中是否需要那麼多“屁股”,但除此之外我喜歡它。

哦,如果這真的是您的第一個自定義控制項,那就太好了。我知道它的靈感來自其他人的程式碼,但它至少看起來具有所有正確的屬性和介面。

引用自:https://stackoverflow.com/questions/328763