新浪微博PC客户端(DotNet WinForm版)——功能实现分解介绍
上一篇:新浪微博PC客户端(DotNet WinForm版)—— 初探
说明一下:只是兴趣,并不是想发布为一个软件,说实在的,如果要作为一个软件发布,要做的工作还有很多。
新浪微博API地址:http://open.t.sina.com.cn/wiki/index.php/API%E6%96%87%E6%A1%A3?retcode=0。
目前提供的SDK:
其它的不清楚,C#的还不完善,而且不是官方的。
当前已实现的功能:
1、HTTP普通鉴权(Basic Authentication)的身份验证方式,说白了就是每次访问API都要发送帐号和密码,当然是不安全的,但是相比OAuth验证方式门槛要低的多。要使用OAuth验证方式可以去看下SDK。
===》account/verify_credentials 验证当前用户身份是否合法
OAuth是一种国际通用的授权方式,它的特点是不需要用户在第三方应用输入用户名及密码。OAuth的技术说明可参看官方网站 http://oauth.net。学习和研究OAuth的意义不仅仅在于新浪围脖,而是对以后开发类似的项目非常有意义和价值(如果你还不知道OAuth授权方式)。
验证通过则返回用户的个人信息,参照下面的API字段说明。
上一篇说了,API返回的结果格式有两种:XML和JSON,在调用API的时候可以指定。
这里我指定的是XML格式,实现代码如下:
登录窗体:FrmLogin.cs,可以看上一篇的截图。
(1)把申请AppKey和AppSecret写到app.config
<configuration>
<appSettings>
<add key="AppKey" value="3476523072"/>
<add key="AppSecret" value="3c909770efa6865fd5843a564545826d"/>
</appSettings>
</configuration>
要申请AppKey,需要登录新浪围脖,然后在这个地址创建应用并生成: http://open.t.sina.com.cn/apps/new。
(2)创建了一个静态类,保存用户信息
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace MBClient.Entities
{
/// <summary>
/// 微博帐号资料
/// </summary>
internal class UserInfo
{
internal UserInfo(){ }
internal static string UserName { get; set; }
internal static string Password { get; set; }
internal static string UserNamePassword { get; set; }
internal static int ID { get; set; }
internal static string ScreenName { get; set; }
internal static string Name { get; set; }
internal static int Province { get; set; }
internal static int City { get; set; }
internal static string Location { get; set; }
internal static string Description { get; set; }
internal static string Url { get; set; }
internal static string ProfileImageUrl { get; set; }
internal static string Domain { get; set; }
internal static string Gender { get; set; }
/// <summary>
/// 粉丝数
/// </summary>
internal static int FollowersCount { get; set; }
/// <summary>
/// 关注数
/// </summary>
internal static int FriendsCount { get; set; }
/// <summary>
/// 微博数
/// </summary>
internal static int StatusesCount { get; set; }
internal static int FavouritesCount { get; set; }
/// <summary>
/// 微博帐号日期
/// </summary>
internal static string CreatedAt { get; set; }
internal static bool Following { get; set; }
internal static bool Verified { get; set; }
internal static bool AllowAllActMsg { get; set; }
internal static bool GeoEnabled { get; set; }
}
}
(3)调用API
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using MBClient.Entities;
using System.Runtime.Serialization.Json;
using System.Data;
using System.Drawing;
using MBClient.Class;
namespace MBClient.API
{
internal class ReadDataBySinaAPI
{
internal ReadDataBySinaAPI() { }
/// <summary>
/// 根据用户登录信息登录并读取用户信息到DataSet
/// </summary>
/// <param name="url">API URL (XML Format)http://api.t.sina.com.cn/account/verify_credentials.xml?source=AppKey</param>
/// <param name="httpRequestMethod)">HTTP请求方式:GET或POST</param>
/// <returns></returns>
internal static DataSet ReadXMLDataToDataSet(string url, string httpRequestMethod)
{
try
{
WebResponse wr = BasicAuthorizationRequest(url, httpRequestMethod).GetResponse();
DataSet ds = new DataSet();
Stream receieveStream = wr.GetResponseStream();
StreamReader reader = new StreamReader(receieveStream, Encoding.UTF8);
ds.ReadXml(reader);
reader.Close();
receieveStream.Close();
wr.Close();
return ds;
}
catch (Exception e)
{
return null;
}
}
/// <summary>
/// HTTP普通验证(Basic Authentication)方式
/// </summary>
/// <param name="url">API URL</param>
/// <param name="httpRequestMethod)">HTTP请求方式:GET或POST</param>
/// <returns></returns>
private static WebRequest BasicAuthorizationRequest(string url, string httpRequestMethod)
{
CredentialCache mycache = new CredentialCache();
mycache.Add(new Uri(url), "Basic", new NetworkCredential(UserInfo.UserName, UserInfo.Password));
WebRequest myReq = WebRequest.Create(url);
myReq.Credentials = mycache;
myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(UserInfo.UserNamePassword)));
myReq.Method = httpRequestMethod;
return myReq;
}
}
}
我这里直接用DataSet读取XML了,当然也可以将XML数据反序列化,后面会看到,我将返回的JSON格式的围脖信息进行了反序列化。
(4)登录窗体代码
using System.Data;
using System.Windows.Forms;
using MBClient.Entities;
using MBClient.API;
using MBClient.Class;
using Sunisoft.IrisSkin;
using System.Threading;
namespace MBClient
{
public partial class FrmLogin : Form
{
string ErrorMsg = null;
public FrmLogin()
{
InitializeComponent();
this.skinEngine.SkinFile = @"skin\msn.ssk";
this.Text = "登录" + Properties.Resources.SoftName;
}
private void FrmLogin_Load(object sender, EventArgs e)
{
//this.TransparencyKey = this.BackColor;
//各线程之间没有互相争抢控件资源的情况,设置允许跨线程访问
CheckForIllegalCrossThreadCalls = false;
cBoxAccountSina.Select();
InitBackGroundWorker();
}
private void InitBackGroundWorker()
{
backWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(backWorker_DoWork);
backWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(backWorker_ProgressChanged);
backWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(backWorker_RunWorkerCompleted);
}
void backWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
//throw new NotImplementedException();
}
void backWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (ErrorMsg == null)
{
System.Threading.Thread.Sleep(200);
FrmMain main = new FrmMain();
this.Hide();
main.Show();
}
else
{
tabControl.Visible = true;
MessageBox.Show(ErrorMsg, "登录失败", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
void backWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
ErrorMsg = null;
UserInfo.UserName = cBoxAccountSina.Text.Trim(); ;
UserInfo.Password = txtPwdSina.Text.Trim();
UserInfo.UserNamePassword = UserInfo.UserName + ":" + UserInfo.Password;
//source参数为申请的新浪微博App Key
string url = Function.ACCOUNTVERIFY + "?source=" + Function.APPKEY;
DataSet ds = ReadDataBySinaAPI.ReadXMLDataToDataSet(url, "GET");
DataRow dr = ds.Tables[0].Rows[0];
#region //将用户信息保存到UserInfo
UserInfo.AllowAllActMsg = bool.Parse(dr["allow_all_act_msg"].ToString());
UserInfo.City = int.Parse(dr["city"].ToString());
UserInfo.CreatedAt = dr["created_at"].ToString();
UserInfo.Description = dr["description"].ToString();
UserInfo.Domain = dr["domain"].ToString();
UserInfo.FavouritesCount = int.Parse(dr["favourites_count"].ToString());
UserInfo.FollowersCount = int.Parse(dr["followers_count"].ToString());
UserInfo.Following = bool.Parse(dr["following"].ToString());
UserInfo.FriendsCount = int.Parse(dr["friends_count"].ToString());
UserInfo.Gender = dr["gender"].ToString();
UserInfo.GeoEnabled = bool.Parse(dr["geo_enabled"].ToString());
UserInfo.ID = int.Parse(dr["ID"].ToString());
UserInfo.Location = dr["location"].ToString();
UserInfo.Name = dr["name"].ToString();
UserInfo.ProfileImageUrl = dr["profile_image_url"].ToString();
UserInfo.Province = int.Parse(dr["province"].ToString());
UserInfo.ScreenName = dr["screen_name"].ToString();
UserInfo.StatusesCount = int.Parse(dr["statuses_count"].ToString());
UserInfo.Url = dr["url"].ToString();
UserInfo.Verified = bool.Parse(dr["verified"].ToString());
#endregion
}
catch (Exception ex)
{
ErrorMsg = ex.Message;
}
}
private void FrmLogin_FormClosing(object sender, FormClosingEventArgs e)
{
Application.ExitThread();
}
private void btnLoginSina_Click(object sender, EventArgs e)
{
tabControl.Visible = false;
backWorker.RunWorkerAsync();
}
private void btnRegSina_Click(object sender, EventArgs e)
{
System.Diagnostics.Process.Start("http://t.sina.com.cn/reg.php?ps=u3&lang=zh");
}
}
}
关于登录窗体的代码,说两点:
(1)使用皮肤
在你的Windows系统盘搜索IrisSkin2.dll
找到后添加到工具箱,右击工具箱——选择项——选择这个dll
然后将SkinEngine控件拖到窗体上,在代码中按照如下方式写:
这个ssk文件我使用了网上的资源,你也可以到网上去搜,可以下载到有20种左右的皮肤,有了皮肤文件,要实现换肤功能就非常简单了。你也可以使用皮肤工具生成皮肤,如:skin++。
(2)使用BackgroundWorker异步加载 :关于BackgroundWorker,MSDN说的很清楚了,这里不再赘述,http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker(v=vs.80).aspx。
2、获取当前登录用户及其所关注用户的最新微博消息,并分页显示,这儿是我代码最多的地方,主要是由于所有显示的控件都动态加载的缘故。
今天看到新浪围脖API更新了(近一个周都没看),之前是只能传页码,默认每页返回20条数据;现在是可以传页码和返回数据的条数。
===》statuses/friends_timeline 获取当前登录用户及其所关注用户的最新微博消息 (别名: statuses/home_timeline)
这里在第一次加载和翻页时也使用BackgroundWorker。具体代码不贴出来了,代码太长,后面提供下载,供参考。
具体实现简单说一下:
根据调用接口返回的JSON数据格式化为泛型对象集合,然后遍历集合在Panel中动态添加控件显示数据,主要一点是要计算好控件的位置。
(1)围脖类
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace MBClient.Entities
{
[DataContract]
internal class Status
{
[DataMember]
internal string created_at { get; set; }
[DataMember]
internal string id { get; set; }
[DataMember]
internal string text { get; set; }
[DataMember]
internal string source { get; set; }
[DataMember]
internal bool favorited { get; set; }
[DataMember]
internal bool truncated { get; set; }
[DataMember]
internal string geo { get; set; }
[DataMember]
internal string in_reply_to_status_id { get; set; }
[DataMember]
internal string in_reply_to_user_id { get; set; }
[DataMember]
internal string in_reply_to_screen_name { get; set; }
[DataMember]
internal string thumbnail_pic { get; set; }
[DataMember]
internal string bmiddle_pic { get; set; }
[DataMember]
internal string original_pic { get; set; }
[DataMember]
internal PostUser user { get; set; }
[DataMember]
internal RetweetedStatus retweeted_status { get; set; }
}
}
(2)用户类
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace MBClient.Entities
{
[DataContract]
internal class PostUser
{
[DataMember]
internal string id { get; set; }
[DataMember]
internal string screen_name { get; set; }
[DataMember]
internal string name { get; set; }
[DataMember]
internal string province { get; set; }
[DataMember]
internal string city { get; set; }
[DataMember]
internal string location { get; set; }
[DataMember]
internal string description { get; set; }
[DataMember]
internal string url { get; set; }
[DataMember]
internal string profile_image_url { get; set; }
[DataMember]
internal string domain { get; set; }
[DataMember]
internal string gender { get; set; }
[DataMember]
internal string followers_count { get; set; }
[DataMember]
internal string friends_count { get; set; }
[DataMember]
internal string statuses_count { get; set; }
[DataMember]
internal string favourites_count { get; set; }
[DataMember]
internal string created_at { get; set; }
[DataMember]
internal string following { get; set; }
[DataMember]
internal string verified { get; set; }
[DataMember]
internal string allow_all_act_msg { get; set; }
[DataMember]
internal string geo_enabled { get; set; }
}
}
(3)转发的围脖类
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace MBClient.Entities
{
[DataContract]
internal class RetweetedStatus
{
[DataMember]
internal string created_at { get; set; }
[DataMember]
internal string id { get; set; }
[DataMember]
internal string text { get; set; }
[DataMember]
internal string source { get; set; }
[DataMember]
internal string favorited { get; set; }
[DataMember]
internal string truncated { get; set; }
[DataMember]
internal string geo { get; set; }
[DataMember]
internal string in_reply_to_status_id { get; set; }
[DataMember]
internal string in_reply_to_user_id { get; set; }
[DataMember]
internal string in_reply_to_screen_name { get; set; }
[DataMember]
internal string thumbnail_pic { get; set; }
[DataMember]
internal string bmiddle_pic { get; set; }
[DataMember]
internal string original_pic { get; set; }
[DataMember]
internal PostUser user { get; set; }
}
}
(4)根据URL输出头像或图片
/// 获取并输出图片
/// </summary>
/// <param name="imgUrl">图片URL地址</param>
/// <returns></returns>
internal static Image GetAvatarImage(string imgUrl)
{
try
{
Uri myUri = new Uri(imgUrl);
WebRequest webRequest = WebRequest.Create(myUri);
WebResponse webResponse = webRequest.GetResponse();
Bitmap myImage = new Bitmap(webResponse.GetResponseStream());
return (Image)myImage;
}
catch (Exception e)
{
return null;
}
}
可以去看一下上一篇我贴出的XML数据,基本就清楚了
(5)读取微博信息并反序列化
/// 以JSON格式字符串返回用户所有关注用户最新n条微博信息。和用户“我的首页”返回内容相同。
/// http://open.t.sina.com.cn/wiki/index.php/Statuses/friends_timeline
/// </summary>
/// <param name="url">API URL (JSON Format) http://api.t.sina.com.cn/statuses/friends_timeline.json?source=AppKey</param>
/// <param name="httpRequestMethod">HTTP请求方式</param>
/// <returns></returns>
internal static string ReadJsonDataToString(string url, string httpRequestMethod)
{
try
{
WebResponse wr = BasicAuthorizationRequest(url, httpRequestMethod).GetResponse();
string content = null;
Stream receieveStream = wr.GetResponseStream();
StreamReader reader = new StreamReader(receieveStream, Encoding.UTF8);
content = reader.ReadToEnd();
reader.Close();
receieveStream.Close();
wr.Close();
return content;
}
catch (Exception e)
{
return null;
}
}
/// <summary>
/// 将JSON格式字符串反序列化为Status集合对象
/// </summary>
/// <param name="url">同ReadJsonDataToString()</param>
/// <param name="httpRequestMethod">HTTP请求方式</param>
/// <returns></returns>
internal static List<Status> DeserializeJsonToListObject(string url, string httpRequestMethod)
{
try
{
List<Status> listObj;
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<Status>));
StreamWriter wr = new StreamWriter(stream);
wr.Write(ReadJsonDataToString(url, httpRequestMethod));
wr.Flush();
stream.Position = 0;
Object obj = ser.ReadObject(stream);
listObj = (List<Status>)obj;
wr.Close();
stream.Close();
return listObj;
}
catch (Exception e)
{
return null;
}
}
3、发表围脖
下面的方法还是写在ReadDataBySinaAPI这个类里。
发表围脖分为两种情况:
(1)纯文本 ===》statuses/update 发布一条微博信息
/// 发表一条微博
/// </summary>
/// <param name="url">API URL http://api.t.sina.com.cn/statuses/update.json </param>
/// <param name="data">AppKey和微博内容 "source=123456&status=" + 微博内容; </param>
/// <param name="httpRequestMethod">HTTP请求方式</param>
/// <returns></returns>
internal static bool PostBlog(string url, string data, string httpRequestMethod)
{
try
{
HttpWebRequest httpRequest = BasicAuthorizationRequest(url, httpRequestMethod) as HttpWebRequest;
httpRequest.ContentType = "application/x-www-form-urlencoded";
Encoding encoding = Encoding.UTF8;//System.Text.Encoding.ASCII
byte[] bytesToPost = encoding.GetBytes(data);//System.Web.HttpUtility.UrlEncode(data)
httpRequest.ContentLength = bytesToPost.Length;
Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(bytesToPost, 0, bytesToPost.Length);
requestStream.Close();
return true;
}
catch
{
return false;
}
}
(2)带图片 ===》statuses/upload 上传图片并发布一条微博信息
/// 发布带图片的微博
/// ref:http://sarlmolapple.is-programmer.com/posts/22435.html
/// </summary>
/// <param name="url">微博API</param>
/// <param name="data">包含微博内容和图片的集合</param>
/// <param name="httpRequestMethod">HTTP请求方式</param>
/// <returns></returns>
internal static bool PostBlogWithPic(string url, List<string> data, string httpRequestMethod)
{
try
{
HttpWebRequest httpRequest = BasicAuthorizationRequest(url, httpRequestMethod) as HttpWebRequest;
httpRequest.PreAuthenticate = true;
httpRequest.AllowWriteStreamBuffering = true;
string boundary = Guid.NewGuid().ToString();
httpRequest.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
string header = string.Format("--{0}", boundary);
string footer = string.Format("--{0}--", boundary);
StringBuilder contents = new StringBuilder();
contents.AppendLine(header);
contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "status"));
contents.AppendLine("Content-Type: text/plain; charset=utf-8");//US-ASCII
contents.AppendLine("Content-Transfer-Encoding: 8bit");
contents.AppendLine();
//微博文字
contents.AppendLine(data[0]);
contents.AppendLine(header);
contents.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", "source"));
contents.AppendLine("Content-Type: text/plain; charset=utf-8");//US-ASCII
contents.AppendLine("Content-Transfer-Encoding: 8bit");
contents.AppendLine();
contents.AppendLine(Function.APPKEY);
byte[] bytesToPost = Encoding.UTF8.GetBytes(contents.ToString());
//微博图片
contents = null;
contents = new StringBuilder();
contents.AppendLine(header);
string fileHeader = string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"", "pic", boundary);
string fileData = Encoding.GetEncoding("iso-8859-1").GetString(File.ReadAllBytes(@data[1]));
contents.AppendLine(fileHeader);
contents.AppendLine("Content-Type: application/octet-stream; charset=utf-8");
contents.AppendLine("Content-Transfer-Encoding: binary");
contents.AppendLine();
contents.AppendLine(fileData);
contents.AppendLine(footer);
byte[] bytesToPost2 = Encoding.GetEncoding("iso-8859-1").GetBytes(contents.ToString());
httpRequest.ContentLength = bytesToPost.Length + bytesToPost2.Length;
Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(bytesToPost, 0, bytesToPost.Length);
requestStream.Write(bytesToPost2, 0, bytesToPost2.Length);
requestStream.Close();
return true;
}
catch(Exception e)
{
return false;
}
}
这里要注意的一点是iso-8859-1这种编码方式不支持中文,所以,把图片内容和非图片内容分开处理了。
(3)验证输入字数不能超过140字符
internal static int GetStringLength(string text)
{
int Len = 0;
//记录非中文、非全角字符为0.5个长度
float charLen = 0;
char[] chars = text.ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
int charLength = Encoding.UTF8.GetByteCount(chars[i].ToString());
if (charLength == 3)
{
Len++;
}
else
{
if (charLen == 0.5)
{
charLen = 0;
}
else
{
charLen = 0.5f;
Len++;
}
}
}
return Len;
}
4、转发围脖 ===》statuses/repost 转发一条微博信息
用的这个方法PostBlog(),HTTP请求方式为POST。
5、评论围脖 ===》statuses/comment 对一条微博信息进行评论
用的这个方法PostBlog(),HTTP请求方式为POST。
6、收藏围脖 ===》favorites/create 添加收藏
用的这个方法ReadXMLDataToDataSet(),HTTP请求方式为POST。
7、降低工作内存
/// 设置工作内存,降低内存占用
/// </summary>
/// <param name="maxWorkingSet">内存中允许的进程的最大工作集大小(以字节为单位)</param>
public static void SetWorkingSet(int maxWorkingSet)
{
System.Diagnostics.Process.GetCurrentProcess().MaxWorkingSet = (IntPtr)maxWorkingSet;
}
HTTP请求方式,API中有说明,所以上面没有一一指出。
可参考资料:
CredentialCache:http://msdn.microsoft.com/zh-cn/library/system.net.credentialcache(v=VS.90).aspx
WebRequest:http://msdn.microsoft.com/zh-cn/library/system.net.webrequest(v=VS.90).aspx
NetworkCredential:http://msdn.microsoft.com/zh-cn/library/system.net.networkcredential(v=VS.90).aspx
Basic Access Authentication:http://en.wikipedia.org/wiki/Basic_access_authentication;Basic:基本;digest:简要
枝枝节节的还有不少内容,不一一说了,稍后我提供源码,有兴趣的可以看下,一起讨论。
最后,围脖=微博,呵呵。
先提供编译后的,供测试:/Files/Ferry/MBPCClient/微博客户端.rar