Trabalhando com atributos de classe e Reflection em C#
Veja neste artigo como trabalhar com atributos de classe em C#, criando atributos customizados e acessando-os através da técnica Reflection.
Para aqueles que ainda não sabem ou até sabem, mas não tinham se dado conta, começamos este artigo com exemplos de atributos de classe.
[Obsolete]
public void Metodo();
[Serializable]
public class Classe();
[WebMethod]
public static void CallAjax();
Esses nomes entre chaves acima de cada nome de método ou declaração de classe são os atributos e servem para identificar e classificar classes e seus campos segundo alguns critérios, atribuindo-lhes propriedades específicas para utilização no contexto em que são utilizadas.
Por exemplo, o atributo Obsolete permite definir uma mensagem indicando que o método está obsoleto e indicando qual utilizar em seu lugar.
Então, assumindo que possuímos o conhecimento de reflexão, vamos partir para o entendimento de como criar atributos de classes.
[AttributeUsage(System.AttributeTargets.Method)]
public class MethodAttribute : Attribute
{
private int _Variavel1 = 0;
public string _Variavel2 = "";
public MethodAttribute()
{
}
public MethodAttribute(string metodo, string metodo1)
{
}
public void Metodo()
{
}
public void Metodo(string entrada)
{
}
}
AttributeUsage()
Responsável pelas regras de como o atributo irá funcionar. Nele indicamos que o atributo servirá de assinaturas, para classes, métodos ou algumas outras opções encontradas na listagem 3.
public enum AttributeTargets
{
Assembly ,
Module,
ClasS,
Struct,
Enum,
Constructor,
Method,
Property,
Field,
Event,
Interface,
Parameter,
Delegate,
ReturnValue,
GenericParameter,
All = GenericParameter | ReturnValue | Delegate | Parameter | Interface | Event | Field | Property | Method | Constructor | Enum | Struct | Class | Module | Assembly
}
Cada opção dessas habilita uma possibilidade diferente para o atributo gerado. Além do AttributeTargets, existem outros dois parâmetros dentro de AttributeUsage, são eles:
- AllowMultiple: Habilita que o atributo seja assinado mais de uma vez, dentro do escopo definido para o mesmo, o valor padrão é false.
- Inherited: Habilita que as classes derivadas herdem o atributo também, o valor padrão é true.
Muito rápido e simples, porém terá um grande ganho de automatização quando tudo estiver programado.
A Estrutura
A estrutura proposta para esse sistema é feita em 3 camadas e uma camada de reflexão. A ideia da estrutura não está no foco.
Repare que o Reflection é o ultimo passo, pois ele será o responsável (neste caso) por efetuar o envio das informações, por isso foi dito que a estrutura não é o foco, pois a DAL e até mesmo a Bussines, neste caso, simplesmente passam as informação para a próxima camada, não realizam tarefas.
Codificando os Atributos
Os atributos é a parte mais importante daqui e fácil de desenvolver. Os atributos são basicamente classes com construtores (não obrigatoriamente) e propriedades.
Existem atributos mais complexos que realizam tarefas de classes robustas, mas na grande maioria os atributos serão simples.
Toda classe de atributo herda de Attribute e a mesma é assinada com AttributeUsage com as definições que foram explicadas acima.
Para este exemplo, foram criados dois atributos, um se chama StoredProcedureAttributes e o outro FieldsAttributes.
public enum ProcedureTypeCommand
{
Insert,
Delete,
Update
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class StoredProcedureAttributes : Attribute
{
public string ProcedureName { get; set; }
public ProcedureTypeCommand ProcedureType { get; set; }
public String[] ProcParams { get; set; }
public StoredProcedureAttributes(string procName, ProcedureTypeCommand procType, params String[] param)
{
ProcedureName = procName;
ProcedureType = procType;
ProcParams = param;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldsAttributes : Attribute
{
public string FieldName { get; set; }
public SqlDbType FieldType { get; set; }
public int Length { get; set; }
public object Value { get; set; }
public FieldsAttributes()
{
}
public FieldsAttributes(string FieldName, SqlDbType FieldType)
{
this.FieldName = FieldName;
this.FieldType = FieldType;
}
public FieldsAttributes(string FieldName, SqlDbType FieldType, int Length)
{
this.FieldName = FieldName;
this.FieldType = FieldType;
this.Length = Length;
}
}
Simples assim foram definidos dois atributos, um é assinatura de método e o outro como assinatura de propriedades. Caso tente colocar um atributo definido para propriedade em uma classe, por exemplo, o interpretador e o compilador Visual Studio irá exibir um erro.
O atributo StoredProcedureAttributes, recebe os dados do Procedure do banco de dados, o tipo de evento DML que ele realiza e os parâmetros existentes no procedure (obrigatórios ou não).
O atributo FieldsAttributes recebe o nome do Parâmetro correspondente na entrada do procedure, o tipo de dado que a coluna foi definida no banco de dados e o length (para Varchar, Char e etc) para definir o tamanho da informação máxima que o parâmetro pode conter.
Codificando as Camadas
As camadas não tem mistério, com exceção da camada de Entity. Nesta camada estão todas as assinaturas dos atributos e é por ela que o Reflection consegue definir o tipo de ação a ser realizada e os parâmetros passados.
Entity
A Entity é um espelho da tabela de dados e possui as regras de “DML” amarradas a ela. Nesta camada estão as definições das assinaturas.
public class Titulos
{
#region Propriedades
private int _id;
private string _nome;
private int _idCategoria;
private string _categoria;
private string _duracaoFilme;
[FieldsAttributes("@ID", System.Data.SqlDbType.Int)]
public int ID
{
get { return _id; }
set { _id = value; }
}
[FieldsAttributes("@NOME", System.Data.SqlDbType.VarChar, 30)]
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
[FieldsAttributes("@IDCATEGORIA", System.Data.SqlDbType.Int)]
public int IdCategoria
{
get { return _idCategoria; }
set { _idCategoria = value; }
}
[FieldsAttributes("@CATEGORIA", System.Data.SqlDbType.VarChar, 30)]
public string Categoria
{
get { return _categoria; }
set { _categoria = value; }
}
[FieldsAttributes("@DURACAOFILME", System.Data.SqlDbType.VarChar, 20)]
public string DuracaoFilme
{
get { return _duracaoFilme; }
set { _duracaoFilme = value; }
}
#endregion
private TitulosBLL titulosBLL;
public Titulos()
{
titulosBLL = new TitulosBLL();
}
#region Metodos Assinados
[StoredProcedureAttributes("STP_INS_TITULOS", ProcedureTypeCommand.Insert, "@NOME", "@IDCATEGORIA", "@CATEGORIA", "@DURACAOFILME")]
public void Salvar()
{
titulosBLL.Salvar(this);
}
[StoredProcedureAttributes("STP_DEL_TITULOS", ProcedureTypeCommand.Delete, "@ID")]
public void Deletar()
{
titulosBLL.Deletar(this);
}
#endregion
}
Veja como ficaram os atributos criados, em cima das propriedades apenas o atributo para propriedade e o mesmo para os métodos. Toda a declaração que seria feita na camada de DAL foi transportado para a entidade, isso é um exemplo para a funcionalidade do Reflection.
Observação: Muitos devotos por patterns e outros, com certeza não aprovariam um modelo desses.
Camada de Reflection
A última camada do escopo realiza a reflexão do objeto passado pela entidade e captura as demais informações para realizar a ação em questão (salvar, deletar).
Primeiramente vamos entender alguns métodos de reflexão dessa classe.
O GetFieldsAttributes é um método responsável por capturar do objeto passado para a camada todas as propriedades as quais levam a assinatura definida anteriormente.
private void GetFieldsAttributes(System.Type type, object obj)
{
ListFields.Clear();
foreach (var propertyInfo in type.GetProperties())
{
foreach (Attributes.FieldsAttributes customAttribute in propertyInfo.GetCustomAttributes(typeof(Attributes.FieldsAttributes), false))
{
customAttribute.Value = propertyInfo.GetValue(obj, null);
ListFields.Add(customAttribute);
}
}
}
O GetStoredProcedureAttributes, por sua vez, é o método responsável por capturar do objeto passado para a camada todas as propriedades as quais levam a assinatura de StoredProcedureAttributes.
private void GetStoredProcedureAttributes(Type type)
{
listProcMethods.Clear();
foreach (var methodInfo in type.GetMethods())
{
foreach (Attributes.StoredProcedureAttributes storedProcedureAttributes in methodInfo.GetCustomAttributes(typeof(Attributes.StoredProcedureAttributes), false))
{
listProcMethods.Add(storedProcedureAttributes);
}
}
}
Eles juntam as informações em listas distintas definidas na classe como static. Essas listas de informações são necessárias para a filtragem dos parâmetros corretos na hora do envio das informações.
O evento Salvar recebe uma variável genérica para permitir receber qualquer objeto dentro do escopo definido.
Sem mais delongas, segue:
public void Salvar<T>(T obj)
{
var type = typeof(T);
GetFieldsAttributes(type, obj);
GetStoredProcedureAttributes(type);
Attributes.StoredProcedureAttributes StoredProcedure = listProcMethods.First(p => p.ProcedureType == Attributes.ProcedureTypeCommand.Insert);
Salvar(StoredProcedure);
}
private void Salvar(Attributes.StoredProcedureAttributes storedProcSalvar)
{
SqlCommand cmd = new SqlCommand();
try
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = storedProcSalvar.ProcedureName;
cmd.Connection = GetConnection();
cmd.Parameters.Clear();
foreach (string procParam in storedProcSalvar.ProcParams)
{
var field = ListFields.First(whe => whe.FieldName == procParam);
cmd.Parameters.Add(new SqlParameter(field.FieldName, field.FieldType, field.Length) { Value = field.Value });
}
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
cmd.Connection.Close();
}
}
Observação: Na linha onde é feito o First para definir qual o procedure deve ser utilizado, poderia ser feito de outra forma, utilizando atributos específicos para cada evento.
Veja como é simples e reutilizável tudo isso. A reflection é capaz de ler os atributos da classe e os valores das propriedades definidos no objeto, minerando toda essa informação e facilitando na automatização do processo de envio.
Essa camada, nesse exemplo, entende os parâmetros que o procedure receberá e e dentro do foreach é feita a separação do que é preciso para a chamada do procedure.
Conclusão
Os atributos são uma vantagem para as aplicações, com eles conseguimos automatizar processos rotineiros, encapsular e reutilizar tarefas repetitivas para todos os desenvolvedores de uma célula. Neste caso do CRUD facilitará em constantes acessos ao banco de dados e em termos de produtividade gera um ganho, pois é possível reutilizar os atributos e a classe de reflexão em qualquer projeto.
Portanto pesquisem mais ainda sobre o assunto. Baixem o código fonte para ver o funcionamento!
Foi disponibilizado uma base dentro do App_Data, caso a mesma falhe, utilize o script de banco de dados e stored procedures disponibilizado dentro do arquivo zip, não se esqueça de editar o web.config para o endereço do banco corretamente.
Obrigado.
Referência
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo