Novidades do C# 6.0 – Operador condicional nulo

No post anterior comentei sobre uma das novidades do C# 6.0, a interpolação de strings.

Agora neste post vou comentar sobre o operador condicional nulo, que particularmente, dentre as novidades, foi a que mais me agradou.

Com certeza você já teve ter passado por uma situação na qual precisasse testar se determinado objeto é nulo para então acessar uma propriedade ou método. Pois se acessarmos uma propriedade ou método de um objeto nulo ocorrerá uma exceção de referência nula.

Para exemplificar, vamos supor que eu possua uma classe Ponto com as propriedades X e Y. E esta mesma classe possua um método estático para instanciar um objeto do tipo Ponto a partir de um objeto Json, conforme código abaixo:

class Ponto
{
    public int X { get; set; }

    public int Y { get; set; }

    public static Ponto FromJson(JObject json)
    {
        if (json != null &&
            json["X"] != null &&
            json["X"].Type == JTokenType.Integer &&
            json["Y"] != null &&
            json["y"].Type == JTokenType.Integer)
        {
            return new Ponto() { X = (int)json["X"], Y = (int)json["Y"] };
        }

        return null;
    }
}

Agora, para simplificar este código vamos utilizar o operador condicional nulo, e vamos reescrever novamente a classe:

class Ponto
{
    public int X { get; set; }

    public int Y { get; set; }

    public static Ponto FromJson(JObject json)
    {
        if (json != null &&
            json["X"]?.Type == JTokenType.Integer &&
            json["Y"]?.Type == JTokenType.Integer)
        {
            return new Ponto() { X = (int)json["X"], Y = (int)json["Y"] };
        }

        return null;
    }
}

Perceba que foi utilizado o caractere ? no código fonte. Isto significa que a propriedade Type somente será acessada se o objeto for diferente de nulo, evitando que ocorra uma exceção. Ainda podemos diminuir mais um pouco o código:

class Ponto
{
    public int X { get; set; }

    public int Y { get; set; }

    public static Ponto FromJson(JObject json)
    {
        if (json?["X"]?.Type == JTokenType.Integer &&
            json?["Y"]?.Type == JTokenType.Integer)
        {
            return new Ponto() { X = (int)json["X"], Y = (int)json["Y"] };
        }

        return null;
    }
}

Agora ele somente acessará o “X” e o “Y” se o objeto json recebido por parâmetro não for nulo.

Outro uso que podemos dar para este operador é para quando estamos trabalhando com eventos. Se você já trabalhou com eventos provavelmente já deve ter escrito o seguinte trecho de código:

if (OnChanged != null)
{
    OnChanged(this, args);
}

Utilizando o operador condicional nulo, podemos reescrever este mesmo código da seguinte forma:

OnChanged?.Invoke(this, args);

O uso deste operador, quando bem utilizado, pode evitar vários erros de referência, além de reduzir o código fonte.

Para quem tiver interesse em conhecer todas as novidades do C# 6.0 acesse o link.

Novidades do C# 6.0 – Interpolação de strings

O Visual Studio 2015, lançado oficialmente no dia 20 de julho de 2015, trouxe algumas novidades sendo uma delas o C# 6.0.

Não foram grandes mudanças, mas surgiram recursos que podem aumentar a produtividade do programador. Dentre as novidades uma delas é a interpolação de strings.

Para exemplificar, vamos considerar o seguinte cenário. Digamos que você precise criar uma classe chamada Ponto com as propriedades X e Y. Esta classe possuirá o método ToString retornando o texto “Ponto (X, Y)”, sendo X e Y o valor das propriedades. Para fazer isso, no método ToString, você usaria o string.Format para fazer a concatenação do meu texto:

class Ponto
{
    public int X { get; set; }

    public int Y { get; set; }

    public override string ToString()
    {
        return string.Format("Ponto ({0}, {1})", X, Y);
    }
}

Com o C# 6.0 podemos escrever este mesmo trecho de código de uma forma mais simples, utilizando a interpolação de strings:

class Ponto
{
    public int X { get; set; }

    public int Y { get; set; }

    public override string ToString()
    {
        return $"Ponto ({X}, {Y})";
    }
}

Observe que ao invés de utilizar um número dentro das chaves, como era feito com o string.Format, podemos colocar diretamente a variável dentro das chaves. Para isto basta colocar no início da string o caractere $.

Para quem tiver interesse em conhecer todas as novidades do C# 6.0 acesse o link.

IRouteHandler no ASP.NET MVC

Neste post vou comentar um pouco sobre a interface IRouteHandler do ASP.NET MVC e como criar uma classe que a implemente. O objetivo é criar uma classe customizada para manipular a rota da aplicação. Esta é uma das inúmeras formas de estender a sua aplicação. Mas antes, vou explicar um pouco sobre como funciona uma requisição numa aplicação ASP.NET MVC.

Toda requisição recebida no ASP.NET MVC passa por um objeto chamado UrlRoutingModule, que é um módulo HTTP. Este módulo analisa a requisição e seleciona qual rota será utilizada. Esta rota é um objeto de uma classe que herda de RouteBase, e é geralmente uma instância da classe Route.

Uma vez obtido o objeto Route, o UrlRoutingModule obtém o objeto que implementa a interface IRouteHandler, que está associada na classe da rota. Por padrão, em uma aplicação MVC, será uma instância de MvcRouteHandler. A instância de IRouteHandler cria o objeto IHttpHandler, que por padrão será a classe MvcHandler. Por fim, o MvcHandler selecionará o controller que irá manipular a requisição.

Como exemplo, vou utilizar o seguinte cenário.  Considere que o seu site possua uma área restrita à determinados países. Por exemplo, determinado país não poderá acessar determinada área do seu site.

O primeiro passo será criar uma classe que implemente a interface IRouteHandler. Esta interface possui um único método chamado GetHttpHandler, que recebe um RequestContext como parâmetro e retorna IHttpHandler. A classe que será criada chamará RestricaoPaiRouteHandler. Ela receberá em seu construtor uma lista de códigos de países que não estão autorizados à acessar o meu site. E no método GetHttpHandler será criada uma instância de uma classe que implementa IHttpHandler, que também será criada posteriormente.

using System.Collections.Generic;
using System.Web;
using System.Web.Routing;

namespace CustomRoute.App_Start
{
    public class RestricaoPaisRouteHandler : IRouteHandler
    {
        #region Campos

        private List paises;

        #endregion

        #region Construtor

        public RestricaoPaisRouteHandler(List paises)
        {
            this.paises = paises;
        }

        #endregion

        #region Métodos

        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            BloqueioIpHandler handler = new BloqueioIpHandler(this.paises, requestContext);

            return handler;
        }

        #endregion
    }
}

Em seguida, será criada a classe BloqueioIpHandler herdando de MvcHandler. A classe MvcHandler já implementa IHttpHandler. Neste caso iremos apenas sobrescrever a sua implementação. Será sobrescrito o método BeginProcessRequest com a seguinte lógica. Será chamado um método ObterCodigoPais, que através do IP irá retornar a sigla do país que está realizando o acesso ao site. E se o país estiver na lista de países bloqueados será criada uma exceção.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;

namespace CustomRoute.App_Start
{
    public class BloqueioIpHandler : MvcHandler
    {
        #region Campos

        private List paises;

        #endregion

        #region Construtor

        public BloqueioIpHandler(List paises, RequestContext requestContext)
            : base(requestContext)
        {
            this.paises = paises;
        }

        #endregion

        #region Métodos

        private string ObterCodigoPais(string ip)
        {
            string query = string.Format("http://api.hostip.info/?ip={0}", ip);

            XDocument doc = XDocument.Load(query);

            XNamespace defaultNamespace = doc.Root.GetDefaultNamespace();
            XNamespace xNamespace = doc.Root.GetNamespaceOfPrefix("gml");

            string pais = doc.Root.Element(xNamespace + "featureMember")
                .Element(defaultNamespace + "Hostip").Element(defaultNamespace + "countryAbbrev").Value;

            return pais; 
        }

        protected override IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
        {
            string pais = ObterCodigoPais(httpContext.Request.UserHostAddress);

            if (this.paises.Contains(pais))
                httpContext.AddError(new Exception("Desculpe! Você não pode acessar esta página a partir do seu país."));

            return base.BeginProcessRequest(httpContext, callback, state);
        }

        #endregion
    }
}

Após a criação das classes, é necessário registrar uma nova rota e definir a classe criada para manipular a mesma. Para isto altere a classe RouteConfig localizada na parta App_Start do projeto. Será definido uma nova rota, além da padrão, e nela será definida a propriedade RouteHandler.

using CustomRoute.App_Start;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CustomRoute
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Restrito",
                url: "Restrito/{action}",
                defaults: new { controller = "Restrito", action = "Index" }
            ).RouteHandler = new RestricaoPaisRouteHandler(new List() { "XX" });

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

Agora ao acessar o controller Restrito será exibida uma mensagem de erro, informando que o país não está autorizado. No exemplo foi definido a sigla XX pois é a sigla que será retornada quando informado um IP local.

Quero ressalvar que este é um simples exemplo de como criar classe que faça um tratamento customizado da rota. Isto possibilita fazer qualquer tratamento da requisição. Há também outras formas de realizar o bloqueio por região, como por exemplo fazer o controle no próprio controller. Para fazer o exemplo foi utilizado o Visual Studio 2015 e o MVC 5.2.

O código fonte de exemplo está disponível no GitHub. Para acessar clique aqui.