Posts Marcados ASP.NET MVC

Imagina na Copa!

Olá pessoal! Acharam que eu tinha abandonado este blog em definitivo?! Negativo, ainda estou firm e forte por aqui! :-)… Só que vou parar de prometer voltar a escrever em um ritmo normal, já que cada vez que faço isto, acontece algo que me impede totalmente… No último post, falei sobre o aplicativo do Sistema Poliedro que estávamos trabalhando e que posteriormente foi denominado P4Ed (ou simplesmente P+). Então, de lá para cá ficamos (e continuamos!) completamente envolvidos com ele, muitas novas funcionalidades, várias versões, centenas de APIs… A compensação é que vemos o resultado, utilizado por milhares de alunos todos os dias, e nos sentimos orgulhosos de termos contribuído. Os aplicativos são um sucesso e nem tudo ainda foi disponibilizado, as próximas versões irão conter ainda mais funcionalidades e características e farão o P+ ser cada vez mais uma referência absoluta de mercado.

O trabalho neste projeto nos fez confrontar vários “dogmas” internos da White Fox. Como visto em posts anteriores, um dos grandes diferenciais da White Fox é o uso de um framework que permite grande produtividade no desenvolvimento de sistemas e, em especial, de interfaces. Porém, nestes 4 anos de empresa, duas coisas aconteceram: como mencionamos no post anterior, o trabalho do P+ nos fez focar na entrega de produtos SEM interface; nós fomos encarregados de desenvolver APIs e regras de negócio de servidor enquanto que empresas parceiras trabalham em paralelo na confecção de interface. Embora isto nos permitiu desenvolver o P+ em tempo recorde, ele fez com que grande parte do nosso framework fosse totalmente descartada. Daí tivemos que nos reinventar pra conseguir, tendo uma API como produto final, ter a mesma produtividade que estávamos acostumados.

O segundo fato importante destes 4 anos foi uma mudança significativa na característica das interfaces. A web continua importante, mas temos também agora, em pé de igualdade, interfaces de dispositivos móveis (em seus vários tipos) e integrações diversas com outros sistemas e plataformas. As próprias interfaces web, graças a uma evolução cada vez maior de frameworks javacript (como AngularJS, Backbone, Knockout etc.) fizeram com que todo o conceito de desenvolvimento mudasse. Com isto, parte de nosso framework perdeu sua aplicabilidade. Sobre isto, espero fazer um ou mais posts específicos, revisitando o assunto de produtividade.

O bom de se trabalhar com desenvolvimento é que o trabalho nunca é monótono. As mudanças acontecem, e em ritmo rápido. A própria Microsoft, talvez pressionada por plataformas diferentes, tem acelerado bastante o ciclo de vida de suas ferramentas e plataformas. Mal o Visual Studio 2013 foi lançado, no final do ano passado, e já tivemos o Update 1 (e, brevemente, o Update 2). Web API, aplicações MVC e a evolução acelerada da plataforma Azure, com um sem número de facilitadores, módulos e serviços prontos, faz com que tenhamos que repensar toda nossa infraestrutura de código. Só que temos que fazer isto com o avião voando; temos um sem número de sistemas pra manter, que devem continuar funcionando e ao mesmo tempo serem evoluídos para fazer uso de toda esta nova tecnologia.

Junte a isto tudo o desafio de escalabilidade do P+, também mencionado anteriormente. Embora, como arquitetos, nós busquemos fazer sistemas que sejam escaláveis, isto vale até certo ponto. Uma coisa é projetar um sistema para 10 usuários que pode chegar a 10.000 em um ano. Outra, totalmente diferente, é projetar um sistema para 5.000 que pode chegar a 1.000.000 de usuários em pouco tempo. Isto exige um grande planejamento, um trabalho grande de identificação de gargalos e, às vezes, soluções pouco ortodoxas. No P+ temos trabalhado incessantemente para buscar arquiteturas que permitam suportar grandes volumes na Cloud sem ter que reescrever a aplicação a cada aumento. É grande desafio e que, quando não adequadamente tratado, gera situações complicadas de gerenciar. Mais ou menos como suportar uma Copa do Mundo em um ambiente sem a infraestrutura adequada. E nem é só uma questão de recursos, se eles forem mal empregados ou seu uso for mal planejado, vocês podem imaginar o resultado – ou vivê-los, como vamos ter a oportunidade de fazer aqui na cidade maravilhosa, em menos de 1 mês.

Vou ficando por aqui. Tenho vários tópicos mais técnicos rascunhados que espero em algum momento transformar em posts. Até a próxima então!

, , , , , , , , , ,

1 comentário

Engine MVC baseada em XSLT

Na série sobre produtividade, no post sobre a camada de interface, eu  falei um pouco sobre o uso de XSLT na transformação de arquivos XML para gerar HTML. Várias pessoas me pediram mais detalhes sobre a engine utilizada. Neste artigo, vou entrar em mais detalhes desta engine usando o MS-MVC e fazer um paralelo com a implementação Monorail. Este artigo é bem técnico e pressupõe que o MS-MVC seja bem conhecido, em especial na parte de criação de custom view engines.

No download do MS-MVC existe uma View Engine baseada em XSLT. Existem também outras iniciativas como a Chris Hampson. Estas não nos atenderam porque se baseiam em um arquivo XML que é transformado diretamente pelo XSLT. No nosso caso é necessário injetar, além do arquivo XML de definição, dados que serão utilizados também pelo XSLT pra construir a tela. Finalmente, as atividades realizadas pelo controller também interferem na página resultante. Por exemplo, se uma ação gera um erro, a página resultante deve ser um redirecionamento para a página de tratamento de erros. Estas razões fizeram com que a gente desenvolvesse a nossa própria ViewEngine.

Em linhas gerais, a nossa ViewEngine simplesmente obtém o XML de definição, o XML de dados (que chamamos de DataIsland) e aplica um XSLT para gerar um HTML resultante (ver o post sobre camada de interface para exemplos destes artefatos). Para fazer isto no MS-MVC, é necessário implementar duas classes: uma que implemente a interface IViewEngine e uma que implemente a IView. Além destas duas, a implementação de nossa engine usa um controller base, para que possamos interceptar alguns métodos (ver adiante).

A IViewEngine tem que implementar os métodos FindView, ReleaseView e o FindPartialView. No nosso caso, a única coisa importante é o FindView. Este método é responsável por identificar o arquivo de definição XML e passar o tipo de ação e dados de apoio para a ViewBase, conforme código a seguir:

private ViewEngineResult FindView(ControllerContext controllerContext) {
            
            var server = controllerContext.HttpContext.Server;
            const string extension = "html";
            var area = string.Empty;

            if (controllerContext.RouteData.Values.ContainsKey("area")) 
                area = controllerContext.RouteData.Values["area"] + "/";

            var controller = (BaseController)controllerContext.Controller;
            var controllerName = 
                controller.GetType().Name.Replace("Controller", "")
                   .ToLowerInvariant();
            var path = string.Empty;
            if (controller.OutputType == OutputType.Html || 
                controller.OutputType == OutputType.XmlView) {
                if (controller.ViewNameOrigin == 
                        BaseController.ViewNameOriginType.Controller 
                    && string.IsNullOrEmpty(controller.ViewName))
                    path += string.Format("~/views/{0}{1}.{2}", area, 
                            controllerName, extension);
                else
                    path += string.Format("~/views/{0}{1}/{2}.{3}", area, 
                        controllerName, controller.ViewName ?? 
                        controller.ActionName, extension);
                if (!File.Exists(server.MapPath(path)))
                    return new ViewEngineResult(new[] { path });
                path = server.MapPath(path);
            }

            var dataIsland = 
                  (controller.OutputType != OutputType.XmlAction) ? 
                                controller.GetDataIsland() : null;
            var view = new XsltView(controller.OutputType, 
                controllerContext.RouteData.Values["area"].ToString(), 
                path, controllerName, controller.ActionName,  
                dataIsland == null ? null : dataIsland.Root, 
                controller.ElementsToRender, controller.Message, 
                controller.RedirectUrl);          
            return new ViewEngineResult(view, this);
        }

 

Como pode ser visto acima, temos dois casos de arquivos de definição. Alguns que tem o próprio nome do controller, e alguns que tem o nome da action, situados em um folder com o nome do controller. A última parte deste método chama o construtor da XsltView, que implementa a IView.

A XsltView é que contém o código principal da ViwEngine. Ela é responsável por efetivamente fazer a transformação XSLT e depois fazer escrever o HTML gerado. A transformação no nosso caso é um pouco mais complexa do que simplesmente rodar o Transform() de uma classe XsltCompiledTransform do C#. A gente faz também uma série de manipulações no XML de origem, fazendo a junção dele com o XML do DataIsland, alterando paths relativos (por exemplo, substituindo string ‘~/’ pelo path físico da aplicação) e suportando áreas que possuem XSLTs diferentes (por exemplo, temos áreas que simplesmente injetam HTML final; temos outras que usam pequenos templates XSLTs para cada controle; e outras que simplesmente fazem referência a um XSLT externo). Esta transformação poderia ser tema de um artigo por si só, quem tiver interesse em saber mais sobre ela, envie-me um email direto.

O método Render da XsltView é mostrado a seguir. Como mencionei acima, dependendo da ação executda nós podemos ter vários resultados possíveis. Temos HTML simples e, para chamadas que foram feitas por AJAX, temos opção de mandar mensagens de alerta, trechos de HTML para serem substituídos ou simples comandos de Redirect. Para suportar isto, fizemos uma mensagem XML que é decodificada por um arquivo .js que é responsável por tomar a decisão correta do que fazer com base nos elementos enviados.

public void Render(ViewContext viewContext, TextWriter writer) {
  XElement result;
  if (outputType == OutputType.Html) {
      var ns = GetNamespaces(contents.Document);
      if (!String.IsNullOrEmpty(redirectUrl)) {
       writer.Write("<html><script language='javascript'>location.href='" 
           + redirectUrl + "';</script></html>");
       return;
      }
      else {
       result = contents.XPathSelectElement(HtmlElementPath, ns);
       writer.Write(@"<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 
 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
      }
  }
  else {
      viewContext.HttpContext.Response.ContentType = "text/xml";
      result = new XElement("Result");                
      if (!string.IsNullOrEmpty(message))
        result.Add(new XElement("Message", message));       
        if (!String.IsNullOrEmpty(redirectUrl)) {
           result.Add(new XAttribute("status", 
                       hasView ? "redirect" : "ok"));
           result.Add(new XElement("Redirect", redirectUrl));
        }
        else {                    
           switch (outputType) {
             case OutputType.XmlAction:
                result.Add(new XAttribute("status",  "ok"));
                break;
             case OutputType.XmlMessage:
                result.Add(new XAttribute("status",  "message"));
                break;
             case OutputType.XmlDataOnly:
                result.Add(new XAttribute("status", "ok"));
                result.Add(contents);
                break;
             case OutputType.XmlView:
                result.Add(new XAttribute("status", "ok"));
                if (elementsToRender != null) {
                   foreach (var element in elementsToRender) {
                      var ns = GetNamespaces(contents.Document);
                      var idName = element;
                      if (element == "Form" || element == "List"
                            || element == "CRUDList") {
                         idName = element + "Contents";
                         result.Add(
                           new XAttribute("configurePage", "true"));
                      }
                      var dataContents =
                      contents.XPathSelectElement(
                      String.Format("//*[name()='div' and @id='{0}']",
                             idName), ns);
                      if (dataContents == null) continue;
                      var item = new XElement("Data");
                      item.Add(new XElement("Id", idName));
                      var html = new XElement("Contents");
                      html.Add(new XCData(dataContents.ToString()));
                      item.Add(html);
                      result.Add(item);
                   }
                }
                break;
          }
     }
   }
   writer.Write(result.ToString());
}

O último componente da engine é o BaseController. Este controller faz o override de dois métodos do MS-MVC, o primeiro é o OnActionExecuting e OnActionExecuted, mostrados a seguir.

protected override void OnActionExecuting(
                                     ActionExecutingContext context) {
    ActionName = 
             context.ActionDescriptor.ActionName.ToLowerInvariant();
    if (HasAttribute<XmlResultAttribute>(context.ActionDescriptor)) {
        OutputType = OutputType.XmlView;
        var viewName = GetViewName(context.ActionDescriptor);
        if (!string.IsNullOrEmpty(viewName)) ViewName = viewName;
    }
    else 
        if (HasAttribute<ActionResultAttribute>(
                                   context.ActionDescriptor)) {
            OutputType = OutputType.XmlAction;
        }
        else
            if (HasAttribute<DataOnlyResultAttribute>(
                                    context.ActionDescriptor))
                OutputType = OutputType.XmlDataOnly;
            else {
                if (HasAttribute <HtmlResultAttribute>(
                                       context.ActionDescriptor)) {
                    var viewName =
                             GetViewName(context.ActionDescriptor);
                    if (!string.IsNullOrEmpty(viewName)) 
                              ViewName = viewName;
                }
                OutputType = OutputType.Html;
            }
    base.OnActionExecuting(context);
}

protected override void OnActionExecuted(ActionExecutedContext context) {
    if (context.Exception != null && !context.ExceptionHandled) {
        if (OutputType != OutputType.Html) {
            ProcessException(context.Exception);
            context.ExceptionHandled = true;
        }
    }
    base.OnActionExecuted(context);
}

Como pode ser visto, antes da execução, através de atributos da action, é possível determinar qual o tipo de retorno desejado. E após a execução, caso tenha ocorrido alguma exceção, é feito um tratamento da mesma.

Acho que deu para ter uma idéia de como a nossa engine foi construída. No Monorail, tudo funciona exatamente da mesma forma, a única diferença são nomes diferentes para interfaces e métodos do controller. No Monorail ao invés da IViewEngine, temos que herdar da ViewEngineBase. Não há interface específica para a View pois quem faz o Render é a própria ViewEngineBase. E no controller, o método a sofrer override é o InvokeMethod. Se alguém desejar informações específicas sobre o Monorail ou sobre qualquer outra área desta engine, basta me mandar um email.

, , , , , ,

Deixe um comentário

Produtividade – Camada de Interface Parte 1

Em mais um post sobre a série sobre produtividade, vou escrever sobre a camada de interface. Por interface eu me refiro à web, já que é raro nós termos grandes desenvolvimentos de aplicativos Windows – e mesmo nestes casos não é algo problemático, o desenvolvimento em Windows Forms do .NET é bastante tranqüilo para aplicações de pequeno porte. Intrefaces baseadas em serviços também são super simples para serem construídas, ainda mais usando a WCF Factory. Assim, o foco é a construção de páginas web. Para não tornar este post muito longo, eu o dividi em duas partes.

Na minha experiência, o desenvolvimento de interfaces web é onde se gasta mais de 90% do tempo de desenvolvimento e manutenção de qualquer sistema de médio ou grande porte. É também onde a evolução tecnológica se faz mais sentir, a disseminação de uso do AJAX, por exemplo, mudou completamente o modo como se desenvolve para web. E para os usuários, a qualidade da interface e navegabilidade muitas vezes é um dos maiores fatores de sucesso ou fracasso de um sistema, fazendo com que o investimento nestes artefatos seja primordial.

Para ilustrar o que busco, vou começar descrevendo o que pra mim é o pior cenário possível para interfaces. Tenho certeza que quem desenvolve para web já viu algo assim em algum momento da carreira. Todo sistema começa bem e o ASP.NET WebForms ainda é o método mais comum de construção utilizado. Assim, no início da vida do sistema, as telas são feitas de maneira mais ou menos rápida. Porém com o tempo, os usuários pedem evoluções para suportar algo pouco usual, um ou outro desenvolvedor menos experiente acaba colocando sua “contribuição” para as telas e o que antes era simples se torna extremamente complexo. Depois de alguns anos (ou meses, dependendo do sistema), o que temos é uma coleção de aberrações, cada tela com código específico, com coisas indo pra sessão outras para viewstate, código inline (com o famigerado <% %>), regras de negócio em .cs de páginas (ou ainda, pesadelo dos pesadelos, no mais famigerado <% %>). Se o sistema foi portado do ASP então, a visão dantesca se completa, pois nestes caos é difícil encontrar qualquer coisa minimamente estruturada. O uso de componentes de terceiros ou frameworks só complica, pois também tendem a gerar ainda mais telas fora do padrão, com todos os tipos de “puxadinhos” imagináveis. Ou seja, em casos como este é muito mais fácil refazer a aplicação do que tentar arrumar. Porém quando se resolve reconstruir, o ciclo se repete e daqui a alguns anos (ou meses!) está tudo como era – normalmente deixando mais um legado para se manter, para a infelicidade de quem ficou.

Como resolver isto? Como desenvolver rapidamente e ainda assim gerar sistemas que não se autodestruam com a manutenção/evolução? A solução que eu uso são frameworks de interface que tentam garantir a estruturação das interfaces de uma maneira que facilite que os desenvolvedores fiquem em um padrão pré-estabelecido e ao mesmo tempo tentando gerar o máximo de flexibilidade possível. Nesta linha, eu tenho sistemas desenvolvidos há quase oito anos que continuam bem estruturados. Naquela época a amarração era tão forte que as telas produzidas eram muito simples, gerando sistemas de usabilidade ruim, comparando com os atuais. Porém existem vários operando até hoje que são simples de manter e (até hoje) de evoluir. De lá pra cá a evolução do framework já permite gerar telas extremamente amigáveis, mantendo ainda os níveis de manutenabilidade originais. Claro que esta não é uma solução para qualquer empresa ou time, o nível de treinamento e envolvimento com o framework é muito alto para ser facilmente replicável. Mas no nosso caso está sendo muito bem sucedido.

O framework utiliza um tipo de padrão MVC, porém com uma separação clara em 4 componentes: 1) Estruturas de Interface – aqui entram caixas de texto, checkbox, botões, listas etc. São itens que contém os dados sendo apresentados e que serão manipulados pelos usuários. 2) Layout – que é como as estruturas de interface são mostradas em tela. No layout entra toda a parte visual como estilos, manipulação visual (via jQuery por exemplo), controles, figuras e até coisas como uso de janelas popup ou chamadas AJAX. 3) Dados – utilizando um conceito que também utilizado no Microsoft Sharepoint, definido em uma ilha de dados XML que contém todas as informações que serão utilizados pela interface. 4) Controllers – controladores da interface, onde os dados são construídos e as ações executadas, fazendo uso das entidades de domínio e chamando os serviços da mesma.

Separando desta forma, fica fácil notar quais elementos são afetados quando há uma mudança, fazendo com que a mesma impacte o menos possível os demais. Esta estruturação faz com que o desenvolvimento de novas telas seja concentrado em 2 pontos, na definição de estrutura de interface e nos controllers. A clara separação existente evita que uma área afete outra.

Na minha experiência, o Layout é um dos pontos que mais evoluem na interface. Embora em linha gerais ele fique estático (p. ex., pode se definir que o sistema vai ter um cabeçalho com o nome do sistema, usuário logado e menu; no meio com uma área de formulários e listas e com inclusões em popup, tudo usando Ajax), as pequenas modificações acabam sendo freqüentes. É a inclusão de um novo elemento no cabeçalho, o suporte de uma lista hierárquica, um novo tipo de componente e assim por diante. Para garantir que não aja uma mistura de responsabilidades, eu uso XSLT para a definição de todos os artefatos de layout. Este é um dos pontos de maior dificuldade de aprendizado no framework, já que não é usual que desenvolvedores usem diariamente XSLT. Mas, como falei no post sobre princípios, o XSLT é uma linguagem declarativa que evita o uso de código imperativo e o uso de artefatos como sessão, viewstate etc. E uma vez dominado, o XSLT é extremamente eficiente para se gerar o HTML final (sem falar que já gera o HTML correto!).

Para gerar o HTML final, os arquivos XSLT de layout utilizam duas fontes de transformação: os dados e a estrutura de interface. Isto já faz com que os dados tenham que ser facilmente descritos em XML. Eu vou a um ponto mais extremo, fazendo com que todos os nossos dados sejam descritos somente em XML – também como é feito no Microsoft Sharepoint. Com isto, minimizamos o uso de objetos DTO e tornamos a estrutura de dados da interface completamente maleável e fácil de evoluir.

Finalmente, a estrutura de interface também deve ser descrita em XML, para que seja facilmente transformável. O que fizemos foi criar uma DSL, integrada ao Visual Studio (com intellisense, claro), para descrever cada elemento de interface. Vejam como fica um exemplo simples de um formulário de login:

Exemplo de DSL de Interface:

<Form defaultFocus="Login">
     <Field data="Login" type="textbox" required="true" label="Login"  />
     <Field data="Senha" type="textbox" required="true" label="Senha"  password="true" />
     <Command label=”Logar” action=”Login”/>
</Form>

Exemplo de XSLT de Layout (super reduzido para efeito de ilustração):

<xsl:stylesheet version="1.0" >
     <xsl:template match="Form">
          <html>
                <body>
                     <form method="post">
                          <xsl:apply-templates select=”Field|Command” mode=”Control”/>
                     </form>
                 </body>
           </html>
      </xsl:template>
</xsl:stylesheet>

No XSLT acima, cada Field é transformado em um input box e o Command em um button. Percebam que não há código inline – não existe o <% %>. Este tipo de arquitetura era muito dificil de ser construída e mantida no ASP.NET Webforms, o que tornava o framework bastante complexo. Felizmente com o advento de bibliotecas MVC, isto ficou muito mais simples. Inicialmente, criamos  uma View Engine para suportar esta DSL no Castle Monorail. Com o advento posterior do ASP.NET MVC, criamos uma a View Engine para ele que funciona com exatamente a mesma DSL e os mesmos arquivos de layout. Esta migração mostrou a capacidade de evolução do framework, o que me deixa tranquilo para encarar a manutenção dos nossos sistemas por, quem sabe, outros 8 anos por vir.

No próximo post vou explicar como as ações são executadas e entrar nos detalhes dos controllers, mostrando como isto tudo se integra.

Até breve!

, , , , , , , , ,

1 comentário