Web Services – Interoperabilidad Java (Server) y .Net (Clients)

Por razones de trabajo me he encontrado algunas veces con este escenario, y después de haber tenido un par de problemas no muy graves pero que consumían tiempo se me ocurrió un esquema que hasta el momento me funciona, y es lo que voy a compartir en el post.
Minimalismo, si esa es la respuesta a esto, ya que regresamos a lo mas simple al exponer los web services retornando un string siempre como resultado del método, y así mismo recibir datos primitivos como parámetros, reemplazando a la estructuras de datos complejas, pero no en su totalidad ya que en ese dato primitivo en realidad contiene una estructura compleja serializada en Json y comprimida con un algoritomo Zip estándar y convertido a Hexadecimal.
La solución puede ser aplicada no solo a cliente .Net sino también a clientes Java, pero vamos a enfocar el post en clientes .net.
Para el lado del server Java vamos a necesitar los siguientes componentes adicionales al JDK:

  • Google Gson (Ya he utilizado esta librería en otros posts, recomendada hasta que el JDK no tenga incluya una implementación estándar de manipulación de Json)
  • Apache Commons Codec

Para los clientes de .net van los siguientes componentes adicionales al framework:

Ya con esto listo, creamos un proyecto web en java en su IDE preferido, yo voy a utilizar eclipse.

Para el ejemplo tengo 3 clases; un bean con una propiedad, luego un bean un poco mas complejo y finalmente uno que contiene a todo para mostrar una clase con una estructura no tan simple como prueba de concepto, aquí el código de cada uno:

public class SuperSimpleBean {
	private int number;

	public SuperSimpleBean(int number){
		this.number = number;
	}

	public SuperSimpleBean(){

	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
}
public class SimpleBean{

	private String code;
	private Date datetime;
	private List superSimpleBeans;

	public SimpleBean(){
		superSimpleBeans = new ArrayList();
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public Date getDatetime() {
		return datetime;
	}

	public void setDatetime(Date datetime) {
		this.datetime = datetime;
	}

	public List getSimpleBeans() {
		return superSimpleBeans;
	}

	public void setSimpleBeans(List superSimpleBeans) {
		this.superSimpleBeans = superSimpleBeans;
	}

}
public class ComplexBean {
	private List simpleBeans;
	private String name;

	public ComplexBean(){
		simpleBeans = new ArrayList();
	}

	public List getSimpleBeans() {
		return simpleBeans;
	}

	public void setSimpleBeans(List simpleBeans) {
		this.simpleBeans = simpleBeans;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

El objeto que va a devolver el web service en string va a pasar por los siguientes pasos:

  • Serializacion a Json
  • Compresion del Json
  • Convertir a un String hexadecimal los bytes comprimidos

Las clases que nos van a ayudar con ese cometido son las siguientes:

public class ZipUtil {

	private static String toHexString(byte[] bytes) {
		return Hex.encodeHexString(bytes);
	}

	private static byte[] toByteArray(String hexString) {
		try {
			return Hex.decodeHex(hexString.toCharArray());
		} catch (DecoderException e) {
			e.printStackTrace();
		}
		return null;
	}

	private static byte[] fromGByteToByte(byte[] gbytes) {
		ByteArrayOutputStream baos = null;
		ByteArrayInputStream bais = new ByteArrayInputStream(gbytes);
		try {
			baos = new ByteArrayOutputStream();
			GZIPInputStream gzis = new GZIPInputStream(bais);
			byte[] bytes = new byte[1024];
			int len;
			while ((len = gzis.read(bytes)) > 0) {
				baos.write(bytes, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	private static byte[] fromByteToGByte(byte[] bytes) {
		ByteArrayOutputStream baos = null;
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
			baos = new ByteArrayOutputStream();
			GZIPOutputStream gzos = new GZIPOutputStream(baos);
			byte[] buffer = new byte[1024];
			int len;
			while ((len = bais.read(buffer)) >= 0) {
				gzos.write(buffer, 0, len);
			}
			gzos.close();
			baos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	public static String compressAndEncodeText(String text) {
		return toHexString(fromByteToGByte(text.getBytes()));
	}

	public static String decompressAndDecode(String data) {
		data = data.replace("-", "");
		byte[] zippedBytes;
		try {
			zippedBytes = toByteArray(data);
			byte[] decompressed = fromGByteToByte(zippedBytes);
			return new String(decompressed);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";

	}
}
public class ParserUtil {

	public static String parseAndCompress(Object o){
		String json = new Gson().toJson(o);
		return ZipUtil.compressAndEncodeText(json);
	}

}

Y ahora en la clase que se va a exponer como web service creamos objetos dummy con datos y devolvemos el resultado serializado y comprimido:

public class WebService {

	public String getData(){

		ComplexBean cBean = new ComplexBean();
		cBean.setName("A complex bean");

		for(int i=0; i<=1000; i++){
			SimpleBean sBean = new SimpleBean();
			sBean.setCode(String.format("Code %s", i));
			sBean.setDatetime(Calendar.getInstance().getTime());

			for(int j=0; j<=10;j++){
				SuperSimpleBean ssBean = new SuperSimpleBean(j);
				sBean.getSimpleBeans().add(ssBean);
			}

			cBean.getSimpleBeans().add(sBean);
		}

		return ParserUtil.parseAndCompress(cBean);
	}
}

Publicamos el web service, lo probamos con soapUI y el resultado es el siguiente:

soapUI

1f8b0800000000000000dddcbd8aa4d71586d15b693a9ea0cefe39679fce2c83338……… un string hexadecimal.

Si devolviera json en el web service, el tamaño de los datos de respuesta seria de 216kb, mientras que la respuesta comprimida es de 6.34kb, ustedes pueden calcular el porcentaje de compresión.

Del lado del cliente .net simplemente hay que hacer las clases con la misma estructura, algo que aun no he tenido tiempo para revisar es que al momento de deserializar el json las clases de .net en compact framework deben tener declarados los campos, en lugar de tener propiedades automáticas, honestamente no he encontrado en la librería Json for CF algún parámetro para que sea case insensitive, pero para el framework estándar no hay problemas:

public class SuperSimpleClassInfo
    {
        public int Number { get; set; }

        public SuperSimpleClassInfo()
        {
        }

        public SuperSimpleClassInfo(int number)
        {
            this.Number = number;
        }
    }
public class SimpleClassInfo
    {
        public string Code { get; set; }
        public DateTime DateTime { get; set; }
        public List<SuperSimpleClassInfo> SuperSimpleBeans { get; set; }

        public SimpleClassInfo()
        {
            SuperSimpleBeans = new List<SuperSimpleClassInfo>();
        }

    }
public class ComplexClassInfo
    {
        public string Name { get; set; }
        public List<SimpleClassInfo> SimpleBeans { get; set; }

        public ComplexClassInfo()
        {
            SimpleBeans = new List<SimpleClassInfo>();
        }
    }

Necesitamos una clase para convertir el texto hexadecimal a bytes, descomprimir y luego “parsear” el json a nuestra estructura de objetos, para descomprimir el texto creamos una clase ZipUtil similar a la que tenemos del lado de java:

public class ZipUtil
    {
        public static byte[] Decompress(byte[] compressed)
        {
            byte[] buffer = new byte[4096];
            using (MemoryStream ms = new MemoryStream(compressed))
            using (GZipStream gzs = new GZipStream(ms, CompressionMode.Decompress))
            using (MemoryStream uncompressed = new MemoryStream())
            {
                for (int r = -1; r != 0; r = gzs.Read(buffer, 0, buffer.Length))
                    if (r > 0) uncompressed.Write(buffer, 0, r);
                return uncompressed.ToArray();
            }
        }

        public static byte[] Compress(byte[] raw)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(memory, CompressionMode.Compress, true))
                {
                    gzip.Write(raw, 0, raw.Length);
                }
                return memory.ToArray();
            }
        }

        public static string CompressAndEncode(string text)
        {
            byte[] compressed = Compress(System.Text.ASCIIEncoding.UTF8.GetBytes(text));
            string hex = BitConverter.ToString(compressed);
            return hex;
        }

        public static byte[] ToByteArray(string hexString)
        {
            hexString = hexString.Replace("-", "");
            int NumberChars = hexString.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
            return bytes;
        }

        public static string DecompressAndDecode(string text)
        {
            byte[] textBytes = ToByteArray(text);
            byte[] uncompressed = Decompress(textBytes);
            string result = System.Text.ASCIIEncoding.ASCII
                    .GetString(uncompressed, 0, uncompressed.Length);
            return result;
        }
    }

Listo, con todo ya preparado agregamos la referencia al web service:

addreference

Una vez agregada la referencia es recomendable actualizar en el archivo de configuración el tamaño máximo de datos permitidos en las propiedades: maxBufferSize, maxReceivedMessageSize y maxStringContentLength con el valor 2147483647.

Listo, procedemos a invocar el metodo del web service, a descomprimir y luego con la libreria Json a convertir los datos a un objeto:

JavaWS.WebServiceClient client = new JavaWS.WebServiceClient();
            string data = client.getData();
            string json = ZipUtil.DecompressAndDecode(data);
            ComplexClassInfo complex = JsonConvert.DeserializeObject<ComplexClassInfo>(json);

El resultado es el objeto complex cargado con toda la información  para enviar datos en el mismo formato se aplica el proceso inverso desde .net a java.

Descargar fuentes

Happy Coding!

Saludos,
gish@c

FileUpload con Eclipse, Tomcat y Primefaces

Devolviendo algo de conocimiento a la comunidad, este ejemplo sencillo pero útil para ademas de cargar archivos con el fileupload de primefaces poder leer parámetros adicionales que se envíen en el formulario.

Para poder iniciar se debe configurar JSF en eclipse, deben descargar una implementacion que puede ser Mojarra o MyFaces, una manera de descargar es en el proyecto web Properties->Project Faces -> Java Server Faces -> Download Library (El icono del lado derecho) y ahí escogen la implementacion, y posteriormente agregar el jar de jstl-1.2 a la carpeta WebContent/WEB-INF/lib.

Para usar primefaces basta con agregar el jar a la carpeta WEB-INF/lib , pero para usar el fileUpload si es necesario agregar los jars de commons-fileupload y commons-io los cuales deben revisar en la documentación de primefaces que versión corresponde según la versión de prime y configurar un par de lineas en el web.xml:

<filter>
		<filter-name>PrimeFaces FileUpload Filter</filter-name>
		<filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>PrimeFaces FileUpload Filter</filter-name>
		<servlet-name>Faces Servlet</servlet-name>
	</filter-mapping>

En la vista vamos a tener el siguiente código:

<f:view>
	<h:head>
		<meta http-equiv="Content-Type"
			content="text/html; charset=ISO-8859-1" />
		<title>File Upload Sample</title>
	</h:head>
	<body>
		<h:form id="myform" enctype="multipart/form-data">
			<h:outputStylesheet library="css" name="style.css" />
			<p:growl id="messages" showDetail="true" />
			<p:fileUpload
				fileUploadListener="#{fileuploadcontroller.uploadAttachment}"
				mode="advanced" update="messages" multiple="true"
				sizeLimit="900000000"
				allowTypes="/(\.|\/)(gif|jpe?g|png|txt)$/"
				uploadLabel="Cargar" cancelLabel="Cancelar" label="Buscar archivo"
				required="true" />
			<br />
			<br />
			<strong>Mensaje</strong> <br/><br />
			<p:inputText id="txtField" style="width:250px" maxlength="40">
				<f:validateRequired for="txtField"></f:validateRequired>
			</p:inputText>
		</h:form>
	</body>
</f:view>

En la configuración del fileUpload definimos el modo advanced para que aparezca con ese estilo de menubar, el tamaño máximo del archivo, que permita varios uploads a la vez , los tipos de archivo permitidos y por ultimo el método del controlador para gestionar la carga del archivo; también podemos notar que tenemos un inputText pero que no esta asociado en la propiedad value a ningun bean, esto sucede ya que al form lo hemos definido como enctype=”multipart/form-data” esto causa que lleguen los valores null al controlador, y la manera en que debemos obtener los parámetros es a través del objeto request que tiene el mapa de parámetros del formulario.

FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("form:parametro")

El codigo del controlador:

@ManagedBean(name="fileuploadcontroller")
@RequestScoped
public class FileUploadController {

	public void uploadAttachment(FileUploadEvent event){
		UploadedFile file = event.getFile();
		ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
		String txtField = ec.getRequestParameterMap().get("myform:txtField");
		String filePath = ec.getRealPath(String.format("/resources/img/%s",file.getFileName()));
		try {
			FileOutputStream fos = new FileOutputStream(filePath);
			fos.write(file.getContents());
			fos.flush();
			fos.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		FacesContext context = FacesContext.getCurrentInstance();
		context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,String.format("Archivo cargado: %s ", file.getFileName()),
				String.format("Mensaje: %s", txtField)));

	}

}

El método uploadAttachment es el que esta asociado en el fileUpload en la propiedad fileUploadListener, dentro de este como vemos podemos obtener el parámetro del inputText a través de :myform:txtField, que corresponden al id del fomulario <form> y el inputText respectivamente, el resultado del ejemplo es cargar el archivo en la carpeta /resources/img y presentar un mensaje con el texto ingresado en el input, que obviamente puede ser utilizado con otros fines acorde al caso que se les presente.

sample

Descargar proyecto eclipse.

Saludos,

@gishac

Lista de Videojuegos!!! NES-Famicon

Saliendo un poco del mundo del desarrollo de software me decidí a hacer un post tratando de listar todos los videojuegos que he podido finalizar desde el NES/Famicon hasta las consolas actuales, no se si sea de interés de alguien pero lo hago antes de sufrir de algún mal que no me permita recordar los juegos que jugué  y seguro en más de uno despertara nostalgia como me lo causo a mi, seguro me faltara uno que otro sobre todo del Famicon ya que en esas consolas venían como 999999 juegos y pues en esa época no tenia mas preocupaciones que dedicarme a jugar… pero no recuerdo todos,  así que aquí va la lista!

NES – Famicon

1. Arkanoid
2. Battletoads
3. Bomberman
4. Batman (Que bakan que era este juego)
5. Captain Tsubasa 2 Super Striker (Supercampeones Version Japonesa) (Cuantas horas de mi vida habre pasado jugando este juego… xD )
6. Castlevania
7. Castlevania III – Dracula’s Curse
8. Circus Charlie – (Ves el video e inmediatamente te acuerdas de la musiquita)
9. Contra
10. Donkey Kong
11. Donkey Kong Jr.
12. Donkey Kong 3
13. Galaga (Lo cuento a pesar de que creo que no tenia fin por la cantidad de niveles que avance xD)
14. Double Dragon (A+B patada giratoria xD )
15. Double Dragon III : The sacred stones
16. Excitebike
17. Faxanadu (Muy bakan este juego)
18. Final Fantasy (El mejor equipo para mi siempre era el figther, brujo blanco, brujo rojo y black belt, desde aqui nació mi fanatismo por esta increíble saga de juegos)
19. Fushigi na Blobby (Mas conocido en el bajo mundo como “A boy and his blob”)
20. Gi. Joe (Demasiado juego)
21. Gi. Joe The Atlantis Factor
22. Goal (Nunca supe como hacer la barrida fantasma)
23. Ice Climber
24. Kung Fu
25. Lode Runner (El abuelo de bomberman)
26. Megaman
27. Megaman 2 (Fue el primero que pase, luego jugue los otros)
28. Megaman 3
29. Megaman 4
30. Megaman 5
31. Megaman 6
32. Mighty Final Fight (Que bakan que era este juego!!!)
33. Mike Tyson’s Punch-Out!! (Aquí empezo a repartir puñete Little Mac)
34. Ninja Gaiden (Ryu Hayabusa en sus primeras aventuras)
35. Ninja Gaiden II: The Dark Sword of Chaos
36. Ninja Gaiden III: The Ancient Ship of Doom
37. Popeye
38. Road Fighter (Nunca entendi la parte en la que aparecia superman)
39. Shatterhand (Este juego era demasiado bakan, no recordaba el nombre pero revisando la lista lo encontre!!!)
40. Super C (Otro Contra)
41. Super Mario Bros.
42. Super Mario Bros. 2
43. Super Mario Bros. 3
44. Teenage Mutant Ninja Turtles II: The Arcade Game
45. Teenage Mutant Ninja Turtles III: The Manhattan Project
46. Terminator 2 Judgment Day
47. Tiny Toon Adventures
48. Tiny Toon Adventures: Troubles in Wackyland
49. Urban Champion
50. Wild Gunman

De esta lista solo me queda el trauma de no haber terminado Zelda, ya que cuando lo estaba pasando se daño el cartucho y perdí los cambios grabados.

Saludos,

gish@c

Desarrollando Software Pluggable con Microsoft .Net

En este post vamos a ver como se puede desarrollar una aplicacion de tal manera que se le puedan agregar features o caracteristicas a traves plugins. Este es un esquema comunmente utilizado por aplicaciones open source para que otros desarrolladores puedan contribuir agregando funcionalidad, aunque tambien hay aplicaciones pagadas que lo hacen, como Microsoft Office por ejemplo.
Para lograr este objetivo se debe exponer un API de tal manera que los plugins o add-ons cumplan con la funcionalidad necesaria y puedan ser identificados como tal por nuestro programa.
Para el caso vamos a desarrollar un ejemplo de un editor de texto que no es nada mas que una aplicacion Windows Forms con un RichTextbox, y la funcionalidad que va a ser extensible son los botones del toolbar para dar formato al texto:

Nuestro programa va a permitir agregar plugins para dar formato al texto, hay varias maneras de comunicarse con los plugins segun la necesidad y arquitectura del software, para el ejemplo vamos a darle total independencia a los plugins y solamente se les va a exigir que implementen una interfaz en comun para ser reconocidos como tal. Empezamos creando un proyecto independiente y exponemos una clase base que va a recibir como parametro en el constructor la instancia del RichTextbox:

Porque un proyecto aparte? Porque la idea es exponer el API necesaria como una dll para que pueda ser importada en proyectos externos. El codigo de la clase OperationBase es:

 public class OperationBase
    {
        protected RichTextBox editor;

        public OperationBase(RichTextBox editor)
        {
            this.editor = editor;
        }

        public virtual System.Windows.Forms.ToolStripItem GettoolStripItem()
        {
            throw new NotImplementedException();
        }

    }

Como vemos estamos forzando a que el constructor reciba la instancia del RichTextbox para que puedan ser aplicados los formatos y un metodo que nos devuelva un ToolStripItem, ya que nuestra aplicacion va a agregar la funcionalidad de los plugins como elementos en el toolbar, por lo cual es obligatorio que nos devuelva el elemento a exponer.
Para implementar el plugin creamos un nuevo proyecto y agregamos una referencia a la dll generada por el proyecto del API.

La funcionalidad del plugin ya es la logica correspondiente dependiendo de lo que queremos que haga, para el ejemplo he desarrollado 2 elementos; uno para darle color al texto seleccionado y otro para subrayado. Para ser identificados como plugins solamente deben heredar de la clase OperationBase expuesta por la dll del API.
El codigo de ambas clases es:

 public class Underline : OperationBase
    {
        private FontStyle style = FontStyle.Underline;

        ///
<summary> ///
 /// </summary>
        ///
        public Underline(RichTextBox editor)
            : base(editor)
        { }

        ///
<summary> ///
 /// </summary>
        ///
        public override ToolStripItem GettoolStripItem()
        {
            ToolStripButton button = new ToolStripButton();
            button.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
            button.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Underline);
            button.ImageTransparentColor = System.Drawing.Color.Magenta;
            button.Name = "tsUnderline";
            button.Size = new System.Drawing.Size(23, 22);
            button.Text = "U";
            button.Click += new EventHandler(button_Click);
            return button;
        }

        ///
<summary> ///
 /// </summary>
        ///
        ///
        void button_Click(object sender, EventArgs e)
        {
            if ((editor.SelectionFont.Style & style) == style)
                editor.SelectionFont = new Font(editor.SelectionFont.FontFamily, editor.SelectionFont.Size,
                 editor.SelectionFont.Style & ~style);
            else
                editor.SelectionFont = new Font(editor.SelectionFont.FontFamily, editor.SelectionFont.Size,
                 editor.SelectionFont.Style | style);
        }

    }
 public class YellowFont : OperationBase
    {

        ///
<summary> ///
 /// </summary>
        ///
        public YellowFont(RichTextBox editor)
            :base(editor)
        { }

        ///
<summary> ///
 /// </summary>
        ///
        public override ToolStripItem GettoolStripItem()
        {
            ToolStripButton button = new ToolStripButton();
            button.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            button.Image = Resources.yellow_square1;
            button.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Underline);
            button.ImageTransparentColor = System.Drawing.Color.Magenta;
            button.Name = "tsYellow";
            button.Size = new System.Drawing.Size(23, 22);
            button.Click += new EventHandler(button_Click);
            return button;
        }

        ///
<summary> ///
 /// </summary>
        ///
        ///
        void button_Click(object sender, EventArgs e)
        {
            if(editor.SelectionColor != Color.Yellow)
                editor.SelectionColor = Color.Yellow;
            else
                editor.SelectionColor = Color.Black;
        }

    }

Para nuestra aplicacion lo que le interesa es que se implementen los metodos de la clase base, ya que la logica como tal del plugin la maneja de manera interna en el evento click, ya que para el ejemplo ambos elementos devuelven un ToolStripButton.
Bueno ya tenemos el API, tenemos 2 plugins listos, veamos como los cargamos a nuestra aplicacion. El objetivo es tener la capacidad de agregarlos onfly (como dicen los gringos), por lo que en nuestra aplicacion tenemos la opcion de cargar plugin, que nos va a permitir buscar cualquier dll externa que pueda agregar funcionalidad.

En nuestra aplicacion definimos una carpeta “Plugins” en la cual buscaremos los plugins disponibles y los cargaremos en nuestra aplicacion. Para agregar plugins externos lo que haremos en el boton “Load Plugin”  sera simplemente abrir un OpenFileDialog y copiar la dll seleccionada a nuestra carpeta de “Plugins”:

 private void tsbLoadPlugin_Click(object sender, EventArgs e)
        {
            if (ofDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                try
                {
                    File.Copy(ofDialog.FileName, PluginLoader.GetPluginFolder().FullName + Path.DirectorySeparatorChar + new FileInfo(ofDialog.FileName).Name, true);
                    PluginLoader.LoadPlugins(toolStrip1, txtContent);
                }
                catch (Exception)
                {
                    MessageBox.Show("The selected plugin is already installed!");
                }

            }
        }

Despues de copiar el archivo seleccionado lo que hacemos es invocar al metodo que carga los plugins a la aplicacion, los metodos principales de la clase PluginLoader son los siguientes:

public static void LoadExternalPlugins(ToolStrip toolStrip, RichTextBox editor)
        {
            FileInfo[] assemblies = GetPluginFolder().GetFiles("*.dll");
            foreach (FileInfo assemblyFile in assemblies)
            {
                Assembly assembly = Assembly.LoadFrom(assemblyFile.FullName);
                LoadOperations(toolStrip, editor, assembly);
            }
        }

En este metodo leemos el contenido de nuestra carpeta Plugins y a traves del Api de Reflection del .net framework cargamos los assemblies, luego de eso tenemos que analizar el contenido del mismo para identificar las clases que pueden ser utilizadas como plugins, el siguiente metodo:

private static void LoadOperations(ToolStrip toolStrip, RichTextBox editor, Assembly assembly)
        {
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                if (type.IsSubclassOf(typeof(OperationBase)))
                {
                    OperationBase operation = (OperationBase)Activator.CreateInstance(type, new object[] { editor });
                    ToolStripItem item = operation.GettoolStripItem();
                    item.Tag = "external";
                    toolStrip.Items.Add(item);
                }
            }
        }

Una vez que cargamos el assembly en el metodo anterior, este lo recibe y obtiene todos los tipos, es decir todas las clases del assembly con el metodo GetTypes, recorremos todos los tipos y verificamos si esa clase es una subclase de OperationBase, es decir, la clase que publicamos en el API de nuestra aplicacion, de esta manera identificamos que ese elemento es un plugin que puede ser agregado a nuestra aplicacion; una vez identificado creamos dinamicamente una instancia de ese tipo a traves de la clase Activator, la cual recibe como parametro el tipo del cual queremos crear una instancia y un arreglo de object que son los parametros que enviamos al constructor de la clase, si lo recuerdan nuestra clase debe recibir una instancia del RichTextbox que es lo que estamos pasando como parametro; finalmente ya con el objeto instanciado invocamos al metodo GetToolStripItem que nos devuelve el control que vamos a agregar al toolbar y agregamos el mismo para que sea visible en nuestro editor.

Como vemos una vez cargada la dll de plugins aparecen 2 nuevos botones en el toolbar, de los cuales no controlamos la funcionalidad, al hacer click ellos manejan internamente el evento y hacen lo que tengan que hacer.

Esta es una de las maneras en las que se puede manejar este esquema de plugins, en aplicaciones reales el API es mucho mas rico, con mucha funcionalidad, para nuestro ejemplo solo publicamos el contrato con el que identificamos los plugins externos.

Saludos,
gish@c

Descargar Fuentes