Using Silverlight with Ruby on Rails

Posted on March 26, 2008. Filed under: adobe, C#, crud, flex, microsoft, mxml, rails, ruby, ruby on rails, silverlight, xaml |

In this post I will be showing a really simple example of creating a Silverlight frontend for a Rails backend. This is what I think will be a three part series comparing Silverlight with Flex.

The steps will be:

  1. Creating a Rails application.
  2. Creating a frontend for it in silverlight.
  3. Creating a frontend for it in Flex.
  4. Comparing the approaches taken in both the frontends.

I will try to keep changes in the backend Rails application to a minimum.

Creating a Rails Application
F:\>rails test
F:\>cd test
F:\test>ruby script\generate scaffold user name:string address:text
F:\test>rake db:create
F:\test>rake db:migrate
F:\test>ruby script\server

Before running the migrations add the following code in the first migration file (./db/migrate/001_create_users.rb) to create some initial data.  So it should look like:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.text :address
      t.timestamps
    end
    
    User.create(:name=> "Gaurav", :address=> "Gaurav's address")
    User.create(:name=> "Rishav", :address=> "Rishav's address")
    User.create(:name=> "Bhavesh", :address=> "Bhavesh's address")
  end

  def self.down
    drop_table :users
  end
end

 

Creating a new C# based Silverlight project

 

Open up Visual Studio 2008 (with Silverlight tools installed. You can also download a trial version of Visual Studio 2008 from here). And create a new project:

vs_step1

vs_step2

 

Open the page.xaml file and add a ListBox to it. It should look like:

<UserControl x:Class="TestFrontEnd.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox x:Name="userList" Height="200"/>
    </Grid>
</UserControl>

 

Now when the server responds with xml, we will parse that using Linq. For that we need a User class that is like the user model on the Rails side. So it should have 3 fields:

  1. id
  2. name
  3. address

It should look like:

namespace TestFrontEnd {
    public class User {
        public int id { get; set; }
        public string name { get; set; }
        public string address { get; set; }
    }
}

 

To make things simpler I have created a HTTPService class that behaves something like the Flex based HTTPService class. Here is the source (too big to paste inline). Add it to your project. You will need to include System.Net assembly your project to to make it work. Its usage is quite simple.

HTTPService userIndexService = new HTTPService();
userIndexService.Url = "http://localhost:3000/users.xml";
userIndexService.ServiceComplete += new HttpServiceCompleteEventHandler(userIndexService_ServiceComplete);
userIndexService.send();

 

You can also give the request method like:

userIndexService.Method = "POST";

 

Now add an ObservableCollection to the Page class:

private ObservableCollection<User> userListItems = new ObservableCollection<User>();

 

We will be using this for databinding with the ListBox to display all the users.

Add the System.Xml.Linq to your project. In the userIndexService_ServiceComplete event handler write:

void userIndexService_ServiceComplete(HttpServiceCompleteEventArgs e) {
    XDocument users = XDocument.Parse(e.Response);
    User[] usersList = (from x in users.Descendants("user")
            select new User
            {
                id = int.Parse(x.Descendants("id").First().Value),
                name = x.Descendants("name").First().Value,
                address = x.Descendants("address").First().Value,
            }).ToArray();
    foreach (User user in usersList)
        userListItems.Add(user);
}

 

So when the service completes all the users will be filled in the ObserveableCollection. Now to bind it to the userList ListBox. In the constructor of the Page class write:

userList.DataContext = userListItems;

 

And modify the code for the userList ListBox to look like:

<ListBox x:Name="userList" Height="200" ItemsSource="{Binding}" DisplayMemberPath="name" />

 

Add a crossdomain.xml file to the rails project’s public folder:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-access-from domain="*" />
</cross-domain-policy>

We are basically allowing our Silverlight app to access the rails server. You won’t be requiring this file if the Rails server is serving the Silverlight application, but currently it is not the case.

Now when you run the code you should see:

vs_step3

Now I will add a few more lines of code to show the selected item in TextBox. Add/Update the following to page.xaml:

<ListBox x:Name="userList" Height="200" ItemsSource="{Binding}" DisplayMemberPath="name" SelectionChanged="userList_SelectionChanged"/>
<StackPanel x:Name="controls">
    <TextBox Text="{Binding name, Mode=Twoway}" x:Name="userName"/>
    <TextBox Text="{Binding address, Mode=Twoway}" x:Name="userAddress"/>
</StackPanel>

The first TextBox is showing name and second is showing the password. The binding mode is set to TwoWay so that the changes in TextBox are automatically to the userListItems.

Add the corresponding event handler to Page class:

private void userList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
    controls.DataContext = userList.SelectedItem as User;
}

 

Now when you run the code you should see:

image

Download the code upto this point:

 

 Implementing the full CRUD :

 

Firstly I willl be doing a bit of refactoring. Move the xml parsing code using Linq to the User class. So it should now look like:

public class User {
    public int id { get; set; }
    public string name { get; set; }
    public string address { get; set; }

    public static User[] users_from_xml(String xml_string){
        XDocument users = XDocument.Parse(xml_string);
        return (from x in users.Descendants("user")
            select new User
            {
                id = int.Parse(x.Descendants("id").First().Value),
                name = x.Descendants("name").First().Value,
                address = x.Descendants("address").First().Value,
            }).ToArray();
    }

    public static User user_from_xml(String xml_string) {
        return users_from_xml(xml_string).First();
    }
}

 

Change the Page class to:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Xml.Linq;

namespace TestFrontEnd {
    public partial class Page : UserControl {
        private ObservableCollection<User> userListItems = new ObservableCollection<User>();

        private HTTPService userIndexService;
        private HTTPService userCreateService;
        private HTTPService userUpdateService;
        private HTTPService userDeleteService;

        public Page() {
            InitializeComponent();
            // setting the databinding
            userList.DataContext = userListItems;
            controls.DataContext = new User();

            // service to get all te users form the server
            userIndexService = new HTTPService();
            userIndexService.Url = "http://localhost:3000/users.xml";
            userIndexService.ServiceComplete += new HttpServiceCompleteEventHandler(userIndexService_ServiceComplete);
            userIndexService.send();

            // create a new user
            userCreateService = new HTTPService();
            userCreateService.Url = "http://localhost:3000/users.xml";
            userCreateService.Method = "POST";
            userCreateService.ServiceComplete += new HttpServiceCompleteEventHandler(userCreateService_ServiceComplete);

            // update a user, we will not be trapping the results
            userUpdateService = new HTTPService();
            userUpdateService.Method = "POST";

            // delete a user
            userDeleteService = new HTTPService();
            userDeleteService.Method = "POST";
        }

        private void _userList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            controls.DataContext = userList.SelectedItem as User;
        }

        private void userIndexService_ServiceComplete(HttpServiceCompleteEventArgs e) {
            foreach (User user in User.users_from_xml(e.Response))
                userListItems.Add(user);
        }
        private void userCreateService_ServiceComplete(HttpServiceCompleteEventArgs e) {
            userListItems.Add(User.user_from_xml(e.Response));
        }

        private void createButton_Click(object sender, RoutedEventArgs e) {
            userCreateService.send(String.Format("{0}={1}&{2}={3}", "user[name]", userName.Text, "user[address]", userAddress.Text));
        }

        private void updateButton_Click(object sender, RoutedEventArgs e) {
            User _selectedUser = userList.SelectedItem as User;
            userUpdateService.Url = "http://localhost:3000/users/" + _selectedUser.id.ToString() + ".xml";
            userUpdateService.send(String.Format("{0}={1}&{2}={3}&{4}={5}", "user[name]", userName.Text, "user[address]", userAddress.Text, "_method", "PUT"));
            userListItems[userList.SelectedIndex] = _selectedUser;
        }

        private void deleteButton_Click(object sender, RoutedEventArgs e) {
            User _selectedUser = userList.SelectedItem as User;
            userDeleteService.Url = "http://localhost:3000/users/" + _selectedUser.id.ToString() + ".xml";
            userDeleteService.send(String.Format("{0}={1}", "_method", "DELETE"));
            userListItems.Remove(_selectedUser);
        }

    }
}

 

To understand the specific parameters to the “send()” method of the HTTPService class you will need a little bit of understanding of how REST is implemented in Rails and how parameters are passed to a url, which is out of scope of this blog post.

 

Change Page.xaml to:

<UserControl x:Class="TestFrontEnd.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Vertical">
            <ListBox x:Name="userList" ItemsSource="{Binding}" DisplayMemberPath="name" SelectionChanged="_userList_SelectionChanged" Height="200"/>
            <StackPanel x:Name="controls">
                <TextBox Text="{Binding name, Mode=Twoway}" x:Name="userName"/>
                <TextBox Text="{Binding address, Mode=Twoway}" x:Name="userAddress"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Content="Create" x:Name="createButton" Click="createButton_Click"/>
                <Button Content="Update" x:Name="updateButton" Click="updateButton_Click"/>
                <Button Content="Delete" x:Name="deleteButton" Click="deleteButton_Click"/>
            </StackPanel>
            <TextBlock x:Name="trace" />
        </StackPanel>
    </Grid>
</UserControl>

 

This is how it looks like now:

image

So this is full CRUD implementation using Silverlight as frontend.

This is not the most optimal way to implement CRUD it is very easy to understand. It also should be noted that I haven’t added error checking of any type in the code and there is a lot of code repetition. But I guess that could be improved upon further once the most simple scenario is implemented. My style of coding is “First make it work and then optimize it”, and as I have mentioned it before it is certainly not the most optimized way to code but it sure works :)

One microsoft releases better databinding capabilities in controls and some simpler classes for HTTP based communication, this code would be much more simpler.

Full code for this aplication can be downloaded from:

  • Rails code
  • Silverlight code
  •  

    About these ads

    Make a Comment

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    4 Responses to “Using Silverlight with Ruby on Rails”

    RSS Feed for Blogging on Technology Comments RSS Feed

    […] This is the second blog post in a series of three posts comparing Flex with Silverlight. In my previous post I demonstrated how to integrate a scaffolded Rails application with Silverlight. Here I will […]

    […] is the second blog post in a series of three posts comparing Flex with Silverlight. In my previous post I demonstrated how to integrate a scaffolded Rails application with Silverlight. Here I will […]

    […] Using Silverlight with Ruby on Rails […]

    You should try out my Rails plug-in that gives you integration with Silverlight: http://github.com/jschementi/silverline.


    Where's The Comment Form?

    Liked it here?
    Why not try sites on the blogroll...

    Follow

    Get every new post delivered to your Inbox.

    %d bloggers like this: