Ejecución de assemblies externos al proyecto

jueves, 29 de octubre de 2009
Si bien un assembly puede ser referenciado en el proyecto en el que estamos trabajando y simplemente usarlo desde ahí, se presentara alguna oportunidad en la que tengamos que ejecutar o usar (como quieran) un asembly que no pertenece al proyecto (assembly externo) .Net 2.0 nos permite realizar este tipo de excentricidades de una forma muy sencilla. Reflection.

Se utiliza Reflection para crear instancias dinámicas de objetos que son agregados a nuestra aplicación después de haber sido compilada, esto permite extender las funcionalidades de una aplicación dada.

Supongamos lo siguiente:

Tenemos una aplicación de comercio electrónico (por ejemplo), y en el modulo de pagos (o cobros) queremos implementar algunas reglas, pero estas reglas pueden variar en el futuro; si las reglas no fueran a cambiar simplemente escribimos el código necesario y lo empaquetamos, pero como estas van a cambiar necesitamos implementar un par de pasos mas en la verificación de los datos (las reglas dinámicas).

Si bien vamos a usar una interfaz para conocer de antemano la forma que tendrán los assemblies externos esto no nos limita a usar un solo assembly con esa forma, pueden ser muchos con diferentes nombres y namespaces que hereden de nuestra interfaz, y en caso que necesitemos otra interfaz, ahí si tenemos que recompilar las modificaciones del “Motor de reglas” simplemente agregar las interfaces necesarias, el código para pasarle los parámetros al momento de la ejecución y listo.

Para poder validar o comparar con algo, el código contempla una cadena de conexión, el tema del acceso a datos no será tocado en este post ya que el tema es amplio.

Una vez que se tienen todos los datos a validar, la interfaz presenta un método Execute (Ejecutar) al que le pasamos los parámetros XML y este nos devuelve, en este caso) un valor boléano (verdadero | falso), verdadero si la ejecución se llevo a cabo sin ningún error y falso si existe algún error, en este caso la propiedad ErrorMessage contendrá el mensaje de error que describa la situación.

En el caso que necesitemos algún otro tipo de retorno, el método ExecuteConstraint retorna un objeto de tipo object en caso de hacer algún tipo de transformación para llevar el resultado a la aplicación.

Como es bien sabido, muchas cosas pueden fallar al momento de ejecutar una aplicación, es por eso que el método ExecuteConstraint viene rodeado de un try/catch con aquellas excepciones más comunes.


Sin más les presento el código.


using System;
using System.Data;
using System.Configuration;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;
using MiApp.AdministradorReglas.Interfaces;
//hace referencia al assembly donde se encuentran las interfaces
namespace MiApp.AdministradorReglas
{
/// <summary>
/// Esta clase fue creada con la intencion de ayudar en la ejecucion de assemblies ///externos a una aplicacion construidos basandose en la interface ///MiApp.AdministradorReglas.Interfaces
/// </summary>
/// <see cref="MiApp.AdministradorReglas.Interfaces"/>
class ConstraintHelper
{
private StringBuilder _errorMessage;
private string identity;
/// <summary>
/// Mensaje de error devuelto de la ejecucion del assembly.
/// </summary>
public string ErrorMessage
{
get { return _errorMessage.ToString(); }
set
{
_errorMessage.Remove(0, _errorMessage.Length);
_errorMessage.Append(value);
}
}
/// <summary>
/// Usado para identificar una constraint especifica.
/// </summary>
public string Identity
{
get { return identity; }

set { identity = value; }

}

/// <summary>
/// Enumeracion publica que permite distinguir los tipos de assemblies disponibles
/// </summary>
public enum enuConstraintType
{
enuApplicationType
}
public ConstraintHelper()
{
_errorMessage = new StringBuilder();
}
/// <summary>
/// Ejecuta una regla en base a sus parametros.
/// </summary>
/// <param name="enuType">Determina el tipo de interfaz a ser utilizado para la ejecucion.</param>
/// <param name="args">Un string XML con valores que seran validados por el assembly externo.</param>
/// <param name="dbConnection">Conexion a la base de datos usada para verificar los datos lmacenados.</param>
/// <param name="userID">Identifica al usuario que solicita la verificacion.</param>
/// <param name="AssemblyPath">ruta en la que se encuentra el assembly externo</param>
/// <remarks>Este metodo extrae y ejecuta el metodo Execute de un assembly externo de tal modo que se pueda usar en el contexto presente.
///<returns>Objec dependiendo del retorno de la regla a ser ejecutada</returns>
public object ExecuteConstraint(enuConstraintType enuType, System.Data.Common.DbConnection dbConnection, string userID, string AssemblyPath, String args)
{
if (args == string.Empty)
{
this._errorMessage.Append("Parametros no validos");
return null;
}
Assembly assembly;
object result = null;
try
{
// Load the requested assembly and get the requested type
assembly = Assembly.LoadFrom(AssemblyPath);
switch (enuType)
{
case enuConstraintType.enuApplicationType:
IApplicationConstraints myApplication = assembly.CreateInstance(TypeName, true) as IApplicationConstraints;
if (myApplication != null)
{
//setting up basic properties
myApplication.DatabaseConnection = dbConnection;
myApplication.UserID = userID;

//executing and returning the result
if (!myApplication.Execute(args))
{
this._errorMessage.Append(myApplication.ErrorMessage);
}
result = (IApplicationConstraints)myApplication;
}
else
{
this._errorMessage.Append("Constraint Not Available");
result = null;
}
break;
}
return result;
}
catch (TargetException ex)
{
this._errorMessage.Append(string.Format("Assembly {0} Not Load", TypeName));
throw new Exception(string.Format("Assembly {0} Not Load", TypeName), ex);
}
catch (TargetInvocationException ex)
{
this._errorMessage.Append(string.Format("{0} Method Not Invoked", TypeName));
throw new Exception(string.Format("{0} Method Not Invoked", TypeName));
}
catch (FileNotFoundException ex)
{
this._errorMessage.Append(string.Format("Assembly {0} Not Found", TypeName));
throw new Exception(string.Format("Assembly {0} Not Found", TypeName), ex);
}
catch (TypeLoadException ex)
{
this._errorMessage.Append(string.Format("Type {0} Not Load", TypeName));
throw new Exception(string.Format("Type {0} Not Load", TypeName), ex);
}
catch (Exception ex)
{
this._errorMessage.Append(ex.Message);
throw new Exception(ex.Message, ex);
}
}
}
}



El código del assembly de interfaces
using System;
using System.Text;
using System.Data;
using System.Data.Common;

namespace MiApp.AdministradorReglas.Interfaces
{
public interface IApplicationConstraints
{
bool Execute(string parameters);
string UserID
{
get;
set;
}
DbConnection DatabaseConnection
{
get;
set;
}
string UserName
{
get;
set;
}
string ErrorMessage
{
get;
set;
}
}
}


Tomar en cuenta que ambos proyectos deben ser de tipo ClassLibrary de tal forma que se pueda usar en cualquier tipo de aplicación, además el assembly de interfaces debe estar referenciado al “Motor de reglas”.

0 comments:

Publicar un comentario