Porting the TwitPic API cURL example to C#, multipart data?
So Twitter changed its way of doing authentication to OAuth, I finally had no choice but to update my app. I got the Twitter stuff working (so I have a nice bit of saved OAuth information in my application). Now I have to get the TwitPic API working again. There are no libraries that handle the OAuth that I found, so I am having to do it by hand based on what I found here:
http://dev.twitpic.com/docs/2/upload/
I am slowly but surely getting there I think. I am not an expert at this kind of stuff by any means, but I got their other API call: http://dev.twitpic.com/docs/2/users_show It is working like a charm, though that isn't multipart data with an image there.
I have done some more research and realize the nice Twitterizer framework I am using doing OAuth does a lot of work for me, namely the signing of each request and only requiring me to pass in a few bits of my OAuth tokens. So I noticed the above method call to upload for TwitPic requires that to be signed the same way, which is the difficult part: getting it signed, and passing it on using webrequest.
Which is also what confuses me, they say the signature the OAuth echo part is passed in a header it seems, is this the same as creating a header using C#'s System.Net.WebHeaderCollection webhc = new System.Net.WebHeaderCollection();
?
I know what I have to do, somehow get the request called with my OAuth tokens (serialized into JSON), build a signature, then make the call to the actual API and pass it the three parameters (JSON Serialized): key, message, file.
The file is tripping me up too, as it is a memory resident file, I am not sure how to pass this data. I do have a code snippet from an older TwitPic library:
string fileContentType = "image/jpeg";//GetImageContentType(filename);
string fileHeader = String.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", "media", filename);
string fileData = Encoding.GetEncoding(encoding).GetString(binaryImageData);
contents.AppendLine(fileHeader);
contents.AppendLine(String.Format("Content-Type: {0}", fileContentType));
contents.AppendLine();
contents.AppendLine(fileData);
The trouble is that I am trying to do all this using JSON. The building up the fileContentType, etc. to append it all to a StringBuilder contents object seems like a lot more manual work than I need.
I wish there was a TwitPic API for Twitter's new authorization where I pass it the file, the message and the OAuth tokens. Alas... any steering in the right direction would be greatly appreciated.
Posted for completeness is the old upload file method I have:
// <summary>
// Uploads the photo and sends a new Tweet
// </summary>
// <param name="binaryImageData">The binary image data.</param>
// <param name="tweetMessage">The tweet message.</param>
// <param name="filename">The filename.</param>
// <returns>Return true, if the operation was succeded.</returns>
public bool UploadPhoto(byte[] binaryImageData, string tweetMessage, string filename)
{
// Documentation: http://www.twitpic.com/api.do
string boundary = Guid.NewGuid().ToString();
string requestUrl = String.IsNullOrEmpty(tweetMessage) ? TWITPIC_UPLADO_API_URL : TWITPIC_UPLOAD_AND_POST_API_URL;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
string encoding = "iso-8859-1";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
request.Method = "POST";
string header = string.Format("--{0}", boundary);
string footer = string.Format("--{0}--", boundary);
StringBuilder contents = new StringBuilder();
contents.AppendLine(header);
string fileContentType = "image/jpeg";//GetImageContentType(filename);
string fileHeader = String.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", "media", filename);
string fileData = Encoding.GetEncoding(encoding).GetString(binaryImageData);
contents.AppendLine(fileHeader);
contents.AppendLine(String.Format("Content-Type: {0}", fileContentType));
contents.AppendLine();
contents.AppendLine(fileData);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "username"));
contents.AppendLine();
//contents.AppendLine(this.Username);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "password"));
contents.AppendLine();
//contents.AppendLine(this.Password.ToInsecure开发者_如何学JAVAString());
if (!String.IsNullOrEmpty(tweetMessage))
{
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "message"));
contents.AppendLine();
contents.AppendLine(tweetMessage);
}
contents.AppendLine(footer);
byte[] bytes = Encoding.GetEncoding(encoding).GetBytes(contents.ToString());
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string result = reader.ReadToEnd();
XDocument doc = XDocument.Parse(result);
XElement rsp = doc.Element("rsp");
string status = rsp.Attribute(XName.Get("status")) != null ? rsp.Attribute(XName.Get("status")).Value : rsp.Attribute(XName.Get("stat")).Value;
return status.ToUpperInvariant().Equals("OK");
}
}
}
}
The fix is actually very simple. The issue is the URL you are posting to. In your line:
private const string TWITPIC_UPLOAD_AND_POST_API_URL = "http://api.twitpic.com/1/uploadAndPost";
You need to change to
private const string TWITPIC_UPLOAD_AND_POST_API_URL = "http://api.twitpic.com/1/uploadAndPost.xml";
OR
private const string TWITPIC_UPLOAD_AND_POST_API_URL = "http://api.twitpic.com/1/uploadAndPost.json";
This will give you the response type. You still need to change the portion of your code that relates to how you are using XDocument to parse the results. Depending on the URL you use above, the response will be either XML or JSON. Your example is good for XML, however the results code isn't close to what you are looking for. If you want to see the result code example, you can view it at http://dev.twitpic.com/docs/1/uploadAndPost/
For example, remove the following lines.
XElement rsp = doc.Element("rsp");
string status = rsp.Attribute(XName.Get("status")) != null ? rsp.Attribute(XName.Get("status")).Value : rsp.Attribute(XName.Get("stat")).Value;
mediaurl = rsp.Element("mediaurl").Value;
Then replace with
mediaurl = doc.Element("image").Element("url").Value;
Or you can just run through the debugger for JSON. If someone needs it and request, I can do the complete code.
Here is a variation on the Twipli API wrapper that allows you to do just that. The only problem I have is that I can't get it to respond with the result for third party manipulation of returned data. However it posts to Twitter and uploads the image correctly. So many heads are better than one so if you come up with a solution let me know.
protected void Button1_Click(object sender, EventArgs e)
{
string ct = img.PostedFile.ContentType.ToString();
string usertoken = Session["usrToken"].ToString();
string userSecret = Session["usrSecret"].ToString();
string conkey = Session["ConsumerKey"].ToString();
string consecret = Session["ConsumerSecret"].ToString();
string twitkey = Session["twitpickey"].ToString();
string _m = m.Text; // This takes the Tweet to be posted
HttpPostedFile myFile = img.PostedFile;
string fileName = myFile.FileName.ToString();
int nFileLen = myFile.ContentLength;
byte[] myData = new byte[nFileLen];
myFile.InputStream.Read(myData, 0, nFileLen);
TwitPic tw = new TwitPic();
upres.Text = tw.UploadPhoto(myData, ct, _m, fileName, twitkey, usertoken, userSecret, conkey, consecret).ToString();
Response.Redirect("twittercb.aspx?oauth_verifier=none");
}
public class TwitPic
{
private const string TWITPIC_UPLADO_API_URL = "http://api.twitpic.com/2/upload";
private const string TWITPIC_UPLOAD_AND_POST_API_URL = "http://api.twitpic.com/1/uploadAndPost";
///
/// Uploads the photo and sends a new Tweet
///
/// <param name="binaryImageData">The binary image data.
/// <param name="tweetMessage">The tweet message.
/// <param name="filename">The filename.
/// Return true, if the operation was succeded.
public string UploadPhoto(byte[] binaryImageData, string ContentType, string tweetMessage, string filename, string tpkey, string usrtoken, string usrsecret, string contoken, string consecret)
{
string boundary = Guid.NewGuid().ToString();
string requestUrl = String.IsNullOrEmpty(tweetMessage) ? TWITPIC_UPLADO_API_URL : TWITPIC_UPLOAD_AND_POST_API_URL;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
string encoding = "iso-8859-1";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
request.Method = "POST";
string header = string.Format("--{0}", boundary);
string footer = string.Format("--{0}--", boundary);
StringBuilder contents = new StringBuilder();
contents.AppendLine(header);
string fileContentType = ContentType;
string fileHeader = String.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", "media", filename);
string fileData = Encoding.GetEncoding(encoding).GetString(binaryImageData);
contents.AppendLine(fileHeader);
contents.AppendLine(String.Format("Content-Type: {0}", fileContentType));
contents.AppendLine();
contents.AppendLine(fileData);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "key"));
contents.AppendLine();
contents.AppendLine(tpkey);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "consumer_token"));
contents.AppendLine();
contents.AppendLine(contoken);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "consumer_secret"));
contents.AppendLine();
contents.AppendLine(consecret);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "oauth_token"));
contents.AppendLine();
contents.AppendLine(usrtoken);
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "oauth_secret"));
contents.AppendLine();
contents.AppendLine(usrsecret);
if (!String.IsNullOrEmpty(tweetMessage))
{
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "message"));
contents.AppendLine();
contents.AppendLine(tweetMessage);
}
contents.AppendLine(footer);
byte[] bytes = Encoding.GetEncoding(encoding).GetBytes(contents.ToString());
request.ContentLength = bytes.Length;
string mediaurl = "";
try
{
using (Stream requestStream = request.GetRequestStream()) // this is where the bug is due to not being able to seek.
{
requestStream.Write(bytes, 0, bytes.Length); // No problem the image is posted and tweet is posted
requestStream.Close();
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) // here I can't get the response
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string result = reader.ReadToEnd();
XDocument doc = XDocument.Parse(result); // this shows no root elements and fails here
XElement rsp = doc.Element("rsp");
string status = rsp.Attribute(XName.Get("status")) != null ? rsp.Attribute(XName.Get("status")).Value : rsp.Attribute(XName.Get("stat")).Value;
mediaurl = rsp.Element("mediaurl").Value;
return mediaurl;
}
}
}
}
catch (Exception ex)
{
ex.ToString();
}
return mediaurl;
}
}
精彩评论