Visão Geral
O Fody representa uma ferramenta extensível dedicada à manipulação de assemblies .NET através da injeção de código IL durante o processo de compilação. Esta técnica permite adicionar funcionalidades automáticas aos programas sem necessidade de escrever código repetitivo manualmente.
Este artigo apresenta dois plugins amplamente utilizados: PropertyChanged.Fody para implementação automática de bindings em ViewModels, e MethodBoundaryAspect.Fody para programação orientada a aspectos (AOP).
PropertyChanged.Fody
O plugin PropertyChanged.Fody automatiza a implementação da interface INotifyPropertyChanged, eliminando a necessidade de escrever manualmente o código de notificação para cada propriedade.
Considere o seguinte ViewModel básico:
public class ClienteViewModel : INotifyPropertyChanged
{
public string Nome { get; set; } = "João Silva";
public event PropertyChangedEventHandler? PropertyChanged;
}
Após a transformação pelo Fody, o código injeta automaticamente as chamadas ao método OnPropertyChanged dentro dos setters das propriedades:
public class ClienteViewModel : INotifyPropertyChanged
{
private string _nome = "João Silva";
public string Nome
{
get => _nome;
set
{
_nome = value;
OnPropertyChanged(nameof(Nome));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Personalizando Comportamentos
Para executar lógica customizada quando uma propriedade sofre alteração, basta definir um método nomeado no formato On[Propriedade]Changed():
public class ProdutoViewModel : INotifyPropertyChanged
{
private decimal _preco;
public decimal Preco
{
get => _preco;
set
{
_preco = value;
OnPropertyChanged(nameof(Preco));
}
}
private void OnPrecoChanged()
{
Console.WriteLine("O preço do produto foi modificado!");
}
public event PropertyChangedEventHandler? PropertyChanged;
}
Para excluir determinada propriedade da injeção automática, utilize o atributo DoNotNotify:
public class PedidoViewModel : INotifyPropertyChanged
{
[DoNotNotify]
public int Identificador { get; set; }
public string Descricao { get; set; } = string.Empty;
public event PropertyChangedEventHandler? PropertyChanged;
}
O atributo AddINotifyPropertyChangedInterface aplicado à classe dispensa a implementação manual da interface:
[AddINotifyPropertyChangedInterface]
public class UsuarioViewModel
{
public string Email { get; set; } = string.Empty;
public string Telefone { get; set; } = string.Empty;
}
O PropertyChanged.Fody também suporta propagação automática de notificações entre propriedades relacionadas através do atributo AlsoNotifyFor.
MethodBoundaryAspect.Fody
Este plugin facilita a implementação de programação orientada a aspectos, permitiendo inserir código personalizado nos pontos de entrada, saída e exceção de métodos.
Imagine um cenário onde é necesário gerenciar o ciclo de vida de uma conexão de banco de dados: abrir, configurar, executar queries, fechar e liberar recursos. Cada operação precisa de tratamento de erros consistente:
internal class ServicoBancoDados
{
public void Conectar()
{
try
{
// Lógica de conexão com banco
}
catch (Exception ex)
{
throw new Exception($"Falha na conexão: {ex.Message}");
}
}
public void Configurar()
{
try
{
// Configurações do banco
}
catch (Exception ex)
{
throw new Exception($"Falha na configuração: {ex.Message}");
}
}
public void ExecutarQuery(string consulta)
{
try
{
// Execução da query
}
catch (Exception ex)
{
throw new Exception($"Falha na execução: {ex.Message}");
}
}
public void Fechar()
{
try
{
// Fechar conexão
}
catch (Exception ex)
{
throw new Exception($"Falha ao fechar: {ex.Message}");
}
}
public void Dispose()
{
try
{
// Liberar recursos
}
catch (Exception ex)
{
throw new Exception($"Falha no dispose: {ex.Message}");
}
}
}
O código cliente também precisa de tratamento estruturado:
try
{
var servico = new ServicoBancoDados();
servico.Conectar();
servico.Configurar();
servico.ExecutarQuery("SELECT * FROM Usuarios");
servico.Fechar();
servico.Dispose();
MessageBox.Show("Operação concluída");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Smiplificando com AOP
Utilizando o MethodBoundaryAspect.Fody, o tratamento de erros e logging podem ser centralizados em um aspecto reutilizável:
internal class AspectoTryCatch : OnMethodBoundaryAspect
{
public override void OnException(MethodExecutionArgs args)
{
throw new Exception($"Erro em {args.Method.Name}: {args.Exception.Message}");
}
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine($"Entrando no método: {args.Method.Name}");
base.OnEntry(args);
}
public override void OnExit(MethodExecutionArgs args)
{
Console.WriteLine($"Saindo do método: {args.Method.Name}");
base.OnExit(args);
}
}
A classe de serviço com o aspecto aplicado fica extremamente limpa:
[AspectoTryCatch]
internal class ServicoBancoDadosAOP
{
public void Conectar() { }
public void Configurar() { }
public void ExecutarQuery(string consulta) { }
public void Fechar() { }
public void Dispose() { }
}
A saída no console demonstra a execução automática do aspecto:
Entrando no método: Conectar
Saindo do método: Conectar
Entrando no método: Configurar
Saindo do método: Configurar
Entrando no método: ExecutarQuery
Saindo do método: ExecutarQuery
Entrando no método: Fechar
Saindo do método: Fechar
Entrando no método: Dispose
Saindo no método: Dispose
Esta abordagem reduz significativamente a duplicação de código e mantém a lógica de cross-cutting concerns isolada em um único local.