Friday, April 23, 2010

ASP.NET MVC vs. Rails3

I recently was contacted to implement an ASP.NET MVC application and I saw this as a great opportunity to compare it with Rails3.
What immediately strikes you when you start with ASP.NET MVC is how similar it is to Rails. No one can steal ideas like Microsoft!
Rails ASP.NET MVC Purpose (if not obvious)
/app/models /Models
/app/controllers /Controllers
/app/views /Views
/public/javascript /Scripts
/public /Content
/db /App_Data Database data, such as migrations, and models.
/test (unit, functional, fixtures, performance) Separate VS projects
/config /Global.asax, /Properties, Web.config Configuration

Project Generation

Both Rails and ASP.NET MVC relies on code generation to get you started, but the methods they use are different.
Rails uses the command line, which is natural since the Rails approach is to not rely on anything but a good programming editor and the command line.
$ rails tapir
      create  .gitignore
   ...
      create  app/controllers/application_controller.rb
      ...
      create  app/views/layouts
      create  config/database.yml
      create  db/seeds.rb
   ...
      create  public/javascripts/application.js
   ...
      create  test/unit
   ...
ASP.NET MVC uses Wizards inside Visual Studio, which is also expected since Microsoft has a long history of IDE-centric application building.
Worth noting is that I didn't really want to use VS Unit Test, since I normally prefer NUnit, but after one hour of googling and testing I gave up and went with VS Unit Test anyway.

Environment

When Rails is installed it comes pre-configured with three different environment, development, test and production. It is as easy as writing RAILS_ENV=test to switch from the default development environment to the test environment. In this area Rails really shines. There is nothing similar in ASP.NET MVC.

The Model

Rails Model

Rails comes pre-configured with an ActiveRecord model, if you don't write otherwise. If you want to use something else it is very easy. Supported frameworks are among others, Neo4J, MongoDB, and DataMapper.
To create a model in Rails3 you use a command line generator. The generator generates a model, a migration and tests. And you can, of course, choose what kind of model you wish to generate (-o), as well as what kind of testing framework you want to use (-t). Here I just go with the default:
$ rails g model customer name:string email:string
      invoke  active_record
      create    db/migrate/20100419094010_create_customers.rb
      create    app/models/customer.rb
      invoke    test_unit
      create      test/unit/customer_test.rb
      create      test/fixtures/customers.yml

Rails comes preconfigured with Sqlite3, and you don't have to do anything to configure the default database. Moreover, the configuration is set up to use three different databases, one for each environment.
Rails, performs all database changes through scripts, migrations. This is invaluable when you want to upgrade a database that is in production, or when multiple developers are changing the same database model. The migration scripts allow seamless migrations between the different databases.
# An example of a migration
class CreateCustomers < ActiveRecord::Migration

  # Called when migrating up to this version
  def self.up
    create_table :customers do |t|
      t.string :name
      t.string :email
      t.timestamps
    end
  end
  
  # Called when migrating down from this version
  def self.down
    drop_table :customers
  end
end

To move between the different versions of the database we use the rake db:migrate command.
# Migrate to the latest version
$ rake db:migrate 

# Migrate to a specific version
$ rake db:migrate VERSION=20080906120000 

# Rollback one version
$ rake db:rollback 

# Rollback three versions
$ rake db:rollback STEP=3

Read more about migrations in the Migrations Guide

ASP.NET MVC Model

ASP.NET MVC is not a full stack framework and it does not come preconfigured with a model. It is possible to choose between many solutions, such as NHibernate, Entity Framework or LINQ-to-SQL. I choose LINQ-to-SQL because I think it is an elegant, lightweight OR-Mapper, that is easy to work with. Too bad, it isn't prioritized by Microsoft.
Unfortunately, there is nothing like migrations in LINQ-to-SQL. So I use the LINQ-to-SQL design tool to design the classes.

You can then create the database from the model. It is also possible to do it the opposite way, to create the database first and then generate the model, but I prefer this way.
// Creating a database from a LINQ-to-SQL DataContext
public void CreateDatabase(bool force)
{
    var db = new TapirDataContext(@"c:\tapirdb.mdf");
    if (db.DatabaseExists() || force)
    {
        Console.WriteLine("Deleting old database...");
        db.DeleteDatabase();
    }
    db.CreateDatabase();
}

As you understand this is not a viable solution for migrating a production database, but it is good enough (almost) for development and testing, since I can allow myself to recreate the database every time. Once I get a larger database, the time it takes to set it up will force me to go with a better way. The lack of migrations hurts.

Query Language

Rails3 uses AREL, Active Record Relations, and LINQ-to-SQL uses LINQ (surprise!). They are both beautiful solutions and remarkably similar. Both solutions create lazy, composable queries, that are not executed until the latest possible time, when a result is needed. In LINQ, you can see Eric Meijer's Haskell shining through like magic, and in AREL, the beauty of Ruby.
# A simple query with AREL
User.where(users[:name].eq('Anders')).order('users.id DESC').limit(20)

// The same with C#
// Lambda Syntax
db.Users.where(u => u.Name == "Anders").orderBy(u => u.Id).Take(20)

// LINQ Syntax
(from u in db.Users
where u.Name == "Anders"
orderby u.Id descending
select u).Take(20);

One thing that you don't get with LINQ-to-SQL is all the methods that are dynamically created, by need, in Rails, such as find_by_name, find_by_name_and_age, etc.

The Controller

In ASP.NET MVC, it is easy to create a controller, you just right-click on the controllers folder and select Add > Controller. You then get a dialog where you can type in the name of the controller and, optionally, if you like to create default methods for the standard CRUD scenario, just select the checkbox.


public class CustomersController : Controller {
      // GET: /Customers/
      public ActionResult Index() {
          return View();
      }

      // GET: /Customers/Details/5
      public ActionResult Details(int id) {
          return View();
      }

      // GET: /Customers/Create
      public ActionResult Create() {
          return View();
      }

      // POST: /Customers/Create
      [HttpPost]
      public ActionResult Create(FormCollection collection) {
          try {
              // TODO: Add insert logic here
              return RedirectToAction("Index");
          } catch {
              return View();
          }
      }
}

As you can see the code generated is simple and clear. That is the benefit of MVC, simpler models, views, and controllers.
The automatic generation of code, that will probably be changed later is commonly known as scaffolding.
Scaffolding is a temporary structure used to support people and material in the construction or repair of buildings and other large structures. --Wikipedia
Scaffolding is not meant to be used as is. It is meant to get you started!
In Rails3 you add controllers with a generator. You have a lot more options, below are just a few of them.
$ rails g controller
Usage:
  rails generate controller NAME [action action] [options]

Options:
  -e, [--template-engine=NAME]  # Template engine to be invoked
                                # Default: erb
  -t, [--test-framework=NAME]   # Test framework to be invoked
                                # Default: test_unit

As you can see it is possible to choose what template-engine you want to use, and exactly what actions you want to create, the view templates are created automatically with every action you create.
$ rails g controller Customer index create
    conflict  app/controllers/customer_controller.rb
      create  app/controllers/customer_controller.rb
       route  get "customer/create"
       route  get "customer/index"
      invoke  erb
      create    app/views/customer
      create    app/views/customer/index.html.erb
      create    app/views/customer/create.html.erb
      invoke  test_unit
    create    test/functional/customer_controller_test.rb
      invoke  helper
    create    app/helpers/customer_helper.rb
      invoke    test_unit
    create      test/unit/helpers/customer_helper_test.rb


If you want to create all the default actions you should instead invoke the scaffold_generator.

$ rails g scaffold_controller
Usage:
  rails generate scaffold_controller NAME [options]

It will create all the default actions and the views that go with them.
The generated code is shown below. Again, you see the similarity between the two solutions. Rails, automatically generates support for both HTML and XML, making it easy to consume the data from other clients.
class FishController < ApplicationController
  # GET /fish
  # GET /fish.xml
  def index
    @fish = Fish.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @fish }
    end
  end

  # GET /fish/1
  # GET /fish/1.xml
  def show
    @fish = Fish.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @fish }
    end
  end

  # GET /fish/new
  # GET /fish/new.xml
  def new
    @fish = Fish.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @fish }
    end
  end

  # GET /fish/1/edit
  def edit
    @fish = Fish.find(params[:id])
  end

  # POST /fish
  # POST /fish.xml
  def create
    @fish = Fish.new(params[:fish])

    respond_to do |format|
      if @fish.save
        format.html { redirect_to(@fish, :notice => 'Fish was successfully created.') }
        format.xml  { render :xml => @fish, :status => :created, :location => @fish }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @fish.errors, :status => :unprocessable_entity }
      end
    end
  end
...
end

Filters

In Rails it is easy to create filters, that can be applied to specific actions.
class ItemsController < ApplicationController
  before_filter :require_user_admin, :only => [ :destroy, :update ]
  before_filter :require_user, :only => [ :new, :create]
end

In ASP.NET MVC, the same thing can be accomplished by overriding OnActionExecuting, in the controller.
override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var action = filterContext.ActionDescriptor.ActionName;
    if (new List<string>{"Delete", "Edit"}.Contains(action)) {
        RequireUserAdmin();
    }
    if ("Create".Equals(action)) {
        RequireUserAdmin();
    }
}

The functionality may also be extracted into an ActionFilter, which can be applied to the controller or an individual method via an Attribute.
[RequireUserAdmin("Delete", "Edit")]
[RequireUser("Create")]
public class CustomersController : Controller

This is a pattern that can be seen everywhere, when Rails uses Class Macros, a term from Meta-Programming Ruby, ASP.NET MVC uses attributes. And this is appropriate since it usually constitutes meta-data.

Routing

The Routing in both Rails and ASP.NET MVC is incredibly flexible.

Rails Routing

In Rails it is possible to put constraints right in the routing table on just about anything.
# config/routes.rb
Tapir::Application.routes.draw do |map|
  resources :animals

  get "customer/index"
  get "customer/create"
  
  match "/:year(/:month(/:day))" => "info#about", 
 :constraints => { :year => /\d{4}/, 
  :month => /\d{2}/, 
  :day => /\d{2}/ }  
  match "/secret" => "info#about", 
 :constraints => { :user_agent => /Firefox/ }     
end


As you can see Rails comes pre-configures with RESTful routing, and it is indeed the recommended way to set up your routes in Rails. To see what the routing result is is easy:
$ rake routes
                GET    /animals(.:format)          {:action=>"index", :controller=>"animals"}
        animals POST   /animals(.:format)          {:action=>"create", :controller=>"animals"}
     new_animal GET    /animals/new(.:format)      {:action=>"new", :controller=>"animals"}
                GET    /animals/:id(.:format)      {:action=>"show", :controller=>"animals"}
                PUT    /animals/:id(.:format)      {:action=>"update", :controller=>"animals"}
         animal DELETE /animals/:id(.:format)      {:action=>"destroy", :controller=>"animals"}
    edit_animal GET    /animals/:id/edit(.:format) {:action=>"edit", :controller=>"animals"}
 customer_index GET    /customer/index             {:controller=>"customer", :action=>"index"}
customer_create GET    /customer/create            {:controller=>"customer", :action=>"create"}
                       /:year(/:month(/:day))      {:year=>/\d{4}/, :month=>/\d{2}/, :day=>/\d{2}/, :controller=>"info", :action=>"about"}
                       /secret                     {:user_agent=>/Firefox/, :controller=>"info", :action=>"about"}


ASP.NET MVC Routing

ASP.NET MVC Routing is not quite as easy, but everything is possible. The standard routing with paths and parameters works easily out of the box.
// Global.asax.cs
 public class MvcApplication : System.Web.HttpApplication { 
 public static void RegisterRoutes(RouteCollection routes) {  
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 
  
  // Constrained route
  routes.MapRoute( "Product", "Product/{productId}", 
   new {controller="Product", action="Details"}, 
   new {productId = @"\d+" } ); // Constraint
  
  // Route with custom constraint, defined below 
  routes.MapRoute( "Admin", "Admin/{action}", 
   new {controller="Admin"}, 
   new {isLocal=new LocalhostConstraint()} ); 
 }
...
}

public class LocalhostConstraint : IRouteConstraint { 
 public bool Match ( HttpContextBase httpContext, Route route,
   string parameterName, RouteValueDictionary values,
   RouteDirection routeDirection ) 
 { 
  return httpContext.Request.IsLocal; 
 } 
} 

As you can see, you have the same power in the ASP.NET MVC Routing, but it requires you to add constraints separately.

Views

In both Rails and ASP.NET MVC, the views are automatically matched in the directory with the same name as the controller, and the file name is named by the action.
app/controllers/customer_controller.rb
app/views/customer
app/views/customer/create.html.erb
app/views/customer/index.html.erb

In Visual Studio it is possible to scaffold a view based on a model, just as it is in Rails. Rails is, of course, more flexible, allowing me to scaffold all or some actions at once. In Visual Studio, it is only possible to do one at a time.

Partials

In both Rails and ASP.NET MVC, a partial is a file that contains a part of a HTML file. The file is written in ASP in ASP.NET MVC and in Rails it is possible to pick the templating language of your choice. Default is ERB, but HAML is also a popular choice.
<!-- Rails -->
<%= render 'form' %>

<!-- ASP.NET MVC -->
<% Html.RenderPartial("Form", Model);%>

In ASP.NET MVC version 2, the preferred way is actually to use two alternate methods to generate the code. They have the advantage that they are type-safe (if that gives you pleasure) and they keep track of nesting.
<%= DisplayFor("Address", m => m.Address ) %>

<%= EditorFor("Address", m => m.Address ) %>

(For Rubyist, the m => m.Address is not a hash key-value expression, but an anonymous function.)

Helpers

A helper is similar to a partial, but it is not a template, it's a method that generates code.
In Rails you create a helper by creating a simple method.
module ApplicationHelper
   def label target, text
     "<label for='#{target}'>#{text}</label>"
   end
 end

And so you can in ASP.NET MVC.
public class LabelHelper {
    public static string Label(string target, string text) {
        return String.Format("<label for='{0}'>{1}</label>", target, text);
    }
}

Conclusion

All in all, ASP.NET MVC with LINQ-to-SQL, is not a bad experience. Eric Meijer and friends have managed to overcome a lot of the hurdles of static typing. They also, let go of it in cases, where dynamic typing is obviously better, such as in the routing and when populating objects from forms. The lambda expressions and the type inference also removes a lot of the boilerplate common in statically typed languages.
Rails is of course a lot more flexible, and faster to work with. The scaffolding is faster and more flexible, the migrations are, as I said before, invaluable when it comes to move between versions.
But, if I'm thrown out of the dynamic Garden of Eden, I'd rather pick my static apples in Redmond, than in California.

47 comments:

Anonymous said...

>There is nothing similar in ASP.NET MVC.

What about debug/release?

>One thing that you don't get with LINQ-to-SQL is all the methods that are dynamically created, by need, in Rails, such as find_by_name, find_by_name_and_age, etc.

With Dynamic Linq and dynamic keywork it's not problem to do that.

Anders Janmyr said...

> What about debug/release?
I don't think debug/release is at all the same as the different environments is Rails.
Can you load different libraries, or connect to different databases based on the debug release environment. Can you copy data from the debug to the release database automatically?

> With Dynamic Linq and dynamic keyword.
Possibly, but, the examples I looked at uses strings instead of late binding. Could you show me an example?

Anonymous said...

I agree, It's not so powerfull, but something, like connectionstring we can change with asp.net 4.0 web.config transformation

example like this http://bugsquash.blogspot.com/2009/05/rails-like-finders-for-nhibernate-with.html ?

Anders Janmyr said...

OK, Thanks for the tip. I'll check it out.

strangy said...

For migrations in asp.net mvc you can use http://code.google.com/p/migratordotnet/wiki/GettingStarted

Jared Ortner said...

The MVC pattern has conceptually existed for some time. Not to sound like a MS fanboy, but a similar feel between two approaches could as easily have come organically. Just my 2 cents.

Simone said...

As for the testing framework: NUnit should have an installer to inject NUnit into the "create test project wizard".

Anonymous said...

I use migratordotnet for my database migration.
http://code.google.com/p/migratordotnet/

I created my own "rake" script that executes the migration, call SqlMetal to generate the data context file and I'm off,

James said...

FWIW, you can change view engines in ASP.NET MVC as well. There is even NHaml which brings Haml to .NET.

Unknown said...

You can use a view engine of your choice in ASP.NET MVC too. For example, HAML is fully supported.

Anders Janmyr said...

@strangy:
I didn't know about the migratordotnet. I don't like having to compile my migrations though.

@Jared Ortner:
Yes, I know the pattern is very old. But it's the way it is used, that makes it obvious to me that, the MS has taken their ideas from Rails. But, I think it is good that they steal or borrow good ideas.

@Simone:
Yes, I couldn't get that to work.

@James Brechtel, @Sami:
Thanks for the info on NHaml, I didn't know about that.

Anonymous said...

The two frameworks only look similar if you are comparing bullet points. No one who has used both would ever use ASPMVC again -- unless you like writing tons of plumbing code, but MS developers are immune to feeling that weight anymore.

Anders Janmyr said...

@Anonymous:
I am certainly not comparing bullet points, I am comparing the experience of using the two frameworks. To me this was a nice surprise since ASP.NET MVC is a lot better than I expected. I would still rather use Rails if given the choice. If you want to know my position on static vs. dynamic typing, feel free to read my previous post. Static Typing is the Root of All Evil

Anonymous said...

>There is nothing similar in ASP.NET MVC.

This is addressed in ASP.NET 4.0. Now you can have any number of configuration files (they derive from main config file). And of course, you can connect to different databases, load different libraries and so on.

BTW, for pre ASP.NET 4.0 era, there was standard way to achieve this. Web Deployment projects. In this case you have to create modifications to the main config for each your environment.

Anonymous said...

Debug/release is for optimization of compiled binaries and whether to include debug info. It is not the same as Rails environment settings which tells rails which database to connect to, user credentials, etc.

Dynamic LINQ defeats the purpose of using LINQ and is frowned upon. Even the template examples here are not typical. .NET developers tend to go crazy with anonymous classes and lambda trickery avoiding literal strings to maintain type checking and safe refactoring. I call it the cleverness over clarity syndrome.

The big problem with ASP.NET MVC is you're stuck on the MS platform. Most of the popular ORMs like NHibernate, Subsonic only officially support SQL Server. There is limited support for open source databases. Deploying to Mono is not a option yet. There are some known GC issues which consume lots of memory in Apache.

Anders Janmyr said...

@Mike Chaliy
It's good that a better solution is coming up.

Unknown said...

@Anonymous
>>Most of the popular ORMs like NHibernate, Subsonic only officially support SQL Server.

NHibernate can use a lot of databases: http://community.jboss.org/wiki/DatabasessupportedbyNHibernate
Also ASP.NET MVC can be run on Mono platform.

Anonymous said...

Thanks for this comparison.

What about asp.net's DisplayFor[Model]/EditorFor[Model] dynamic scaffolding? Did Ruby has same features for dynamically generation partials like in below link?
http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx

Anders Janmyr said...

@anonymous No, Ruby doesn't have dynamic partial generation built in. It would be very simple to build. Take a look at Ryan Bates nifty_scaffold for an example of generating code from an existing model.

Unknown said...

Thanks for a very nice writeup.

ASP.NET MVC developer and enough very smart people rave about Rails that I've been curious to compare.

I'm sure the MicroSofties stole as much as they could and I'm happy they did.

Mark E. said...

I prefer Rails for web development. Professionally, I'm a C# developer. I have tried out MVC 2 and the Dynamic Data Applications as well.

There is another pain point missing from your article. ActiveRecord (in Rails) and other Ruby ORMs support a concept of built-in validation errors. There is nothing like that built into any of the .NET ORMs. Since there is no standard API, there is no way to have a standard display of errors handled for me. Either the controls have to do their validations or it has to be custom added. The model is where those data validations belong. More work, less speed.

Then little things like no "flash" messages in .NET. Sure, I can code it, but it will be specific to that application. More work, less speed.

All in all, I still greatly prefer Rails for web development over any ASP.NET solution. I've been doing both for over 3 years now. And now I hate ASP.NET.

Enjoyed the article.

Anders Janmyr said...

Mark: You can actually put validations in the model layer via Attributes. The attributes are only validated by the controllers though, so the solution is not perfect.

I agree with you that Rails is a much more pleasant and productive environment and it is my preferred choice.

sukumar said...

Rails3 is program ? I can't believe that program. anyway mostly are using that ASP.Net. it's friendly user. ASP.Net developers India

sukumar said...

Rails3 is program ? I can't believe that program. anyway mostly are using that ASP.Net. it's friendly user. ASP.Net developers India

Anders Janmyr said...

@sukumar I'm not sure what you are asking, but Rails3 is the newest version of Ruby on Rails which is an alternative and in my opinion a lot better web framework.
You can find more information here.
http://guides.rails.info/

Chris W said...

This is a nice comparison.
However, what about Performance and Scalability? Is Ruby on Rails capable to scale - Recently I heard that Twitter was trying to move away from Ruby on Rails because of Performance and Scalability issues.

Anders Janmyr said...

@ChrisW: Rails can scale very well.
Here is an old post by DHH.
http://www.loudthinking.com/arc/000479.html
And here is another site about scaling Rails http://railslab.newrelic.com/scaling-rails

burmajam said...

About debug/release ... you can make other "environments" too, it is not supposed to work as RAILS_ENV but can be used for some of those things too. You can do:

#if (DEBUG)
// Load some "development" configuration
#else
// Load some "production" configuration
#endif


Regarding scaling rails. There are great screencasts on that subject by Gregg Polack @ http://railslab.newrelic.com/scaling-rails. It is for Rails2, with Rails 3 it is even better, so don't be afraid of scaling issues.

I've been working with C# for few years but middle tiers and WinForms. I've started one project one year ago in ASP.NET MVC, it is much, much better than WebForms, but it was back than half baked solution. It didn't cover full stack from front to backend from scratch. I think of Rails as application framework, but ASP.NET MVC as FrontEnd framework.

I think also that superiority of rails over asp.net mvc comes from power of ruby in 80%, but as of dotNET 4 and dynamic support in C# 4.0 I think that gap will be less.

Web Redesign said...

Excellent post... Many thanks for this.. it was very nice post on Rails3..

Anders Janmyr said...

@WebRedesign Thanks, I'm glad you liked it.

Anonymous said...

MS didn't borrow from Rails. They borrowed from MonoRail, which borrowed from Rails.

Unknown said...

As someone who is just learning rails, I have fallen in love with rake db:migrate. Sure there are ways to do this in .net, but not as cleanly, and not baked into the framework.

Anders Janmyr said...

@Victor I totally agree, I have tried many different solutions, but nothing that works as smoothly as migrations.

joomla development said...

Several things in here' haven't considered before.Thank you for making this type of awesome publish that is really perfectly written, is going to be mentioning lots of buddies relating to this. Maintain blogging.

Alex Knight said...

Awesome post on rails 3...thanks for sharing this nice resource.

Anders Janmyr said...

@Alex, Thanks! I'm glad you liked it.

Alex Knight said...

@ Anders My pleasures

ASP NET Application Development said...

Thanks for demonstrating step by step comparison between ASP NET application development vs RR. Both has their own advantages and flaws. Both uses code generation technique but in different way; one sues command line and other uses wizard. But RR can be advantageous as it is very flexible. But it really depends on the person's usage and requirement of the project to opt which technology.

Richard Byrdk said...
This comment has been removed by the author.
Anders Janmyr said...

@Richard, :D I'm glad you liked it.

Girin Jackson said...
This comment has been removed by a blog administrator.
Anders Janmyr said...

@Girin, :) I'm glad you liked it.

jagdish Prajapat said...
This comment has been removed by a blog administrator.
Anders Janmyr said...

@jagdish, Thanks, I'm glad you liked it :)

Instadp said...
This comment has been removed by a blog administrator.
AsH said...

The two frameworks only look similar if you are comparing bullet points. No one who has used both would ever use ASPMVC again -- unless you like writing tons of plumbing code, but MS developers are immune to feeling that weight anymore.

Anders Janmyr said...

@AsH, the blog post is based on years of personal experience with Rails and a couple of projects experience with ASP.MVC.
As you can tell from the date on the blog post, it was written eight years ago and I don't know what the status of the frameworks are now. But, even though I like Rails better, writing ASP.MVC was not an unpleasant experience and didn't involve writing tons of plumbing code.