Archive

Archive for the ‘Web Service’ Category

Insert multiple oData records with a single post

6 February 2011 Leave a comment

A few months ago I was playing around with some user interface ideas. I used the jQuery sortable which will save to my oData service after the user had made a change. This caused multiple posts to the database, depending on the amount of items added, removed or reordered should the user navigate away before it was all completed.
After some searching I found a way around this which I decided to play around with again today for a project I will be doing soon. The idea is to combine all the updates/inserts into a sequence and then submit it in a single post. This will make it a lot quicker and also have a lot less code to display a single message when all the updates are complete.

This post will not go into much detail about getting the whole lot set up, so here is what you need already set up:

  • ASP.NET website building and running
  • WCF Data Service set up to return values when queried through the browser
  • MS AJAX Toolkit (Had to download the source to get all the .js libraries I needed)

For the sake of not struggling I copied all the files from the .\MS Ajax\SampleWebSites\AjaxClientWebSite\Scripts\MicrosoftAjax folder into my website’s Scripts folder and referenced only Start.debug.js (or Start.js for release). This takes care of loading all the rest of the required libraries.

My data service was called TestDataService.svc with a single table containing two columns since this was just the bare minimum to get my head around it again.

In my html page I have the following code to load, display and insert data.

    
    
        // We require this library
        Sys.require([Sys.components.dataView, Sys.components.openDataContext, Sys.components.openDataServiceProxy]);
        
        // A reference to our service
        var exampleService;

        // Page loaded and DOM ready
        Sys.onReady(function () {
            // Create the proxy to our data service
            exampleService = new Sys.Data.OpenDataServiceProxy("/TestDataService.svc");
            // Load the data from the service
            loadData();
        });

        function loadData() {
            // Query the service
            exampleService.query("/testTables", cbSuccess, cbFailure);
        }

        // Success callback for the loadData query
        function cbSuccess(result, context, operation, userContext) {
            // Clear the list
            $("#dbData").children().remove();
            // Add all the items from the database/service to the list
            $.each(result, function (index, row) {
                $("#dbData").append("<li>" + row.name + ": " + row.value + "</li>");
            });
        }

        // Failure callback for the loadData query
        function cbFailure() {
            alert("Error contacting service");
        }

        // Insert multiple recors into the database using a single post
        function insertMultiple() {
            // Sequence action for inserting data
            var actionSequence = exampleService.createActionSequence();
            // Creating 3 records
            for (var i = 0; i &lt; 3; i++) {
                var tmpData = {
                    name: "Sequence" + i, // Column name for the table
                    value: "" + i // Column name for the table
                };
                // Add the freshly create item to the sequence for testTable
                actionSequence.addInsertAction(tmpData, "/testTables");
            }
            // Execute the sequence
            actionSequence.execute(cbInsertComplete, "Inserted all three records");
        }

        // Success callback for the bulk insert
        function cbInsertComplete(results) {
            // Results returned after successful insert as _result. 
            $.each(results, function (index, row) {
                $("#dbData").append("<li>" + row._result.name + ": " + row._result.value + "</li>");
            });
        }
    

There is not much to the HTML

        <p>
            <ul id="dbData">
                        <!-- Data gets loaded here -->
            </ul>
        </p>
        <p>            
            <input id="Button2" type="button" value="Reload Data" onclick="loadData();" />
            <input id="Button1" type="button" value="Insert Data" onclick="insertMultiple();" />
        </p>

As you can see in the image below, when running this through FireBug you can see the first request to load the data which is currently empty, then there is a single post which posts the data and get the results back. We use the results to add the new data to the list.

This code is a very rough guide to executing a sequence of actions using oData services and should not be used as is in a live environment. No error checking is done and no best practices are followed in this post, it is purely the very basics to getting started.

If you would like more detailed information then please do contact me, I will be more than happy to help where I can.

jEditable Dropdown Text

9 June 2010 3 comments

I was stuck for a few minutes trying to figure out how to display the text of the selected item in the dropdown when using jEditable.

My script was querying an odata service (WCF Data Service) which returned a list of values with their ids.  I wanted this to be represented in a dropdown with the text displayed instead of the default “selected value”.  After struggling for a bit I came up with an idea, since someone else was struggling with the same problem I decided to create a post about it.

Here is my JavaScript code:

var settings = new Array(); // holds settings
function loadSettings(selected) {
    // loads the settings from the database via oData service
    $.ajax({
        type: "GET",
        url: "dataService.svc/Settings?$orderby=Code%20asc",
        data: {}, // required for Chrome
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        async: false, // wait until it's done before setting up jEditable
        success: function (data) {
            /* Settings
             *   int SettingId (PK)
             *   string Title
             */
            // step through all the results
            $.each(data.d, function () {
                // add them to a hashtable
                settings[this.SettingId] = this.Title;
            });
            // check if a selected one was passed in
            if (selected != undefined)
                settings["selected"] = selected; // set the selected one
        },
        error: function (msg, a) {
            // call error method
            // TODO: Display correct error here by checking resulting code
            showError("ERROR: Could not load the settings from the database");
        }
    });

    // get the selected one's TEXT from the hashtable
    var set = settings[val];
    // TODO: Make this code better, use .append("<span />");
    $('#settings').html('<span id="ddSettings">' + set + '</span>');

    // setup field for inline editing
    $('#ddSettings').editable(function (value, settings) {
        saveSettings(value, docId, settings); // function to save settings
        return suffixes[value]; // return the text from the hastable
    }, 
    {
        data: settings, // my hashtable
        cancel: 'Cancel',
        placeholder: '---',
        submit: 'Save',
        tooltip: 'Click to edit',
        type: 'select',
        style: 'display: inline;'
    });
}

And my html:

<html>
<head>
    <script src="/Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script src="../Scripts/jquery.jeditable.mini.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            loadSettings();  // load the settings dropdown
        });

        // put the JavaScript source from above in here
    </script>
</head>
    <body>
        Settings:&nbsp;<div id="settings"></div>
    </body>
</html>

I hope this will help someone get around this problem.

ETag value in header different from object ETag

1 June 2010 4 comments

This is more a note to self which will hopefully help someone.

The last couple of weeks I have been working on a web application that uses WCF Data Services (oData) and jQuery.  I started receiving the following error: "The etag value in the request header does not match with the current etag value of the object.".  After hours of struggling I found out that it was the oninsert trigger in my database causing the problem.  It seems that when you insert the data the trigger changes that and then it is different from what you inserted and it then throws an error instead of returning the record you have just inserted.

Invalid JSON primitive in Chrome

24 April 2010 2 comments

I ran into the strangest problem today.  My Ajax call to my web service failed with the error “Invalid JSON primitive”, but only in Chrome…it worked in all my other browsers.

After reading up a bit it seemed to be a problem with the data being sent to the service.  Strange enough I was not sending any…and that turned out to be the problem. 

So for whoever runs into this problem, try setting the data parameter in jQuery to nothing, like in the code below.

$.ajax({
    type: "POST",
    contentType: "application/json; charset=utf-8",
    url: "DataService.asmx/ProjectList",
    dataType: "json", //response type
    data: "{}", // IMPORTANT
    success: function(msg, success) {
        if (success == 'success') {
            //good response
            data = eval(msg.d);
            // attach the template
            $("#slideshow").setTemplateElement('projectFaderTemplate', null, { runnable_functions: true });
            // process the template
            $("#slideshow").processTemplate(msg);
        } else {
            $('#msg').html('Error: ' + msg.d); //service returned an error
        }
    },
    error: function(msg, e) {
        $('#msg').html('Error.  Please contact Technical.'); //error with ajax call
    }
});

Hope this helps someone.

Linq to XML

23 April 2010 Leave a comment

Here is a very short post giving an example on how to use Linq to XML. 

Let’s say we have the following XML

<?xml version="1.0" encoding="utf-8" ?>
<projects>
    <project title="Kraankuil">
        <screenshot>kraankuil.jpg</screenshot>
        <uri>http://www.kraankuil.co.za</uri>
        <technologies>Gimp, ASP.NET, CMS</technologies>
        <launched>12 March 2010</launched>
        <description>Website for a small family owned B&amp;B in South Africa.</description>
    </project>
</projects>

We will do a class to easily handle the data like this

public class Project
{
    public string Title { get; set; }
    public string Screenshot { get; set; }
    public string Uri { get; set; }
    public string Technologies { get; set; }
    public string Launched { get; set; }
    public string Description { get; set; }
}

I will be using this is a web service where I use this code

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public List<Project> ProjectList()
{ 
    XDocument dataDoc;

    // load the file - from cache if possible
    if (HttpContext.Current.Cache["dataDoc"] == null)
    {
        dataDoc = XDocument.Load(Server.MapPath("~/App_Data/Data.xml"));
        HttpContext.Current.Cache["dataDoc"] = dataDoc; // store it in the cache
    } else {
        dataDoc = (XDocument)HttpContext.Current.Cache["dataDoc"];
    }

    //get the projects from the file
    var projectList = from project in dataDoc.Descendants("project")
                      select new Project
                      {
                          Title = project.Attribute("title").Value,
                          Screenshot = project.Element("screenshot").Value,
                          Technologies = project.Element("technologies").Value,
                          Launched = project.Element("launched").Value,
                          Description = project.Element("description").Value,
                          Uri = project.Element("uri").Value
                      };

    return projectList.ToList();
}

Note: This is NOT a best practices guide so do use common sense, it is only meant to get you started

Domain name availability checker

25 August 2009 3 comments

I was puzzled about how they do domain name availability checks for some time now.  It was only when I actually needed it for a website that I am doing that I actually looked into this.  This blog entry will cover the basics of getting a domain checker up and running, using a web service to do the actual checking.  I will cover the Ajax part in a next post.

The theory behind it all is actually really simple;  connect to a whois server and do a check on that domain, then check the response for a specific string and voila, we know whether the domain had been registered or not.

First off  create a new ASP.NET project.  Then add a new web service, I called mine DomainService.asmx.

You now got a brand new service with a default HelloWorld method, so the first thing we need to do is replace that with our own method, in this case I’ll call it CheckDomain and pass the domain to check through as a string.

First thing I do is check whether we have some text passed in, if not, throw and exception so that the calling client can handle it.  You might want to do a check first to see whether it is someone else trying to use your service but I won’t cover that here to keep it simple.

Next we need to create a new TcpClient instance.  You should notice when you type the CaSe right, you will get a red accelerator, then click on it (or hit ctrl+. [full stop]) and you will see what appears in the image below, select the using System.Net.Sockets; option.  You can manually add it to the “uses” at the top of the page, but that involves effort :).

AcceleratorOpen

The next thing to do is create a byte[] of the domain and ASCII encode it.  We also need to add the new line and line feed characters to the end of that.

//convert to byte[] to send over stream    
byte[] byteDomain = Encoding.ASCII.GetBytes(domain + "\r\n"); 

Now that we have the domain in an ASCII byte array we can send it to the server, but first we need to create the connection.  Also, a stream is needed to write to the server using our TcpClient connection so we create this as well.  Now lets open the connection, get the stream, write our domain to it, read the response and then close the connection.  I also included some debugging text so we can see the response and make life a little bit easier.

//create connection 
tcpClient = new TcpClient("YOUR_WHOIS_SERVER", 43); 
//get stream
Stream objStream = tcpClient.GetStream(); 
//write to the stream
objStream.Write(byteDomain, 0, byteDomain.Length);
//get response
StreamReader objReader = new StreamReader(tcpClient.GetStream(), 
    Encoding.ASCII); 
//read the response 
response = objReader.ReadToEnd();
#if DEBUG 
    Debug.WriteLine("--[" + domain + "]--"); 
    Debug.WriteLine(response); 
    Debug.WriteLine("--------"); 
#endif 
//close connection 
tcpClient.Close(); 

We now have a response from the server if nothing had gone wrong.  The response can now be analysed for the string.  Note that this response string I am talking about is different for most servers.  Just check through your response and you will find a common string.

//string to return
string returnValue = string.Empty;
//see if it is registered or not
if (Regex.IsMatch(response, "No match for ", RegexOptions.IgnoreCase))
{
    //yes it is registered
    returnValue = "available";
}
else if (Regex.IsMatch(response, "Registrar:", RegexOptions.IgnoreCase))
{
    //no it is not registered
    returnValue = "registered";
}
else
{
    //maybe some error returned from the server, i.e. disconnected 
    throw new Exception("Unknown response from server");
}
return returnValue;

As you probably noticed by now, I love throwing exceptions.  This makes life an awful lot easier when something goes wrong.

Now let’s test this.  Running the webservice (http://localhost:1929/DomainService.asmx in my case) by hitting F5 in Visual Web Developer brings you to the domain page.  Click on the CheckDomain option and enter a test domain (microsoft.com).  If you used the same server as me then you would have gotten (are my tenses right?) an exception, just try a different domain since loads of microsoft.com.* domains exists.  Our company domain works perfect for me, it returns “registered”.

Here is my full source code for this web service

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Net.Sockets;
using System.Text;
using System.IO;
#if DEBUG
    using System.Diagnostics;
using System.Text.RegularExpressions;
#endif

namespace DnsChecker
{
    /// <summary>
    /// Summary description for DomainService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    // [System.Web.Script.Services.ScriptService]
    public class DomainService : System.Web.Services.WebService
    {

        [WebMethod]
        public string CheckDomain(string domain)
        {
            //some error checking
            if (string.IsNullOrEmpty(domain))
                throw new ArgumentNullException(domain, "Domain was not specified");

            //will contain the response from the server
            string response = string.Empty;
            TcpClient tcpClient = null;
            try
            {
                //convert to byte[] to send over stream
                byte[] byteDomain = Encoding.ASCII.GetBytes(domain + "\r\n");
                //create connection
                tcpClient = new TcpClient("whois.crsnic.net", 43);//possible exception that extension is not in list
                //get stream
                Stream objStream = tcpClient.GetStream();
                //write to the stream
                objStream.Write(byteDomain, 0, byteDomain.Length);
                //get response
                StreamReader objReader = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
                //read the response
                response = objReader.ReadToEnd();

#if DEBUG
                Debug.WriteLine("--[" + domain + "]------------------");
                Debug.WriteLine(response);
                Debug.WriteLine("---------------------------------------");
#endif
            }
            catch (Exception ex)
            {
#if DEBUG
                Debug.WriteLine("--[" + domain + "]------------------");
                Debug.WriteLine("ERROR FOR " + domain + ex.Message);
                Debug.WriteLine("---------------------------------------");
#endif
                //maybe send some mail in case the whois server is down
                throw;//throw the error after writing the debug info so the client can deal with it
            }
            finally
            {
                //close connection
                tcpClient.Close();
            }

            //string to return
            string returnValue = string.Empty;
            //see if it is registered or not
            if (Regex.IsMatch(response, "No match for ", RegexOptions.IgnoreCase))
            {
                //yes it is registered
                returnValue = "available";
            }
            else if (Regex.IsMatch(response, "Registrar:", RegexOptions.IgnoreCase))
            {
                //no it is not registered
                returnValue = "registered";
            }
            else
            {
                //maybe some error returned from the server, i.e. disconnected 
                throw new Exception("Unknown response from server");
            }
            return returnValue;
        }
    }
}

There you have it, a skeleton web service that checks whether a domain is available or not. You should really update this code and make it safer.  In a next post I will connect this service up to a nice Ajax front end to dynamically check the domain as the user enters it, using jQuery.

This is not guaranteed to be the best way of doing things, but they work for me and others.  Your comments, good or bad, would be greatly appreciated as it helps and inspires me to improve.