2008-11-30 20 views
10

Actualización: Esto se convirtió en una entrada de blog, con enlaces actualizados y código, encima en mi blog: https://egilhansen.com/2008/12/01/how-to-take-control-of-style-sheets-in-asp-net-themes-with-the-styleplaceholder-and-style-control/Cómo tomar el control de las hojas de estilo en ASP.NET Temas con la StylePlaceHolder y Estilo de control


El problema es bastante simple. Al usar ASP.NET Themes, no tiene mucho que decir sobre cómo se procesan sus hojas de estilo en la página.

El motor de renderización agrega todas las hojas de estilo que tenga en su carpeta de temas en orden alfabético, utilizando la anotación 0ref = 0 ...href = "...".

Todos sabemos que el orden de las hojas de estilo es importante, afortunadamente las deficiencias de asp.nets se pueden sortear prefijando las hojas de estilo con 01, 02, ..., 99 y forzando el orden que desee (vea Rusty Swayne blog post sobre la técnica para más información).

Esto es especialmente importante si utiliza una hoja de estilo de reinicio, que recomiendo encarecidamente; hace que sea mucho más fácil diseñar un sitio de forma consistente en todos los navegadores (eche un vistazo a Reset Reloaded from Eric Meyer).

También se pierde la posibilidad de especificar un tipo de medio (por ejemplo, pantalla, impresión, proyección, braille, voz). Y si prefiere incluir hojas de estilo usando el método @import, también se queda afuera en el frío.

Otra opción que falta es el comentario condicional, que es especialmente útil si utiliza una hoja de estilo "ie-fix.css".

Antes de explicar cómo el control StylePlaceholder y Style resuelve los problemas anteriores, crédito por el que se debe crédito, mi solución está inspirada en Per Zimmerman’s blog post sobre el tema.

El control StylePlaceHolder se coloca en la sección del encabezado de la página o página maestra. Puede alojar uno o más controles de Estilo, y eliminará los estilos añadidos por el motor de renderización de manera predeterminada, y agregará los propios (solo eliminará los estilos agregados del tema activo actual).

El control de estilo puede alojar estilos en línea entre sus etiquetas de apertura y cierre y una referencia a un archivo de hoja de estilo externo a través de su propiedad CssUrl. Con otras propiedades, usted controla cómo se procesa la hoja de estilos en la página.

Déjame mostrarte un ejemplo. Considere un proyecto simple de sitio web con una página maestra y un tema con tres hojas de estilo: 01reset.css, 02style.css, 99iefix.cs. Nota: los he nombrado utilizando la técnica de prefijación descrita anteriormente, ya que mejora la experiencia de diseño. Además, el prefijo de etiqueta de los controles personalizados es "ass:".

En la sección de encabezado de la página maestra, añadir:

<ass:StylePlaceHolder ID="StylePlaceHolder1" runat="server" SkinID="ThemeStyles" /> 

En su directorio de temas, agregar un archivo de la piel (por ejemplo Styles.skin) y añadir el siguiente contenido:

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

Eso es Básicamente. Hay más propiedades en el control de Estilo que se pueden usar para controlar la representación, pero esta es la configuración básica. Con eso en su lugar, puede agregar fácilmente otro tema y reemplazar todos los estilos, ya que solo necesita incluir un archivo de máscara diferente.

Ahora al código que hace que todo suceda. Debo admitir que la experiencia del tiempo de diseño tiene algunos caprichos.Probablemente se deba al hecho de que no soy muy hábil para escribir controles personalizados (de hecho, estos dos son mis primeros intentos), por lo que me gustaría recibir comentarios sobre lo siguiente. En un proyecto actual basado en WCAB/WCSF que estoy desarrollando, veo errores como este en la vista de diseño de Visual Studios, y no tengo idea de por qué. El sitio compila y todo funciona en línea.

Example of design time error in Visual Studio http://www.egil.dk/wp-content/styleplaceholder-error.jpg

El siguiente es el código para el control 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("<{0}:StylePlaceHolder runat=\"server\" SkinID=\"ThemeStyles\"></{0}:StylePlaceHolder>")] 
    [ParseChildren(true, "Styles")] 
    [Themeable(true)] 
    [PersistChildren(false)] 
    public class StylePlaceHolder : Control 
    { 
     private List<Style> _styles; 

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

     [Browsable(false)] 
     public List<Style> Styles 
     { 
      get 
      { 
       if (_styles == null) 
        _styles = new List<Style>(); 
       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<HtmlLink>() 
        .Where(link => 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); 
     } 

    } 
} 

Y el código para el control Estilo:

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("<{0}:Style runat=\"server\"></{0}:Style>")] 
    [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 ? "<!{0}>" : "<!--{0}>", 
           ConditionalCommentExpression); 

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

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

      if(!string.IsNullOrEmpty(InlineStyle)) 
      { 
       // <style type="text/css">... inline style ... </style> 
       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 ? "<![endif]>" : "<![endif]-->"); 
      } 
     } 
    } 

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

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

Entonces, ¿qué piensan ustedes? ¿Es esta una buena solución para el problema del tema asp.net? ¿Y el código? Realmente me gustaría obtener información sobre esto, especialmente en lo que respecta a la experiencia del tiempo de diseño.

Cargué un zipped version of the Visual Studio solution que contiene el proyecto, en caso de que alguien esté interesado.

Saludos cordiales, Egil.

Respuesta

2

Encontré la respuesta a mi propia pregunta.

El motivo de los errores de representación que obtengo en el modo de diseño es un error evidente en Visual Studio SP1, which Microsoft has yet to fix.

De modo que el código anterior funciona como se espera, también en modo de diseño, siempre que solo incluya los controles personalizados en un ensamblaje precompilado, y no a través de otro proyecto en la misma solución.

Consulte el enlace de arriba para obtener una explicación más detallada de cómo y por qué.

0

Funciona muy bien.

Para aquellos como yo que nunca recuerdan la sintaxis de <% etiquetas, esto es lo que necesita agregar al principio de la definición de página maestra y el archivo skin para registrar el espacio de nombre.

<%@ Register TagPrefix="ass" Namespace="Assimilated.WebControls.Stylesheet" %> 

No estoy seguro de querer tanto 'asno' en todo mi código, pero de lo contrario me gusta.

Ah, y si este es realmente su primer gran trabajo de control personalizado. Sé que fue inspirado por el código de otra persona, pero al menos aparece para tener todos los atributos e interfaces correctos.

+0

Gracias por las palabras amables Simon. Este es de hecho mi primer control personalizado, pero ahora ha sido refactorizado algunas veces, así que primero control personalizado, no primer intento :) –

+0

@egil es gracioso que me cambié a MVC al día siguiente, en parte porque me estaba cansando de tener que buscar soluciones inteligentes como la tuya para resolver problemas simples. afortunadamente estoy desarrollando un sitio nuevo, así que me permitieron el lujo de jugar con MVC y realmente lo estoy disfrutando –

0

Re: utilizando archivos CSS de medios específicos, puede utilizar la declaración @media CSS, funciona bien.

Cuestiones relacionadas