Puresharp es un conjunto de funciones para .NET 4.5.2+ / .NET Core 2.1 para mejorar la productividad mediante la producción de aplicaciones flexibles y eficientes.
Puresharp proporciona principalmente herramientas arquitectónicas para construir los conceptos básicos de aplicaciones profesionales:
Este marco se divide en 2 partes:
IPuresharp es un paquete nuget dedicado a reescribir ensamblajes (usando Mono.Cecil ) para permitir que sean altamente personalizables en tiempo de ejecución. IPuresharp no agregará una nueva referencia de biblioteca a los ensamblados, sino que solo incluirá un proceso posterior a la compilación para reescribir automáticamente los ensamblados justo después de una compilación exitosa.
Install-Package IPuresharp -Version 5.0.5
Se puede usar manualmente con la línea de comando para administrar ensamblajes de terceros.
IPuresharp.exe "FullnameToAssembly.dll"
Puresharp es un paquete nuget que ofrece varias funciones útiles para diseñar una arquitectura saludable y productiva. Este paquete también incluye toda la artillería para manejar fácilmente los elementos que trae el escritor IL IPuresharp. El paquete nuget agrega una biblioteca (Puresharp.dll) sin ninguna otra dependencia.
Install-Package Puresharp -Version 5.0.5
nota: Se recomienda instalar el paquete IPuresharp nuget en todos los proyectos e instalar el paquete Puresharp nuget solo en el proyecto donde lo necesite explícitamente.
El flujo de trabajo global del DI Container es similar a otros: configurar una composición, crear un contenedor y crear una instancia desde el contenedor de algunos componentes.
Ejemplo de interfaces a configurar
public interface IA
{
}
public interface IB
{
}
public interface IC
{
}
Ejemplo de implementaciones para vincularse a interfaces.
public class A : IA
{
public A(IB b, IC c)
{
}
}
public class B : IB
{
public B(IC c, int constant)
{
}
}
public class C : IC
{
}
Crear una composición
var _composition = new Composition();
Composición de configuración para IA, IB, IC con respectivamente A, B, C
_composition.Setup<IA>(() => new A(Metadata<IB>.Value, Metadata<IC>.Value), Instantiation.Multiton);
_composition.Setup<IB>(() => new B(Metadata<IC>.Value, 28), Instantiation.Multiton);
_composition.Setup<IC>(() => new C(), Instantiation.Multiton);
Crear un contenedor desde la configuración de la composición
var _container = _composition.Materialize();
Crear una instancia de un módulo de IA desde el contenedor
using (var _module = _container.Module<IA>())
{
var _ia = _module.Value;
}
nota: el módulo es IDisposable y controla el ciclo de vida de todas las dependencias.
¿Cómo se gestiona el ciclo de vida de las dependencias? Cuando un módulo se configura en composición, se requiere el modo de creación de instancias y puede ser Singleton (una única instancia con un ciclo de vida relacionado con el contenedor), Multiton (una nueva instancia para cada módulo con un ciclo de vida relacionado con el módulo en sí) o Volátil (siempre una nueva instancia). con ciclo de vida relacionado con el módulo propietario). Tanto el contenedor como el módulo son IDisposable para liberar los componentes creados.
¿Mis interfaces deberían implementar IDisposable para que coincida con la gestión del ciclo de vida? Por el contrario, la interfaz de un componente nunca debe implementar la interfaz IDisposable, que es una cuestión puramente de infraestructura. Sólo las implementaciones podrían serlo. El contenedor se asegura de eliminar las implementaciones correctamente cuando implementa la interfaz IDisposable.
¿Por qué utilizar la expresión lambda para configurar componentes en lugar del parámetro genérico clásico? La expresión lambda ofrece una forma de seleccionar el constructor de destino, especificar cuándo usar las dependencias y capturar constantes.
¿Cómo se configura la dependencia? Simplemente use Metadata<T>.Value en la expresión cuando necesite recuperar la dependencia del contenedor.
¿La inyección del constructor evita la referencia cíclica entre componentes? No, las referencias cíclicas son una característica. Cuando se crea una instancia, en realidad no es así; se prepara una instancia de proxy diferido para minimizar la retención de recursos no utilizados y permitir referencias cíclicas.
En la vista previa, solo se utilizan constructores para configurar el componente, ¿se limita a la inyección del constructor? No, las expresiones son totalmente abiertas. Puedes inyectar métodos estáticos, constructores, miembros e incluso mezclar diferentes estilos.
Flujo de trabajo:
Ejemplo de interfaz
[AttributeUsage(AttributeTargets.Method)]
public class Read : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)]
public class Operation : Attribute
{
}
public interface IService
{
[Operation]
void SaveComment(int id, string text);
[Read]
[Operation]
string GetComment(int id);
}
Ejemplo de implementación
public class Service : IService
{
public void SaveComment(int id, string text)
{
}
public string GetComment(int id)
{
return null;
}
}
Supongamos que queremos registrar todas las operaciones de solo lectura. Para eso, tenemos que definir un Pointcut que represente todos los métodos que son operaciones de solo lectura (donde se colocan el atributo de lectura y el atributo de operación).
public class ReadonlyOperation : Pointcut.And<Pointcut<Operation>, Pointcut<Read>>
{
}
Defina un consejo para iniciar sesión antes con Trace.WriteLine, por ejemplo, al llamar a métodos
public class Log : IAdvice
{
private MethodBase m_Method;
public Log(MethodBase method)
{
this.m_Method = method;
}
public void Instance<T>(T instance)
{
}
public void Argument<T>(ref T value)
{
}
public void Begin()
{
Trace.WriteLine(this.m_Method);
}
public void Await(MethodInfo method, Task task)
{
}
public void Await<T>(MethodInfo method, Task<T> task)
{
}
public void Continue()
{
}
public void Throw(ref Exception exception)
{
}
public void Throw<T>(ref Exception exception, ref T value)
{
}
public void Return()
{
}
public void Return<T>(ref T value)
{
}
public void Dispose()
{
}
}
Definir un aspecto que utilice consejos de registro
public class Logging : Aspect
{
override public IEnumerable<Advisor> Manage(MethodBase method)
{
yield return Advice
.For(method)
.Around(() => new Log(method));
}
}
Crear una instancia de Aspecto y tejerlo en nuestro ReadonlyOperation Pointcut
var _logging = new Logging();
_logging.Weave<ReadonlyOperation>();
Felicitaciones, el aspecto de registro ahora se inyecta en todos los contratos de operación de solo lectura.
Aquí un conjunto de ejemplos para permitirle ver diferentes formas de crear y asesorar.
public class Logging : Aspect
{
override public IEnumerable<Advisor> Manage(MethodBase method)
{
//Use classic interceptor to create an 'Around' advisor (place break points in interceptor methods to test interception).
yield return Advice
.For(method)
.Around(() => new Interceptor());
//Use linq expression to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(invocation =>
{
return Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Constant($"Expression : { method.Name }")
);
});
//Use linq expression to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before
(
Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Constant($"Expression2 : { method.Name }")
)
);
//Use ILGeneration from reflection emit API to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(advice =>
{
advice.Emit(OpCodes.Ldstr, $"ILGenerator : { method.Name }");
advice.Emit(OpCodes.Call, Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)));
});
//Use simple Action to generate a 'Before' advisor.
yield return Advice
.For(method)
.Before(() => Console.WriteLine($"Action : { method.Name }"));
//Use an expression to generate an 'After-Returning-Value' Advisor
yield return Advice
.For(method)
.After()
.Returning()
.Value(_Execution =>
{
return Expression.Call
(
Metadata.Method(() => Console.WriteLine(Metadata<string>.Value)),
Expression.Call
(
Metadata.Method(() => string.Concat(Metadata<string>.Value, Metadata<string>.Value)),
Expression.Constant("Returned Value : "),
Expression.Call(_Execution.Return, Metadata<object>.Method(_Object => _Object.ToString()))
)
);
});
//Validate an email parameter value.
yield return Advice
.For(method)
.Parameter<EmailAddressAttribute>()
.Validate((_Parameter, _Attribute, _Value) =>
{
if (_Value == null) { throw new ArgumentNullException(_Parameter.Name); }
try { new MailAddress(_Value.ToString()); }
catch (Exception exception) { throw new ArgumentException(_Parameter.Name, exception); }
});
}
}
¿Puedo entrelazar varios aspectos en el mismo Pointcut? Sí, sólo ten cuidado con el orden de tejido.
¿Cómo puedo eliminar un aspecto de un Pointcut? Hay un método Release definido en Aspect para deshacerse de Aspect desde Pointcut .
¿Se requieren atributos para definir Pointcut? No, Pointcut se puede definir heredando directamente de Pointcut e implementando el método abstracto Match que toma una MethodBase como argumento único y devuelve un valor booleano para indicar si un método está en el alcance de Pointcut .
¿Por qué tengo que usar IPuresharp? La interceptación se basa en IPuresharp. De hecho, IPuresharp agrega una acción de compilación para reescribir CIL y hacer que el ensamblaje sea "apto para arquitectos" mediante la inyección de elementos transparentes y ocultos para otorgar control total de ejecución en tiempo de ejecución.
¿Puedo interceptar al constructor? En caso afirmativo, ¿cómo lo implemento? Se admite la intercepción de constructores y se trata como otro método que declara el tipo como primer argumento y es nulo para el tipo de retorno.
¿Se admiten tipos y métodos genéricos? Los tipos y métodos genéricos son totalmente compatibles con la inyección.
¿Puedo interceptar métodos asíncronos? Los métodos asíncronos son totalmente compatibles con la inyección y ofrecen una forma de interceptar cada paso asincrónico.