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.