Some XSockets and SisoDb fun – Part 2 of 2

This is a continuation of Some XSockets and SisoDb fun – Part 1 of 2 which more was about getting started with XSockets.Net. This post is about putting SisoDb to use. The steps we will go through are:

Get the Code

The code is located at GitHub. Both the SisoDbClient.Js and a sample app matching the sample we will create here.

  • Step 1 – Create a model
  • Step 2 – Inserts – Add support to the socket handler
  • Step 3 – Inserts – Hook up the UI
  • Step 4 – GetById – Add support to the socket handler
  • Step 5 – GetById – Hook up the UI
  • Step 6 – Updates – Add support to the socket handler
  • Step 7 – Updates – Hook up the UI
  • Step 8 – Queries – Add support to the socket handler
  • Step 9 – Queries – Hook up the UI
  • Step 10 – DeleteById – Add support to the socket handler
  • Step 11 – DeleteById – Hook up the UI

Step 1 – Create a model

First of, we need a simple model to work with. Create a class library project named “Model” and add the following class:

public class Article
{
    public Guid ArticleId { get; set; }
    public int PLU { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Now, it’s time to extend our socket handler to support inserts of the newly created model.

Step 2 – Inserts – Add support to the socket handler

It’s time to start using SisoDb and we will now make the handler support inserts. But to do that we will need to setup some plumbing, which of course, also will be used for updates, deletes and queries. Now, lets install the “SisoDb core package” to the “XSocketHandler” project.

To support dynamic lambda expression when querying, we also need the “SisoDb.Dynamic” package so install that to.

Now, lets create the shell method accepting inserts. Add this to the “SisoDbHandler” class in the “XSocketHandler” project:

[HandlerEvent("Insert")]
public void Insert(InsertCommand command)
{
    //Use SisoDb, but resolve it via a configurable Runtime class.
}

Instead of locking down our handler to a specific provider of SisoDb, we will configure this in the application entry point instead, more precisely, in the “Server”. But before that, create a new class-library project named “Core”.

Now, install the “SisoDb core package” to this project as well.

The Core project will define a “Runtime” class that holds some resources that we will consume in our handler. This is a quick solution/hack and you would probably do it another way. Perhaps using MEF or some IoC-container framework. But lets keep it fairly simple. Add a new class called “Runtime” to the “Core project”.

public static class Runtime
{
    public static IResources Resources { get; set; }
}

We need to be able to resolve a ISisoDatabase and, given a type name, locate a certain structure/entity type. Lets define two simple Funcs for this.

public interface IResources
{
    Func<ISisoDatabase> DbResolver { get; set; }
    Func<string, Type> StructureTypeResolver{ get; set; } 
}

Lets initialize the Runtime. Install a specific SisoDb provider into your Server project. I will choose the Sql2012 provider.

To support dynamic lambda expression when querying, we also need the “SisoDb.Dynamic” package so install that to.

Add a class called “Resources” to the Server project and setup the two Func members that resolves a Db and Structure types. NOTE! The ISisoDatabase should be kept long lived since it contains caches of e.g Db-Schemas.

public class Resources : IResources
{
    private readonly ISisoDatabase _db;
    private readonly IDictionary<string, Type> _structureTypes;

    public Func<ISisoDatabase> DbResolver { get; set; }
    public Func<string, Type> StructureTypeResolver{ get; set; }

    public Resources()
    {
        //Func should resolve the SAME ISisoDatabase each time
        //Demo is the name of the connection string in App.Config
        _db = "Demo".CreateSql2012Db();
        _db.CreateIfNotExists();
        DbResolver = () => _db;

        //Simple key-value map for acceptable structure types
        _entityTypes = GetEntityTypes().ToDictionary(t => t.Name);
        StructureTypeResolver= name => _structureTypes[name];
    }

    private IEnumerable<Type> GetStructureTypes()
    {
        //This determines what Entities/Structures that will be accessible
        yield return typeof (Article);
    }
}

Now, assign an instance of the Resource class to the Runtime

static void Main(string[] args)
{
    Runtime.Resources = new Resources();
    //...
    //leave the rest untouched
    //...
}

Add a connection string to the App.Config in the Server project.

<connectionStrings>
  <add name="Demo" connectionString=
       "data source=.;initial catalog=Demo;integrated secuirty=true;"/>
</connectionStrings>

Now, lets fix the handler. I couldn’t get a constructor with arguments to work so we will resolve the Db inside it. Add a default constructor to the “SisoDbHandler” and resolve a Db to be used:

public class SisoDbHandler : XBaseSocket
{
    private readonly ISisoDatabase _db;

    public SisoDbHandler()
    {
        _db = Runtime.Resources.DbResolver();
    }

    //...
    //leave the rest untouched
    //...

Almost there. Fix the Insert method:

[HandlerEvent("Insert")]
public void Insert(InsertCommand command)
{
    var structureType = Runtime
        .Resources
        .StructureTypeResolver(command.StructureName);

    var result = new InsertResult
    {
        StructureName = command.StructureName,
        Json = _db.UseOnceTo().InsertJson(structureType, command.Json)
    };

    this.AsyncSend(result, "OnInserted");
}

The last steps that are missing is to create the “InsertCommand” and the “InsertResult”. Lets add them.

//Namespace: XSocketHandler.Commands
public class InsertCommand
{
    public string StructureName { get; set; }
    public string Json { get; set; }
}
//Namespace: XSocketHandler.Results
public class InsertResult
{
    public string StructureName { get; set; }
    public string Json { get; set; }
}

Our handler is now ready to serve insert calls. Lets consume it.

Step 3 – Inserts – Hook up the UI

The next step is to update our demo.html page and make it use the insert method. This is what we will start out from, with just a few minor adjustments from the previous post:

<!DOCTYPE html>
<html>
<head>
    <title>Demo - SisoDb-XSockets</title>
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Scripts/JXSockets-1.0.4.beta.js" type="text/javascript"></script>
    <script src="http://cloud.github.com/downloads/danielwertheim/
                 SisoDb-XSockets/sisodbclient-debug-v0.3.0.js" 
            type="text/javascript"></script>
        
    <script type="text/javascript">
        var my = {};

        $(function () {
            //Hook up client
            my.client = new SisoDbClient("ws://127.0.0.1:4502/SisoDbHandler");
            my.client.logger.enabled = true;
            my.client.connect();

            //Hookup UI to consume client
            $("#ping button").on("click", function () {
                my.client.ping($("#ping_message").val());
            });

            //Hookup listeners
            my.client.onPinged(function (data) {
                my.dumpResult("onPinged", data.Message);
            });

            //Some helpers
            my.dumpResult = function (action, result) {
                $("<li>").addClass(action)
                         .text(result)
                         .appendTo(".outputs ul:eq(1)");
            };
        });
    </script>
    <link href="Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <section class="inputs">
        <fieldset id="ping">
            <legend>Ping</legend>
            <label for="ping_message">Message</label>
            <input type="text" id="ping_message" placeholder="Message" />
            <button>Ping</button>
        </fieldset>
    </section>
    <section class="outputs">
        <ul>
            <li class="onPinged description">Ping result</li>
            <li class="onInserted description">Insert result</li>
            <li class="onDeletedById description">Delete by id result</li>
            <li class="onGetById description">Get by id result</li>
            <li class="onQuery description">Query result</li>
            <li class="onUpdated description">Update result</li>
        </ul>
        <ul></ul>
    </section>
</body>
</html>

First add a new Fieldset below the one for Ping.

<fieldset id="article_insert">
    <legend>Insert</legend>
    <div class="form-item">
        <label for="article_insert_name">Name</label>
        <input type="text" id="article_insert_name" placeholder="Name" />
    </div>
    <div class="form-item">
        <label for="article_insert_plu">PLU</label>
        <input type="text" id="article_insert_plu" placeholder="PLU" />
    </div>
    <div class="form-item">
        <label for="article_insert_price">Price</label>
        <input type="text" id="article_insert_price" placeholder="Price" />
    </div>
    <div class="form-actions">
        <button class="insert">Insert</button>
    </div>
</fieldset>

Now we need to make the UI invoke the socket handler when we click on Insert.

$(".inputs #article_insert .insert").on("click", function () {
    my.client.insert("Article", {
        Name: $("#article_insert_name").val(),
        PLU: $("#article_insert_plu").val(),
        Price: $("#article_insert_price").val()
    });
});

We also want to react on whenever an item is inserted, so lets hook that up:

my.client.onInserted(function (data) {
    my.dumpResult("onInserted", data.Json);
});

Now, fire up the Server and the MVC application and visit the demo.html page. Below I have tried the Ping operation and made a simple insert.

Step 4 – GetById – Add support to the socket handler

This time, most of the plumbing is already in place in the handler. Lets add GetById:

[HandlerEvent("GetById")]
public void GetById(GetByIdCommand command)
{
    var structureType = Runtime
        .Resources
        .StructureTypeResolver(command.StructureName);
    var structureSchema = GetStructureSchema(structureType);

    var id = ConvertId(command.Id, structureSchema);
    var result = new GetByIdResult
    {
        StructureName = command.StructureName,
        Id = command.Id,
        Json = _db.UseOnceTo().GetByIdAsJson(structureType, id)
     };

     this.AsyncSend(result, "OnGetById");
}

The above code needs two helper methods:

private IStructureSchema GetStructureSchema(Type structureType)
{
    return _db.StructureSchemas.GetSchema(structureType);
}

private static object ConvertId(string id, IStructureSchema structureSchema)
{
    return StructureId.Create(id, structureSchema.IdAccessor.IdType).Value;
}

Now, lets finish the GetById support in the handler. Add the GetByIdCommand and GetByIdResult.

//Namespace: XSocketHandler.Commands
public class GetByIdCommand
{
    public string StructureName { get; set; }
    public string Id { get; set; }
}
//Namespace: XSocketHandler.Results
public class GetByIdResult
{
    public string StructureName { get; set; }
    public string Id { get; set; }
    public string Json { get; set; }
}

Step 5 – GetById – Hook up the UI

Time to make the UI support the GetById operation. First add a new FieldSet that lets you enter an Id to return an item for.

<fieldset id="article_getById">
    <legend>Get by Id</legend>
    <div class="form-item">
        <label for="article_getById_id">Id</label>
        <input id="article_getById_id" type="text" placeholder="id"/>
    </div>
    <div class="form-actions">
        <button class="get">Get</button>
    </div>
</fieldset>

Make the UI invoke the handler:

$(".inputs #article_getById .get").on("click", function () {
    my.client.getById("Article", $("#article_getById_id").val());
});

Now, hook up the UI to react on the result of the GetByIdCommand.

my.client.onGetById(function (data) {
    my.dumpResult("onGetById", data.Json);
});

You should now be able to perform GetById operations, which will look like this:

Step 6 – Updates – Add support to the socket handler

Time to fix Updates. Add this to the handler:

[HandlerEvent("Update")]
public void Update(UpdateCommand command)
{
    var structureType = Runtime.Resources.StructureTypeResolver(command.StructureName);
    var structure = _db.Serializer.Deserialize(structureType, command.Json);
    var structureSchema = GetStructureSchema(structureType);

    _db.UseOnceTo().Update(structureType, structure);
            
    var result = new UpdateResult
    {
        StructureName = command.StructureName,
        Id = structureSchema.IdAccessor.GetValue(structure).Value.ToString()
    };

    this.AsyncSend(result, "OnUpdated");
}

Also add the UpdateCommand and the UpdateResult.

//Namespace: XSocketHandler.Commands
public class UpdateCommand
{
    public string StructureName { get; set; }
    public string Json { get; set; }
}
//Namespace: XSocketHandler.Results
public class UpdateResult
{
    public string StructureName { get; set; }
    public string Id { get; set; }
}

Step 7 – Updates – Hook up the UI

Time to make the UI support the Update operation. First add a new FieldSet that lets you enter an Id to load an item for. Then some controls that lets you update the values.

<fieldset id="article_update">
    <legend>Update - Load</legend>
    <div class="form-item">
        <label for="article_update_id">Id</label>
        <input id="article_update_id" type="text" placeholder="id"/>
    </div>
    <div class="form-actions">
        <button class="load">Load</button>
    </div>

    <legend>Update - Save</legend>
    <div class="form-item">
        <label for="article_update_name">Name</label>
        <input type="text" id="article_update_name" placeholder="Name" />
    </div>
    <div class="form-item">
        <label for="article_update_plu">PLU</label>
        <input type="text" id="article_update_plu" placeholder="PLU" />
    </div>
    <div class="form-item">
        <label for="article_update_price">Price</label>
        <input type="text" id="article_update_price" placeholder="Price" />
    </div>
    <div class="form-actions">
        <button class="update">Update</button>
    </div>
</fieldset>

Make the UI invoke the handler:

$(".inputs #article_update .load").on("click", function () {
    my.client.getById("Article", $("#article_update_id").val());
});

$(".inputs #article_update .update").on("click", function () {
    my.client.update("Article", {
        ArticleId: $("#article_update_id").val(),
        Name: $("#article_update_name").val(),
        PLU: $("#article_update_plu").val(),
        Price: $("#article_update_price").val()
    });
});

Now, extend the “onGetById callback”, since it now could represent either “GetById” or “Load”.

my.client.onGetById(function (data) {
    if ($("#article_update_id").val()) {
        var doc = JSON.parse(data.Json);
        $("#article_update_name").val(doc.Name);
        $("#article_update_plu").val(doc.PLU);
        $("#article_update_price").val(doc.Price);
    }
    else
        my.dumpResult("onGetById", data.Json);
});

Also hook up the “onUpdated callback”:

my.client.onUpdated(function (data) {
    my.dumpResult("onUpdated", data.Id.toString());
});

You should now be able to perform Update operations, which will look like this:

Step 8 – Queries – Add support to the socket handler

Time to fix Queries. Add this to the handler:

using SisoDb.Dynamic; //IMPORTANT! IMPORTANT! IMPORTANT!

[HandlerEvent("Query")]
public void Query(QueryCommand command)
{
    var structureType = Runtime
        .Resources
        .StructureTypeResolver(command.StructureName);
            
    var result = new QueryResult
    {
        StructureName = command.StructureName
    };

    using (var session = _db.BeginSession())
    {
        result.Json = session.Query(structureType).Where(command.Predicate).ToArrayOfJson();
    }

    this.AsyncSend(result, "OnQuery");
}

Also add the QueryCommand and the QueryResult.

//Namespace: XSocketHandler.Commands
public class QueryCommand
{
    public string StructureName { get; set; }
    public string Predicate { get; set; }
}
//Namespace: XSocketHandler.Results
public class QueryResult
{
    public string StructureName { get; set; }
    public string[] Json { get; set; }
}

Step 9 – Queries – Hook up the UI

Time to make the UI support Queries with lambda style syntax. First add a new FieldSet that lets you enter a query.

<fieldset id="article_query">
    <legend>Query</legend>
    <div class="form-item">
        <label for="article_query_predicate">Predicate</label>
        <input id="article_query_predicate" type="text" placeholder="predicate"/>
    </div>
    <div class="form-actions">
        <button class="query">Query</button>
    </div>
</fieldset>

Make the UI invoke the handler:

$(".inputs #article_query .query").on("click", function () {
    my.client.query("Article", $("#article_query_predicate").val());
});

Hook up the UI to display the query result for the “onQuery callback”.

my.client.onQuery(function (data) {
    $.each(data.Json, function (i, e) {
        my.dumpResult("onQuery", e);
    });
});

You should now be able to perform queries using syntax like "x => x.PLU >= 100"

Step 10 – DeleteById – Add support to the socket handler

Time to fix DeleteById. Add this to the handler:

[HandlerEvent("DeleteById")]
public void DeleteById(DeleteByIdCommand command)
{
    var structureType = Runtime
        .Resources
        .StructureTypeResolver(command.StructureName);
    var structureSchema = GetStructureSchema(structureType);

    var id = ConvertId(command.Id, structureSchema);

    _db.UseOnceTo().DeleteById(structureType, id);

    var result = new DeleteByIdResult
    {
        StructureName = command.StructureName,
        Id = command.Id
    };

    this.AsyncSend(result, "OnDeletedById");
}

Also add the DeleteByIdCommand and the DeleteByIdResult.

//Namespace: XSocketHandler.Commands
public class DeleteByIdCommand
{
    public string StructureName { get; set; }
    public string Id { get; set; }
}
//Namespace: XSocketHandler.Results
public class DeleteByIdResult
{
    public string StructureName { get; set; }
    public string Id { get; set; }
}

Step 11 – DeleteById – Hook up the UI

Again, lets just add some simple controls to allow you to enter an Id to delete.

<fieldset id="article_deleteById">
    <legend>Delete by Id</legend>
    <div class="form-item">
        <label for="article_deleteById_id">Id</label>
        <input id="article_deleteById_id" type="text" placeholder="id"/>
    </div>
    <div class="form-actions">
        <button class="delete">Delete</button>
    </div>
</fieldset>

Make the UI invoke the handler:

$(".inputs #article_deleteById .delete").on("click", function () {
    my.client.deleteById("Article", $("#article_deleteById_id").val());
});

Now, hook up the UI to react on the result of the GetByIdCommand.

my.client.onDeletedById(function (data) {
    my.dumpResult("onDeletedById", data.Id.toString());
});

You should now be able to perform DeleteById operations, which will look like this:

The SisoDbClient.Js

The client is using the XSockets.JsApi and is written using CoffeeScript. It’s not large, have a look at it on GitHub.

Where to go from here

The code is hosted on GitHub. Both the SisoDbClient.Js (which is authored using CoffeeScript) and the XSocketHandler. If this looks interesting, pull it down and extend the code yourself. E.g adding exception handling, but as of now, I have nothing more to cover in this post. Happy coding!

//Daniel

About these ads

4 thoughts on “Some XSockets and SisoDb fun – Part 2 of 2

  1. Great work as always Daniel!
    On behalf of Team XSockets I can only say… Impressive and very cool!

    Looking forward to try this out! :)

    Regards
    Uffe, Team XSockets

    • Thanks! It leaves some bits out. Like adding exception handling and validation. Just hoping to show another side of Websockets than the traditional media streaming or chat applications. Thanks for a great work on XSockets.

      //Daniel

    • Looking forward to the feedback! BTW, thanks for a great work on XSockets.

      //Daniel

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