使用 C# 的 Google 云打印

Posted

技术标签:

【中文标题】使用 C# 的 Google 云打印【英文标题】:Google Cloud Print using C# 【发布时间】:2011-12-12 08:42:14 【问题描述】:

我尝试使用 C# 使用 Google 云打印。互联网上只有一个example,他写了 Josh Goebel。 我不会发布完整的示例,这是发送文件进行打印的唯一方法:

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 ;

我运行这段代码,然后我的打印机有一个界面,但没有发生打印。我的打印机界面说要打印的页数为0,文件大小 与我发送给打印机的不符。

谷歌云打印说任务(作业)添加成功,但在谷歌云打印界面文档名称旁边显示“错误”。

我认为 HttpUtility.UrlEncodeConvert.ToBase64String 可能有问题,但我尝试了反向转换 - 一切正常。

有人有什么想法吗?

【问题讨论】:

我通过 Twitter 写信给 Josh Goebel - 他说了没有写这个例子的东西。嘿,认识这个程序员的人,谁写了这个? :) 已经很久了,所以我忘记了答案:) 但是我看看我的代码和我看到的(只是与上面的代码比较):“&contentType =” + HttpUtility.UrlEncode(“ url") + "&content=" + HttpUtility.UrlEncode(content);我还删除了字符串 responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd(); 之后的所有代码 【参考方案1】:

我很欣赏这个问题现在有点老了,但我最近不得不看看这个我在工作中做的事情,虽然发布的示例代码让我朝着正确的方向前进,但它确实让我很一段时间让它完全工作。

“列表打印机”功能如所述工作正常,但我无法让提交正常工作,它只是直接进入错误,如 OP 所述。

在使用 Fiddler 在 Chrome 中分析实际提交请求并查看 google 网站上的 php 示例代码后,我发现提交需要是多部分 MIME 消息,否则将无法正常工作。

这是一个 HTTP POST 消息示例,它最终使提交操作正常工作。注意gcp Printer Id需要在请求URL中传递,数据(无论是PDF、JPG还是PNG)需要经过Base64编码并设置正确的mime类型(application/pdf、image/jpeg.. .) 对于“内容”块(下面列表中的最后一个):

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--

让我大吃一惊的是,标题中指定的边界值比它们的实际用法少了 2 个连字符 (--)(当你盯着一些想知道它为什么不工作的东西时不明显!),最后一个边界实例的末尾有两个额外的连字符,我需要去掉 C# Expect100Continue 标头(通过使用 request.ServicePoint.Expect100Continue = false)。

更新:这是我当时写的完整代码,以防它帮助任何人。

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";
        
    

【讨论】:

过去几天我一直在努力解决这个问题……您的回答终于帮助我做对了。让谷歌错误地记录他们的服务...... 如果您设法让 Cloud Print 使用 C# 并可以粘贴一个工作示例,那就太好了。 Deevodavis,我将您移植到 Monodroid 的代码发布在:github.com/slackshot/GoogleCloudPrintMonodroid Xamarin 将做一些示例。我在 MIT 许可下发布了它。如果我可以在这里给你发消息,我会请求许可,但既然你把它贴在这里,我假设你希望人们从中受益。 后续:如何获得可以使用数小时以上的云打印令牌?例如。这里问什么:***.com/questions/15538143/… 你从未给 Source 变量赋值【参考方案2】:

对于任何为此苦苦挣扎的人,我创建了一个 github 存储库,其中包含有关如何通过服务帐户使用 Google 云打印的代码和说明,并针对他们的新 OAuth2 身份验证方法进行了更新:

https://github.com/io7/GoogleCloudPrint

【讨论】:

【参考方案3】:

看起来问题的原因是您发送到服务器的数据的编码。 在这种情况下,最可靠的解决方案是在发送文档时使用数据 URI 方案。 为此,您需要将 contentType 设置为“dataUrl”并按以下格式传递数据: "data:application/pdf;base64," + Convert.ToBase64String(document)

【讨论】:

我整个晚上都在尝试让云打印在 Java 上运行,这是我唯一错过的事情!谢谢好心的先生!【参考方案4】:

需要注意的是,发送capabilities 是强制性的。不发送将导致作业在提交后立即变为错误。应始终发送默认值:

"capabilities":[]

【讨论】:

以上是关于使用 C# 的 Google 云打印的主要内容,如果未能解决你的问题,请参考以下文章

从 javascript 调用 Google 云打印/搜索 API

未找到 Google 云打印 OAuth 范围

通过 Google 云打印进行打印

XSRF 令牌验证失败 Google 云打印

如何使用 Google 脚本打印驱动器文档

OAuth2