A implementação de telas de cadastro representa um tipo de ocorrência bastante comum dentro do desenvolvimento de soluções voltadas à Internet. Estes formulários para inclusão e/ou alteração de informações podem, muito provavelmente, possuir campos em que serão preenchidos valores monetários. Também não é difícil de imaginar que os usuários destas aplicações esperam que tais campos sejam compatíveis com os padrões de sua localidade, prevendo para isto o formato da moeda para o país/região em que os mesmos se encontram.

As principais plataformas de desenvolvimento atuais contam com alternativas que suportam de maneira transparente os mais variados padrões numéricos, representações de tempo e datas, além de outros tipos de informações. Isto também acontece no .NET Framework, através do conjunto de recursos conhecidos como globalização. Graças a este mecanismo, aplicações construídas sob as diferentes tecnologias desta plataforma (como ASP.NET, por exemplo) podem ser facilmente adaptadas a diferentes culturas, sem que isto implique em grandes esforços de codificação.

Apesar de toda essa preocupação com a questão da padronização, ajustes adicionais se farão necessários em algumas situações. Este é o caso do ASP.NET MVC no que se refere à manipulação de dados financeiros seguindo o formato brasileiro:

  • Um valor como "1234,56" podem ser interpretado como "123456", já que a vírgula é um separador de milhar no padrão norte-americano (adotado como default dentro do próprio framework ASP.NET MVC);
  • Pelo fato dos separadores “,” e “.” não serem equivalentes no Brasil e nos EUA, inconsistências poderão ocorrer durante a validação em tela de um ou mais campos. Estas verificações em Views do ASP.NET MVC utilizam instruções JQuery/JavaScript extensivamente e, novamente, as diferenças entre as configurações brasileiras e norte-americanas podem conduzir a erros em tempo de execução.

O objetivo deste artigo é apresentar uma solução para este tipo de problema no ASP.NET MVC. Isto será conseguido por meio da implementação de uma classe baseada na interface IModelBinder (que é parte integrante do framework MVC), além da criação de versões que irão sobrepor algumas funções JQuery empregadas no processo de validação de informações.

Um exemplo de uso de um campo monetário numa aplicação MVC

O exemplo demonstrado neste artigo faz uso do .NET Framework 4.5 e da versão 4.0 do ASP.NET MVC, sendo que o mesmo se baseia em um cadastro de produtos de uma loja hipotética.

A manipulação de informações referentes a produtos acontecerá a partir de uma classe chamada Produto. O preço de comercialização de um item estará vinculado a uma propriedade chamada PrecoVenda, conforme indicado na Listagem 1.

Quanto à forma como a propriedade PrecoVenda foi declarada, é possível observar:

  • O uso do atributo DisplayNameAttribute (namespace System.ComponentModel). Ao ser utilizada em conjunto com o framework ASP.NET MVC, essa estrutura permite a geração automática de legendas em telas de consulta, inclusão e/ou alteração de registros (através de uma string que é informada como parâmetro no construtor de DisplayNameAttribute);
  • Já o atributo RangeAttribute (namespace System.ComponentModel.DataAnnotations) tem por finalidade definir os valores mínimo e máximo que poderão ser atribuídos ao preço de venda de um produto. A classe RangeAttribute faz parte de um conjunto de estruturas conhecidas como Data Annotations, as quais tornam possíveis validações do lado cliente empregando para isto mecanismos de script como JQuery.

Listagem 1: Classe Produto com a propriedade PrecoVenda


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TesteMVC.Models
{
    public class Produto
    {
        ...
        
        [Range(10, 99999.99,
             ErrorMessage = "O Preço de Venda deve estar entre " +
                            "10,00 e 99999,99.")]
        [DisplayName("Preço de Venda")]
        public decimal? PrecoVenda { get; set; }
    }
}

OBSERVAÇÃO: por questões de simplificação, está sendo apresentada apenas a definição da propriedade PrecoVenda no código que implementa o tipo Produto.

O primeiro passo no sentido de possibilitar o correto preenchimento do preço de venda será configurar o elemento globalization dentro do arquivo Web.config (Listagem 2), de maneira que a aplicação adote como default a cultura que representa os padrões de formatação seguidos no Brasil.

Listagem 2: Arquivo Web.config com a cultura devidamente configurada


<?xml version="1.0" encoding="utf-8"?>
<configuration>

  ...

  <system.web>

    ...

    <globalization culture="pt-BR" uiCulture="pt-BR" />

    ...

  </system.web>

  ...

</configuration>

Caso não se efetue mais nenhum ajuste envolvendo a manipulação de valores monetários, poderá ocorrer um erro como o que consta na Figura 1 durante o cadastramento do preço de um novo produto.

Erro durante o preenchimento do preço de venda

Figura 1: Erro durante o preenchimento do preço de venda

Conforme se nota, o valor “1.222,40” representa um valor válido dentro dos padrões brasileiros. No entanto, a aplicação MVC foi incapaz de compreender isto (mesmo com o ajuste no arquivo Web.config determinando a utilização das configurações regionais seguidas no Brasil).

Isto se deve ao fato das funções JQuery/JavaScript empregadas na validação não preverem o formato brasileiro. É importante lembrar que tais instruções foram implementadas originalmente em conformidade com o padrão em uso nos países de língua inglesa.

Uma alternativa possível para este problema consiste, basicamente, na criação de um arquivo de scripts em que se sobreponham as funcionalidades de validação que haviam sido escritas em JQuery. Na Listagem 3 é apresentado o código que deverá constar em tal arquivo (definiu-se como nome para o mesmo “jquery.validate.custom.pt-br.js”):

Listagem 3: Funções de validação já adaptadas para o padrão brasileiro


$.validator.methods.range = function (value, element, param) {
    var globalizedValue = value.replace(".", "");
    globalizedValue = globalizedValue.replace(",", ".");
    return this.optional(element) ||
        (globalizedValue >= param[0] &&
         globalizedValue <= param[1]);
};

$.validator.methods.number = function (value, element) {
    return this.optional(element) ||
        /^-?(?:\d+|\d{1,3}(?:[\s\.,]\d{3})+)(?:[\.,]\d+)?$/
            .test(value);
};

Já na Listagem 4 é possível observar o arquivo de scripts criado anteriormente sendo referenciado dentro do método RegisterBundles, o qual pertence à classe estática BundleConfig. Essa operação é acionada ao se iniciar uma aplicação MVC, sendo o ponto de partida para a utilização de uma técnica conhecida como Bundling.

Permitindo que vários arquivos sejam combinados em um simples arquivo (simplificando assim a forma como scripts e folhas de estilo são referenciadas em um site MVC), Bundling é um recurso que foi introduzido a partir do ASP.NET 4.5. Este mecanismo procura diminuir a quantidade de requisições HTTP utilizadas na obtenção das diferentes partes que formam o conteúdo de um documento HTML (como arquivos CSS e de scripts, conforme já mencionado), aumentando desse modo a performance no carregamento da página em questão. O uso do caracter “*” possibilita que o ASP.NET MVC busque a versão mais recente de um arquivo (considerando para isto que o conjunto de caracteres substituídos por este símbolo corresponda a um número de versão).

Listagem 4: Ajustes a serem efetuados na classe BundleConfig


using System.Web;
using System.Web.Optimization;

namespace TesteSQLAzure.MVC
{
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {

            ...

            // Validações no padrão brasileiro
            bundles.Add(
                new ScriptBundle("~/bundles/validations_pt-br")
                    .Include(
                        "~/Scripts/jquery.validate.custom.pt-br*"));

            ...

        }
    }
}

Mais ajustes ainda precisarão ser feitos na aplicação, a fim de que não ocorram erros durante a conversão de valores em que constem separadores de decimais e milhar (vírgulas e ponto no caso brasileiro).

A transferência de valores entre o código HTML pertencente uma View MVC e um Controller é feita por meio de um tipo de estrutura conhecida como Model Binder. O ASP.NET MVC já conta por default com diversas construções baseadas neste recurso, sendo possível ainda adicionar a uma aplicação customizações específicas.

Em termos práticos, todo Model Binder corresponde a uma classe derivada da interface IModelBinder (namespace System.Web.Mvc). A manipulação de informações acontece a partir de um método chamado BindModel.

Na Listagem 5 está a definição da classe DecimalBinder. Este tipo será responsável pela conversão em tempo de execução de strings para valores do tipo decimal, considerando para isto o padrão de representação monetária vigente no Brasil.

Quanto ao método BindModel, esta operação foi implementada da seguinte forma:

  • O primeiro dos parâmetros recebidos por BindModel é uma instância do tipo ControllerContext (namespace System.Web.Mvc), a partir da qual é possível se obter informações sobre a requisição HTTP atual e o Controller associado a esta última. Já o segundo parâmetro corresponde a uma referência da classe ModelBindingContext (namespace System.Web.Mvc), com este objeto permitindo o acesso a funcionalidades para a manipulação de um tipo de valor específico, pertencente à classe de modelo ao qual o mesmo está vinculado;
  • A execução da operação BindModel se inicia com a obtenção de uma instância do tipo ValueProviderResult (namespace System.Web.Mvc), a partir do parâmetro bindingContext. Esta referência corresponde ao valor monetário que se estará convertendo posteriormente;
  • Uma instância baseada na classe ModelState (namespace System.Web.Mvc) é gerada neste momento. Este objeto será empregado posteriormente pelas próximas instruções que constam em BindModel;
  • A conversão do valor para o formato decimal será feita dentro de um bloco try-catch, considerando para isto a cultura atual (que foi configurada no arquivo Web.config como “pt-BR”). Caso ocorram problemas durante este procedimento, uma mensagem é adicionada à propriedade Errors do objeto modelState;
  • A referência vinculada ao parâmetro bindingContext é então acionada novamente, de forma a se preencher a propriedade ModelState deste objeto com informações relativas à propriedade de uma classe de modelo que se está verificando;
  • Finalmente, o valor convertido é retornado como resultado da execução de BindModel.

Listagem 5: Classe DecimalModelBinder


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Globalization;

namespace TesteMVC
{
    public class DecimalModelBinder : IModelBinder
    {
        public object BindModel(
            ControllerContext controllerContext,
            ModelBindingContext bindingContext)
        {
            ValueProviderResult valueResult =
                bindingContext.ValueProvider
                    .GetValue(bindingContext.ModelName);
            ModelState modelState =
                new ModelState { Value = valueResult };
            object actualValue = null;
            try
            {
                actualValue = Convert.ToDecimal(
                    valueResult.AttemptedValue,
                    CultureInfo.CurrentCulture);
            }
            catch (FormatException e)
            {
                modelState.Errors.Add(e);
            }

            bindingContext.ModelState.Add(
                bindingContext.ModelName, modelState);
            return actualValue;
        }
    }
}

Já a Listagem 6 apresenta os ajustes que precisarão ser efetuados junto ao arquivo Global.asax. O método Application_Start da classe MvcApplication foi alterado, de forma que se registre o Model Binder criado anteriormente: tal procedimento é realizado adicionando o relacionamento entre um tipo de dado e a classe responsável pelo tratamento de informações baseadas no mesmo.

No caso específico do exemplo aqui discutido, a estrutura DecimalModelBinder está associada a valores baseados no tipo decimal (considerando inclusive a utilização do recurso conhecido Nullable).

Listagem 6: Ajustes a serem efetuados no arquivo Global.asax


using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace TesteSQLAzure.MVC
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {

            ...

            ModelBinders.Binders.Add(
                typeof(decimal), new DecimalModelBinder());
            ModelBinders.Binders.Add(
                typeof(decimal?), new DecimalModelBinder());
        }
    }
}

Por fim, na Listagem 7 está o trecho de código que deverá ser adicionado em Views nas quais serão empregados os recursos de validação do framework ASP.NET MVC. A primeira instrução refere-se ao conjunto de scripts de validação gerados automaticamente ao se criar um projeto MVC, ao passo que a segunda linha de código visa carregar o script descrito anteriormente (e que contempla as validações de valores monetários segundo o padrão brasileiro).

Listagem 7: Código a ser adicionado ao final de Views em que acontecerão validações


@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/bundles/validations_pt-br")
}

Conclusão

A construção de telas envolvendo a inclusão/atualização de registros representa um tipo de tarefa bastante frequente no dia-a-dia dos desenvolvedores de software. Muitos destes cadastros podem contar com campos em que se espera o preenchimento de valores baseados no formato de moeda adotado no Brasil.

Procurei então com este artigo demonstrar uma possível solução para problemas envolvendo a manipulação de valores monetários no ASP.NET MVC. Espero que o conteúdo aqui apresentado possa lhe ser útil em algum momento. Até uma próxima oportunidade!

Veja também