image[1]

Esta é uma edição especial da série de IronRuby com C#. Este seria o quarto post. Vejam os anteriores:

  1. Rodando Ruby com C#: Parte 1
  2. Rodando Ruby com C#: Parte 2 – chamando uma classe Ruby no C#
  3. Rodando Ruby com C#: Parte 3 – chamando uma classe C# a partir do Ruby

Nesse eu vou pegar uma tarefa maior: vou colocar pra rodar o IronRuby com ASP.Net MVC 2.0, .Net 4.0, e usando o Visual Studio 2010.

O que você vai precisar:

  1. IronRuby CTP para .Net 4 Beta 2
  2. Visual Studio 2010 Beta 2
  3. A solução do projeto (sem incluir o banco Northwind)

O trabalho está baseado no que já havia sido desenvolvido pelo Jimmy Schementi. Eu não criei tudo do zero, simplesmente evolui uma idéia muito boa que já existia. Boa mas bastante desorganizada. Encontrei muito código incompleto, ou que não fazia nada. Gastei um bom tempo limpando o projeto, reorganizando, removendo arquivos desnecessários, refatorando. Atualizei todas as referências, do IronRuby, do .Net, das bibliotecas de testes, enfim, todas estão com as últimas versões disponíveis neste momento. Não está 100% do jeito que eu gostaria, mas está bom o suficiente para ser entendido.

A solução funcionando:

Home Page:

Home Page

Tela de categoria de produtos:

Tela de categoria de produtos

Selecionando uma categoria os produtos desta categoria são exibidos:

Selecionando uma categoria os produtos desta categoria são exibidos

Editando um produto:

Editando um produto

Tela de Login (não tem master page):

Tela de Login (não tem master page)

Aqui está a solução no Visual Studio 2010, notem os arquivos .rb e .erb, arquivos Ruby, responsáveis pela execução:

Solution Explorer

Há três projetos: um de teste (IronRubyMvcLibrary.Tests), um de web (IronRubyMvcWeb), e um de infraestrutura (IronRubyMvcWeb.Core). O projeto de infraestrutura, que vocês podem ver lá embaixo do Solution Explorer contém todo o código necessário para integrar o ASP.Net MVC e o IronRuby.

Vou mostrar neste post as bases de como funciona o projeto MVC. O projeto de infra fica para outro dia.

O projeto é um projeto de ASP.Net MVC 2 comum do Visual Studio 2010. O projeto é baseado fortemente em convenções. Para registrar as rotas, você coloca um arquivo rotas.rb na raiz. O meu usa o caminho padrão de rotas. Vejam:

#default routes
$routes.ignore_route("{resource}.axd/{*pathInfo}");
$routes.map_route("default", "{controller}/{action}/{id}", {:controller => 'Home', :action => 'index', :id => ''})

O web.config é um web.config normal, não foi alterado em nada específico.

O modelo

O modelo é baseado em LINQ to SQL para obter os dados, que claro, é baseado em um banco de dados Northwind.

Modelo LINQ to SQL

Ainda que existam 4 classes definidas, só produtos e categorias estão sendo usadas. Não vou entrar em detalhes do LINQ to SQL, até porque quase ninguém hoje em dia começa projeto com LINQ to SQL.

O projeto utiliza o padrão repositório. Uma classe IronRubyRepository, feita em C#, define os métodos de obtenção dos dados. Vejam aqui alguns deles (ocultei pedaços da classe por brevidade):

public class IronRubyRepository 
{
    private readonly NorthwindDataContext _dataContext;

    public IronRubyRepository () : this(new NorthwindDataContext()){}

    public IronRubyRepository(NorthwindDataContext dataContext)
    {
        _dataContext = dataContext;
    }

    public virtual IQueryable<Category> Categories
    {
        get { return _dataContext.Categories; }
    }

    private Category GetCategoryByName(string name)
    {
        return Categories.SingleOrDefault(c => c.CategoryName == name);
    }
    //abreviada por brevidade
}

E uma classe ProductsRepository, definida em Ruby, herda dela:

require 'helpers/model'

class ProductsRepository < IronRubyRepository
end

Viram o require ‘helpers/model’? Ele indica que precisamos do arquivo model.rb do diretório helpers. Nele estão umas declarações de importações de namespaces .Net:

include IronRubyMvcWeb::WebApp::Models
include IronRubyMvcWeb::WebApp::Models::Northwind

O ProductsRepository simplesmente herda do IronRubyRepository, e não muda nada. Ele vai ser usado no controller de produtos. Temos ainda um arquivo de modelo para a Home, o HomeModel.rb:

class HomeModel
  def salutation
    "ASP.NET MVC <3 IronRuby!!!"
  end
end

Os controllers

Os controladores são definidos diretamente em Ruby. Há toda uma infraestrutura por trás, que vou discutir outro dia. Basicamente eles herdam de uma classe base Controller, que também é definida em Ruby no projeto de Infra. Abaixo vocês vêem o HomeController, também editado por brevidade. Nele há ação “index” para a página principal, e as ações para fazer o login, que na verdade só está simulado (autenticação ainda não está integrada). Notem que a  ação index coloca um item no view data, e chama o método view, passando nulo para o nome da view, “layout” para o nome da master page, e um novo modelo HomeModel.

require "HomeModel"
require 'MyFilter'

class HomeController < Controller

  def index
    view_data['salutation'] = "Hello there!"
    view(nil, 'layout', HomeModel.new)
  end

  def validation    
    view  
  end

  def validate
      #simple validation, just to prove a point
    if (params[:username] != "giovanni" || params[:password] != "brazil")
        model_state.add_model_error("username".to_clr_string, "You must specify a username.") 
        model_state.add_model_error("password".to_clr_string, "You must specify a password.") 
        model_state.add_model_error "_FORM", "The current password is incorrect or the new password is invalid."
        view 'validation', nil
    else
        redirect_to :action=>"index"
    end
  end

  #editado por brevidade
end

O controlador de produtos é mais interessante. Ele interage com o repositório, também um pouco cortado por brevidade:

require 'ProductsRepository'

class ProductsController < Controller

  def index
    repository = ProductsRepository.new    
    @categories = repository.categories
    view nil, 'layout', @categories
  end

  def list
    @category = params[:id]    
    repository = ProductsRepository.new
    @products = repository.get_products_for_category @category    
    view nil, 'layout', nil
  end

  def edit
    @id = params[:id]    
    repository = ProductsRepository.new
    @product = repository.get_product @id
    view nil, 'layout', nil
  end

  def update
    id = params[:id]
    category = params[:category_name]    
    repository = ProductsRepository.new
    @product = repository.get_product id
    @product.product_name = request.form.get_Item('Product.productname')
    @product.unit_price = to_decimal request.form.get_Item('Product.unitprice')
    repository.submit_changes    
    redirect_to 'list', {:id => category }
  end

  def to_decimal(value)
      System::Convert::ToDecimal value
  end
end

Vejam que o código é extremamente simples. A única diferença nos métodos é que todas as letras são minusculas e separadas por underscore, de resto é muito parecido com um código C#. Não vou explicar mais, acho que o código fala por si só.

As views

O ViewEngine foi feito sob medida. Há todo um mecanismo para exibir as views e as master pages, muito parecido com uma view aspx. Vejam aqui a view da ação index do controlador Home:

<div class="main">
  <p>
    The following message is brought to you by the 
    <code>salutation</code> property of the Ruby <code>HomeModel</code>
    object within <code>/Models/HomeModel.rb</code>.
  </p>
  <blockquote>
    <p>
      <strong class="salutation">
        <%= model.salutation %>
      </strong>
    </p>
  </blockquote>

  <p>
    And the following message is brought to you by the view data.
  </p>
  <blockquote>
    <p>
      <strong class="salutation">
        <%= view_data['salutation'] %>
      </strong>
    </p>
  </blockquote>

  <% 8.times do |i| %>
    Testing Loop <%= i %><br />
  <% end %>
  <%= html.render_partial("partial", "my model!") %>
  <p>
    Click on <strong>
      <%= html.action_link("Products", "index", "products") %>
    </strong> to get started.
  </p>
</div>

Na linha 10 ele exibe o valor do modelo e na linha 21 do view data. Na linha 29 há uma chamada a uma view parcial chamada “partial”, e o modelo “my model!” é passado. Vejam no solution explorer que tipo de view é essa. É uma view aspx. Aqui está ela:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<h2>Hello from <%=ViewData.Model%> in .aspx land!</h2>

E ela integra no código exibido na view de Ruby. Interessante, não é?

Da mesma forma, a view de validação, aquela que não tem master page, é uma view aspx também.

E aqui está o código da master, que vem do arquivo views/shared/layout.html.erb (editada por brevidade):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>IronRuby on ASP.NET MVC Demo</title>
    <link rel="stylesheet" type="text/css" href="/content/style.css" media="screen" />
</head>
<body>
    <div id="wrap">
        <div id="header">
            <h1><%= html.ActionLink("ASP.NET MVC w/ IronRuby Demo", {:controller => "Home", :action => "index"}) %></h1>
            <h2>DLR and ASP.NET MVC BFF Edition</h2>
        </div>

        <div id="menu">
            <ul>
                <li><%= html.ActionLink("Login", {:controller => "Home", :action => "Validation"}) %></li>
                <li><%= html.ActionLink("Home", {:controller => "Home", :action => "index"}) %></li>
                <li><%= html.ActionLink("Products", {:controller => "Products", :action => "index"}) %></li>
            </ul>
        </div>

        <div id="content">
            <div class="main">
                <% yield %>
            </div>
        </div>

        <div id="bottom"></div>

    </div>
</body>
</html>

Vejam o yield na linha 24. É lá que é inserida a página na master. Notem também que os html helpers funcionam perfeitamente, nas linhas 16 a 18, e que, ao invés de usar objetos anônimos como no C#, no Ruby usa-se símbolos.

Toda a iteração de postbacks funciona perfeitamente, e você pode editar os arquivos ruby (controller, model, view, etc) sem precisar recompilar a aplicação, já que no Ruby é tudo interpretado. Na prática a experiência de trabalhar com ASP.Net MVC utilizando IronRuby é muito parecida com a de utilizar C#. Fica uma opção, caso o Ruby se mostra uma opção mais adequada para atender algum cenário específico.

No próximo post farei a segunda parte, explicando como foi feito para tudo funcionar, vou explicar o projeto do infra. Ou você se adianta, baixa a solução, lê o código e entende.

O que você achou? Vale a pena fazer um projeto com ASP.Net MVC e IronRuby? Ou pedaços dele?

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.