i"m want to send notification from my server side (c#) via urbanairship api
is there any example in c# how to do it?
thanks
Basically...
using System;
using System.IO;
using System.Net;
using System.Text;
namespace Examples.System.Net
{
public class WebRequestPostExample
{
public static void Main ()
{
// Create a request using a URL that can receive a post.
WebRequest request = WebRequest.Create ("https://go.urbanairship.com/api/push/");
// Set the Method property of the request to POST.
request.Method = "POST";
// Create POST data and convert it to a byte array.
broken out to multiple lines so you can read it
string postData = "{
"device_tokens": [
"some device token",
"another device token"
],
"aliases": [
"user1",
"user2"
],
"tags": [
"tag1",
"tag2"
],
"schedule_for": [
"2010-07-27 22:48:00",
"2010-07-28 22:48:00"
],
"exclude_tokens": [
"device token you want to skip",
"another device token you want to skip"
],
"aps": {
"badge": 10,
"alert": "Hello from Urban Airship!",
"sound": "cat.caf"
}
}";
and then
byte[] byteArray = Encoding.UTF8.GetBytes (postData);
// Set the ContentType property of the WebRequest.
request.ContentType = "application/json";
// Set the ContentLength property of the WebRequest.
request.ContentLength = byteArray.Length;
//Do a http basic authentication somehow
string username = "<application key from urban airship>";
string password = "<master secret from urban airship>";
string usernamePassword = username + ":" + password;
CredentialCache mycache = new CredentialCache();
mycache.Add( new Uri( "https://go.urbanairship.com/api/push/" ), "Basic", new NetworkCredential( username, password ) );
request.Credentials = mycache;
request.Headers.Add( "Authorization", "Basic " + Convert.ToBase64String( new ASCIIEncoding().GetBytes( usernamePassword ) ) );
// Get the request stream.
Stream dataStream = request.GetRequestStream ();
// Write the data to the request stream.
dataStream.Write (byteArray, 0, byteArray.Length);
// Close the Stream object.
dataStream.Close ();
// Get the response.
WebResponse response = request.GetResponse ();
// Display the status.
Console.WriteLine (((HttpWebResponse)response).StatusDescription);
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream ();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader (dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd ();
// Display the content.
Console.WriteLine (responseFromServer);
// Clean up the streams.
reader.Close ();
dataStream.Close ();
response.Close ();
}
}
}
See api docs, msdn and here for more on https
The accepted answer doesn't work, you need to change the following line:
request.ContentType = "application/x-www-form-urlencoded";
to
request.ContentType = "application/json";
Complete working code shown below:
using System;
using System.IO;
using System.Net;
using System.Text;
namespace UrbanAirship_Tes_1
{
class Program
{
public static void Main()
{
// Create a request using a URL that can receive a post.
WebRequest request = WebRequest.Create("https://go.urbanairship.com/api/push/");
request.Credentials = new NetworkCredential("pvYMExk3QIO7p2YUs6BBkg", "rO3DsucETRadbbfxHkd6qw");
// Set the Method property of the request to POST.
request.Method = "POST";
// Create POST data and convert it to a byte array.
//WRITE JSON DATA TO VARIABLE D
string postData = "{\"aps\": {\"badge\": 1, \"alert\": \"Hello from Urban Airship!\"}, \"device_tokens\": [\"6334c016fc643baa340eca25bc661d15055a07b475e9a6108f3f644b15dd05ac\"]}";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
// Set the ContentType property of the WebRequest.
request.ContentType = "application/json";
// Set the ContentLength property of the WebRequest.
request.ContentLength = byteArray.Length;
// Get the request stream.
Stream dataStream = request.GetRequestStream();
// Write the data to the request stream.
dataStream.Write(byteArray, 0, byteArray.Length);
// Close the Stream object.
dataStream.Close();
// Get the response.
WebResponse response = request.GetResponse();
// Display the status.
// Console.WriteLine(((HttpWebResponse)response).StatusDescription);
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
Console.WriteLine(responseFromServer);
// Clean up the streams.
reader.Close();
dataStream.Close();
response.Close();
}
}
}
public class PushNotificationHelper
{
private readonly ILog log4netEngine;
private string UrbanAirshipApplicationKey { get; set; }
private string UrbanAirshipApplicationSecret { get; set; }
private string UrbanAirshipApplicationMasterSecret { get; set; }
public PushNotificationHelper(string UrbanAirshipApplicationKey, string UrbanAirshipApplicationSecret, string UrbanAirshipApplicationMasterSecret)
{
log4netEngine = LogManager.GetLogger(typeof(PushNotificationHelper).Name);
this.UrbanAirshipApplicationKey = UrbanAirshipApplicationKey;
this.UrbanAirshipApplicationSecret = UrbanAirshipApplicationSecret;
this.UrbanAirshipApplicationMasterSecret = UrbanAirshipApplicationMasterSecret;
}
public void PushNotification2iPhones(string alertText, string[] apids, string extra)
{
if (!string.IsNullOrEmpty(alertText) && apids.Length > 0)
{
iPhonePushNotification pushNotification = new iPhonePushNotification
{
MessageBody = new iPhonePushNotificationMessageBody
{
Alert = alertText
},
Extra = extra,
APIDs = apids
};
string jsonMessageRequest = pushNotification.ToJsonString();
try
{
SendMessageToUrbanAirship(jsonMessageRequest);
log4netEngine.InfoFormat("Push Notification Success , iPhoneDevice:{0}, message:{1},extra:{2}", string.Join(",", apids), alertText, extra);
}
catch (Exception ex)
{
log4netEngine.InfoFormat("Push Notification Error:{0}, iPhoneDevice:{1}, message:{2},extra:{3}", ex.Message, string.Join(",", apids), alertText, extra);
}
}
}
public void PushNotification2Androids(string alertText, string[] apids, string extra)
{
if (!string.IsNullOrEmpty(alertText) && apids.Length > 0)
{
AndroidPushNotification pushNotification = new AndroidPushNotification
{
MessageBody = new AndroidPushNotificationMessageBody
{
Alert = alertText,
Extra = extra
},
APIDs = apids
};
string jsonMessageRequest = pushNotification.ToJsonString();
try
{
SendMessageToUrbanAirship(jsonMessageRequest);
log4netEngine.InfoFormat("Push Notification Success , androidDevice:{0}, message:{1},extra:{2}", string.Join(",", apids), alertText, extra);
}
catch (Exception ex)
{
log4netEngine.InfoFormat("Push Notification Error:{0}, androidDevice:{1}, message:{2},extra:{3}", ex.Message, string.Join(",", apids), alertText, extra);
}
}
}
private void SendMessageToUrbanAirship(string jsonMessageRequest)
{
var uri = new Uri("https://go.urbanairship.com/api/push/");
var encoding = new UTF8Encoding();
var request = WebRequest.Create(uri);
request.Method = "POST";
request.Credentials = new NetworkCredential(this.UrbanAirshipApplicationKey, this.UrbanAirshipApplicationMasterSecret);
request.ContentType = "application/json";
request.ContentLength = encoding.GetByteCount(jsonMessageRequest);
using (var stream = request.GetRequestStream())
{
stream.Write(encoding.GetBytes(jsonMessageRequest), 0, encoding.GetByteCount(jsonMessageRequest));
stream.Close();
var response = request.GetResponse();
response.Close();
}
}
}
public class NotificationToPush
{
public int ReceiverUserID { get; set; }
public string Message { get; set; }
public Dictionary<string, string> Extra { get; set; }
}
[DataContract(Name = "PushNotificationBody")]
internal class PushNotification
{
public string ToJsonString()
{
var result = JsonConvert.SerializeObject(this);
return result;
}
}
[DataContract(Name = "iPhonePushNotification")]
internal class iPhonePushNotification : PushNotification
{
[DataMember(Name = "aps")]
public iPhonePushNotificationMessageBody MessageBody { get; set; }
[DataMember(Name = "extra")]
public string Extra { get; set; }
[DataMember(Name = "device_tokens")]
public string[] APIDs { get; set; }
}
[DataContract(Name = "iPhonePushNotificationMessageBody")]
internal class iPhonePushNotificationMessageBody
{
[DataMember(Name = "alert")]
public string Alert { get; set; }
}
[DataContract(Name = "AndroidPushNotification")]
internal class AndroidPushNotification : PushNotification
{
[DataMember(Name = "android")]
public AndroidPushNotificationMessageBody MessageBody { get; set; }
[DataMember(Name = "apids")]
public string[] APIDs { get; set; }
}
[DataContract(Name = "AndroidPushNotificationMessageBody")]
internal class AndroidPushNotificationMessageBody
{
[DataMember(Name = "alert")]
public string Alert { get; set; }
[DataMember(Name = "extra")]
public string Extra { get; set; }
}
Here is how to do it using the System.Net.Http.HttpClient async methods.
var handler = new HttpClientHandler { Credentials = new NetworkCredential(urbanairshipapiKey, urbanairshipApiAppMasterSecret) };
var client = new HttpClient(handler);
var added = client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/vnd.urbanairship+json; version=3;");
var response = await client.PostAsync(apiUrl + "/push/", new StringContent(json, Encoding.UTF8, "application/json"));
I wrote a c# library to simplify the use of the UrbanAirship API
https://github.com/JeffGos/urbanairsharp
Hope it helps!
Related
I want to call to a API using a class library.
Here is the way they need us to send the post request
Or
To achecive this I use this code
public class Keey
{
public string username { get; set; }
public string passward { get; set; }
public string src { get; set; }
public string dst { get; set; }
public string msg { get; set; }
public string dr { get; set; }
}
public void SendSms(string phoneNo, string SMS)
{
Keey account = new Keey
{
username = "*****",
passward = "*********",
src = "test.com",
dst = phoneNo,
msg = SMS,
dr = "1",
};
WebRequest request = WebRequest.Create("http://testserver.com ");
// Set the Method property of the request to POST.
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = SMS.Length;
CredentialCache.DefaultNetworkCredentials.UserName = account.username;
CredentialCache.DefaultNetworkCredentials.Password = account.passward;
request.Credentials = CredentialCache.DefaultNetworkCredentials;
WebResponse response = request.GetResponse();
}
But I dont know where to add those port,src,dst,msg etc... Please help me
Code is in C#
That is depend on your Web-Api Service. for what input parameter needed.
There are several ways to perform POST requests:
HttpWebRequest (not recommended for new work)
using System.Net;
using System.Text; // For class Encoding
using System.IO; // For StreamReader
var request = (HttpWebRequest)WebRequest.Create("http://testserver.com/test.aspx");
//your data should place in here
var postData = "thing1=" + Uri.EscapeDataString("hello");
postData += "&thing2=" + Uri.EscapeDataString("world");
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
recommended way to get this work:
using System.Net.Http;
private static readonly HttpClient client = new HttpClient();
POST
var values = new Dictionary<string, string>
{
{ "thing1", "hello" },
{ "thing2", "world" }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("http://testserver.com/test.aspx", content);
var responseString = await response.Content.ReadAsStringAsync();
I've got some problems with getting REST API response that allows to get members of the group( GET {serviceUrl}/v3/conversations/{conversationId}/members). I'm debugging my bot in Bot Emulator, and it runs fine here
- that's what it shows in the Bot Simulator
- But that's how it shows on Azure.
The main problem is that i really can't recognize the problem.I know that the problem is in requesting because the getResponse method crash all programm when it is on server(but why this works in the bot emulator?).Here is the code(I'm using .NET CORE, Microsoft Bot Framework). P.S.{id} in Authorization is correct, and i get it from another request(but unlike the problem request that request works everywhere), the {url} is turnContext.Activity.ServiceUrl
HttpWebRequest webRequest23 = (HttpWebRequest)WebRequest.Create($"{url}/v3/conversations/{turnContext.Activity.Conversation.Id}/members");
await turnContext.SendActivityAsync($"{url}/v3/conversations/{turnContext.Activity.Conversation.Id}/members");
await turnContext.SendActivityAsync(turnContext.Activity.Conversation.Id);
webRequest23.ContentType = "application/json";
webRequest23.Headers["Authorization"] = $"Bearer {id}";
WebResponse response2 = webRequest23.GetResponse();//crash here
await turnContext.SendActivityAsync("3");
string smth25 = "";
using (StreamReader stream = new StreamReader(response2.GetResponseStream(), true))
{
smth25 = stream.ReadToEnd();
}
JArray jsonArray = JArray.Parse(smth25);
List<string> names = new List<string>();
foreach(var x in jsonArray)
{
var name = JObject.Parse(x.ToString())["name"].ToString();
names.Add("#" + name);
}
string s = "This group consists of - ";
foreach (var x in names){
s += "" + x + ",";
}
await turnContext.SendActivityAsync(s);
Here is how i get id:
string stringData = "grant_type=client_credentials&client_id=66b16ef5-d086-40b7-ae9c-2f50a1f028c6&client_secret=yRUYg4g.:ANuw3Y_01V=#.JkAv=#g3EE&scope=https%3A%2F%2Fapi.botframework.com%2F.default";
var data = Encoding.Default.GetBytes(stringData);
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token");
webRequest.Host = "login.microsoftonline.com";
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
var newStream = webRequest.GetRequestStream();
newStream.Write(data, 0, data.Length);
newStream.Close();
WebResponse response = webRequest.GetResponse();
string line = string.Empty;
string smone = "";
using (StreamReader stream = new StreamReader(response.GetResponseStream(), true))
{
smone = stream.ReadToEnd();
}
var id = JObject.Parse(smone)["access_token"].ToString();
I don't understand exactly what your "turnContext.SendActivityAsync" lines are supposed to do, but it might be working in the bot emulator because the emulator doesn't require a proper authenticated session like Teams does. Anyway, something like this should actually help you:
First, define this class somewhere:
public class MicrosoftTeamUser
{
public string Id { get; set; }
public string ObjectId { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string UserPrincipalName { get; set; }
public string TenantId { get; set; }
}
then use:
List<ChannelAccount> teamMembers = (await turnContext.TurnState.Get<IConnectorClient>().Conversations.GetConversationMembersAsync(
turnContext.Activity.GetChannelData<TeamsChannelData>().Team.Id).ConfigureAwait(false)).ToList();
List<MicrosoftTeamUser> teamsUsers = new List<MicrosoftTeamUser>();
foreach (var item in teamMembers)
{
var teamsUser = JsonConvert.DeserializeObject<MicrosoftTeamUser>(item.Properties.ToString());
teamsUser.Id = item.Id;
teamsUsers.Add(teamsUser);
}
Then you'll have the list of members in the teamMembers variable
I am getting an invalid cast exception that the specified cast is not valid. On this line:
RootObject mountain = JsonConvert.DeserializeObject<RootObject>(json1);
From the documentation this should be fine? I can see the console output is fine?
Response: [{"Height_ft": 2999.0, "Height_m": 914.0, "ID": "c1",
"Latitude": 57.588007, "Longitude": -5.5233564, "Name": "Beinn Dearg",
"humidity": 0.81, "snowCover": 4.99, "temperature": 63.0}]
Spinner spinner = (Spinner)sender;
string urlmountain = "http://removed.azurewebsites.net/api/Mountains?name=";
JsonValue json1 = FetchMountain(urlmountain+string.Format("{0}", spinner.GetItemAtPosition(e.Position)));
//below.................................
RootObject mountain = JsonConvert.DeserializeObject<RootObject>(json1); //this line
string toast = mountain.Name;
Toast.MakeText(this, toast, ToastLength.Long).Show();
private JsonValue FetchMountain(string urlmountain)
{
// Create an HTTP web request using the URL:
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(urlmountain));
request.ContentType = "application/json";
request.Method = "GET";
// Send the request to the server and wait for the response:
using (WebResponse response = request.GetResponse())
{
// Get a stream representation of the HTTP web response:
using (Stream stream = response.GetResponseStream())
{
// Use this stream to build a JSON document object:
JsonValue jsonDoc1 = JsonObject.Load(stream);
Console.Out.WriteLine("Response: {0}", jsonDoc1.ToString());
// Return the JSON document:
return jsonDoc1;
}
}
}
public class RootObject
{
public string ID { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public string Name { get; set; }
public double? Height_m { get; set; }
public double? Height_ft { get; set; }
public double? temperature { get; set; }
public double? humidity { get; set; }
public double? snowCover { get; set; }
public override string ToString()
{
return Name;
}
}
The json data being returned is an array of objects, not a single object, as denoted by the opening and closing brackets []. You need to deserialize to an array or a list:
var mountains = JsonConvert.DeserializeObject<List<RootObject>>(json);
To access the first mountain from the deserialized payload, use .FirstOrDefault().
var mountain = mountains.FirstOrDefault();
if (mountain != null)
{
string toast = mountain.Name;
Toast.MakeText(this, toast, ToastLength.Long).Show();
}
It looks like your JSON is an Array of objects. You should be able to deserialize the array and get the first one like so:
RootObject mountain = JsonConvert.DeserializeObject<RootObject[]>(json1)[0];
One thing to note is that you are sort of mixing technologies here. JsonValue is from the System.Json namespace, whereas JsonConvert is from the Newtonsoft.Json (i.e. JSON.Net) namespace. If you wanted to go strictly with JSON.Net, you could do something like this:
private RootObject FetchMountain(string urlmountain)
{
// Create an HTTP web request using the URL:
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(urlmountain));
request.ContentType = "application/json";
request.Method = "GET";
using (WebResponse response = request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader streamReader = new StreamReader(stream))
{
JsonSerializer serializer = new JsonSerializer();
RootObject[] mountains = (RootObject[])serializer.Deserialize(streamReader, typeof(RootObject[]));
return (mountains.Length > 0) ? mountains[0] : null;
}
}
I need to call a WebApi using WebClient where have to pass an object as parameter. I have my WebApi method as similar to bellow code:
example URI: localhost:8080/Api/DocRepoApi/PostDoc
[HttpPost]
public string PostDoc (DocRepoViewModel docRepo)
{
return string.enpty;
}
and then DocRepoViewModel is:
public class DocRepoViewModel
{
public string Roles { get; set; }
public string CategoryName { get; set; }
public List<AttachmentViewModel> AttachmentInfo { get; set; }
}
AttachmentViewModel is:
public class AttachmentViewModel
{
public string AttachmentName { get; set; }
public byte[] AttachmentBytes { get; set; }
public string AttachmentType { get; set; }
}
New I need to call PostDoc method from my MVC controller (not javascript ajax). How can I pass this specific parameter that I can make the call and get all data in my WebApi method. Can we do it by WebClient? Or there are better way. Please help.
You could use the PostAsJsonAsync method as follows:
static async Task RunAsync()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("localhost:8080/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
var docViewModel = new DocRepoViewModel() { CategoryName = "Foo", Roles= "role1,role2" };
var attachmentInfo = new List<AttachmentViewModel>() { AttachmentName = "Bar", AttachmentType = "Baz", AttachmentBytes = File.ReadAllBytes("c:\test.txt" };
docViewModel.AttachmentInfo = attachmentInfo;
response = await client.PostAsJsonAsync("DocRepoApi/PostDoc", docViewModel);
if (response.IsSuccessStatusCode)
{
// do stuff
}
}
}
Asp .net reference
Check out following code.
string url = "Your Url";
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.KeepAlive = false;
request.ContentType = "application/json";
request.Method = "POST";
var entity = new Entity(); //your custom object.
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(entity));
request.ContentLength = bytes.Length;
Stream data = request.GetRequestStream();
data.Write(bytes, 0, bytes.Length);
data.Close();
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string message = reader.ReadToEnd();
var responseReuslt = JsonConvert.Deserialize<YourDataContract>(message);
}
}
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?
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";
}
}
}
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
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)
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":[{}]}