Atributos com reflection

Figura 1: Atributos com 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.

Listagem 1: Um Atributo 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.

Listagem 2: Criando um atributo simples


  [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.

Listagem 3: AttributeTargets Enum


  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.

Estrutura da aplicação

Figura 2: Estrutura da aplicação

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.

Listagem 4: Codificando os atributos


  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.

Listagem 5: Entidade e os atributos


      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.

Listagem 6: Código do GetFieldsAttributes


  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.

Listagem 7: GetStoredProcedureAttributes


  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:

Listagem 8: Método Salvar


  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