5,427,303 members and growing! (18,063 online)
Email Password   helpLost your password?
Web Development » ASP.NET » Samples     Beginner License: The Code Project Open License (CPOL)

Online Circular Chess in Silverlight, ASP.NET AJAX, WCF Web Services, and LINQ to SQL

By Perrin01

An application for users to play Circular Chess over the internet based on Silverlight, ASP.NET AJAX, WCF Web Services, and LINQ to SQL.
Javascript, C# (C# 3.0, C#), .NET (.NET, .NET 3.0, .NET 3.5), Visual Studio (VS2008, Visual Studio), WCF, XAML, Ajax, LINQ, ASP.NET

Posted: 20 Dec 2007
Updated: 31 Dec 2007
Views: 28,486
Bookmarked: 57 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
45 votes for this Article.
Popularity: 7.67 Rating: 4.64 out of 5
2 votes, 5.1%
1
0 votes, 0.0%
2
1 vote, 2.6%
3
3 votes, 7.7%
4
33 votes, 84.6%
5

board.jpg

Introduction

A few months ago, a friend of mine mentioned Circular Chess to me, and I thought it looks really cool. However, I was unable to find any online Circular Chess game. Implementing such a game with standard HTML and JavaScript would not be possible because of the box model, and I am not familiar with Flash or Java Applets, so I wasn't able to implement the game. And, then I heard about Silverlight, and watched some demos, and thought to myself what a great platform it is for the implementation of Circular Chess.

In this article, I want to discuss the methods I used for creating an online game with ASP.NET AJAX 3.5 and WCF Web Services, and with the help of LINQ to SQL and, of course, Silverlight 1.1.

If you're interested in the rules of Circular Chess, check out this site.

Background

You should be familiar with ASP.NET and databases, in general. I hope to take care of those not familiar with Web Services.

Contents

Running the Game

In order for the game to work, make sure you have the Silverlight 1.1 runtime, which you can get here, Siverlight 1.1 SDK installed, which you can get here, and ASP.NET 3.5 Extensions CTP Preview, which you can get here. Other than that, running the game is just a matter of running Default.aspx in the CircularChessWebsite, either with VS2008 or IIS. If you want to test it, you can do it with Firefox or IE. If you want to play with others, make sure they have the Silverlight 1.1 runtime installed, and put it in some server (or in your localhost, if you know how to configure it).

The Structure of a Silverlight Solution

First of all, you must be using Visual Studio 2008 (the Beta 2 worked just fine, but halfway through the writing, I passed to the complete version). Now, a Silverlight solution has to have at least two projects: The Silverlight Project (which creates a Siverlight 1.1 object) or Silverlight JavaScript Application, and an ASP.NET Website to make use of the Silverlight Project. The website doesn't necessarily need to be ASP.NET, but it is the preferred option, as we get better compatibility. I also suggest that if you can, you make an ASP.NET 3.5 Extensions Web Site, because it's easier to embed Silverlight there. If you need Web Services in your solution, create them as part of the website, because Silverlight can access them only in the same domain (unless you're using IIS).

Apart from those two main projects, you may add helper classes. Silverlight controls (or any code used by a Silverlight Project) will be made in Silverlight Class Libraries, because you won't be able to add a reference to them otherwise. In my case, I have a helper Drag and Drop object, and some helper classes used both by the Silverlight project and the ASP.NET Website. Note that if you want to include helper classes, you must make sure those classes do not include any Silverlight features (not even using directives, as some might prove to end up in runtime errors). The skeleton of such a file would be something like this:

using System;

namespace CChess
{
    public class CChessBoard
    {
        // Implementation of the class
    }
}

This is what our solution looks like:

Screenshot - solution_structure.jpg

So, that said, I'll leave the Silverlight subject for a while, and concentrate on the other parts of the application.

Using ASP.NET AJAX 3.5 with WCF Web Services

First of all, Web Services in a sentence (and believe me, I know people who think Web Services are the same as Web Applications): they're a standard way to expose information in your Web Application. Normally, you'd get access to the information in your database through the C# code in your code-behind files, but with Web Services, you can call a certain method through a URL, get your information back, and use it as you may.

So, you may be asking, why WCF Web Services? They are, after all, harder to wire up than simple ASMX Web Services. Well, there are some reasons to prefer them:

  • Their structure is better than that of ASMX Web Services.
  • You have better control of them.
  • You'll most probably get new features in favor of them in the near future.
  • A LINQ feature I'll talk about later.

So, even though today you may have a harder time building them, tomorrow you will feel happy you did.

In order to use WCF Web Services, you must configure them first to work on JSON serialization for now, as it's the only supported method (currently). To do that, you must configure your endpoint to enable client script. That is done in your web.config file, like this:

<system.serviceModel>
    <behaviors>
        <serviceBehaviors>
            <behavior name="CChess.CChessBehavior">
            <serviceMetadata />
                <serviceDebug includeExceptionDetailInFaults="True"/>
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="jsonConfiguration">
                <enableWebScript/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <bindings>
        <webHttpBinding>
            <binding name="webBindingConfig" allowCookies="true"></binding>
        </webHttpBinding>
    </bindings>
    <services>
        <service name="CChess.CChessService" behaviorConfiguration="CChess.CChessBehavior">
            <endpoint 
                address="" 
                binding="webHttpBinding" 
                bindingConfiguration="webBindingConfig" 
                behaviorConfiguration="jsonConfiguration" 
                contract="CChess.ICChessService">
                <identity>
                    <dns value="localhost"/>
                </identity>
            </endpoint>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
    </services>
</system.serviceModel>

So, for those of you less familiar with WCF Web Services, I just gave the endpoint a behaviourConfiguration attribute, to which I refer in the endpointBehaviours, and added to it the empty enableWebScript node. Two other things to note, the endpoints have to be given a webHttpBinding for now, as it's the only one supported for our purposes, and we also added a <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> node, which essentially gives us access to the HttpContext.Current, as if we were in a normal ASMX Web Service. For those who don't know what that is, it's the object that contains all the Session, Request, and Profile objects, just to name a few. If you're debugging, or you simply want to catch exceptions you throw, you'll also have to add the serviceDebug node I've added in the service behavior.

Another thing to take into consideration is the namespace given to the contract. That is the namespace that will later be used in the JavaScript proxy created by ASP.NET (the Microsoft AJAX Library provides an implementation of namespaces for JavaScript, if you were wondering). You do it this way:

[ServiceContract(Namespace = "CChessService")]
public interface ICChessService
{
    [OperationContract]
    void Login(CChessPlayer new_player);

    // Other Web Methods
}

One very last thing you must do, is to add an attribute to the implementation of your contract, like this:

[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CChessService : ICChessService
{
    // Implementation of the Contract
}

Well, now that we're all set up for using WCF Web Services, let's get to use them!

First of all, you must make a ScriptManager element in the pages where you make use of Web Services, and add to it a reference to the Web Service, like this:

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Services>
        <asp:ServiceReference Path="~/CChessService.svc" />
    </Services>
    <Scripts>
        <asp:ScriptReference Path="~/App_Script/HelperFunctions.js" />
        <asp:ScriptReference Path="~/Default.aspx.js" />
    </Scripts>
</asp:ScriptManager>

That tells ASP.NET to create a proxy to the Web Service, which you can use in your JavaScript. Note that in VS2008, you even get Intellisense for them, which is pretty neat.

Screenshot - ajax_service_intellisense.jpg

Screenshot - ajax_service_params.jpg

My script manager also has references to two JavaScript files, which would be equal to referencing them in standard HTML, but this is the recommended way of doing this by Microsoft, so be it. One thing to note is that ASP.NET also creates a JavaScript version of classes or enums used by the Web Service, so you can make use of them like I'll show you in a moment. In order to use that proxy through JavaScript, you must first create an instance of it, and you can then call any of the methods you provided, with two callback functions, one for successful calls and another one for failed calls. Here's an example:

/// <reference path="Default.aspx" />
/// <reference path="App_Script/HelperFunctions.js" />

// Default.aspx.js

function pageLoad() 
{
    var nick_textbox = $get("NicknameInput");
    var button = $get("SubmitButton");
    
    button.onclick = LoginButtonClicked;
    // If 'enter' is pressed on nick_textbox,
    // fire whatever event is fired when button is
    // clicked
    BindTextboxToButton(nick_textbox, button); 
}

function LoginButtonClicked()
{
    var nick = $get("NicknameInput").value;
    var proxy = new CChessService.ICChessService();
    $get("ResultsDiv").innerHTML = "Please Wait...";
    var new_player = new CChessPlayer();
    new_player.player_nick = nick;
    proxy.Login(new_player, OnLoginComplete, OnLoginFailed);
}
  
function OnLoginComplete()
{
    $get("ResultsDiv").innerHTML = "Login Successful";
    window.location = "MainPage.aspx";
    // Redirect the user to the logged in page
}

function OnLoginFailed(error)
{
    $get("ResultsDiv").innerHTML = error.get_message();
}

Let's go step by step (like the name of the first ending of Detective Conan!). Because in VS2008 we have Intellisense in JavaScript, we also need a way to "include" .js files in other .js files, in order to get their variables and functions and classes and stuff in the Intellisense too. The way to do that is by using a triple-slash comment, and a reference element, like I did up in the example. pageLoad is a function called by the Microsoft AJAX Library when the page is loaded (if you're not content with the name or something, you can always hook the window.onload event yourself). There I hook up the click event of the login button (the $get function being a short for document.getElementById, again, courtesy of the Microsoft AJAX Library), and also use a little helper function I created to "bind" the textbox to that button (which does exactly what the comment says). When the button is clicked, we get the value from the textbox, and create an instance of the proxy. Note that the namespace matches the namespace given to the contract, and the proxy matches the name of the contract. We then create a new CChessPlayer (the "C" at the beginning is because I'm too lazy to write "Circular" all the time, it has nothing to do with MFC), and assign it a player_nick, much like we'd do in normal C#. We then call the Login method I showed you before, passing it the player object, and the two callbacks, so that if the login is successful, he'll be redirected; if it failed, he'll see what's his mistake.

That's more or less all there is to it really. If you were expecting a result to be returned by the method, it is passed as the first parameter to the successful callback, but because the Login method returns void, I left it empty. In case you don't get the correct Intellisense, try refreshing with Edit>Intellisense>Update JScript Intellisense. One thing I really encourage you to do is to use Firefox with Firebug installed; it's an extremely useful tool that shows you all the AJAX calls you make, has a JavaScript debugger, and much more. I find it extremely useful when developing this type of applications.

firebug_img.jpg

The Database

In this application, I'm using an SQL Server 2005 database. It's a pretty simple database, so let me introduce you to it.

Screenshot - database_relationships.jpg

The two main tables of the database are players and games. The players table contains all the players that entered the game ever. Online players are differentiated from offline players with the help of the last_activity field discussed later. The games table contains all the games ever created.

The games_players is the many-to-many table joining the games and the players. I chose this over hard-coding two players (white and black) into the games table, because this way, it's much easier to filter players, and it's more extensible. The games_moves table stores all the moves done in each game. It may seem like a bit expensive, and maybe it is, and a better solution would be to save the state of the chess board at any given moment, but I prefer it this way because it is, by far, much more flexible. For example, if I needed to implement an undo feature in the future, without storing each move, it wouldn't be possible.

The last table is used for the chat box, nothing interesting there.

LINQ to SQL

Before I begin, let me point out that this subject contains much more visual activity than our previous subject, and because of that, you may find it easier to watch some movies about it. You can find some here.

So, with ASP.NET AJAX and WCF Web Services behind us, let's jump to our next station, LINQ to SQL. As you probably already know, LINQ is a an abbreviation for Language Integrated Query, and it does a good job living up to its name. It is a mini-query language which you write from inside C#, or any other .NET language, and which lets you do queries on collections (IEnumerable objects). LINQ to SQL is a sub-category of LINQ, but instead of working on collections, it works on SQL tables. So, what are the advantages of LINQ to SQL? For starters, its syntax is simpler than the SQL one, and it produces efficient queries. You also get Intellisense through the whole process of writing the queries. It even has support for SQL Functions, and SQL Stored Procedures. But none of that is the reason I like LINQ to SQL so much. I used to spend hours at a time writing classes that each mapped to a table in my database, and writing Stored Procedures and CRUDs, and that was a terribly time consuming and boring job. But now with LINQ to SQL, and VS2008, all that is reduced to the drag and drop of a table to the visual editor! You not only get all the CRUD written for you for free, you also get extensible partial classes to which you can add any method or property you want.

Enough with the "whys", let's get to the hows! You start this by adding a "LINQ to SQL Classes" item to the App_Code folder of your application, which adds a .dbml file for you. You can then open your Server Explorer, open the connection to your database, and drag and drop your tables into the tables area. You can do the same for your Stored Procedures, only you'll drop them in the area on the right of the tables area. The end result looks like this:

LINQ to SQL

Note that, by default, the names of the classes will match the names of your tables, and the names of the attributes will match the names of the columns. If you're not content with this, you can always edit them, but make sure you never do it directly to the generated source code, as it'll be edited automatically sooner or later, but only through the visual editor. You can do that simply by double clicking on the names you want to change. For each of the Stored Procedures you created, also make sure they have the expected return value in the Properties window:

Stored Procedure Return Type

If you have problems changing the return type, it means your Stored Procedure isn't compatible with it. Now comes the moment when we become happy we chose WCF as our web service provider, because the LINQ to SQL editor provides one simple property which we can change, and all the generated classes instantly get the [DataContract], and all the properties instantly get the [DataMember] one. This means we can use the generated classes as return types from our WCF Web Services! In order to do that, change the Serialization Mode property of the DataContext from "None" to "Unidirectional" (you can see it by not having focus on any table or stored procedure).

WCF Serialization in LINQ

One last thing, before the examples. Because the classes created are partial, they can be extended (and are actually built with extension in mind). I wanted to show you an example of how it can be useful:

// From App_Code/CChessGamesPlayers.cs
using System;
using System.Web;
using System.Data.Linq;
using System.Runtime.Serialization;

public partial class CChessGamesPlayers
{
    [DataMember]
    public String player_nick
    {
        get { return CChessPlayer.player_nick; }
        set { }
    }

    partial void OnValidate(ChangeAction action)
    {
    // A player can't be inserted into a game,
    // if he's currently inside another game
        if (action == ChangeAction.Insert)
        {
            CChessDataContext cdc = new CChessDataContext();

            CChessPlayer current_player = 
              ((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
            if (current_player.IsInAGame())
            {
                throw new Exception("You're already in a game.");
            }
        }
    }
}

So, the first thing I added was a property to be serialized. Because CChessGamesPlayers is a table used for the many to many relationship of games and players, it has both a CChessGame property, and a CChessPlayer property, but neither of them are marked as DataMembers, which I find as a great thing, because if it were marked that way, it means we would get a lot of extra data that we probably won't need returned to us, and that's a waste. But, what if we wanted to return some of that data anyways? Well, that's no problem, just add a property, give it the DataMember attribute, and just return whatever you need in the get part (leave the set empty).

Another very neat feature is the use of partial methods. They're methods introduced in .NET 3.0 that are called only if an implementation is provided in another partial implementation of the class, otherwise they're totally ignored (unlike events, that are still called, and cause overhead). They're ideal for the creation of extensible classes, and LINQ to SQL makes awesome use of them. Here, we implement the OnValidate method, which is called when the object is going to be either inserted or updated to the database. If it does not pass the validation, we simply throw an exception. I prefer this way to validate my objects, rather than in the body of the method inserting them, because it makes much more sense.

OK, let's see some examples from the service:

public List<ChatMessage> GetLastNMessages(int n)
{
    CChessDataContext cdc = new CChessDataContext();
    List<ChatMessage> messages = (from msgs in cdc.ChatMessages
                                  orderby msgs.message_id descending
                                  select msgs).Take(n).ToList();

    return messages;
}

Pretty much self-explanatory for anyone used to writing SQL. You must first create a DataContext object, and then you can select from any of the tables in it (in this case, ChatMessages). Because we return a generic List, we must convert the result to List. We also don't take every record, but only n records. The SQL equivalent would be:

SELECT msgs.message_id, msgs.message_writer, msgs.message_content, msgs.sent_timestamp
FROM chat_table AS msgs
ORDER BY msgs.message_id DESC
LIMIT n

For simplicity's sake, I used the LIMIT statement that is not supported in T-SQL, but you get the point! In order to make a paging functionality, you can combine this with the Skip(n) method. OK, another example, now with Lambda Expressions (which I'll, of course, explain as well):

public CChessGamesPlayers GetYou()
{
    CChessDataContext cdc = new CChessDataContext();
    ProfileCommon profile = (ProfileCommon)HttpContext.Current.Profile;

    CChessGamesPlayers you = cdc.CChessGamesPlayers.Single(
        p => p.player_id == profile.CChessPlayer.player_id);

    return you;
}

As you can see, the tables inside the data context have some methods (here, we use Single, but there are several others) that accept a Lambda Expression as a parameter. A Lambda Expression is just a shorter version of anonymous methods introduced in .NET 2.0. After defining the parameters, you use the => operator, and write some operations. In this case, the Lambda Expression gets one player as a parameter, and checks the player's ID against another ID. It'll return all the players with that player_id. Because we're talking about an ID, it's only one or 0. But because we're also using the Single method, it must be 1, or an exception will be thrown. If Single does not meet your needs, you can choose any other one (they have self-explanatory names as well).

Because we're talking about a database, chances are you're going to want to update, insert, and delete records. Here's how to insert:

public void SendMessage(String content)
{
    CChessPlayer current_player = 
        ((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
    if (current_player == null)
    {
        return;
    }

    ChatMessage msg = new ChatMessage
    {
        message_writer = current_player.player_id,
        message_content = content,
        sent_timestamp = UnixDate.GetCurrentUnixTimestamp()
    };
    CChessDataContext cdc = new CChessDataContext();
    cdc.ChatMessages.InsertOnSubmit(msg);
    cdc.SubmitChanges();
}

Just create a new message, initialize it (note that I used here the new syntax in .NET 3.5), call its table's InsertOnSubmit method with it as a parameter, and submit changes. The very same would be done for deleting, only instead of InsertOnSubmit, call DeleteOnSubmit. If you have a collection, use InsertAllOnSubmit and DeleteAllOnSubmit, respectively.

Updating is a bit more tricky. In order for a record to be updated, the DataContext must be aware it has changed, and that happens only if we're talking about records just pulled out of the DataContext. You would then make your changes, call the SubmitChanges method, like you did with the insert and delete, and your records would be updated. But, what if there's an object you have kept around for some time, and want to update it? You have two choices: either pull it back again (with the Single method, or any other way you prefer), edit it, and submit the changes; or, alternatively, you could use Stored Procedures, like this:

public void UpdateUserActivity()
{
    CChessPlayer player = 
       ((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
    if (player == null)
    {
        throw new Exception("You're not online.");
    }

    CChessDataContext cdc = new CChessDataContext();

    // Now, the reason I've used a stored procedure instead of standard Linq
    // is because in order to get player to be traced by the DataContext,
    // I'd need to select it again, and then update it. In this case, a 
    // standard stored procedure is a far better option (I'd even say
    // an easier solution).
    int affected_rows = cdc.UpdateUserActivity(player.player_id, 
                               UnixDate.GetCurrentUnixTimestamp());
    if (affected_rows < 1)
    {
        throw new Exception("Unable to update activity.");
    }
}

And if you're interested, here's the SQL of the procedure:

ALTER PROCEDURE UpdateUserActivity
    @player_id bigint,
    @timestamp int
AS

UPDATE players
SET last_activity = @timestamp
WHERE player_id = @player_id
RETURN @@ROWCOUNT

Note that I return @@ROWCOUNT implicitly. Other than that, I think this is pretty self-explanatory.

OK, so we're done with LINQ to SQL. Let's take a break from the new functionality for a while, and take a look at how the system works!

Login and Logout

unable_username.jpg

Unlike normal websites, where a user logs in and is authorized for certain things, and can then logout, this website needs to keep track of the users at real time. Imagine what would happen if a player disappeared in the middle of a game, and his opponent didn't even know! So, my method for keeping track of the user is by having a unix_timestamp column in my database that is updated at real time. For those not familiar with Unix Timestamps, they're the number of seconds elapsed since January 1, 1970, at 00:00. Having come from a PHP background, I find it really easy to use them. The way to check if a user is logged off would be that the current timestamp minus the last activity timestamp is greater than a certain amount. For those of you who prefer code to words, here you go:

// Short version of MainPage.aspx.js

function pageLoad() 
{
    GetOnlinePlayers();
    UpdateUserActivity();
    GetLastestFiveMessages();
    GetCurrentGamePlayers();
    
    BindTextboxToButton($get("NewMessageInput"), 
                        $get("MessageSubmitButton"));
}

function GetOnlinePlayers()
{
    var proxy = new CChessService.ICChessService();
    proxy.GetOnlineUsers(OnlinePlayersGotten);
}

function OnlinePlayersGotten(result)
{
    var found;
    for(var i = 0; i < result.length; i++)
    {
        found = false;
        
        for(var j = 0; j < online_players.length; j++)
        {
            if(result[i].player_id == online_players[j].player_id)
            {
                found = true;
                break;
            }
        }
        if(!found)
        {
            AddUser(result[i]);
        }
    }
    
    for(var i = 0; i < online_players.length; i++)
    {
        found = false;
        
        for(var j = 0; j < result.length; j++)
        {
            if(online_players[i].player_id == result[j].player_id)
            {
                found = true;
                break;
            }
        }
        if(!found)
        {
            RemoveUser(online_players[i]);
        }
    }
    
    online_players = result;
    setTimeout("GetOnlinePlayers()", 5000);
}

function AddUser(user)
{
    var tmp_div = document.createElement("div");
    tmp_div.id = "user_" + user.player_id.toString();
    tmp_div.innerHTML = user.player_nick;
    $get("OnlinePlayers").appendChild(tmp_div);
}

function RemoveUser(user)
{
    $get("OnlinePlayers").removeChild($get("user_" + user.player_id));
}

function UpdateUserActivity()
{
    var proxy = new CChessService.ICChessService();
    // Updates the last activity so you don't get offline
    proxy.UpdateUserActivity(UserActivityUpdated, FailedActivityUpdate);
}

function UserActivityUpdated()
{
    // Delay the next update 20 seconds
    setTimeout("UpdateUserActivity()", 20000);
}

function FailedActivityUpdate()
{
    $get("MessageDiv").innerHTML = "You have gone offline. Please login again.";
    // Wait 5 seconds for the user to read the message, then redirect him
    setInterval("window.location = 'Default.aspx';", 5000);
}

And the relevant service methods:

public void Login(CChessPlayer new_player)
{
    CChessDataContext cdc = new CChessDataContext();

    new_player.last_activity = UnixDate.GetCurrentUnixTimestamp();
    cdc.CChessPlayers.InsertOnSubmit(new_player);
    cdc.SubmitChanges();
    // Store the player in his profile
    ProfileCommon profile = (ProfileCommon)HttpContext.Current.Profile;
    profile.CChessPlayer = new_player;
}

public void UpdateUserActivity()
{
    CChessPlayer player = 
      ((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
    if (player == null)
    {
        throw new Exception("You're not online.");
    }

    CChessDataContext cdc = new CChessDataContext();

    // Now, the reason I've used a stored procedure instead of standard Linq
    // is because in order to get player to be traced by the DataContext,
    // I'd need to select it again, and then update it. In this case, a 
    // standard stored procedure is a far better option (I'd even say
    // an easier solution).
    int affected_rows = cdc.UpdateUserActivity(player.player_id, 
                            UnixDate.GetCurrentUnixTimestamp());
    if (affected_rows < 1)
    {
        throw new Exception("Unable to update activity.");
    }
}

public List<CChessPlayer> GetOnlineUsers()
{
    CChessDataContext cdc = new CChessDataContext();

    return cdc.GetOnlinePlayers(CChessPlayer.LEGAL_INACTIVITY_SECONDS, 
           UnixDate.GetCurrentUnixTimestamp()).ToList<CChessPlayer>();
}

And for completeness sake, the GetOnlinePlayers Stored Procedure SQL:

ALTER PROCEDURE GetOnlinePlayers
    @legal_inactivity_seconds int,
    @unix_timestamp int
AS
SELECT *
FROM players
WHERE @unix_timestamp - last_activity < @legal_inactivity_seconds

So, when the page loads, I call all the functions that update the UI regularly. They get the data back, update the UI, and call themselves again with a certain interval (as big as it can get, and still be natural).

Note that we could have very easily made a regular registration and login system, and the implementation would have changed much, and that's how you'd probably do it in a complete website, but because this is only a demo, I kept it as anonymous. Note that I used the Profile object to store data necessary across requests (again, I got access to it through the HttpContext.Current, which I enabled at the beginning).

Creating New Games

So, before starting to talk about Silverlight in the application, I'll review the creation of new games quickly. Any user who wants to start a new game can create it, and he'll be black or white, depending on his choice. After that, he's redirected to the Circular Chess Board, where he waits for someone to join him, and when someone does, they can start playing! One nice thing I wanted to point out was the function that creates the new game:

<a href="javascript: CreateNewGame(CChessGameColors.White);">Create new game as white</a> 
<a href="javascript: CreateNewGame(CChessGameColors.Black);">Create new game as black</a>

As you can see, we call the function with the enums we created in the server side. I think it's really neat that even those get translated by the proxy.

Silverlight

Even though I named the article Online Circular Chess in Silverlight, I get to talk about it only now... Oh well, here we go! At the introduction, I kind of compared Silverlight to Flash and Java Applets, and the truth is that Silverlight is Microsoft's competence towards Adobe's Flash. Because I don't have time to learn ActionScript and Flash from scratch, I learned Silverlight. In this article, I'll talk about the more technical parts of Silverlight, rather than artistic ones (because I'm not an artistic person). If what you need is to create complex XAML (Extensible Application Markup Language, the language that lets you be artistic in Silverlight) to decorate your application, download Expression Blend 2 here (at the moment of the writing of this article being the December Preview trial), and start playing with it. Also, take in mind that right now, there are no common controls in Silverlight, that means no Textboxes, no Buttons, Combo Boxes, etc. A few were released in September (which you can get here), but you can expect a full release in the first quarter of 2008. So, again, before beginning, I wanted to point you out to some great videos from which I learned a lot, you can get them here.

Silverlight Basics

A Silverlight 1.1 project has, by default, the following files:

  • A TestPage.html page, in which you embed your Silverlight object for testing.
  • A TestPage.html.js which defines a createSilverlight() function, which embeds the Silverlight with the parameters specified in it.
  • A Silverlight.js file, that's a helper file by Microsoft for embedding Silverlight.
  • Page.xaml, in which you define the elements of your Silverlight object (TextBlocks, Rectangles, etc.).
  • Page.xaml.cs, it works as a code-behind for Page.xaml, it's used pretty much like a code-behind is in ASP.NET.
  • You may also add custom controls.

Let's explore each of the files.

<Canvas
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="parentCanvas"
        x:Class="CircularChess.Page;assembly=ClientBin/CircularChess.dll"
        xmlns:CChessControls=
         "clr-namespace:CircularChess;assembly=ClientBin/CircularChess.dll"
        Width="800"
        Height="600"
        >

  <TextBlock Canvas.Left="5" Canvas.Top="3" x:Name="my_textblock" 
             Canvas.ZIndex="1" Text="Loading..." Foreground="#FFFFFF" />
  <CChessControls:PlayerPicture Canvas.Top="10" Canvas.Left="650" 
             x:Name="opponent_textblock"></CChessControls:PlayerPicture>
  <CChessControls:PlayerPicture Canvas.Top="450" Canvas.Left="650" 
             x:Name="your_textblock"></CChessControls:PlayerPicture>

</Canvas>

That is from ChessBoardPage.xaml, by default called Page.xaml. Self explanatory, isn't it? Well, everything is wrapped in a Canvas. We have a TextBlock where we display messages, and two custom controls that represent the white king with the name of the player under it. I'll explain the syntax for adding the controls, later in the article.

Let me talk about some of the attributes. The Canvas.Top and Canvas.Left attributes tell the elements where to be positioned in relation to their containing Canvas, and if for some reason, you were to take them out of the Canvas, they'd stop working. Note that everything is absolutely positioned (unlike the DOM). If you want a certain element to have a greater Z-Index than another, you do it with the Canvas.ZIndex property (which works the same as the CSS z-index property). There's also the x:Name attribute, which simply lets us reference the attribute in the code-behind by the name specified. We'll see more of it when we see the code-behind. For now, let's check TestPage.html and TestPage.html.js:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<!-- saved from url=(0014)about:internet -->
<head>
    <title>Silverlight Project Test Page </title>
    
    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="TestPage.js"></script>
    <style type="text/css">
        body
        {
            padding: 0;
            margin: 0;
        }
        
        .silverlightHost { width: 800px; height: 800px; }
    </style>
</head>

<body>
    <div id="SilverlightControlHost" class="silverlightHost" >
        <script type="text/javascript">
            createSilverlight();
        </script>
    </div>
</body>
</html>

TestPage.html.js:

// JScript source code

//contains calls to silverlight.js, example below loads Page.xaml
function createSilverlight()
{
    Silverlight.createObjectEx({
        source: "ChessBoardPage.xaml",
        parentElement: document.getElementById("SilverlightControlHost"),
        id: "SilverlightControl",
        properties: {
            width: "100%",
            height: "100%",
            version: "1.1",
            enableHtmlAccess: "true"
        },
        events: {}
    });
       
    // Give the keyboard focus to the Silverlight control by default
    document.body.onload = function() {
      var silverlightControl = document.getElementById('SilverlightControl');
      if (silverlightControl)
      silverlightControl.focus();
    }

}

As you can see, TestPage.html is extremely simple. The only thing worth noting is that you must give the element containing the Silverlight object a width and height for it to be visible. We do it in the CSS at the top of the page. TestPage.html.js is pretty simple too. It uses the Silverlight.js created for us by VS2008 to create a simple createSilvelight function. It uses the static method in the Silverlight class with some self-explanatory parameters that embed the Silverlight into the page. Now, in order to embed the Silverlight in an existing website (because I doubt you'll embed it in the TestPage.html), you'd have to add a Silverlight Link to the Silverlight Project you have.

Add Silverlight Link

It'll copy Page.xaml into the website (note that VS2008 takes care of keeping it up to date with any changes you make to it in the Silverlight Project). You can then manually copy TestPage.html.js and Silverlight.js, and embed the Silverlight as shown above. Note that I didn't use this approach in order to embed the Silverlight into my page in the website though. I wanted to show you this approach because chances are your server doesn't yet have ASP.NET 3.5 Extensions installed, and if you want to embed Silverlight in your website, this would be the only way you have. If you do have access to the Extensions though, embedding Silvelight is as easy as inserting any other ASP.NET control into your page. After putting a ScriptManager in your page, just add a <asp:Silverlight runat="server" ID="Silverlight ID" Width="800" Height="800" Version="1.1" Source="~/Path/to/XAML.xaml" /> anywhere in your page, and you're ready to go!

Hooking Events on Silverlight Controls

This is quiet trivial, but I felt bad not to mention it at all. Hooking up an event on a Silverlight control is the same as hooking up an event on a WinForms application, or even on a DOM element. You can hook mouse and keyboard events on objects that derive from FrameworkElement. You can find an example in my DragAndDrop class:

public void MakeDraggable(FrameworkElement elem)
{
    if (_root == null)
    // Load the root
    {
        FrameworkElement tmp = elem.Parent as FrameworkElement;
        while (tmp.Parent != null)
        {
            tmp = tmp.Parent as FrameworkElement;
        }
        _root = tmp as FrameworkElement;
        _root.MouseMove += new MouseEventHandler(MouseMove);
        _root.MouseLeftButtonUp += new MouseEventHandler(MouseLeftButtonUp);
    }

    elem.MouseLeftButtonDown += new MouseEventHandler(MouseLeftButtonDown);
}

public void RemoveDraggable(FrameworkElement elem)
{
    elem.MouseLeftButtonDown -= MouseLeftButtonDown;
}

void MouseLeftButtonDown(object sender, MouseEventArgs e)
{
    FrameworkElement elem = sender as FrameworkElement;
    FrameworkElement parent = (FrameworkElement)elem.Parent;
    _current_element = elem;
    _starting_point = new Point((double)elem.GetValue(Canvas.LeftProperty), 
                                (double)elem.GetValue(Canvas.TopProperty));
    
    // Do Work.......
}

WCF Web Services in Silverlight 1.1

So we have the Silverlight embedded into the website, the next step is to create a proxy to the web service. And this is were Silverlight gets ugly, and that's because, for some reason, VS2008 is unable to create a proxy for WCF web services, although it works great with ASMX web services. I read that some people actually got it to work pretty easily, but no matter how many times I tried, it just doesn't work, and I know I'm not the only one. Well, fortunately, there's a workaround, and while not really nice, it works for now, until this bug, if that's indeed what it is, gets fixed. First, create an ASMX web service that implements only the "skeleton" of the web service. You can easily do this in VS2008 by implementing the contract interface and adding the [WebMethod] attribute to all the generated methods, like this:

Implement Contract

After that, back in your Silverlight Project, right click in your project, and select "Add Web Reference".

Add Web Reference

In the dialog that opens, go to "Web Services in this Solution", and select your ASMX web service, and not you SVC web service (don't worry, we'll end up using the SVC one).

Screenshot - asmx_web_service.jpg

You may now give the reference a name, click on Add Reference, and the proxy will be created for you. Now, luckily for us, the proxy created for ASMX web services works exactly the same for WCF Web Services. The only thing we'd need to do is upon creation of the proxy, we'll change the URL of the service to the SVC one, like this:

CChessProxy.CChessSkeleton proxy = new CChessProxy.CChessSkeleton();
proxy.Url = "CChessService.svc";
proxy.DoWork();

The Code-Behind File and Using the Proxy

Let's first bring the code-behind file, ChessBoardPage.xaml:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using Silverlight.Controls;
using CChess.Controls;
using System.Windows.Browser;
using System.Net;
using CircularChess.CChessProxy;

namespace CircularChess
{
    public partial class Page : Canvas
    {
        public Page()
        {
            Loaded += new EventHandler(Page_Loaded);
            current_selected_square = null;
            _last_move = new CChessMove() { move_id = 0 };

            proxy = new CircularChess.CChessProxy.CChessSkeleton();
            // Because the proxy was generated
            // with an ASMX web service, we must
            // redirect it to the WCF web service.
            proxy.Url = "CChessService.svc";

            drag_drop = new DragAndDrop { CenterOnMouseDown = true };
            drag_drop.CustomMouseDown += 
              new DragAndDropEventHandler(_drag_drop_CustomMouseDown);
            drag_drop.CustomMouseMove += 
              new DragAndDropEventHandler(_drag_drop_CustomMouseMove);
            drag_drop.CustomMouseUp += 
              new DragAndDropEventHandler(_drag_drop_CustomMouseUp);
        }

        #region Fields

        private DragAndDrop drag_drop;
        private CChessSquare current_selected_square;
        private CChessBoard my_chessboard;
        private CChessProxy.CChessGamesPlayers you;
        private CChessProxy.CChessGamesPlayers opponent;
        private CChessProxy.CChessSkeleton proxy;
        private HtmlTimer opponent_get_timer;
        private HtmlTimer new_move_timer;
        private CChessMove _last_move;
        private const int tile_img_width = 159;
        private const int tile_img_height = 159;
        private const String tile_img_url = "images/wood7.jpg";

        #endregion

        public void Page_Loaded(object o, EventArgs e)
        {
            // Required to initialize variables
            InitializeComponent();

            proxy.BeginGetYou(new AsyncCallback(YouGotten), null);
            proxy.BeginGetOpponent(new AsyncCallback(OpponentGotten), null);

            opponent_get_timer = new HtmlTimer();
            opponent_get_timer.Interval = 5000;
            opponent_get_timer.Tick += new EventHandler(opponent_timer_Tick);

            new_move_timer = new HtmlTimer();
            new_move_timer.Interval = 4000;
            new_move_timer.Tick += new EventHandler(_new_move_timer_Tick);
            new_move_timer.Start();

            SetTileBackground();
        }

        public void SetTileBackground()
        {
            Image tmp;
            for (int i = 0; tile_img_width * i < Width; i++)
            {
                for (int j = 0; tile_img_height * j < Height; j++)
                {
                    tmp = new Image();
                    tmp.SetValue(Canvas.ZIndexProperty, -1);
                    tmp.SetValue(Canvas.LeftProperty, i * tile_img_width);
                    tmp.SetValue(Canvas.TopProperty, j * tile_img_height);
                    tmp.Source = new Uri(tile_img_url, UriKind.RelativeOrAbsolute);
                    Children.Add(tmp);
                }
            }
        }

        public void YouGotten(IAsyncResult iar)
        {
            you = proxy.EndGetOpponent(iar);
            your_textblock.Text = you.player_nick;

            proxy.BeginGetAllMoves(you.game_id, AllMovesGotten, null);
        }

        public void AllMovesGotten(IAsyncResult iar)
        {
            CChessMove[] moves = proxy.EndGetAllMoves(iar);
            CChess.CChessBoard board = new CChess.CChessBoard();
            for (int i = 0; i < moves.Length; i++)
            {
                board.MovePiece(moves[i].piece_id, moves[i].destination_ring, 
                                moves[i].destination_square);
                _last_move = moves[i];
            }

            Color your_color = (you.color == (byte)CChess.CChessColors.White) ? 
                                CChessBoard.white_color : CChessBoard.black_color;
            my_chessboard = new CChessBoard(your_color, 275, 100, board);
            my_chessboard.SetValue<double>(Canvas.TopProperty, 0);
            my_chessboard.SetValue<double>(Canvas.LeftProperty, 
                                          (Width / 2 - my_chessboard.Width / 2));
            my_chessboard.SetValue<double>(Canvas.TopProperty, 
                                          (Height / 2 - my_chessboard.Height / 2));
            my_chessboard.OnLoad += new CChessBoard.NoParametersEventHandler(
                                                    _my_chessboard_OnLoad);
            Children.Add(my_chessboard);

            my_textblock.Text = "Ready.";
        }

        void _my_chessboard_OnLoad()
        {
            foreach (CChessPiece piece in my_chessboard.Pieces)
            {
                if (piece.IsAlive)
                {
                    drag_drop.MakeDraggable(piece);
                }
            }

            foreach (CChessSquare square in my_chessboard.Squares)
            {
                square.MovingAnimationComplete += 
                   new EventHandler(piece_storyboard_Completed);
            }

            SelectCurrentPlayer();
        }

        void SelectCurrentPlayer()
        {
            if ((byte)my_chessboard.CurrentMoveColor == you.color)
            {
                opponent_textblock.UnselectPlayer();
                your_textblock.SelectPlayer();
            }
            else
            {
                your_textblock.UnselectPlayer();
                opponent_textblock.SelectPlayer();
            }
        }

        void _new_move_timer_Tick(object sender, EventArgs e)
        {
            if (opponent != null && (byte)my_chessboard.CurrentMoveColor != you.color)
            {
                new_move_timer.Stop();
                proxy.BeginGetLastMove(_last_move.move_id, 
                      new AsyncCallback(LastMoveGotten), null);
            }
        }

        public void LastMoveGotten(IAsyncResult iar)
        {
            CChessProxy.CChessMove last_move = proxy.EndGetLastMove(iar);
            if (last_move != null && _last_move != null && 
                last_move.move_id != _last_move.move_id)
            {
                _last_move = last_move;

                CChessPiece the_piece = my_chessboard.Pieces[last_move.piece_id - 1];
                CChessSquare the_square = 
                  my_chessboard.Squares[last_move.destination_ring - 1, 
                                        last_move.destination_square - 1];

                SetPieceOnTop(the_piece);

                the_piece.Square.AnimatePieceToSquare(the_square);
            }
            else
            {
                new_move_timer.Start();
            }
        }

        void piece_storyboard_Completed(object sender, EventArgs e)
        {
            SetPieceOnBottom(my_chessboard.Pieces[_last_move.piece_id - 1]);

            my_chessboard.MovePiece(_last_move.piece_id, _last_move.destination_ring, 
                                    _last_move.destination_square);
            SelectCurrentPlayer();
            new_move_timer.Start();
        }

        void opponent_timer_Tick(object sender, EventArgs e)
        {
            proxy.BeginGetOpponent(new AsyncCallback(OpponentGotten), null);
            opponent_get_timer.Stop();
        }

        public void OpponentGotten(IAsyncResult iar)
        {
            CChessProxy.CChessGamesPlayers p = proxy.EndGetOpponent(iar);
            if (p == null)
            {
                if (opponent == null)
                {
                    opponent_textblock.Text = "Waiting for someone to join.";
                }
                else
                {
                    opponent_textblock.Text = opponent.player_nick + " has left.";
                }
            }
            else
            {
                opponent = p;
                opponent_textblock.Text = opponent.player_nick;
            }

            opponent_get_timer.Start();
        }

        /// <summary>
        /// Sets a high z-index on the piece
        /// </summary>
        void SetPieceOnTop(CChessPiece piece)
        {
            CChessSquare square = piece.Square;
            // Keeps the piece on top while dragging it.
            square.SetValue<int>(Canvas.ZIndexProperty, 1);
        }

        /// <summary>
        /// Sets a low z-index on the piece
        /// </summary>
        void SetPieceOnBottom(CChessPiece piece)
        {
            CChessSquare square = piece.Square;
            square.SetValue<int>(Canvas.ZIndexProperty, 0);
        }

        void LastMoveSent(IAsyncResult iar)
        {
            try
            {
                _last_move = 
                 new CChessMove() { move_id = proxy.EndMovePiece(iar) };
                SelectCurrentPlayer();
            }
            catch (WebException ex)
            {
                my_textblock.Text = ex.GetBaseException().Message;
            }
        }
    }
}

(I left out the drag and drop region, I'll discuss it later). OK, let's explore this little by little. In the constructor, I initialize the fields of the page, between them the proxy like I described in the previous section, and hook the Loaded event of the Canvas. In Page_Loaded, I start asynchronous calls to the web service. When the proxy is created, not only are regular methods created, also asynchronous versions of them are added. Those have the "Begin" prefix, and "End" prefix. Asynchronous calls are preferred to normal calls because they don't stuck the browser while waiting for a response. In order to make such calls, call the "Begin" version of the method you want (for example, BeginGetOpponent), and give it an AsyncCallback delegate (in this case, we gave it the OpponentGotten(IAsyncResult iar) method). When the result arrives, the method you provided gets called. There, you call the "End" version of the method (EndGetOpponent, in our case), and retrieve the result of the call to the service. Now, because we're in an online game, these calls must be repeated constantly, but we'd also want to make intervals as not to overkill the server. We can manage this with an HtmlTimer object, and, while the compiler will tell you that the object is obsolete, it works perfectly well for our purposes, so until we get a better alternative, we'll stick to it. After you create a new Timer, give it an Interval, hook an event to call when the timer ticks, and call the Start method. When the timer ticks, stop it, do whatever you need, and start it again, and thus you successfully create intervals between calls to the web service in Silverlight.

Another thing to note in the code-behind, is how we add and manipulate controls programmatically. Take a look at the AllMovesGotten method. There, we create a new CChessBoard with its constructor, set its Canvas.TopProperty and Canvas.LeftProperty, and add it to the Children collection of the Page (that is a Canvas), that in it's turn, adds it to the UI.

Custom Silverlight Controls

In order to add a new control, add a new Silverlight User Control item. Let's take a look at the PlayerPicture control. First the XAML:

<Canvas xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Background="#442233"
        Width="100"
        >

  <Image Source="images/king_picture.png" x:Name="player_img"></Image>
  <!-- Set the Textblock under the image -->
  <TextBlock Canvas.Top="85" x:Name="player_name" 
      Foreground="#FFFFFF" TextWrapping="Wrap" Width="100"></TextBlock>
  <Rectangle x:Name="border" Canvas.Top="0" Canvas.Left="0"></Rectangle>
  
</Canvas>

And the code-behind:

using System;