SOE REST Web Service - How to return different MIME content types?

5888
11
07-09-2010 02:04 PM
BrooksShannon
New Contributor
Folks,

I posted this in the ArcObjects forum, but thought I'd see if anyone here might have an idea, too.

I've been messing around with the new types in ESRI.ArcGIS.SOESupport, starting at 10.0 Prerelease.  I've successfully built two little server object extensions that each expose a REST resource and REST operation.

My first SOE, which encapsulates some spatial and attribute query business logic and returns results as JSON, works fine when upgraded to 10.0 Release.  However, my other SOE, which does not return JSON, but instead a PNG image, doesn't work as expected.

The latter's REST operation functionality is generally (for the purposes of this question) encapsulated in a method whose signature conforms to the ESRI.ArcGIS.SOESupport.OperationHandler delegate.  As such, I'm able to simply return my PNG image as an array of bytes.  Back at 10.0 Prerelease, this worked splendidly.  I was able to do some heavy processing on the server, dynamically create a PNG, and return it back to the client.

After upgrading to 10.0 Release, and making the requisite changes to my REST operation's method signature (by adding an additional parameter, requestProperties), my SOE no longer returns a PNG image that clients can use; instead, it returns the PNG's byte array representation as text.

I've spent some time today looking into the cause of this, and it seems like this is the case:

1) At 10.0 Prerelease, my SOE's HTTP response contained no content type.
2) At 10.0 Release, the response contains this content type: "text/plain;charset=utf-8"

This seems to explain why I'm not seeing an honest-to-God image, but just a text representation of the bytes that make up the image.

Upon discovering this, I hit the documentation, trying to see if there was a way I could coax the SOE into specifying a different content type within the response.  The documentation on resources.arcgis.com didn't appear to say much (in fact, the ESRI.ArcGIS.SOESupport namespace doesn't even appear to exist in the API reference).  For the heck of it, I started poking around in VS 2010's Object Browser.

To my surprise, I noticed a new class - RestResponseProperties.  Looking at its documentation in the object browser, this seemed to be something useful, and might just give me what I need.  However, I'm not actually able to get it to work.  When I try something like this...


private byte[] MyRESTOperation(NameValueCollection boundVariables,
                                       JsonObject operationInput,
                                       string outputFormat,
                                       string requestProperties,
                                   out string responseProperties)
{

// Create the PNG image, and get it as an array of bytes.
byte[] pngAsBytes = myImageGeneratorClass.Generate();

// Then attempt to specify the HTTP response's 
// content type.  Let it be a PNG image (and not something else, like 
// plain text).
RestResponseProperties props = new RestResponseProperties();
props.ContentType = "image/png";
responseProperties = props.ToString();           

// Return the bytes.
return pngAsBytes; 

}


...I get the following response (in Firefox, fwiw)

{"error":{"code":500,"message":"An unexpected error occurred processing the request.","details":[]}}


which, unfortunately, doesn't give me a lot of love.

Does anyone know what the issue might be?  Are there only certain content types that I can use for RestResponseProperties.ContentType?  Or, are there certain values for these four parameters of an OperationHandler

operationInput: A JsonObject instance with the input for the operation.
outputFormat: The requested output format, for example "json".
requestProperties: Well-known properties of the request.
responseProperties: Well-known properties for the response.


that I need to set, or check for?  Perhaps an output format other than "json"?

Thanks!
Brooks
0 Kudos
11 Replies
BrooksShannon
New Contributor


NOTE: There's no extra charset=utf-8 . I believe this extra encoding bit makes all the difference. Please verify that you do not add this encoding somewhere else in your code.



I think you're correct; and, as it stands, I don't add the encoding specification anywhere in my code. 

However, with your help, I was able to get my SOE to work again! I noticed in your sample that you were doing things in basically the same way I was, except for one critical difference. Your REST operation is constructed like this

RestOperation png = new RestOperation("png",
                                                      null,
                                                      new string[] { "png" },
                                                      PngHandler);


While mine looked something like this

RestOperation png = new RestOperation("png",
                                                      null,
                                                      new string[] { "json" },
                                                      PngHandler);


As such, my request would contain a format parameter f=json, whereas your format parameter was f=png.

All I had to do was swap "json" for "png" and specify "png" as the return format in the request.  That did the trick!

The funny thing is, back at 10.0 prerelease, it worked just fine to use "json" as the return format.  Now, however, somewhere along the line, the data must get automatically encoded in UTF8 if the return format is "json" but not if it's something else.

Thanks for helping me solve this, Sergey!  Without your help this would have probably gone unresolved.  I really appreciate you taking the time to investigate it.

Regards,
Brooks
0 Kudos
MCMADMIN
New Contributor II
... and the handler itself:
 private byte[] PngHandler(System.Collections.Specialized.NameValueCollection boundVariables,
            ESRI.ArcGIS.SOESupport.JsonObject operationInput,
            string outputFormat,
            string requestProperties,
            out string responseProperties)
        {

            responseProperties = "{\"Content-Type\" : \"image/png\"}";
            System.IO.FileStream fs = new System.IO.FileStream("c:\\temp\\8540.png", System.IO.FileMode.Open);
            System.IO.BinaryReader br = new System.IO.BinaryReader(fs);


            System.IO.MemoryStream ms = new System.IO.MemoryStream();

            const int count = 1024;
            while(true)
            {
                byte[] buf = br.ReadBytes(count);
                ms.Write(buf, 0, buf.Length);
                if (buf.Length < count)
                    break;
            }

            br.Close();
            fs.Close();
            fs.Dispose();

            return ms.ToArray();
        }



That was a real good template, but can be simplified and generalized in several ways:

  • For reading a file in .net you can simply use File.ReadAllBytes(path);

  • Determining the correct mime type is discussed here

  • Setting the responseProperties is much easier when using the JsonObject

This results in two little functions, which do the job:

protected string GetMimeType(FileInfo fileInfo)
{
    string mimeType = "application/unknown";
    RegistryKey regKey = Registry.ClassesRoot.OpenSubKey(fileInfo.Extension.ToLower());
    if (regKey != null)
    {
        object contentType = regKey.GetValue("Content Type");
        if (contentType != null)
            mimeType = contentType.ToString();
    }
    return mimeType;
}
 
protected byte[] FileHandler(string path, out string responseProperties)
{
    FileInfo f = new FileInfo(path);
    string contentType = GetMimeType(f);
    long contentLength = f.Length;
    JsonObject props = new JsonObject();
    props.AddString("Content-Type", contentType);
    props.AddLong("Content-Length", contentLength);
    props.AddString("Pragma", "public");
    props.AddString("Cache-control", "must-revalidate");
    props.AddString("Content-Disposition", "inline; filename=\"" + path + "\"");
    responseProperties = props.ToJson();
    return File.ReadAllBytes(path);
}