Vishful thinking…

Making HTTP Post with Silverlight a little easier

Posted in C#, Silverlight by viswaug on September 29, 2009

In my earlier post, I talked about how to go about making an HTTP Post in Silverlight. The whole process is a little complicated to say the least. Silverlight does not come with an utility class like WebClient that makes making a HTTP Get a little easier. So, I created a class called ‘HTTPPostHelper’ that does for HTTP Post what WebClient does for HTTP Get. The ‘HTTPPostHelper’ class listed below, makes the whole process a lot easier. To use the class, do the following

  • Create an instance of the HTTPPostHelper class with the type of the input class (T) and the type of the returned class (K) as the generic arguments.
  • Listen to the ‘PostCompleted’ event that gets called when the Post operation completes successfully. The EventArgs of the event contains an instance of the return class type.
  • Listen to the ‘PostFailed’ event that gets called when the Post operation fails. The EventArgs of the event contains the exception information.
  • Call the ‘Post’ method on the HTTPPostHelper with the input object and the URL address to make the Post operation happen.
  • The input object gets serialized by default using the DataContractSerializer. But this can be overridden by setting the Serializer property of the HTTPPostHelper with an appropriate delagate that serializes the input object to a stream.
  • The output from the HTTP Post operation is deserialized by default using the DataContractSerializer. But this too can be overridden by the Deserializer property of the HTTPPostHelper.
  • For example, if you want to send and receive JSON data to the HTTP endpoint, just override the default Serializer & Deserializer with delegates that use DataContractJsonSerializer instead or your own custom binary serializer if need be.
  • The ‘ContentType’ used for the Post operation can also set before calling the Post method.
  • The class handles all the thread hopping internally. The ‘PostCompleted’ and the ‘PostFailed’ events get raised on the same thread from which they were called from.

 

public class HTTPPostHelper<T, K>

{

    SynchronizationContext syncContext;

    HttpWebRequest _request;

 

    public string ContentType

    {

        get;

        set;

    }

 

    public event EventHandler<EventArgs<K>> PostCompleted;

 

    public event EventHandler<ExceptionEventArgs> PostFailed;

 

    public Action<T, Stream> Serializer = null;

 

    public Func<Stream, K> Deserializer = null;

 

    public HTTPPostHelper()

    {

        Serializer = DefaultSerializer;

        Deserializer = DefaultDeserializer;

        ContentType = “application/stream”;

    }

 

    void DefaultSerializer(T input, Stream output)

    {

        DataContractSerializer serializer = new DataContractSerializer(typeof(T));

        serializer.WriteObject(output, input);

        output.Flush();

    }

 

    K DefaultDeserializer(Stream input)

    {

        DataContractSerializer serializer = new DataContractSerializer(typeof(K));

        K result = (K)serializer.ReadObject(input);

        return result;

    }

 

    public void Post(Uri address, T input)

    {

        syncContext = SynchronizationContext.Current;

 

        _request = (HttpWebRequest)WebRequest.Create(address);

        _request.Method = “POST”;

        _request.ContentType = “application/stream”;

 

        _request.BeginGetRequestStream(new AsyncCallback(PostRequestReady), new HttpWebRequestData<T>()

        {

            Request = _request,

            Data = input

        });

    }

 

    private void PostRequestReady(IAsyncResult asyncResult)

    {

        HttpWebRequestData<T> requestData = asyncResult.AsyncState as HttpWebRequestData<T>;

        HttpWebRequest request = requestData.Request;

        Stream requestStream = request.EndGetRequestStream(asyncResult);

        Serializer(requestData.Data, requestStream);

        requestStream.Close();

 

        request.BeginGetResponse(new AsyncCallback(PostResponseCallback), request);

    }

 

    private void PostResponseCallback(IAsyncResult ar)

    {

        try

        {

            HttpWebRequest request = ar.AsyncState as HttpWebRequest;

            HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;

            Stream responseStream = response.GetResponseStream();

            K urls = DefaultDeserializer(responseStream);

 

            syncContext.Post(PostExtractResponse, new HttpWebResponseData<K>()

            {

                Response = response,

                Data = urls

            });

        }

        catch (Exception ex)

        {

            syncContext.Post(RaiseException, ex);

        }

    }

 

    private void RaiseException(object state)

    {

        Exception ex = state as Exception;

        if (PostFailed != null)

            PostFailed(this, new ExceptionEventArgs(ex));

    }

 

    private void PostExtractResponse(object state)

    {

        HttpWebResponseData<K> responseData = state as HttpWebResponseData<K>;

        if (PostCompleted != null)

            PostCompleted(this, new EventArgs<K>(responseData.Data));

    }

}

 

public class ExceptionEventArgs : EventArgs

{

    public Exception ExceptionInfo { get; set; }

 

    public ExceptionEventArgs(Exception ex)

    {

        ExceptionInfo = ex;

    }

}

 

 

 

public class EventArgs<T> : EventArgs

{

    private T eventData;

 

    public EventArgs(T eventData)

    {

        this.eventData = eventData;

    }

 

    public T EventData

    {

        get

        {

            return eventData;

        }

    }

}

Advertisement

Black magic behind the MSD Map Services?

Posted in ArcGIS, ESRI by viswaug on September 29, 2009

Much has been said about the performance boost that can be achieved by running MSD map services over MXD map services. Here I will talk a little bit about the other side of the MSD map services which trips up developers like me. The only information that I was able to find about the capabilities/short-comings of the MSD map services from ESRI was on their blog post here. Since, I develop and use a few custom Server Object Extensions (SOEs), I noticed that some of the SOEs that I had written earlier had stopped working when registered with MSD map services. And the following piece of information from the blog post mentioned earlier seemed to indicate the cause of the problem.

What capabilities (server object extensions) are supported for MSD-based map services?

The WMS and KML capabilities are available with MSD-based services, as well as the default Mapping capability. Custom server object extensions can also work with MSD-based services but they cannot access fine-grained ArcObjects; they must use the coarse-grained methods on MapServer and its related classes.

The important part of the above statement is tha ‘custom SOEs cannot access fine-grained ArcObjects’. Well, what does that really mean? It kinda means that you cannot access the fine-grained ArcObjects features that are exposed through the IMapServerObjects interface on the MapServer server object. But being from the .NET world, I had trouble wrapping my head around how the same MapServer server object class can serve both the MXD map services and also the MSD map services and behave differently for each service type. How can the same MapServer class offer the IMapServerObjects features for MXD map services but not for MSD map services? I tried casting(QI) the MapServer server object obtained from a MSD map service to an IMapServerObjects interface which actually resulted in an exception being thrown. But the same cast worked on the MapServer object obtained from a MSD map service. My first thought when I noticed this behavior was that maybe the MSD map services are actually served by a different new server object class and not the MapServer class that serves the MXD map services. The ESRI  9.3.1 documentation doesn’t mention anything about a new server object. So, either ESRI didn’t document it or the same MapServer has developed multiple-personalities in AGS 9.3.1. To test it out, I obtained the CLSID of the server object that served MSD map services and the MXD map services. The were the exact SAME. That probably means that the same MapServer class is just behaving differently in different usage scenarios by leveraging some COM black magic which I don’t have too much knowledge about.

Since, we can’t get at the rich features in the IMapServerObjects interface, the custom SOEs on MSD map services really become only a little more useful than custom COM utility objects. So, some of the custom server object extensions that developers wrote earlier for MXD map services will not work with MSD map services. And that is a real bummer for me. One way (that I really really hate) to overcome this limitation is to run another MXD map service for the MSD map service. The MSD map service makes the maps and the MXD map service is only used to obtain and use the features of the  custom server object extension. I do really hate the idea of running a another expensive map service process on the AGS machine only for the purpose of using the features of the custom server object extensions. Although, I do have some ideas on a better ways of working around these limitations. The most frustrating part about this whole thing is that I think internally ESRI does have access to fine grained ArcObjects for the MSD map services and they just don’t let us know how to gain access to the fine grained ArcObjects. Why/How do I say that? The reason for my suspicions that the above is the case is because, the WMS and KML server object extensions in-fact DO work with MSD map services also. Which means that those server object extensions are able to access fine-grained ArcObjects to do what they need to do.

Update : Another change that relates to SOEs in AGS 9.3.1 is regarding it registration process. As many might have noticed, their SOEs shows (or doesn’t showup :)) up without any label in the ArcCatalog->Service Properties->Capabilities tab. It took me a while to realize what was happening. I had initially assumed that the SOE wasn’t showing up in the list. But later, luckily I discovered that the SOE was in-fact displaying but there was just no label for it in the Capabilities list box. In 9.3, the IServerObjectExtensionType interface has been superseded by the IServerObjectExtensionType2 interface ArcObjects style. So, when you register the SOE with ArcGIS Server by calling the CreateExtensionType, the returned object should be casted to the type IServerExtensionType2 and the ‘DisplayName‘ property should be set on it before calling AddExtensionType. This ‘DisplayName‘ property is what shows up in the capabilities tab and if it is not set, the entry for the SOE in the capabilities tab will be empty. Earlier, the extension’s Name property used to show up in the capabilities tab. But no longer, it is the DisplayName property that is used to label the entry in the capabilities tab list box. I hope ESRI finds some time to update their documentation about this over here.

Tagged with: ,

Thoughts on making the ESRI REST API a little more scalable

Posted in ESRI by viswaug on September 22, 2009

These are just a couple of my thoughts on how the ESRI REST API can be made a little more scalable. The ESRI REST API has been a boon to me and probably many other GIS developers out there who had to get knee deep in the ADF and build usable apps with it. The ADF has gotten a lot better in 9.3 and the ADF guys at ESRI have done a good job of improving the framework by leaps and bounds. But still, I stay away from the ADF partly because it is still not easy to use and debug. Definitely not as easy to use as the REST API with JS/Flex/Silverlight clients. The REST API inherently leverages the power of ‘HTTP goodness’ and makes the scalability of the apps that are built with the light-weight clients much more scalable. The scalability is achieved because of the fact that the data is cached as necessary both the REST server and the client browser.

One of the concerns I (and others) have with the current architecture of the REST server in AGS 9.3 is with the way how the cached map services are served out by the server. Even though, all the tiles have already been cooked and saved on disk. The default way the tiles are served out by the server is through the AGS REST server. This means that the tiles/images on the disk are not directly served out by IIS itself but the AGS REST handlers (implemented as IHTTPHandler in the library) need to get involved. Every tile request coming into the handler is served out really fast but IIS does have to call into the ASP.NET module that spins up a new thread for this request and this thread is blocked until the image request has been completed. Again, let me re-iterate. This image request-response by the REST handler is very fast. But, why do we need to be satisfied with fast when we can be FASTER. If the tile requests are made directly to just the tile images on a web server (doesn’t need to have AGS) by default, the request doesn’t even need to get to the AGS (which now doesn’t need to be externally accessible and can be behind your firewall if need be) and this tile request can be satisfied by just the web server without having AGS involved. This way cached layers can be served to the light-weight clients by just the web server which could in the cloud or the images can be stored and served directly from Amazon storage services. These things are currently possible with the light-weight clients but ESRI can definitely do their part to make that process much easier. All that’s fine but why is the performance going to be any better? Well, if the web server (IIS) is just serving out tile images from the disk, we can enable kernel-mode caching in IIS to server out these tiles which is magnitudes faster than serving it through the handler. When kernel-mode caching is enabled in IIS, the request is satisfied purely by IIS without the request having to enter the ASP.NET and be served by the IIS worker process. So, yeah it is MUCH MUCH faster with kernel-mode caching.

Also, in my snooping around the REST API, I found that the handlers in the REST API are implemented as IHTTPHandlers. The scalability of the API can be improved a little if these handled are written as IHTTPAsyncHandlers. When using IHTTPAsyncHandlers the threads serving these requests are not blocked until the request is served. Since, the serialization to JSON etc currently happen on the REST web server in AGS 9.3, the time to serve the requests might be non-trivial. So, this makes a higher number of threads available in the thread pool for the IIS worker process to use. This will definitely increase the scalability of the ESRI REST API and your web application. Given, there are things that we need to pay attention to when using this async programming model since, the number of threads could explode when serving very large number of requests and the process might just spend a lot time setting up the threads and cleaning up after them.

Those are just two thoughts I had on the subject today and I managed to put in words. I am also all ears to other ideas/criticisms on the subject.

I am also hoping that in the near future, ESRI’s SOAP and REST API have the exact same service contracts that are just exposed at different endpoints. Currently, the SOAP API seems to more feature-filled and the REST API seems to be playing catch up. Aren’t SOAP and REST just different ways of accessing the same exact GIS feature set in ArcGIS Server? Alright, I will end the rant right here. 🙂

Making a HTTP Post in Silverlight a.k.a thread hopping

Posted in C#, Silverlight by viswaug on September 17, 2009

Making a HTTP GET request is as easy as pie using the WebClient class that is available in the Silverlight library. The DownloadStringCompleted event callback of the WebClient class is invoked on the main UI thread thus allowing the developer to access UI elements in the callback method itself. If the web service call resulted in a failure, then that condition can be easily determined in the DownloadStringCompleted event callback method by checking if the Error property on the DownloadStringCompletedEventArgs argument passed into the callback method is NULL. The code snippet below illustrates one way to use the WebClient class.

    WebClient webClient = new WebClient();

    webClient.DownloadStringCompleted += (sender, e) =>

    {   

        if (e.Error == null)   

        {       

            //Process web service result here       

            txtBox.Text = e.Result;   

        }    else   

        {       

            //Process web service failure here       

            txtBox.Text = e.Error.Message;   

        }    callback();

    };

    webClient.DownloadStringAsync( new Uri( “URL address that needs to be fetched” ) );

 

But when it comes to making a HTTP POST in Silverlight, things aren’t so simple. Silverlight doesn’t come with any helper classes like the WebClient (for making HTTP GET requests) to help take the hassle out of making a HTTP POST. So, to make a HTTP POST request, we must jump through two threads before we get back on the main UI thread where we will be able to access the UI elements and update them as needed. Here is a simple illustration of the process that we have to go through to make a HTTP POST.

SilverlightHTTPPost

Before we get into code snippets for actually accomplishing the above, let’s get into a little bit of why the above needs to happen. When the user initiates an UI event (button click etc) upon which we must make the HTTP request, we need to ask Silverlight/browser to open a HTTP request pipe to the specified URL. We get this request stream in a different non-UI thread. The reason for this being that Silverlight makes HTTP requests through the browser stack (Silverlight 3 can use its own HTTP stack if needed, but uses the browser stack by default). As you might know, browsers limit the number of outgoing HTTP requests to a given domain name. This limit used to be 2 but modern day browsers have increased this limit (8 in IE8 etc). So, given this limit on the outgoing HTTP requests, there is no guarantee that the browser can open a HTTP request pipe to the URL when demanded by Silverlight. So, we end up getting the request stream in a non-UI thread. We can write/post the necessary data to this request pipe and wait to get a response from the web service. The results from the web service is received on another different non-UI thread. Once we read all the information from the response stream, we can’t start updating the UI elements since we are still on a non-UI thread. So, we need to pass this response from the web service back to the UI thread before we can start updating the UI elements.

Since, we need to pass data between threads as explained above, I had written these tiny little classes to help us through the process of making the HTTP POST request.

 

    public class HttpWebResponseData<T>

    {

        public HttpWebResponse Response { get; set; }

        public T Data { get; set; }

    }

 

    public class HttpWebRequestData<T>

    {

        public HttpWebRequest Request { get; set; }

        public T Data { get; set; }

    }

 

    public class ExceptionEventArgs : EventArgs

    {

        public Exception ExceptionInfo { get; set; }

 

        public ExceptionEventArgs(Exception ex)

        {

            ExceptionInfo = ex;

        }

    }

 

    public class EventArgs<T> : EventArgs

    {

        private T eventData;

 

        public EventArgs(T eventData)

        {

            this.eventData = eventData;

        }

 

        public T EventData

        {

            get

            {

                return eventData;

            }

        }

    }

 

We need to store away the SynchronizationContext of the UI thread before we begin making the HTTP POST request and start jumping threads. This is so that we can get back on the UI thread when the HTTP POST web service call has returned. So, create a variable to hold the in the SynchronizationContext Silverlight UserControl.

        SynchronizationContext syncContext;

 

The following code snippet illustrates how to use the HTTPWebRequest and the HTTPWebResponse classes in Silverlight to perform the HTTP POST.

    //This method will called on user action (button click etc); We are on the UI thread

    private void DoHTTPPost(List<string> stuffToPost)

    {

        //Obtain and store away the SynchronizationContext of the UI thread in the private variable declared above

        syncContext = SynchronizationContext.Current;

        HttpWebRequest request = ( HttpWebRequest ) WebRequest.Create( “URL address to POST to” ) );

        request.Method = “POST”;

        request.ContentType = “text/xml”;

        request.BeginGetRequestStream( new AsyncCallback( DoHTTPPostRequestReady ), new HttpWebRequestData<List<string>>()

        {

            Request = request,

            Data = stuffToPost

        } );

    }

 

    //This method will be called on the non-UI request thread

    private void DoHTTPPostRequestReady(IAsyncResult asyncResult)

    {

        HttpWebRequestData<List<string>> requestData = asyncResult.AsyncState as HttpWebRequestData<List<string>>;

        HttpWebRequest request = requestData.Request;

 

        Stream requestStream = request.EndGetRequestStream(asyncResult);

        DataContractSerializer serializer = new DataContractSerializer(typeof(List<string>));

 

        //Write the string list as XML to the request stream

        StreamWriter sw = new StreamWriter(requestStream);

        serializer.WriteObject(requestStream, requestData.Data);

        sw.Flush();

        requestStream.Close();

 

        request.BeginGetResponse(new AsyncCallback(DoHTTPPostResponseCallback), request);

    }

 

    //This methods will be called on the non-UI response thread

    private void DoHTTPPostResponseCallback(IAsyncResult ar)

    {

        //Ignoring error handling here to keep things simple

        HttpWebRequest request = ar.AsyncState as HttpWebRequest;

        HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;

        Stream responseStream = response.GetResponseStream();

        DataContractSerializer serializer = new DataContractSerializer(typeof(string));

        string result = serializer.ReadObject(responseStream) as string;

 

        //Post the response from the web service back to the UI thread using the SynchronizationContext obtained when initiating the HTTP POST request

        syncContext.Post(DoHTTPPostExtractResponse, new HttpWebResponseData<string>()

        {

            Response = response,

            Data = result

        });

    }

 

    //We are back on the UI thread when this method is called

    private void DoHTTPPostExtractResponse(object state)

    {

        string response = (state as HttpWebResponseData<string>).Data;

        //We can update the UI elements with the response here since we are back on the UI thread

        txtBox.Text = response;

    }

 

I will be posting more helper methods for the above pretty soon. Also, I will delve more into making HTTP POST requests to WCF REST endpoints in future posts.