SDL Tridion: Mi primera extensión

¿Qué es una extensión? ¿Por qué quiero una?

Una extensión es un software (librerías, ficheros de configuración, imágenes, páginas, etc.) para añadir o extender funcionalidad o característica a SDL Tridion. Puede servir para facilitar el uso diario de la herramienta, para dar funcionalidad interna distinta a la que tiene actualmente, para conectarse con sistemas externos, o incluso para facilitar tareas repetitivas.

Veamos cómo crear una extensión, la comunicación Editor/Model y las llamadas a servicio.

Editor/Model

La parte del editor de la extensión, es la parte frontal del desarrollo, es decir, la parte visual que se mostrara y que los usuarios podrán utilizar.
Por otro lado el Model es el modelo de datos que se va a utilizar, consultas a la base de datos,etc...

En primer lugar, creamos una carpeta donde estará nuestra extensión, y dentro una carpeta para la extensión del Model y otra para Editor.
Para cada una de las extensiones crearemos una carpeta Configuration, donde estarán nuestros ficheros config.

config

config
El siguiente paso es crear los directorios virtuales.
Abrimos el IIS, y en la ruta /Sites/SDL Tridion/WebUI encontramos los directorios Editors y Models, en ellos creamos los directorios virtuales para nuestras extensiones.

ruta /Sites/SDL Tridion/WebUI

ruta /Sites/SDL Tridion/WebUI
Creamos un nuevo directorio virtual, uno para Editor y otro para Model, apuntando cada uno a la carpeta creada anteriormente.
Añadimos directorios en el IIS porque necesitamos un servidor donde desplegar la aplicación que sera nuestra extensión y para poder acceder de forma relativa a los ficheros de nuestra extensión.

directorio virtual

directorio virtual
Después de crear los dos directorios virtuales, encontramos esta estructura.

estructura directorio virtual

estructura directorio virtual
A continuación añadimos las dos extensiones en el fichero System.Config que encontramos en la siguiente ruta:
Tridion/web/WebUI/WebRoot/Configuration
En la parte del editor añadimos la siguiente configuración:
	<editor name="MiPrimeraExtension">
		<installpath>C:TridionExtensionesMiPrimeraExtensionEditor</installpath> 
		<configuration>ConfigurationMiPrimeraExtensionEditor.config</configuration>
		<vdir>MiPrimeraExtension</vdir> 
	</editor>
Y en la parte del model:
  <model name="MiPrimeraExtension">
    <installpath>C:TridionExtensionesMiPrimeraExtensionModel</installpath>
    <configuration>ConfigurationMiPrimeraExtensionModel.config</configuration>
    <vdir>MiPrimeraExtension</vdir>
  </model>

Estructura directorio virtual

Para ambas extensiones debemos rellenar la configuración de la siguiente manera:

Installpath ⇨ Donde están los archivos de la extensión
Configuration ⇨ Donde está el config de la extensión.
Vdir ⇨ Corresponde al alias que hemos asignado al directorio virtual

Para la extensión del editor, creamos la siguiente estructura de carpeta.
En la carpeta /Pages necesitamos 2 archivos.

MiPrimeraExtension.aspx

	<%@ Page Language="C#" AutoEventWireup="true" 
    CodeBehind="MiPrimeraExtension.aspx.cs" 
    Inherits="Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtension" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <cc:TridionManager runat="server" Editor="CME">
		<dependencies runat="server">
			<dependency runat="server">Tridion.Web.UI.Editors.CME</dependency>
			<dependency runat="server">Tridion.Web.UI.Editors.CME.commands</dependency>
			<dependency runat="server">Tridion.Web.UI.Editors.CME.Commands.Settings</dependency>
		</dependencies>
	</cc:TridionManager>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <c:Button runat="server" ID="ShowHelloButton" Label="Show Hello" Title="Show Hello" IsToggle="false" hasImageDiv="false" CommandName="ShowHello" />
    </div>
    </form>
</body>
</html>

MiPrimeraExtension.aspx.cs

Type.registerNamespace("Fecron.MiPrimeraExtension.Editor.Pages");

Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtensionHome = function MiPrimeraExtensionHome() {
    Tridion.OO.enableInterface(this, "Fecron.MiPrimeraExtension.Editor.Pages.QuoteNBuyHome");
    //Esta linea hace ser a es la que hace cargar estos comandos en la vista
    this.addInterface("Tridion.Cme.View");
};

Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtensionHome.prototype.initialize = function MiPrimeraExtensionHome$initialize() {
    $evt.addEventHandler($("#ShowHelloButton"), "click", this.getDelegate(this.onButtonClick));
};

Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtensionHome.prototype.onButtonClick = function MiPrimeraExtensionHome$onButtonClick() {
    $commands.getCommand("ShowHello").invoke();
};


$display.registerView(Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtensionHome);
En la carpeta /Script, creamos una carpeta Commands y dentro un fichero .js para crear el comando.

ShowHello.js

 Type.registerNamespace("Fecron.MiPrimeraExtension.Editor.Commands");

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello = function Commands$ShowHello() {
    Tridion.OO.enableInterface(this, "Fecron.MiPrimeraExtension.Editor.Commands.ShowHello");
    //Esta linea hace a este objeto ser un comando
    this.addInterface("Tridion.Cme.Command");
};

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello.prototype._isEnabled = function Commands$ShowHello$_isEnabled(selection) {
    return true;
};

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello.prototype._isAvailable = function Commands$ShowHello$_isAvailable(selection) {
    return true;
};

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello.prototype._execute = function Commands$ShowHello$_execute(selection, pipeline) {
	var onSuccess = this.getDelegate(this._handleGetPublicationsInfo);
	Fecron.MiPrimeraExtension.Model.Services.MiPrimeraExtension.GetData(3, onSuccess, this._getErrorHandler());
};

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello.prototype._handleGetPublicationsInfo = function Commands$ShowHello$_handleGetPublicationsInfo(response) {
    alert("Data : " + response);
};

Fecron.MiPrimeraExtension.Editor.Commands.ShowHello.prototype._getErrorHandler = function Commands$ShowHello$_getErrorHandler(result) {
    // Do Something with the error

    var errorMessage = "An error occured" || result;
    $messages.registerError(null, errorMessage, errorMessage, true, false);
};
A continuación modificados el fichero de configuración:

MiPrimeraExtensionEditor.config

 <?xml version="1.0" encoding="utf-8"?>

<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge"  
				xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" 
				xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" 
				xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
  <resources cache="true">
	<cfg:groups>
	  <cfg:group name="Fecron.MiPrimeraExtension.Editor.CommandFiles" merge="always">
		<cfg:fileset>
		  <cfg:file type="script">/Scripts/Commands/ShowHello.js</cfg:file>
		  <cfg:file type="reference">Fecron.MiPrimeraExtension.Editor.Commands</cfg:file>
		</cfg:fileset>
		<cfg:dependencies>
		  <cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
		  <cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
		</cfg:dependencies>
	  </cfg:group>
	  <cfg:group name="Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtension">
		<cfg:fileset>
		  <!--<cfg:file type="style">/Pages/MiPrimeraExtension.aspx.js</cfg:file>-->
		  <cfg:file type="script">/Pages/MiPrimeraExtension.aspx.js</cfg:file>
		</cfg:fileset>
		<cfg:dependencies>
		  <cfg:dependency>Fecron.MiPrimeraExtension.Editor.CommandFiles</cfg:dependency>
		  <cfg:dependency>Tridion.Web.UI.Editors.CME.Controls.Callout</cfg:dependency>
		  <cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
		</cfg:dependencies>
	  </cfg:group>
	</cfg:groups>
  </resources>
  <definitionfiles/>
  <extensions>
	<ext:editorextensions>
	  <ext:editorextension target="CME">
		<ext:editurls />
		<ext:listdefinitions />
		<ext:itemicons />
		<ext:taskbars />
		<ext:commands />
		<ext:commandextensions />
		<ext:contextmenus />
		<ext:lists />
		<ext:tabpages />
		<ext:toolbars />
		<ext:ribbontoolbars />
		<ext:extendedareas />
		<ext:optionspanels />
	  </ext:editorextension>
	</ext:editorextensions>
	<ext:dataextenders/>
  </extensions>
  <commands>
	<cfg:commandset id="Fecron.MiPrimeraExtension.Editor.Commands">
	  <cfg:command name="ShowHello" implementation="Fecron.MiPrimeraExtension.Editor.Commands.ShowHello" />
	  <cfg:dependencies>
		<cfg:dependency>Fecron.MiPrimeraExtension.Editor.CommandFiles</cfg:dependency>
		<!--<cfg:dependency>Extensions.Resources.Base</cfg:dependency>-->
	  </cfg:dependencies>
	</cfg:commandset>
  </commands>
  <contextmenus/>
  <localization/>
  <settings>
	<dependencies>
	  <editor>CME</editor>
	</dependencies>
	<defaultpage />
	<editurls />
	<listdefinitions />
	<theme>
	  <path>/Themes/Carbon/</path>
	</theme>
	<resourceextensions />
	<customconfiguration />
  </settings>
</Configuration>
Aparte de estos ficheros, necesitamos generar una .dll donde estará la siguiente clase, donde se define el pageType:

MiPrimeraExtension.cs

 using System;
using System.Web.UI.HtmlControls;
using Tridion.Web.UI.Controls;
using Tridion.Web.UI.Core.Controls;

namespace Fecron.MiPrimeraExtension.Editor.Pages
{
  [ControlResources("Fecron.MiPrimeraExtension.Editor.Pages.MiPrimeraExtension")]
  public class MiPrimeraExtension : TridionPage
  {

    protected void Page_Load(object sender, EventArgs e)
    {
    }
  }
}

Ya está terminada la extensión del editor.

Model

Para la extensión del model, aparte de la configuración, necesitamos dos ficheros:

Service.svc

 <%@ ServiceHost Language="C#" Debug="true" Service="Fecron.MiPrimeraExtension.Model.Services.MiPrimeraExtensionService" CodeBehind="Service.svc.cs" %>

Web.config

 <configuration>
  <system.serviceModel>
    <services>
      <service 
        name="Fecron.MiPrimeraExtension.Model.Services.MiPrimeraExtensionService"
        behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.DeveloperBehavior">
        
        <endpoint 
          
          address=""
          behaviorConfiguration="Tridion.Web.UI.ContentManager.WebServices.AspNetAjaxBehavior" 
          binding="webHttpBinding" 
          bindingConfiguration="Tridion.Web.UI.ContentManager.WebServices.WebHttpBindingConfig" 
          contract="Fecron.MiPrimeraExtension.Model.Services.IMiPrimeraExtensionService" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Aparte de estos ficheros, necesitamos una dll donde encontraremos el service.

IMiPrimeraExtensionService.cs

En esta clase se define la interfaz del servicio.

using System.ServiceModel;
using System.ServiceModel.Web;

namespace Fecron.MiPrimeraExtension.Model.Services
{
  [ServiceContract(Name = "MiPrimeraExtension", Namespace = "Fecron.MiPrimeraExtension.Model.Services")]
  public interface IMiPrimeraExtensionService
  {
    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    string GetData(int value);

    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);
  }
}

MiPrimeraExtensionService.cs

En esta clase se implementan los métodos del servicio.
using System;
using System.ServiceModel.Activation;

namespace Fecron.MiPrimeraExtension.Model.Services
{
  [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
  public class MiPrimeraExtensionService : IMiPrimeraExtensionService
  {
    public string GetData(int value)
    {
      return string.Format("You entered: {0}", (object) value);
    }

    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
      if (composite == null)
        throw new ArgumentNullException("composite");
      if (composite.BoolValue)
      {
        CompositeType compositeType = composite;
        string str = compositeType.StringValue + "Suffix";
        compositeType.StringValue = str;
      }
      return composite;
    }
  }
}
Después de todo esto, reiniciamos el IIS y ya tendremos la extensión.