Puresharp é um conjunto de recursos para .NET 4.5.2+/.NET Core 2.1 para melhorar a produtividade produzindo aplicativos flexíveis e eficientes.
Puresharp fornece principalmente ferramentas de arquitetura para construir o básico de aplicativos profissionais:
Este quadro está dividido em 2 partes:
IPuresharp é um pacote nuget dedicado a reescrever assemblies (usando Mono.Cecil ) para permitir que sejam altamente personalizáveis em tempo de execução. O IPuresharp não adicionará uma nova referência de biblioteca aos assemblies, mas apenas incluirá um processo de pós-construção para reescrever automaticamente os assemblies logo após a construção bem-sucedida.
Install-Package IPuresharp -Version 5.0.5
Pode ser usado manualmente com linha de comando para gerenciar montagens de terceiros
IPuresharp.exe "FullnameToAssembly.dll"
Puresharp é um pacote nuget que oferece vários recursos úteis para projetar uma arquitetura saudável e produtiva. Este pacote também inclui toda a artilharia para lidar facilmente com os elementos que o gravador IL IPuresharp traz. O pacote nuget adiciona uma biblioteca (Puresharp.dll) sem quaisquer outras dependências.
Install-Package Puresharp -Version 5.0.5
nota: é recomendado instalar o pacote nuget IPuresharp em todos os projetos e instalar o pacote nuget Puresharp apenas no projeto onde você explicitamente precisa dele.
O fluxo de trabalho global do DI Container é semelhante a outros: configure uma composição, crie um contêiner e instancie alguns componentes do contêiner.
Exemplo de interfaces para configurar
public interface IA
{
}
public interface IB
{
}
public interface IC
{
}
Exemplo de implementações para vincular 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
{
}
Crie uma composição
var _composition = new Composition();
Composição de configuração para IA, IB, IC com 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);
Crie um contêiner a partir da configuração da composição
var _container = _composition.Materialize();
Instancie um módulo de IA do contêiner
using (var _module = _container.Module<IA>())
{
var _ia = _module.Value;
}
nota: o módulo é IDisposable e cronometra o ciclo de vida para todas as dependências.
Como é gerenciado o ciclo de vida das dependências? Quando um módulo é configurado em composição, o modo de instanciação é necessário e pode ser Singleton (uma única instância com um ciclo de vida relacionado ao contêiner), Multiton (uma nova instância para cada módulo com ciclo de vida relacionado ao próprio módulo) ou Volátil (sempre uma nova instância com ciclo de vida relacionado ao módulo proprietário). Container e Módulo são IDisposable para liberar componentes criados.
Minhas interfaces deveriam implementar IDisposable para combinar com o gerenciamento do ciclo de vida? Pelo contrário, a interface de um componente nunca deve implementar a interface IDisposable que é uma preocupação puramente de infra-estrutura. Somente implementações poderiam ser. O contêiner garante o descarte adequado das implementações ao implementar a interface IDisposable.
Por que usar a expressão lambda para configurar componentes em vez do parâmetro genérico clássico? A expressão lambda oferece uma maneira de direcionar o construtor a ser usado, especificar quando usar dependências e capturar constantes.
Como a dependência é configurada? Basta usar Metadata<T>.Value na expressão quando precisar recuperar a dependência do contêiner.
A injeção do construtor evita referência cíclica entre componentes? Não, as referências cíclicas são um recurso. Quando uma instância é criada, não é realmente o caso, uma instância de proxy lento é preparada para minimizar a retenção de recursos não utilizados e permitir referências cíclicas.
Na visualização, apenas construtores são usados para configurar o componente. Isso é limitado à injeção de construtor? Não, as expressões são totalmente abertas. Você pode injetar métodos estáticos, construtores, membros e até misturar estilos diferentes.
Fluxo de trabalho:
Exemplo de interface
[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);
}
Exemplo de implementação
public class Service : IService
{
public void SaveComment(int id, string text)
{
}
public string GetComment(int id)
{
return null;
}
}
Suponhamos que queremos registrar todas as operações somente leitura. Para isso, temos que definir um Pointcut que represente todos os métodos que são operação somente leitura (onde são colocados o atributo Read e o atributo Operation)
public class ReadonlyOperation : Pointcut.And<Pointcut<Operation>, Pointcut<Read>>
{
}
Defina um conselho para registrar antes com Trace.WriteLine por exemplo ao chamar 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()
{
}
}
Defina um aspecto que use conselhos de log
public class Logging : Aspect
{
override public IEnumerable<Advisor> Manage(MethodBase method)
{
yield return Advice
.For(method)
.Around(() => new Log(method));
}
}
Instancie Aspect e combine-o com nosso ReadonlyOperation Pointcut
var _logging = new Logging();
_logging.Weave<ReadonlyOperation>();
Parabéns, o Aspecto de registro agora injetado em todos os contratos de operação somente leitura.
Aqui está um conjunto de exemplos para permitir ver diferentes maneiras de criar e orientar.
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); }
});
}
}
Posso entrelaçar vários Aspectos no mesmo Pointcut? Sim, apenas tome cuidado com a ordem da tecelagem.
Como posso remover um Aspecto de um Pointcut? Existe um método Release definido em Aspect para se livrar de Aspect de Pointcut .
Os atributos são necessários para definir o Pointcut? Não, Pointcut pode ser definido herdando diretamente de Pointcut e implementando o método abstrato Match que recebe um MethodBase como argumento único e retorna um booleano para indicar se um método está no escopo Pointcut .
Por que tenho que usar o IPuresharp? A interceptação é baseada em IPuresharp. Na verdade, o IPuresharp adiciona uma ação de construção para reescrever o CIL para tornar a montagem "amigável ao arquiteto", injetando transparências e recursos ocultos para garantir controle total de execução em tempo de execução.
Posso interceptar o construtor? Se sim, como faço para implementá-lo? A interceptação do construtor é suportada e tratada como outro método com declaração de tipo como primeiro argumento e void para tipo de retorno.
Tipos e métodos genéricos são suportados? Tipos e métodos genéricos são totalmente suportados por injeção.
Posso interceptar métodos assíncronos? Os métodos assíncronos são totalmente suportados por injeção e oferecem uma maneira de interceptar cada etapa assíncrona.