Mi publicación anterior trató sobre cómo usar la API de Magento con WCF. Otro aspecto de Magento que encontré es su apoyo extremadamente flexible para el tema.
Puede diseñar un nuevo tema que se vea radicalmente diferente al predeterminado. No solo puede cambiar las imágenes y los colores en las hojas de estilo en cascada, sino que también puede redefinir las regiones (encabezado, contenido, pie de página ...) que componen una página. Por región puede especificar qué HTML se inyecta en ella. Esto le brinda la máxima potencia de personalización para temas de su sitio.
El tema en ASP.NET es compatible fuera de la caja. Usando la carpeta App_themes, puede personalizar la apariencia de su sitio, por engorroso que este sistema puede ser.
Magento se construye utilizando el patrón MVC, al igual que el marco ASP.NET MVC. Y ese es el foco de este artículo. ¿Cómo hacemos para implementar temas en ASP.NET MVC?
Empecemos ...
Antes de comenzar a codificar, resumamos los objetivos que queremos lograr. Supongamos que somos dueños de una empresa que importa muebles hechos en China, Vietnam, Corea del Sur ... etc. No vendemos directamente a los usuarios finales, sino a los revendedores.
Queremos diseñar una aplicación web ASP.NET MVC MVC que todos nuestros revendedores puedan utilizar como sitio de comercio electrónico. Cada revendedor tiene su propio nombre de dominio y quiere vender sus productos en línea. Cada uno de estos dominios está vinculado a nuestra aplicación web única.
La funcionalidad ofrecida por la aplicación web es la misma para cada revendedor, pero cada revendedor quiere personalizar su tienda en línea aplicando un tema personalizado. Apoyaremos las siguientes situaciones:
Por lo tanto, tenemos que descubrir cómo podemos reemplazar dinámicamente la hoja de estilo (CSS), la página maestra, las vistas y las vistas parciales.
Antes de que podamos comenzar, necesitamos sentar las bases. Comencemos por construir los conceptos básicos para una aplicación de demostración simple.
Observación : En esta sección doy una visión general rápida de configurar una aplicación de demostración para que se pueda demostrar la función de temas. Este es solo un enfoque rápido y fácil. El enfoque de este artículo no se trata de cómo diseñar un modelo de dominio, diseñando su capa lógica de negocios ... etc. Así que mantengo esto lo más corto posible. Siéntase libre de mejorarlo.
Base de datos
Cree una nueva base de datos utilizando SQL Server Express (2005 o 2008). Inspirado en la base de datos Northwind, llamé a mi base de datos [WindDirection].
Esta base de datos contiene exactamente una tabla llamada [revendedor]. Diseñe la tabla como se muestra en la siguiente figura.
Figura 1 - tabla [revendedor]
![Mesa de revendedor [Reseller] Table Design](https://images.downcodes.com/uploads/20250616/img_68500176a3ae030.png)
Como puede ver, la columna ID es la clave principal y utiliza la especificación de identidad (= Autoincrement). También agregue una restricción única en la columna de dominio ya que cada revendedor tiene su propio dominio único.
Observación : El código fuente que acompaña a este artículo contiene un script (ddl.sql) que le permite generar rápidamente esta tabla si no desea diseñarlo a mano.
La última parte de configurar nuestra base de datos es ingresar algunos registros ficticios para la tabla [revendedor]. Ingrese los siguientes registros:
Figura 2 - revendedores

Tenemos cuatro revendedores. El primer revendedor no tiene un tema personalizado y recae en el predeterminado. Todos los demás tienen su propio tema personalizado definido.
Inicie Visual Studio 2008 y cree una nueva solución en blanco titulada "MVCapplication". Agregue una nueva biblioteca de códigos y llámelo "cgeers.winddirection.database". Elimine el archivo class1.css generado automáticamente.
A continuación, agregue un nuevo elemento LINQ al elemento de las clases SQL y fírelo "DatacLasses". Cambie el nombre del nuevo DataContext a "WindDirectionDataContext". Ahora arrastre la tabla [revendedor] desde la pestaña Explorador del servidor en la superficie del diseñador LINQ a SQL.
Figura 3 - Entidad del revendedor

Establezca la propiedad de conexión de DataContext en "Ninguno" y elimine la configuración de la aplicación Connection String y el archivo de configuración de la aplicación (App.Config). No me gusta el hecho de que Visual Studio inyecte la cadena de conexión para mí. Me gusta hacerlo yo mismo.
Es por eso que he agregado la siguiente clase parcial a este ensamblaje que maneja la inicialización de DataContext con una cadena de conexión. La única parte en la que tenemos que estar de acuerdo es que la cadena de conexión se llama "WindDirection".
Listado 1 - Clase 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 ) { }
}No olvide agregar una referencia al ensamblaje del sistema. Siempre y cuando incluirá una cadena de conexión llamada "WindDirection" en las aplicaciones que hacen referencia a este ensamblaje, funcionará bien.
Estamos casi allí. Solo espera, lo lograremos. Ahora agregue una nueva biblioteca de código a la solución llamada "cgeers.winddirection.managers". Elimine el archivo class1.cs generado automáticamente y agregue una referencia al ensamblaje System.Data.Linq.
Agregue una nueva clase llamada Administrador y agregue el siguiente código:
Listado 2 - Manager abstracto
public abstract class Manager
{
protected Manager ( )
{
Context = new WindDirectionDataContext ( ) ;
}
public WindDirectionDataContext Context { get ; set ; }
}Esta clase muy simple crea un nuevo DataContext en el que podemos liberar nuestras consultas LINQ más adelante.
A continuación, agregue una clase llamada "Resellermanager" al proyecto y agregue el código que se muestra en el Listado 3.
Listado 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" ;
}
}Esta clase de administrador desciende de nuestra clase de administrador abstracto y agrega un método llamado getThemefordomain (...). Este método busca el tema de un revendedor basado en un nombre de dominio dado. Como cada dominio está vinculado de manera única a un revendedor, esto no plantea ningún problema.
Voila, ese es todo el acceso a los datos que se necesita para nuestra aplicación de demostración. Necesitamos descubrir el tema de un revendedor basado en el dominio de la solicitud entrante y luego tenemos que aplicarlo.
Observación : tenga cuidado con el uso del contexto LINQ a SQL en una aplicación alimentada con ASP.NET. Aunque no se demuestra en este artículo, debido a que distraería demasiado de nuestro esfuerzo principal, es aconsejable crear solo un contexto por solicitud. Almacene el contexto en el httpContext de la solicitud para que pueda acceder a él en todo momento durante la solicitud.
Hace un tiempo, escribí un artículo específicamente sobre esto, consulte el artículo de Entity Framework ObjectContext aquí. Aunque se ocupa del marco de la entidad en lugar de LINQ a SQL, todavía es aplicable.
El último paso para completar nuestra aplicación de demostración básica es agregar un nuevo proyecto de sitio web a la solución. Agregue un nuevo proyecto a la solución basada en la plantilla del proyecto ASP.NET MVC Web Application y nombrelo "MVCapplication". Se le preguntará si también desea crear un proyecto de prueba unitario para esta aplicación. Elija "No" para omitir esto, ya que no lo necesitamos para este artículo.
Visual Studio generará una aplicación "¡Hola, mundo!"-Escriba la aplicación ASP.NET MVC que contenga una serie de páginas predeterminadas (inicio, sobre, iniciar sesión ... etc.). Agregue su cadena de conexión al archivo web.config y agregue referencias a cgeers.winddirection.database y cgeers.winddirection.managers ensamblados.
Observación : Web.config contiene una serie de configuraciones de configuración que se refieren a la membresía ASP.NET, perfil, roles ... proveedores. Puedes seguir adelante y eliminarlos ya que no los necesitamos.
Su explorador de soluciones debe parecerse a la Figura 4.
Figura 4 - Explorador de soluciones

Observación : Al momento de escribir este artículo, estoy usando ASP.NET MVC versión 1.0. Sin embargo, la versión 2.0 se lanzará en el futuro cercano.
Al ejecutar la aplicación web, lo primero que debe averiguar es el tema que necesita aplicar. Esto debe hacerse para cada solicitud. Por lo tanto, enchufar un módulo HTTP personalizado en el Pipeline de solicitud parece apropiado.
Agregue una nueva clase al proyecto MVCAPPLICATION y llámelo temohttpmodule. Haga que la clase implemente la interfaz IhttpModule. El código completo para esta clase se muestra en el Listado 4.
Este artículo no es una introducción al escribir módulos HTTP, por lo que si necesita más información, consulte el artículo "Tutorial: Creación y registro de un módulo HTTP personalizado" en MSDN.
Listado 4 - Temohttpmodule
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 ( ) { }
}Este módulo HTTP agrega un controlador de eventos para el evento BeginRequest. Este evento ocurre como el primer evento en la cadena de ejecución de la tubería HTTP cuando ASP.NET responde a una solicitud.
Aquí extraemos el nombre de dominio de la solicitud entrante. A continuación, recuperamos el tema para este dominio utilizando el método getThemefordomain (...) del Resellermanager. El resultado se almacena en caché. La próxima vez que se active una solicitud de este dominio, se recuperará el tema y no se disparará ninguna consulta de base de datos.
El método getDomain () es un método de extensión para la clase URI. Consulte el código fuente de este artículo para ver cómo funciona. De manera similar, podría optar por extraer el subdominio (por ejemplo: www, administrador ... etc.) de la solicitud. Luego puede expandir su motor de temas para aplicar diferentes temas para cada subdominio de un dominio.
Por último, pero no menos importante, registre el themehttpmodule creando una entrada en el archivo web.config. Esto se requiere para suscribir el módulo HTTP a las notificaciones de solicitud-Pipeline.
Listado 5 - Registre el themehttpmodule
< httpModules >
< add name = " ThemeHttpModule " type = " MvcApplication.ThemeHttpModule " />
<!-- ... -->
</ httpModules >Cuando inicie la aplicación web, recibirá la apariencia predeterminada como se muestra en la Figura 5. Visual Studio generará algunas páginas predeterminadas (Inicio, Acerca, Iniciar sesión ... etc.), incluida una página maestra y una hoja de estilo. Usaremos estos archivos para compensar nuestro tema predeterminado.
Figura 5 - ASP.NET MVC Aplicación Tema predeterminado

Por defecto, todos los archivos se guardan en las carpetas de contenido y vistas. Necesitamos implementar nuestra propia estructura de directorio para que podamos agrupar lógicamente nuestros temas. Por lo tanto, cree una nueva carpeta llamada temas. Cree una subcarpeta para el directorio de temas y llámelo predeterminado. Mueva el directorio de contenido y vistas en este directorio predeterminado.
¡Después de mover las carpetas de contenido y vistas, debe ajustar la propiedad MasterPageFile de la Directiva de página para cada una de las vistas! El valor anterior hace referencia a una ubicación que ya no existe. Cambie maestroPageFile = "~/vistas/compartido/sitio.master" a maestroPageFile = "~/themes/default/views/shared/site.master" !
Figura 6 - Tema predeterminado

Voila, nuestro tema predeterminado ha sido configurado. Si desea crear un nuevo tema, solo necesita crear una nueva carpeta y colocarla debajo de la carpeta de temas. Como puede ver en la captura de pantalla anterior, crearemos otros temas (verde, naranja, rojo) más adelante.
Acabamos de mover nuestras páginas maestras, hojas de estilo, vistas ... etc. a otro directorio. Si iniciamos nuestra aplicación web ahora recibiremos la siguiente excepción:
Figura 7 - InvalioperationException

No se pudo encontrar la vista 'índice' o su maestro. Se registraron las siguientes ubicaciones:
MVC está tratando de localizar una vista para su página de inicio predeterminada, pero no puede encontrarla en las ubicaciones predeterminadas que busca, por lo que recibe una excepción. Movimos estos archivos a nuestra carpeta de tema predeterminada y pronto crearemos otros temas. Necesitamos una forma de informar a MVC de las ubicaciones dónde buscar sus vistas, página maestra, vistas parciales ... etc. Estas ubicaciones difieren dependiendo del tema del revendedor.
Entonces, básicamente, todo lo que necesitamos hacer para admitir temas en ASP.NET MVC es:
Para hacer esto, necesitamos escribir nuestro propio motor de vista. MVC utiliza un motor View para representar páginas para la respuesta. Este motor de vista es responsable de localizar la página maestra, vistas y vistas parciales. Por defecto, se utiliza WebFormViewEngine.
Necesitamos reemplazar este motor de vista predeterminado por nuestro. Para hacer esto, agregue una nueva clase llamada ThemedViewEngine al proyecto MVCapplication y haga que descienda de la clase WebFormViewEngine.
Listado 6 - themedViewEngine
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
}En el constructor de este nuevo motor de vista, establecemos MasterLocationFormats, ViewLocationFormats y ParcialViewLocationFormats en nuevas ubicaciones, por ejemplo: ~/themes/{2}/vistas/{1}/{0} .aspx.
Cada ruta contiene 3 partes que se determinan dinámicamente.
Para usar el nuevo motor View, debe registrarlo. Haga esto agregando el siguiente código al controlador de eventos Application_Start ubicado en el archivo global.asax.cs.
Listado 7 - Registre the ThemedViewEngine
protected void Application_Start ( )
{
ViewEngines . Engines . Clear ( ) ;
ViewEngines . Engines . Add ( new ThemedViewEngine ( ) ) ;
RegisterRoutes ( RouteTable . Routes ) ;
}Aquí borra los motores de vista que pueden haberse cargado antes y inyectar los suyos. Ahora todo lo que queda es instruir al motor View cómo formatear las nuevas rutas de búsqueda para que encuentre correctamente los archivos solicitados. Para hacer esto, debe anular los siguientes dos métodos:
Listado 8 - Métodos FindPartialView (...) y FindView (...)
public override ViewEngineResult FindPartialView ( ControllerContext controllerContext , string partialViewName , bool useCache )
public override ViewEngineResult FindView ( ControllerContext controllerContext , string viewName , string masterName , bool useCache )No voy a incluir el código para estas dos funciones aquí, porque es bastante largo y tiene algunas referencias a los métodos privados de ayuda. Básicamente, estos dos métodos siguen el mismo patrón:
Por lo tanto, nuestro nuevo motor de vista básicamente busca nuestra carpeta de temas y si no puede encontrar la página maestra solicitada, la vista o la vista parcial, utiliza el del tema predeterminado. Por supuesto, el tema predeterminado debe estar completo y no puede tener ningún archivo faltante.
Esto le permite crear temas que solo contienen una página maestra que a su vez hace referencia a una hoja de estilo diferente o temas que contienen vistas y / o vistas parciales para aquellas secciones que desea diseñar de manera diferente.
Siguiendo este patrón, puede crear temas que solo anulen ciertas vistas y recurran a las vistas del tema predeterminado si no se proporciona una vista personalizada.
Basé mi motor de vista en el trabajo del excelente artículo de Chris Pietschmann sobre temas en ASP.NET MVC. Le sugiero que revise su artículo, ya que contiene más información sobre cómo funciona el motor View internamente.
Con el nuevo motor View en su lugar, podemos ejecutar la aplicación web nuevamente sin ninguna excepción, ya que ahora puede resolver las solicitudes de la página maestra, vistas y vistas parciales.
Observación : Alteré un poco el código para que cuando el motor View no pueda resolver una solicitud de una página maestra, vista o vista parcial, recurre a las que se encuentran en el tema predeterminado. Así que asegúrese de consultar el código fuente de este artículo también.
Creemos rápidamente un nuevo tema. Agregue una nueva carpeta llamada "Rojo" debajo de la carpeta de temas. Copie el sitio.master y el sitio.css del tema predeterminado como se muestra en la siguiente figura.
Figura 8 - Tema rojo

Abra la hoja de estilo del tema rojo y cambie la propiedad de color de fondo del elemento del cuerpo. Establecerlo en rojo. Ahora abra la tabla [revendedor] y configure el campo de tema en "rojo" para el revendedor cuyo dominio está establecido en localhost. Reinicie la aplicación web y ahora debería estar utilizando la página maestra y la hoja de estilo del tema rojo.
Figura 9 - Tema rojo en acción

Del mismo modo, puede crear un tema naranja que no solo contenga una página maestra sino también una vista diferente para la página de inicio.
Figura 10 - Tema naranja

El tema Orange representará la nueva vista para la página de inicio en lugar de la vista predeterminada. Si desea reemplazar una vista parcial, puede hacerlo de la misma manera. Simplemente copie la vista parcial predeterminada en la misma ubicación en la carpeta del nuevo tema y ajustela según sea necesario.
Para cada tema, ahora puede servir diferentes páginas maestras, vistas y vistas parciales. Hay un escenario restante que deseo apoyar. Los revendedores que están satisfechos con la vista predeterminada pero que solo desean ajustar el logotipo, algunos colores ... etc. Se puede contentar fácilmente aplicando una hoja de estilo diferente al tema predeterminado.
Agregue una nueva carpeta de temas en el directorio de temas y llámelo verde. Copie la hoja de estilo del tema predeterminado al tema verde como se muestra en la siguiente figura.
Figura 11 - Tema verde

Abra la hoja de estilo del tema verde y ajuste la propiedad de color de fondo del elemento del cuerpo a verde. Si establece el tema para el revendedor con el dominio localhost en verde e inicia la aplicación, notará que todavía está utilizando la hoja de estilo del tema predeterminado.
Esto es causado por el hecho de que el tema verde no tiene su propia página maestra. Utiliza la página maestra del tema predeterminado y esta página maestra hace referencia a su propia hoja de estilo.
Abra la página maestra del tema predeterminado y reemplace la línea:
< link href =" ../../Content/Site.css " rel =" stylesheet " type =" text/css " />con
< link href =" <% " ="Html.GetThemedStyleSheet()" % /> rel="stylesheet"
type="text/css" / >El método GetThemedStylesHeet () es un método de extensión para la clase de utilidad HTML. Agregue una nueva clase llamada htmlHelPerextensions al proyecto y agregue el siguiente código.
Listado 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 ) ;
}
}El método GetThemedStylesHeet () carga el tema de la memoria caché y verificación del httpapplication si este tema tiene su propia hoja de estilo. Si no, recae en la hoja de estilo del tema predeterminado. El código contiene algunas cadenas codificadas, aunque no es óptimo, hace el truco. Siéntase libre de mejorar este método.
Si comienza la aplicación web ahora obtendrá un buen fondo verde.
Este artículo le muestra cómo puede habilitar la tema en ASP.NET MVC. Para hacerlo, solo necesita implementar dos cosas, a saber:
El sistema de temas que implementamos utiliza un tema predeterminado y verifica si necesita reemplazar partes de este tema predeterminado con el de un tema personalizado. Puede apoyar fácilmente uno de los siguientes escenarios o combinarlos: