Google Cloud Print using C#
Asked Answered
A

4

7

I try to use Google Cloud Print using C#. Internet has just one example, who wrote Josh Goebel. I will not publish the complete example, here is the only method that sends a file to print:


public CloudPrintJob PrintDocument(string printerId, string title, byte[] document)
    {
        try
        {
            string authCode;
            if (!Authorize(out authCode))
                return new CloudPrintJob() { success = false };

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.com/cloudprint/submit?output=json");
            request.Method = "POST";

            string queryString =
                "printerid=" + HttpUtility.UrlEncode(printerId) +
                "&capabilities=" + HttpUtility.UrlEncode("") +
                "&contentType=" + HttpUtility.UrlEncode("application/pdf") +
                "&title=" + HttpUtility.UrlEncode(title) +
                "&content=" + HttpUtility.UrlEncode(Convert.ToBase64String(document));

            byte[] data = new ASCIIEncoding().GetBytes(queryString);

            request.Headers.Add("X-CloudPrint-Proxy", Source);
            request.Headers.Add("Authorization", "GoogleLogin auth=" + authCode);

            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = data.Length;

            Stream stream = request.GetRequestStream();
            stream.Write(data, 0, data.Length);
            stream.Close();

            // Get response
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            string responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd();

            DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(CloudPrintJob));
            MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(responseContent));
            CloudPrintJob printJob = serializer.ReadObject(ms) as CloudPrintJob;

            return printJob;
        }
        catch (Exception ex)
        {
            return new CloudPrintJob() { success = false, message = ex.Message };
        }
    }

I run this code, then there is an interface of my printer, but print is not happening. The interface of my printer says that the pages to print 0, and the file size does not coincide with the one I sent to the printer.

Google Cloud Print says that the task(job) is successfully added, but at the interface of Google Cloud Print next to the name of the document display "Error".

I thought that might have a problem with HttpUtility.UrlEncode or Convert.ToBase64String, but I tried the reverse transformation - everything works.

Does anyone have any ideas?

Archibald answered 21/10, 2011 at 7:5 Comment(2)
I wrote to Josh Goebel via Twitter - he said what didn't wrote this example. Hey, guys who knows this programmer, who wrote this? :)Archibald
It's has been a long time, so I forgot the answer :) But I look at my code and what I see (just compare with code upper): "&contentType=" + HttpUtility.UrlEncode("url") + "&content=" + HttpUtility.UrlEncode(content); Also I delete all code after string responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd();Archibald
V
7

I appreciate this question is a little old now, but I've recently had to look at this for something I'm doing at work, and whilst the sample code posted started me off in the right direction it did take me quite a while to get it working completely.

The "list printers" function worked OK as described, but I couldn't get the Submit working properly, it just went straight to Error as the OP describes.

After profiling an actual submit request in Chrome using Fiddler, and looking at the PHP sample code on google's website, I discovered that the submit needed to be a multipart MIME message or it wouldn't work properly.

This is an example of the HTTP POST message which finally got the Submit operation working. Note that the gcp Printer Id needs to be passed in the request URL, and the data (whether it be a PDF, JPG or PNG) needs to be Base64 encoded and the correct mime type set (application/pdf, image/jpeg ...) for the "content" block (the last one in the list below):

POST http://www.google.com/cloudprint/submit?printerid=<printerid>&output=json HTTP/1.1
Host: www.google.com
Content-Length: 44544
X-CloudPrint-Proxy: Google-JS
Content-Type: multipart/form-data; boundary=----CloudPrintFormBoundaryqeq6g6ncj5v7

------CloudPrintFormBoundaryqeq6g6ncj5v7
Content-Disposition: form-data; name="capabilities"

{"capabilities":[{}]}
------CloudPrintFormBoundaryqeq6g6ncj5v7
Content-Disposition: form-data; name="contentType"

dataUrl
------CloudPrintFormBoundaryqeq6g6ncj5v7
Content-Disposition: form-data; name="title"

zodiac-pig-pic.jpg
------CloudPrintFormBoundaryqeq6g6ncj5v7
Content-Disposition: form-data; name="content"

data:image/jpeg;base64,JVBERi0xLjQKJeHp69MKMiAwIG...2NgolJUVPRg==
------CloudPrintFormBoundaryqeq6g6ncj5v7--

Things which tripped me up were that the Boundary value specified in the header has 2 less hyphens (--) than the actual usage of them (not obvious when you're staring at something wondering why it's not working!), the very last boundary instance has two additional hyphens at the end, and that I needed to get rid of the C# Expect100Continue header (by using request.ServicePoint.Expect100Continue = false).

UPDATE: Here is the complete code I wrote at the time, in case it helps anyone out.

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using GoogleCloudPrintServices.DTO;

namespace GoogleCloudPrintServices.Support
{
    public class GoogleCloudPrint
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Source { get; set; }

        private const int ServiceTimeout = 10000;

        public GoogleCloudPrint (String source)
        {
            Source = source;
        }

        public CloudPrintJob PrintDocument (string printerId, string title, byte[] document, String mimeType)
        {
            try
            {
                string authCode;
                if (!Authorize (out authCode))
                    return new CloudPrintJob { success = false };

                var b64 = Convert.ToBase64String (document);

                var request = (HttpWebRequest)WebRequest.Create ("http://www.google.com/cloudprint/submit?output=json&printerid=" + printerId);
                request.Method = "POST";

                // Setup the web request
                SetupWebRequest (request);

                // Add the headers
                request.Headers.Add ("X-CloudPrint-Proxy", Source);
                request.Headers.Add ("Authorization", "GoogleLogin auth=" + authCode);

                var p = new PostData ();

                p.Params.Add (new PostDataParam { Name = "printerid", Value = printerId, Type = PostDataParamType.Field });
                p.Params.Add (new PostDataParam { Name = "capabilities", Value = "{\"capabilities\":[{}]}", Type = PostDataParamType.Field });
                p.Params.Add (new PostDataParam { Name = "contentType", Value = "dataUrl", Type = PostDataParamType.Field });
                p.Params.Add (new PostDataParam { Name = "title", Value = title, Type = PostDataParamType.Field });

                p.Params.Add (new PostDataParam
                {
                    Name = "content",
                    Type = PostDataParamType.Field,
                    Value = "data:" + mimeType + ";base64," + b64
                });

                var postData = p.GetPostData ();
                Trace.WriteLine (postData);

                byte[] data = Encoding.UTF8.GetBytes (postData);

                request.ContentType = "multipart/form-data; boundary=" + p.Boundary;

                Stream stream = request.GetRequestStream ();
                stream.Write (data, 0, data.Length);
                stream.Close ();

                // Get response
                var response = (HttpWebResponse)request.GetResponse ();
                var responseContent = new StreamReader (response.GetResponseStream ()).ReadToEnd ();

                var serializer = new DataContractJsonSerializer (typeof (CloudPrintJob));
                var ms = new MemoryStream (Encoding.Unicode.GetBytes (responseContent));
                var printJob = serializer.ReadObject (ms) as CloudPrintJob;

                return printJob;
            }
            catch (Exception ex)
            {
                return new CloudPrintJob { success = false, message = ex.Message };
            }
        }

        public CloudPrinters Printers
        {
            get
            {
                var printers = new CloudPrinters ();

                string authCode;
                if (!Authorize (out authCode))
                    return new CloudPrinters { success = false };

                try
                {
                    var request = (HttpWebRequest)WebRequest.Create ("http://www.google.com/cloudprint/search?output=json");
                    request.Method = "POST";

                    // Setup the web request
                    SetupWebRequest (request);

                    // Add the headers
                    request.Headers.Add ("X-CloudPrint-Proxy", Source);
                    request.Headers.Add ("Authorization", "GoogleLogin auth=" + authCode);

                    request.ContentType = "application/x-www-form-urlencoded";
                    request.ContentLength = 0;

                    var response = (HttpWebResponse)request.GetResponse ();
                    var responseContent = new StreamReader (response.GetResponseStream ()).ReadToEnd ();

                    var serializer = new DataContractJsonSerializer (typeof (CloudPrinters));
                    var ms = new MemoryStream (Encoding.Unicode.GetBytes (responseContent));
                    printers = serializer.ReadObject (ms) as CloudPrinters;

                    return printers;
                }
                catch (Exception)
                {
                    return printers;
                }
            }
        }

        private bool Authorize (out string authCode)
        {
            var result = false;
            authCode = "";

            var queryString = String.Format ("https://www.google.com/accounts/ClientLogin?accountType=HOSTED_OR_GOOGLE&Email={0}&Passwd={1}&service=cloudprint&source={2}",
                UserName, Password, Source);
            var request = (HttpWebRequest)WebRequest.Create (queryString);

            // Setup the web request
            SetupWebRequest (request);

            var response = (HttpWebResponse)request.GetResponse ();
            var responseContent = new StreamReader (response.GetResponseStream ()).ReadToEnd ();

            var split = responseContent.Split ('\n');
            foreach (var s in split)
            {
                var nvsplit = s.Split ('=');
                if (nvsplit.Length == 2)
                {
                    if (nvsplit[0] == "Auth")
                    {
                        authCode = nvsplit[1];
                        result = true;
                    }
                }
            }

            return result;
        }

        private static void SetupWebRequest (HttpWebRequest webRequest)
        {
            // Get the details
            var appSettings = ConfigurationManager.AppSettings;

            // Create some credentials
            if (!String.IsNullOrWhiteSpace (appSettings["ProxyUsername"]))
            {
                var cred = new NetworkCredential (appSettings["ProxyUsername"], appSettings["ProxyPassword"],
                                                 appSettings["ProxyDomain"]);

                // Set the credentials
                webRequest.Credentials = cred;
                webRequest.Proxy = WebRequest.DefaultWebProxy;
                webRequest.Proxy.Credentials = cred;
            }

            // Set the timeout
            webRequest.Timeout = ServiceTimeout;
            webRequest.ServicePoint.ConnectionLeaseTimeout = ServiceTimeout;
            webRequest.ServicePoint.MaxIdleTime = ServiceTimeout;

            // Turn off the 100's
            webRequest.ServicePoint.Expect100Continue = false;
        }
    }
}



using System.Runtime.Serialization;

namespace GoogleCloudPrintServices.DTO
{
    [DataContract]
    public class CloudPrinter
    {
        [DataMember (Order = 0)]
        public string id { get; set; }

        [DataMember (Order = 1)]
        public string name { get; set; }

        [DataMember (Order = 2)]
        public string description { get; set; }

        [DataMember (Order = 3)]
        public string proxy { get; set; }

        [DataMember (Order = 4)]
        public string status { get; set; }

        [DataMember (Order = 5)]
        public string capsHash { get; set; }

        [DataMember (Order = 6)]
        public string createTime { get; set; }

        [DataMember (Order = 7)]
        public string updateTime { get; set; }

        [DataMember (Order = 8)]
        public string accessTime { get; set; }

        [DataMember (Order = 9)]
        public bool confirmed { get; set; }

        [DataMember (Order = 10)]
        public int numberOfDocuments { get; set; }

        [DataMember (Order = 11)]
        public int numberOfPages { get; set; }
    }
}



using System.Collections.Generic;
using System.Runtime.Serialization;

namespace GoogleCloudPrintServices.DTO
{
    [DataContract]
    public class CloudPrinters
    {
        [DataMember (Order = 0)]
        public bool success { get; set; }

        [DataMember (Order = 1)]
        public List<CloudPrinter> printers { get; set; }
    }
}



using System.Runtime.Serialization;

namespace GoogleCloudPrintServices.DTO
{
    [DataContract]
    public class CloudPrintJob
    {
        [DataMember (Order = 0)]
        public bool success { get; set; }

        [DataMember (Order = 1)]
        public string message { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace GoogleCloudPrintServices.Support
{
    internal class PostData
    {
        private const String CRLF = "\r\n";

        public string Boundary { get; set; }
        private List<PostDataParam> _mParams;

        public List<PostDataParam> Params
        {
            get { return _mParams; }
            set { _mParams = value; }
        }

        public PostData ()
        {
            // Get boundary, default is --AaB03x
            Boundary = "----CloudPrintFormBoundary" + DateTime.UtcNow;

            // The set of parameters
            _mParams = new List<PostDataParam> ();
        }

        public string GetPostData ()
        {
            var sb = new StringBuilder ();
            foreach (var p in _mParams)
            {
                sb.Append ("--" + Boundary).Append (CRLF);

                if (p.Type == PostDataParamType.File)
                {
                    sb.Append (string.Format ("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName)).Append (CRLF);
                    sb.Append ("Content-Type: ").Append (p.FileMimeType).Append (CRLF);
                    sb.Append ("Content-Transfer-Encoding: base64").Append (CRLF);
                    sb.Append ("").Append (CRLF);
                    sb.Append (p.Value).Append (CRLF);
                }
                else
                {
                    sb.Append (string.Format ("Content-Disposition: form-data; name=\"{0}\"", p.Name)).Append (CRLF);
                    sb.Append ("").Append (CRLF);
                    sb.Append (p.Value).Append (CRLF);
                }
            }

            sb.Append ("--" + Boundary + "--").Append (CRLF);

            return sb.ToString ();
        }
    }

    public enum PostDataParamType
    {
        Field,
        File
    }

    public class PostDataParam
    {
        public string Name { get; set; }
        public string FileName { get; set; }
        public string FileMimeType { get; set; }
        public string Value { get; set; }
        public PostDataParamType Type { get; set; }

        public PostDataParam ()
        {
            FileMimeType = "text/plain";
        }
    }
}
Vilify answered 17/4, 2012 at 8:16 Comment(6)
I have been working on this for the last couple days... Your answer finally helped me get it right. Leave it to Google to incorrectly document their services...Headless
If you managed to get Cloud Print working with C# and could paste a working example it would be great.Hellbox
Deevodavis, I posted your code ported to Monodroid at : github.com/slackshot/GoogleCloudPrintMonodroid Xamarin will be making some examples. I released it under the MIT License. If I could have messaged you on here, I would have, to ask permission, but since you've posted it here, I assume that you wanted people to benefit from it.Oleoresin
A follow on: How do you get a token for cloud print that is good for more than a few hours? E.g. what is asked here: #15538643Ahouh
You never gave the Source variable a valueRuthenious
On PHP, you need to add a parameter contentTransferEncoding=base64 and encode the document in base64 to prevent white pages. I got inspired from github.com/yasirsiddiqui/php-google-cloud-print/blob/master/…Taxexempt
S
7

For anyone struggling with this, I have created a github repository with code and instructions on how to use Google cloud print with a service account, updated for their new OAuth2 authentication method:

https://github.com/io7/GoogleCloudPrint

Stereography answered 19/8, 2015 at 7:49 Comment(0)
A
3

Looks like the reason of the problem is encoding of the data you sent to the server. The most reliable solutions in this case would be to use data URI scheme when you send the document. To do that you need to set contentType to "dataUrl" and pass data in the following format: "data:application/pdf;base64," + Convert.ToBase64String(document)

Atomic answered 27/10, 2011 at 5:18 Comment(1)
I spent whole evening trying to make cloud print work on Java, and this is the only thing that I missed! Thank you kind sir!Quadruplicate
G
0

It should be noted that sending capabilities is mandatory. Not sending it will result in the job turning to Error immediately after submission. A default value should always be sent:

{"capabilities":[{}]}
Gamopetalous answered 10/10, 2012 at 2:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.