Este artículo proviene de un motor de traducción automática.
Orchard CMS
Extensibilidad en Orchard
Descargar el código de ejemplo
La mayoría de las aplicaciones Web tienen mucho en común, pero al mismo tiempo presentan muchas diferencias. Todos ellos tienen páginas estáticas ("términos de uso," "Nosotros" y así sucesivamente). Presentan contenidos dentro de un diseño común. Tienen menús de navegación. Tengan búsqueda, comentarios, valoraciones e integración de redes sociales. Pero algunos son blogs, algunos venden libros, algunos mantienen en contacto a amigos y algunos contienen cientos de miles de artículos de referencia sobre sus tecnologías favoritas.
Un sistema de gestión de contenido (CMS) tiene como objetivo proporcionar las piezas comunes mientras no imponer ninguna restricción sobre el tipo de sitio que se está construyendo. Es un ejercicio delicado de extensibilidad.
Los creadores de la CMS Orchard (orchardproject.net), que incluye me — han optado por un enfoque que depende enormemente de composición y Convención. En este artículo, te presento algunos ejemplos de simples extensiones del sistema que debe ser un buen punto de partida para sus propios módulos.
Un sistema de tipo dinámico
No importa qué CMS se utilizar para construir su sitio, habrá una entidad central de contenido que va por diferentes nombres. En Drupal, se llama un nodo, y en el huerto, es un elemento de contenido. Elementos de contenido son átomos de contenido como blogs, páginas, productos o actualizaciones de Estado, widgets. Algunos de ellos corresponden a una dirección URL, y otros no. Sus esquemas varían mucho, pero lo que tienen en común es que son las unidades más pequeñas de contenido en el sitio. ¿O son?
Dividir el átomo
Como desarrolladores, nuestra primera reacción es identificar elementos de contenido como instancias de clases (post, página, producto o widget), que es una medida correcta. De la misma manera que las clases se componen de miembros (campos, propiedades y métodos), tipos de contenido (las "clases" de elementos de contenido) son objetos compuestos. En lugar de ser compuesto de propiedades simples con tipos que son propios de las clases, están compuestas de piezas de contenido, que son los átomos del comportamiento de su contenido. Esta distinción es importante, que voy a ilustrar con un ejemplo.
Un blog normalmente se compone de una dirección URL, título, fecha, cuerpo de texto enriquecido, una lista de etiquetas y una lista de comentarios de los usuarios. Ninguna de las partes es específica para una entrada de blog. Lo que hace un blog post es la composición específica de las partes, no sólo alguna de las partes.
La mayoría de los blogs tienen comentarios, pero comentarios también podrían utilizarse en un sitio de comercio electrónico para implementar revisiones. Del mismo modo, las etiquetas son potencialmente útiles como una forma de clasificar cualquier elemento de contenido, no sólo entradas de blog. El cuerpo de texto enriquecido de un puesto no es diferente del cuerpo de la página. La lista continúa. En este punto debe quedar claro que la unidad de comportamiento en un sitio Web es menor que la unidad de contenido.
Otro hecho sobre CMSes es que los tipos de contenido no fijados de antemano. Entradas del blog solían ser texto simple, pero rápidamente se convirtió en mucho más. Ahora rutinariamente contienen vídeos, galerías de imagen o podcasts. Si blog acerca de sus viajes alrededor del mundo, tal vez querrá Añadir geolocalización a sus puestos.
Una vez más, el contenido partes acudir al rescate. ¿Necesita una longitud y latitud? Ampliar el tipo de entrada de blog mediante la adición de una parte de la asignación (tenemos varias disponibles en nuestra galería de módulo: Gallery.orchardproject. (NET). Rápidamente se hace evidente cuando se piensa en lo que esta operación de agregar un elemento a un tipo existente se realizará más a menudo por el propietario del sitio, no por un desarrollador. Por lo tanto, no debería ser posible sólo mediante la adición de una propiedad compleja a una cuenta de Microsoft.Tipo de NET Framework. Tiene que ser basado en metadatos y suceder en tiempo de ejecución, por lo que podemos construir una administración de interfaz de usuario para hacerlo (véase figura 1).
Figura 1 el Editor de tipos de contenido de Huerta
Se trata de la primera forma de extender la huerta: Puede crear y extender contenido tipos en van desde la interfaz de usuario de admin. Por supuesto, cualquier cosa que se puede hacer desde el administrador de IU, se puede hacer desde código, como ésta:
item.Weld(part);
Este código dinámicamente las soldaduras una parte en un elemento de contenido. Es una posibilidad interesante, ya que permite instancias de tipos para extender dinámicamente en tiempo de ejecución. En lenguajes dinámicos, esto se llama un mezcla-en, pero es un concepto que es casi inaudito en estáticamente con lenguajes como C#. Esto abre nuevas posibilidades, pero no es exactamente lo mismo que lo que estábamos haciendo desde la interfaz de usuario de admin. También queremos poder agregar el elemento al tipo una vez por todas en lugar de agregar para cada instancia, como se muestra aquí:
ContentDefinitionManager.AlterTypeDefinition(
"BlogPost", ctb => ctb.WithPart("MapPart")
);
Esto es realmente exactamente cómo el tipo de contenido de post de blog se define en primer lugar:
ContentDefinitionManager.AlterTypeDefinition("BlogPost",
cfg => cfg
.WithPart("BlogPostPart")
.WithPart("CommonPart", p => p
.WithSetting("CommonTypePartSettings.ShowCreatedUtcEditor", "true"))
.WithPart("PublishLaterPart")
.WithPart("RoutePart")
.WithPart("BodyPart")
);
Puede observar en este fragmento de código que las etiquetas y comentarios parecen ser falta de entradas del blog. Se trata de un ejemplo más de una cuidadosa separación de preocupaciones. El módulo de blog realmente sabe nada de etiquetas y comentarios, no más que las etiquetas y los comentarios hacen módulos sobre blogs. Un tercero es responsable de ponerlos juntos.
Recetas sabrosas
Durante la instalación, se ejecuta una receta que se encarga de este tipo de tarea. Es una descripción XML de la configuración inicial del sitio. Huerta viene con tres recetas predeterminadas: Blog predeterminado y núcleo. El código siguiente muestra la parte de la receta del blog que agrega las etiquetas y comentarios a las entradas del blog:
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<CommentsPart />
<TagsPart />
<LocalizationPart />
</BlogPost>
He mostrado hasta el momento las distintas maneras en que pueden ser compuestos piezas de contenido en elementos de contenido. Mi próximo paso será explicar cómo crear sus propios componentes.
Una parte de la construcción
Para ilustrar el proceso de construcción de un nuevo artículo, voy a confiar en el ejemplo de la función de la Meta de mi módulo de industrias Vandelay (descargarlo desde bit.ly/u92283). La función de Meta añade palabras clave y descripción propiedades para fines de optimización de motor de búsqueda (SEO) (véase figura 2).
Figura 2 el Editor de datos Meta SEO
Estas propiedades se procesará en la sección head de la página tal como la entendemos metatags estándar que los motores de búsqueda:
<meta content="Orchard is an open source Web CMS built on ASP.NET MVC."
name="description" />
<meta content="Orchard, CMS, Open source" name="keywords" />
El registro
La primera pieza del rompecabezas será una descripción de la forma en que los datos se van a persistir en la base de datos. Estrictamente hablando, no todas las partes necesitan un registro, porque no todas las partes almacenan sus datos en la base de datos del huerto, pero la mayoría lo. Un registro es sólo un objeto regular:
public class MetaRecord : ContentPartRecord {
public virtual string Keywords { get; set; }
public virtual string Description { get; set; }
}
La clase MetaRecord deriva de ContentPartRecord. Esto no es absolutamente necesario, pero es conveniente definitivamente como obtiene de la plomería de la forma. La clase tiene dos propiedades de cadena, palabras clave y descripción. Estas propiedades deben marcarse virtuales para el marco puede "mezcla-en" su propia lógica para construir la clase concreta que se utilizará en tiempo de ejecución.
Responsabilidad exclusiva del registro es la persistencia de base de datos, con la ayuda de una declaración para el mecanismo de almacenamiento de información que puede encontrarse en el MetaHandler:
public class MetaHandler : ContentHandler {
public MetaHandler(
IRepository<MetaRecord> repository) {
Filters.Add(
StorageFilter.For(repository));
}
}
El almacenamiento también tiene que ser inicializado. Las primeras versiones de Huerta fueron inferir el esquema de base de datos de las clases de registros, pero adivinando puede sólo llevarte hasta ahora, y desde entonces ha sido reemplazado con un sistema de migración más exacto donde las modificaciones de esquema se definición explícitamente, como se muestra en figura 3.
Figura 3 define explícitamente modificaciones de esquema
public class MetaMigrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("MetaRecord",
table => table
.ContentPartRecord()
.Column("Keywords", DbType.String)
.Column("Description", DbType.String)
);
ContentDefinitionManager.AlterPartDefinition(
"MetaPart", cfg => cfg.Attachable());
return 1;
}
}
Se crea la tabla MetaRecord con un nombre que el sistema será capaz de asignar por Convenio a la clase MetaRecord. Tiene las columnas de sistema para un elemento contenido registro había agregado por la llamada para el método ContentPartRecord, además de las palabras clave y descripción columnas de cadena que se asignarán automáticamente por la Convención a las propiedades correspondientes de la clase de registro.
La segunda parte del método de migración dice que la nueva pieza será embargable desde la interfaz de usuario de admin para cualquier tipo de contenido existente.
El método Create siempre representa la migración inicial y normalmente devuelve 1, que es el número de migración. La Convención es que en el futuro las versiones del módulo, el desarrollador puede agregar métodos de UpdateFromX, donde x es sustituido por el número de migración desde que el método funciona. El método debe devolver un nuevo número de migración que corresponde al nuevo número de migración del esquema. Este sistema permite actualizaciones suaves, independientes y flexibles de todos los componentes en el sistema.
Para representar la parte real, se utiliza una clase separada, que te mire ahora.
La clase parte
La representación de la parte de sí mismo es otra clase que se deriva de ContentPart <TRecord>:
public class MetaPart : ContentPart<MetaRecord> {
public string Keywords {
get { return Record.Keywords; }
set { Record.Keywords = value; }
}
public string Description {
get { return Record.Description; }
set { Record.Description = value; }
}
}
La parte actúa como un proxy para el registro de palabras clave y descripción propiedades como una conveniencia, pero si no, el registro y sus propiedades aún estaría disponibles a través de la propiedad pública de registro de la clase base de ContentPart.
Cualquier código que tiene una referencia a un elemento de contenido que tiene la parte MetaPart podrá acceder de forma inflexible a las palabras clave y propiedades Descripción llamando al método de As, que es el análogo en el sistema de tipo de huerto de un CLR conversión la operación:
var metaKeywords = item.As<MetaPart>().Keywords;
La clase parte es también donde se implemente cualquier comportamiento específico de los datos de la pieza. Por ejemplo, un producto compuesto podría exponer métodos o propiedades para acceder a sus subproductos o calcular el precio total.
Comportamiento que se refiere a la interacción del usuario (el código de orquestación que sería en un ASP regular.Aplicación NET MVC se encuentra en el controlador) es otro asunto. Aquí es donde los conductores.
El controlador
Cada parte en un elemento de contenido tiene que tener la oportunidad de participar en el ciclo de vida de la solicitud y hacer eficazmente el trabajo de una aplicación ASP.NET MVC controlador, pero se debe hacer en la escala de la pieza en lugar de hacerlo en la escala de la solicitud completa. Un controlador de contenido parte desempeña el papel de un controlador de tamaño reducido. No tiene la riqueza total de un controlador que no hay asignación a sus métodos de rutas. En cambio, está hecha de métodos de gestión de eventos bien definidos, tales como la visualización o Editor. Un controlador es sólo una clase que deriva de ContentPartDriver.
El método de visualización es lo que se llama cuando Huerta necesita representar la parte en formato de sólo lectura (véase figura 4).
Figura 4 método de visualización del conductor prepara la representación de la parte
protected override DriverResult Display(
MetaPart part, string displayType, dynamic shapeHelper) {
var resourceManager = _wca.GetContext().Resolve<IResourceManager>();
if (!String.IsNullOrWhiteSpace(part.Description)) {
resourceManager.SetMeta(new MetaEntry {
Name = "description",
Content = part.Description
});
}
if (!String.IsNullOrWhiteSpace(part.Keywords)) {
resourceManager.SetMeta(new MetaEntry {
Name = "keywords",
Content = part.Keywords
});
}
return null;
}
Este controlador es de hecho un poco atípico, porque la mayoría de los conductores como resultado simple representación (más que en un momento), mientras que la parte de la Meta se necesita procesar sus metatags en la cabeza en el lugar. La sección head de un documento HTML es un recurso compartido, por lo que adoptar precauciones especiales. Huerta ofrece APIs para acceder a los recursos compartidos, que es lo que estamos viendo aquí: Voy a través del administrador de recursos para establecer las metatags. El administrador de recursos se encargará de la representación de las etiquetas reales.
El método devuelve null porque no hay nada para representar en el lugar en este escenario específico. La mayoría de métodos de controlador en cambio devolverá un objeto dinámico que se llama una forma, que es análoga a un modelo de vista en ASP.NET MVC. Podrá volver a las formas en un momento cuando llegue el momento de que sean en HTML, pero por el momento, baste decir que son objetos muy flexibles donde usted puede pegarse todo lo que va a ser relevante para la plantilla que representará, sin tener que crear una clase especial, como se muestra aquí:
protected override DriverResult Editor(MetaPart part, dynamic shapeHelper) {
return ContentShape("Parts_Meta_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Meta",
Model: part,
Prefix: Prefix));
}
El método de Editor es responsable de preparar la representación del editor de interfaz de usuario para la parte. Normalmente devuelve un tipo especial de forma que sea apropiada para la construcción de interfaces de usuario de edición compuesto.
Lo último sobre el conductor es el método que controlará los puestos desde el editor:
protected override DriverResult Editor(MetaPart part,
IUpdateModel updater, dynamic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
El código de este método llama a TryUpdateModel para actualizar automáticamente la parte con los datos que se registró de nuevo. Una vez que se hace con esta tarea, contacta con el primer método de Editor a fin de devolver la misma forma de editor que estaba produciendo.
Formas de representación
Llamando todos los controladores para todas las partes, Huerta es capaz de construir un árbol de formas: un modelo de vista grande compuesto y dinámico para la solicitud de todo. Su siguiente tarea consiste en averiguar cómo resolver cada una de las formas en las plantillas que podrán hacerlos. Lo hace así por mirar el nombre de cada forma (Parts_Meta_Edit en el caso del método Editor) y tratando de que se asignan a los archivos en lugares bien definidos del sistema, como el tema actual y el módulo de vistas de carpetas. Este es un punto de extensibilidad importante porque le permite reemplazar la representación predeterminada de nada en el sistema por sólo colocar un archivo con el nombre correcto en su tema local.
En la carpeta Views\EditorTemplates\Parts de mi módulo, he bajado un archivo llamado Meta.cshtml (véase figura 5).
Figura 5 el Editor de plantillas para el MetaPart
@using Vandelay.Industries.Models
@model MetaPart
<fieldset>
<legend>SEO Meta Data</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Keywords)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Keywords, new { @class = "large text" })
@Html.ValidationMessageFor(model => model.Keywords)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
@Html.TextAreaFor(model => model.Description)
@Html.ValidationMessageFor(model => model.Description)
</div>
</fieldset>
Todo está contenido
Antes de pasar a otros temas de extensibilidad, me gustaría mencionar que una vez que comprende el sistema de tipo de elemento de contenido, usted bajopie el concepto más importante en el huerto. Muchas entidades importantes del sistema se definen como elementos de contenido. Por ejemplo, un usuario es un elemento de contenido, que permite módulos de perfil agregar propiedades arbitrarias a ellos. También tenemos elementos de contenido de widget, que pueden llegar a zonas que define un tema. Se trata de cómo las formas de búsqueda, blog archives, nubes de etiqueta y otro sidebar IU se crean en el huerto. Pero el uso más sorprendente de elementos de contenido podría ser el propio sitio. Configuración del sitio es elementos de contenido efectivamente en el huerto, que tiene mucho sentido una vez que entienda cómo se gestiona multitenancy en huerto. Si desea agregar su propia configuración de sitio, todo lo que tienes que hacer es agregar un elemento al tipo de contenido de sitio, y puede crear una interfaz de usuario de edición de admin para que siguiendo los mismos pasos exactos que descrito anteriormente. Un sistema de tipo unificado de contenido extensible es un concepto extremadamente rico.
Extensiones de embalaje
He mostrado cómo crear sus propios componentes para huerta, y he referido a los conceptos de módulos y temas sin definir estos términos. En resumen, son las unidades de implementación en el sistema. Las extensiones se distribuyen como módulos, y el aspecto visual se distribuye como temas.
Un tema es normalmente un montón de imágenes, hojas de estilo y plantilla de anulaciones, envasadas en un directorio bajo el directorio de temas. También tiene un theme.txt archivo en su raíz para definir los metadatos como el autor del tema de manifiesto.
Del mismo modo, un módulo es un directorio bajo el directorio de módulos. También es una aplicación Web ASP.Zona NET MVC, con pocos giros. Por ejemplo, necesita un archivo de manifiesto de module.txt adicionales que declara algunos metadatos para el módulo, como su autor, sitio Web, nombres de funciones, dependencias o número de versión.
Siendo sólo un área de un sitio más grande, un módulo necesita jugar bonito con pocos recursos compartidos. Por ejemplo, las rutas que utiliza deben definirse por una clase que implementa IRouteProvider. Huerta basará en la tabla de ruta completa de lo que fue aportado por todos los módulos. Del mismo modo, módulos pueden contribuir a la creación del menú de administración mediante la implementación de la interfaz INavigationProvider.
Curiosamente, el código de los módulos generalmente no es entregado como binarios compilados (aunque esto es técnicamente posible). Esta fue una decisión consciente que hemos realizado para fomentar la piratería módulo, donde se inicia desde un módulo que se descarga de la galería y retocarlo para satisfacer sus necesidades específicas. Ser capaz de modificar el código para nada es uno de los puntos fuertes de un PHP CMS como Drupal o WordPress, y queríamos proporcionar ese mismo tipo de flexibilidad en el huerto. Cuando se descarga un módulo, descargar el código fuente, y ese código se compila dinámicamente. Si realiza un cambio en un archivo de código fuente, obtiene recogió el cambio y se vuelve a compilar el módulo.
Inyección de dependencias
Hasta ahora, he centrado en un tipo de extensibilidad, el sistema de tipos, ya que representa la mayoría de los módulos de huerta, pero hay muchos otros puntos de extensibilidad, demasiados, de hecho, para mí la lista aquí. Quiero añadir algunas cosas acerca de los principios generales que trabajan en todo el marco.
Una cosa clave para obtener el derecho en un sistema altamente modular es acoplamiento flexible. En el huerto, casi todo por encima de plomería de bajo nivel es un módulo. Incluso los módulos son administrados por un módulo! Si desea que estos módulos para trabajar como independiente uno de otro posible — si desea una implementación de una función para ser intercambiables con el otro, no puede tener dependencias duras.
Una forma clave para alcanzar este objetivo es usar la inyección de dependencias. Cuando se necesita usar los servicios de otra clase, usted no sólo instancia, como establecería una dependencia dura en esa clase. En su lugar, se inyecta una interfaz que implementa esta clase, como un parámetro de constructor (véase figura 6).
Figura 6 inyección de dependencias a través de parámetros de Constructor
private readonly IRepository<ContentTagRecord> _contentTagRepository;
private readonly IContentManager _contentManager;
private readonly ICacheManager _cacheManager;
private readonly ISignals _signals;
public TagCloudService(
IRepository<ContentTagRecord> contentTagRepository,
IContentManager contentManager,
ICacheManager cacheManager,
ISignals signals)
_contentTagRepository = contentTagRepository;
_contentManager = contentManager;
_cacheManager = cacheManager;
_signals = signals;
}
Este modo que es su dependencia de la interfaz, no la clase y la aplicación puede intercambiarse sin tener que cambiar el código. La implementación específica de la interfaz que obtiene inyectada ya no es la decisión del consumidor de la interfaz. Control es inverso aquí, y es el marco que toma esa decisión.
Por supuesto, esto no se limita a las interfaces que define el huerto. Cualquier módulo puede proporcionar sus propios puntos de extensibilidad declarando simplemente una interfaz que se deriva de IDependency. Es así de simple.
Algunos otros puntos de extensibilidad
Sólo he arañado la superficie de extensibilidad aquí. Hay muchas interfaces que pueden utilizarse en huerto para extender el sistema de forma creativa. Incluso se podría decir que Huerta es esencialmente nada más que un motor de extensibilidad. Todas las piezas en el sistema son intercambiables y extensible.
Antes de concluir este artículo, te menciono algunas de las interfaces más útiles en el sistema que desee retirar. No tengo suficiente espacio aquí para entrar en profundidad, pero puedo darle punteros, y puede entrar en el código y seguir el uso de las interfaces. Se trata de una forma genial de aprender, por cierto.
- IWorkContextAccessor permite que el código para tener acceso al contexto de trabajo para la solicitud actual. El contexto de trabajo, a su vez, proporciona acceso al HttpContext actual diseño, configuración del sitio, usuario, tema y cultura. También proporciona facilidades para obtener una implementación de una interfaz, desde aquellos lugares donde no se puede hacer inyección de dependencias o donde deba retrasar hasta después de la construcción.
- IContentManager ofrece todo que lo necesario para consultar y administrar elementos de contenido.
- IRepository <T> proporciona acceso a los métodos de acceso de datos de nivel inferior, para esas ocasiones cuando IContentManager no es suficiente.
- IShapeTableProvider permite a un grupo de escenarios de manipulación de forma sobre la marcha. Básicamente, enlazar eventos acerca de las formas, y a partir de ellos puede crear formas alternativas para utilizarse en determinadas situaciones, formas de transformar, agregar a miembros a ellos, moverlos alrededor del diseño y así sucesivamente.
- IBackgroundTask, IScheduledTask y IScheduledTaskHandler son las interfaces a utilizar si necesita tareas repetidas o retrasos que se ejecute en segundo plano.
- IPermissionsProvider permite que los módulos exponer sus propios permisos.
Aprende más
Huerto es un potente extensibilidad y presentar todo lo que ofrecen en este espacio limitado es un desafío. Mi esperanza es que dio el deseo de aprender más acerca de él y profundizar en ella. Tenemos una comunidad amigable y viva orchard.codeplex.com/discussions que será feliz para guiarle y responder a sus preguntas. Con tanto para implementar y tantas ideas para explorar, aquí es una gran oportunidad para ofrecer una contribución significativa.
Bertrand Le Roy comenzó su carrera de desarrollador profesional en 1982 cuando publicó su primer videojuego. En 2002 lanzó lo que fue probablemente el primer CMS para ejecutarse en ASP.NET. Un año más tarde, fue contratado por Microsoft ASP.NET el equipo y se trasladó a los Estados Unidos. Ha trabajado en ASP.NETAS versiones 2.0 a 4 y ASP.NET AJAX; contribuyó a hacer jQuery parte oficial de la.NET de caja de herramientas del desarrollador; y representa a Microsoft en el Comité Directivo de OpenAjax Alliance.
Gracias al siguiente experto técnico para revisar este artículo: Sebastien Ros