quarta-feira, outubro 23, 2013

Não deixe o básico da orientação a objetos de lado, nunca!

Quando olho para trás lembro-me de quando eu era um humilde padawan que queria ser um monge Jedi em Java, e lembro claramente dos conselheiros Jedi do RSJUG falando "vá pelo caminho da orientação a objetos". Se passaram alguns muitos anos e eu ainda lembro e sou fiel a esse princípio, e quando vejo por aí classes ou entidades de banco de dados, que eu irei ter que transformar em classes¹, contendo 181 campos (não é piada!) minha gastrite quase me mata!

Eu não sei por que as pessoas teimam em ter campos como CPF, CNPJ, data de inicio e de fim, etc em certas classes. A quem vai ficar destinada a tarefa de validar um CNPJ/CPF? Ou se a data de início não é maior do que a de fim? Não tenha receio de criar classes que não serão persistidas. As pessoas tem a imagem de que classes de domínio/modelo só devem existir se forem ser persistidas, o resto é classe útil, DAO, service, etc. e não é bem por aí.

É do domínio da minha aplicação um CNPJ/CPF! E eu tenho que ter um objeto para isso, com a responsabilidade de validar se ele mesmo é válido. Não vou jogar essa responsabilidade para uma classe megazord no pacote útil responsável por validar isso e, talvez, mais coisas em um único lugar (olha o acoplamento aíííííííí gente).

Bem, para exemplificar isso, vou mostrar um código/abordagem que gosto muito, onde eu separo o campo nome da classe usuário em um objeto a parte.

Se você parar para pensar, o nome deve conter sempre 3 campos: primeiro nome, inicial do meio e último(s) nome(s). Mas e o usuário SISTEMA? Ele não tem sobrenome. E o John Doe que não tem nome do meio?

Então eu tenho a classe PersonName:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class PersonName
    {
        private String firstName;
        private String middleInitial;
        private String lastName;
        
        public String FirstName
        {
            set { this.firstName = value; }
            get { return this.firstName; }
        }
        
        public String MiddleInitial
        {
            set { this.middleInitial = value; }
            get { return this.middleInitial; }
        }
        
        public String LastName
        {
            set { this.lastName = value; }
            get { return this.lastName; }
        }
        
        public PersonName()
        {
        }
        
        public PersonName(String firstName)
        {
            this.FirstName = firstName;
        }
        
        public PersonName(String firstName, String middleInitial,
                          String lastName)
        {
            this.FirstName = firstName;
            this.MiddleInitial = middleInitial;
            this.LastName = lastName;
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            sb.Append(this.LastName);
            sb.Append(", ");
            sb.Append(this.FirstName);
            sb.Append(" ");
            sb.Append(this.MiddleInitial);
            
            return sb.ToString();
        }
    }
}

E agora a classe User:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class User
    {
        #region attributes
        
        Int64 id;
        String username;
        PersonName personName;
        char gender;
        String password;
        char active;
        
        #endregion
        
        #region properties
        
        public Int64 Id
        {
            set { this.id = value; }
            get { return this.id; }
        }
        
        public String Username
        {
            set { this.username = value; }
            get { return this.username; }
        }
        
        public PersonName PersonName
        {
            set { this.personName = value; }
            get { return this.personName; }
        }
        
        public char Gender
        {
            set { this.gender = value; }
            get { return this.gender; }
        }
        
        public String Password
        {
            set { this.password = value; }
            get { return this.password; }
        }
        
        public char Active
        {
            set { this.active = value; }
            get { return this.active; }
        }
        
        #endregion
        
        /// <summary>
        /// Returns a string with this instance's data
        /// The format of the string is:
        /// namespace + class name + [ + the fields most representative
        /// of the class + ].
        /// </summary>
        /// <returns>Returns a string with this instance's data</returns>
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            sb.Append("com.blogspot.jeanjmichel.model.User [");
            sb.Append(this.PersonName);
            sb.Append(" as '");
            sb.Append(this.Username);
            sb.Append("'");
            sb.Append(" Active: ");
            sb.Append(this.Active);
            sb.Append("]");
            
            return sb.ToString();
        }
        
        public User()
        {
        }
        
        public User(String username, PersonName personName, String password,
                    char active)
        {
            this.Username = username;
            this.PersonName = personName;
            this.Password = password;
            this.Active = active;
        }
        
        public User(Int64 id, String username, PersonName personName,
                    char gender, String password, char active)
        {
            this.Id = id;
            this.Username = username;
            this.PersonName = personName;
            this.Gender = gender;
            this.Password = password;
            this.Active = active;
        }
    }
}

Agora criando um usuário:

using System;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel
{
    class Program
    {
        static void Main(string[] args)
        {
            PersonName pn = new PersonName("Jean", "J", "Michel");
            User u = new User("jmichel", pn, "Senha", 'Y');
            
            Console.WriteLine(u);
            Console.ReadKey();
        }
    }
}

A saída é: com.blogspot.jeanjmichel.model.User [Michel, Jean J as 'jmichel' Active: Y].

Quase tudo funcionou.
Mas no teste unitário quando eu digo que criando o usuário System assim:

using System;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel
{
    class Program
    {
        static void Main(string[] args)
        {
            PersonName pn = new PersonName("System");
            User u = new User("sysadmin", pn, "Senha", 'Y');
            
            Console.WriteLine(u);
            Console.ReadKey();
        }
    }
}

Eu espero o resultado: com.blogspot.jeanjmichel.model.User [System as 'sysadmin' Active: Y] o que acontece?

Failed TestUserToStringMethod TestProject Assert.AreEqual failed.
Expected:<com.blogspot.jeanjmichel.model.User [System as 'sysadmin' Active: Y]>.
Actual..:<com.blogspot.jeanjmichel.model.User [, System as 'sysadmin' Active: Y]>.


Holly crap! Então, a resposabilidade da minha classe PersonName é validar esse tipo de coisa. Refatorando-a teremos:

using System;
using System.Text;

namespace com.blogspot.jeanjmichel.model
{
    public class PersonName
    {
        private String firstName;
        private String middleInitial;
        private String lastName;
        
        public String FirstName
        {
            set { this.firstName = value; }
            get { return this.firstName; }
        }
        
        public String MiddleInitial
        {
            set { this.middleInitial = value; }
            get { return this.middleInitial; }
        }
        
        public String LastName
        {
            set { this.lastName = value; }
            get { return this.lastName; }
        }
        
        public PersonName()
        {
        }
        
        public PersonName(String firstName)
        {
            this.FirstName = firstName;
        }
        
        public PersonName(String firstName, String middleInitial,
                         String lastName)
        {
            this.FirstName = firstName;
            this.MiddleInitial = middleInitial;
            this.LastName = lastName;
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            
            if (String.IsNullOrEmpty(this.LastName))
            {
                sb.Append(this.FirstName);
            }
            else
            {
                sb.Append(this.LastName);
                sb.Append(", ");
                sb.Append(this.FirstName);
                sb.Append(String.IsNullOrEmpty(this.MiddleInitial) ? "" :
                " " + this.MiddleInitial);
            }
            
            return sb.ToString();

        }
    }
}

Eu faço o mesmo com CNPJ/CPF, datas (eu crio uma classe chamada período) e outras.

¹ Eu não criei uma classe com 181 campos, mas também não pude refatorar a classe, a tabela, etc (software de terceiros). então criei uma classe com o set mínimo de informações da tabela, que deu mais de 30 campos, e faço o enxerto do resto com os valores padrão para cada campo. Uma pena eu não poder quebrar essa maldita tabela em meia dúzia de 8 tabelas mais produtivas e reutilizáveis. Enfim, coisas da TI que o McGyver aprovaria :)

terça-feira, outubro 08, 2013

Utilizando métodos seguros no WCF

Recentemente estive trabalhando com o WCF (Windows Communication Foundation) da Microsoft. E depois de fazer algumas experiências básicas lá fui eu me certificar da forma de transmissão segura e criptografadas.
Não vou me alongar na explicação porque isso tem no MSDN e em n blogs melhores escritor do que o meu :-), mas, como de praxe vou registrar um exemplo aqui (cabe ressaltar que daria para refatorar um pouco esse meu exemplo, que eu tive que fazer as pressas, principalmente a classe Greetting.cs que ficou bem porca mesmo. Admito, sem orgulho :-| #ficaAdica).

Para quem não sabe o que é o WCF, aqui vai a definição da Microsoft:

Windows Communication Foundation (WCF) is Microsoft's unified programming model for building service-oriented applications. It enables developers to build secure, reliable, transacted solutions that integrate across platforms and interoperate with existing investments.

Sacou?

Então vamos ao que interessa: código!
Em meu exemplo eu criei uma classe chamada Credential.cs que irá armazenar um token (que no meu caso é uma senha) e a informação de que tipo de device está consumindo meu serviço.
Além disso, há uma classe onde a lógica do meu programa está implementada e outra classe que é o serviço em sí (a lógica do tratamento das chamadas ao serviço).
Simplão, o funcionamento é: conferir se a senha passada está correta e se estiver chamar a classe que vai gerar a saudação e retorná-la ao client, caso contrário é lançar uma exceção.

Para começar essa é a classe da credencial (Credential.cs):

using System;
using System.ServiceModel;
using System.Net.Security;

namespace com.blogspot.jeanjmichel.services
{
    /// <summary>
    /// This class represents the credential.
    /// </summary>
    [MessageContract]
    public class Credential
    {
        /// <summary>
        /// The token is a password that have be valid to authorize the 
        /// client to consume the service.
        /// </summary>
        [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public String Token;
        
        /// <summary>
        /// The DeviceCategory is an inrelevant information that is not 
        /// encrypted and is just used to show the kind of device is 
        /// consuming the service (Whindows Phone, iOS, Android, web page, 
        /// etc).
        /// </summary>
        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.None)]
        public String DeviceCategory;

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Credential()
        {
        }
    }
}


Notem que a informação do token irá trafegar criptografada, no header da mensagem. Já a informação do device do client não irá trafegar criptografada.
Greeting.cs, este é o core, ou kernel, do programa, dependendo do quanto nojento você queira parecer falando isso :-) (roubei essa do Elemar Jr):

using System;
using System.ServiceModel;
using System.Net.Security;

namespace com.blogspot.jeanjmichel.model
{
    /// <summary>
    /// This class represents a greeting.
    /// </summary>
    [MessageContract]
    public class Greeting
    {
        private String userGreeting;

        /// <summary>
        /// This method will set an appropriate greeting based on the server's 
        /// local time.
        /// </summary>
        private void SetGreeting()
        {
            DateTime now = DateTime.Now;

            if (now.Hour >= 7 && now.Hour <= 11)
            {
                this.userGreeting = "Good morning";
            }
            else if (now.Hour >= 12 && now.Hour <= 17)
            {
                if (now.Hour == 12 || now.Hour == 13)
                {
                    this.userGreeting = "Good afternoon, it's lunch time!";
                }
                else
                {
                    this.userGreeting = "Good afternoon";
                }
            }
            else if (now.Hour >= 18 && now.Hour <= 20)
            {
                this.userGreeting = "Good evening";
            }
            else
            {
                this.userGreeting = "Good night";
            }
        }

        /// <summary>
        /// This property returns the greeting in the message body,
        /// and this will move across the network ever encrypted.
        /// </summary>
        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public String UserGreeting
        {
            get { return this.userGreeting; }
        }


        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Greeting()
        {
            this.SetGreeting(); //Call the method on construction time.
        }
    }
}


Fica a dica para quem quiser refatorar essa classe: usar internacionalização para retornar a saudação em n idiomas.

E por fim, mas não menos importante :-), a interface (contrato) do serviço e a implementação dele:

using System.ServiceModel;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel.services.contract
{
    /// <summary>
    /// This interface defines the service methods.
    /// </summary>
    [ServiceContract(Namespace = "http://jeanjmichel.blogspot.com/services/v0.0.1")]
    public interface IGetGreeting
    {
        /// <summary>
        /// This method will returns a greeting based based on the server's 
        /// local time.
        /// </summary>
        /// <param name="credential">An access credential that will be validated
        ///                          in order to authorize the access to the 
        ///                          method.</param>
        /// <returns>A Greeting object.</returns>
        [OperationContract]
        Greeting GetGreeting(Credential credential);
    }
}


using System;
using System.ServiceModel;
using com.blogspot.jeanjmichel.services.contract;
using com.blogspot.jeanjmichel.model;

namespace com.blogspot.jeanjmichel.services
{
    /// <summary>
    /// This class is the service. Is where the logic behind the services' 
    /// calls occurs (validade credencial, invoke the business layer, etc).
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
                     Namespace = "http://jeanjmichel.blogspot.com/services/v0.0.1")]
    public class GetGreetingService: IGetGreeting
    {
        /// <summary>
        /// This method will returns a greeting based based on the server's 
        /// local time.
        /// </summary>
        /// <param name="credential">An access credential that will be validated
        ///                          in order to authorize the access to the 
        ///                          method.</param>
        /// <returns>A Greeting object.</returns>
        public Greeting GetGreeting(Credential credential)
        {
            if (String.IsNullOrEmpty(credential.Token))
            {
                throw new FaultException("Inform the security phrase," +
                                         " and try again.");
            }
            else
            {
                if (credential.Token.Equals("mySeCuriTyP@ss"))
                {
                    Greeting g = new Greeting();
                    return g;
                }
                else
                {
                    throw new FaultException("Wrong password.");
                }
            }
        }
    }
}


Só falta configurar a aplicação no app.config, na tag <endpoint> o atributo binding de basicHttpBinding por wsHttpBinding:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="GetGreetingService">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8081/soa" />
          </baseAddresses>
        </host>
        <endpoint address ="" 
                  binding="wsHttpBinding"
                  contract="com.blogspot.jeanjmichel.services.contract.IGetGreeting"
                  bindingNamespace="http://jeanjmichel.blogspot.com/services/v0.0.1" >
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <useRequestHeadersForMetadataAddress>
            <defaultPorts>
              <add scheme="http" port="8010" />
            </defaultPorts>
          </useRequestHeadersForMetadataAddress>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>


Agora o client para consumir o serviço:

using ConsoleClient.GetGreetingService; using System; using System.ServiceModel;   namespace ConsoleClient {     public class Program     {         static void Main(string[] args)         {             Console.WriteLine("Connecting to the endpoint...");             ChannelFactory<IGetGreeting> factory =                  new ChannelFactory<IGetGreeting>("WSHttpBinding_IGetGreeting",                      new EndpointAddress("http://192.168.0.173:8081/soa/GetGreetingService.svc"));               Console.WriteLine("Creating the proxy...");             IGetGreeting proxy = factory.CreateChannel();               Console.WriteLine("Creating the credential...");             Credential credential = new Credential();             credential.Token = "mySeCuriTyP@ss";             credential.DeviceCategory = "Windows Phone 7.8";               try             {                 Console.WriteLine("Call the service...");                 Greeting greeting = proxy.GetGreeting(credential);                   Console.WriteLine("Greeting: " + greeting.UserGreeting);             }             catch (Exception e)             {                 Console.WriteLine("An error has occurred. Message: " + e.Message);             }                          Console.ReadKey();         }     } }

Rodando esse client assim como está aqui a saída seria:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... Greeting: Good evening

Se você errar a senha:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... An error has occurred. Message: Wrong password.

E se você anular a senha:

Connecting to the endpoint... Creating the proxy... Creating the credential... Call the service... An error has occurred. Message: Inform the security phrase, and try again.

Vale ressaltar que eu estou hospedando a aplicação em um IIS local para testes.

Era isso. Qualquer dúvida é só comentar.

sexta-feira, outubro 04, 2013

Problema para usar o OpenId do wordpress.com

Semana passada estava tentando logar no http://www.stackoverflow.com usando a minha conta do http://www.wordpress.com (o OpenId). Mas o que eu conseguia mesmo era um erro dizendo "You do not own that identity". Como assim? Quer dizer que eu não sou eu?

Na verdade o que ocorria é que meu usuário não era o mesmo da minha URL (eu já tinha o blog http://www.anonymousbiker.wordpress.com) no http://www.wordpress.com, então dava esse erro bizarro. Eu tive que criar o blog http://www.jeanjmichel.wordpress.com para poder usar o OpenId.

Fica a dica.