上一篇:新浪微博PC客户端(DotNet WinForm版)—— 初探
说明一下:只是兴趣,并不是想发布为一个软件,说实在的,如果要作为一个软件发布,要做的工作还有很多。
新浪微博API地址:http://open./wiki/index.php/API%E6%96%87%E6%A1%A3?retcode=0。
目前提供的SDK:
1Adobe Air2Flash SDK3PHP4C++5C#6Java SDK7Python SDK8Javascript SDK(JSSDK)9iOS SDK10OAuth Library
其它的不清楚,C#的还不完善,而且不是官方的。
当前已实现的功能:
1、HTTP普通鉴权(Basic Authentication)的身份验证方式,说白了就是每次访问API都要发送帐号和密码,当然是不安全的,但是相比OAuth验证方式门槛要低的多。要使用OAuth验证方式可以去看下SDK。
===》account/verify_credentials验证当前用户身份是否合法
OAuth是一种国际通用的授权方式,它的特点是不需要用户在第三方应用输入用户名及密码。OAuth的技术说明可参看官方网站 。学习和研究OAuth的意义不仅仅在于新浪围脖,而是对以后开发类似的项目非常有意义和价值(如果你还不知道OAuth授权方式)。
验证通过则返回用户的个人信息,参照下面的API字段说明。
API字段说明
status微博信息内容user用户资料信息comment评论信息描述direct_message私信信息省份城市编码表上一篇说了,API返回的结果格式有两种:XML和JSON,在调用API的时候可以指定。
这里我指定的是XML格式,实现代码如下:
登录窗体:FrmLogin.cs,可以看上一篇的截图。
(1)把申请AppKey和AppSecret写到app.config
<? xmlversion="1.0"encoding="utf-8" ?>
< configuration >
< appSettings >
< add key ="AppKey" value ="3476523072" />
< add key ="AppSecret" value ="3c909770efa6865fd5843a564545826d" />
</ appSettings >
</ configuration >
要申请AppKey,需要登录新浪围脖,然后在这个地址创建应用并生成:http://open./apps/new。
(2)创建了一个静态类,保存用户信息
代码 using System;
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;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ;
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>
/// <paramname="url"> APIURL(XMLFormat) http://api./account/verify_credentials.xml?source=AppKey </param>
/// <paramname="httpRequestMethod)"> HTTP请求方式:GET或POST </param>
/// <returns></returns>
internal static DataSetReadXMLDataToDataSet( string url, string httpRequestMethod)
{
try
{
WebResponsewr = BasicAuthorizationRequest(url,httpRequestMethod).GetResponse();
DataSetds = new DataSet();
StreamreceieveStream = wr.GetResponseStream();
StreamReaderreader = new StreamReader(receieveStream,Encoding.UTF8);
ds.ReadXml(reader);
reader.Close();
receieveStream.Close();
wr.Close();
return ds;
}
catch (Exceptione)
{
return null ;
}
}
/// <summary>
/// HTTP普通验证(BasicAuthentication)方式
/// </summary>
/// <paramname="url"> APIURL </param>
/// <paramname="httpRequestMethod)"> HTTP请求方式:GET或POST </param>
/// <returns></returns>
private static WebRequestBasicAuthorizationRequest( string url, string httpRequestMethod)
{
CredentialCachemycache = new CredentialCache();
mycache.Add( new Uri(url), " Basic " , new NetworkCredential(UserInfo.UserName,UserInfo.Password));
WebRequestmyReq = 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;
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,EventArgse)
{
// this.TransparencyKey=this.BackColor;
// 各线程之间没有互相争抢控件资源的情况,设置允许跨线程访问
CheckForIllegalCrossThreadCalls = false ;
cBoxAccountSina.Select();
InitBackGroundWorker();
}
private void InitBackGroundWorker()
{
backWorker.DoWork += new ponentModel.DoWorkEventHandler(backWorker_DoWork);
backWorker.ProgressChanged += new ponentModel.ProgressChangedEventHandler(backWorker_ProgressChanged);
backWorker.RunWorkerCompleted += new ponentModel.RunWorkerCompletedEventHandler(backWorker_RunWorkerCompleted);
}
void backWorker_ProgressChanged( object sender,ponentModel.ProgressChangedEventArgse)
{
// thrownewNotImplementedException();
}
void backWorker_RunWorkerCompleted( object sender,ponentModel.RunWorkerCompletedEventArgse)
{
if (ErrorMsg == null )
{
System.Threading.Thread.Sleep( 200 );
FrmMainmain = new FrmMain();
this .Hide();
main.Show();
}
else
{
tabControl.Visible = true ;
MessageBox.Show(ErrorMsg, " 登录失败 " ,MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
void backWorker_DoWork( object sender,ponentModel.DoWorkEventArgse)
{
try
{
ErrorMsg = null ;
UserInfo.UserName = cBoxAccountSina.Text.Trim();;
UserInfo.Password = txtPwdSina.Text.Trim();
UserInfo.UserNamePassword = UserInfo.UserName + " : " + UserInfo.Password;
// source参数为申请的新浪微博AppKey
string url = Function.ACCOUNTVERIFY + " ?source= " + Function.APPKEY;
DataSetds = ReadDataBySinaAPI.ReadXMLDataToDataSet(url, " GET " );
DataRowdr = 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 (Exceptionex)
{
ErrorMsg = ex.Message;
}
}
private void FrmLogin_FormClosing( object sender,FormClosingEventArgse)
{
Application.ExitThread();
}
private void btnLoginSina_Click( object sender,EventArgse)
{
tabControl.Visible = false ;
backWorker.RunWorkerAsync();
}
private void btnRegSina_Click( object sender,EventArgse)
{
System.Diagnostics.Process.Start( " /reg.php?ps=u3&lang=zh " );
}
}
}
关于登录窗体的代码,说两点:
(1)使用皮肤
在你的Windows系统盘搜索IrisSkin2.dll
找到后添加到工具箱,右击工具箱——选择项——选择这个dll
然后将SkinEngine控件拖到窗体上,在代码中按照如下方式写:
this .skinEngine.SkinFile = @" skin\msn.ssk " ;
这个ssk文件我使用了网上的资源,你也可以到网上去搜,可以下载到有20种左右的皮肤,有了皮肤文件,要实现换肤功能就非常简单了。你也可以使用皮肤工具生成皮肤,如:skin++。
(2)使用BackgroundWorker异步加载 :关于BackgroundWorker,MSDN说的很清楚了,这里不再赘述,/zh-cn/library/ponentmodel.backgroundworker(v=vs.80).aspx。
2、获取当前登录用户及其所关注用户的最新微博消息,并分页显示,这儿是我代码最多的地方,主要是由于所有显示的控件都动态加载的缘故。
今天看到新浪围脖API更新了(近一个周都没看),之前是只能传页码,默认每页返回20条数据;现在是可以传页码和返回数据的条数。
===》statuses/friends_timeline获取当前登录用户及其所关注用户的最新微博消息 (别名: statuses/home_timeline)
这里在第一次加载和翻页时也使用BackgroundWorker。具体代码不贴出来了,代码太长,后面提供下载,供参考。
具体实现简单说一下:
根据调用接口返回的JSON数据格式化为泛型对象集合,然后遍历集合在Panel中动态添加控件显示数据,主要一点是要计算好控件的位置。
(1)围脖类
代码 using System;
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 PostUseruser{ get ; set ;}
[DataMember]
internal RetweetedStatusretweeted_status{ get ; set ;}
}
}
(2)用户类
代码 using System;
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;
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 PostUseruser{ get ; set ;}
}
}
(4)根据URL输出头像或图片
/// <summary>
/// 获取并输出图片
/// </summary>
/// <paramname="imgUrl"> 图片URL地址 </param>
/// <returns></returns>
internal static ImageGetAvatarImage( string imgUrl)
{
try
{
UrimyUri = new Uri(imgUrl);
WebRequestwebRequest = WebRequest.Create(myUri);
WebResponsewebResponse = webRequest.GetResponse();
BitmapmyImage = new Bitmap(webResponse.GetResponseStream());
return (Image)myImage;
}
catch (Exceptione)
{
return null ;
}
}
可以去看一下上一篇我贴出的XML数据,基本就清楚了
(5)读取微博信息并反序列化
代码 /// <summary>
/// 以JSON格式字符串返回用户所有关注用户最新n条微博信息。和用户“我的首页”返回内容相同。
/// http://open./wiki/index.php/Statuses/friends_timeline
/// </summary>
/// <paramname="url"> APIURL(JSONFormat) http://api./statuses/friends_timeline.json?source=AppKey </param>
/// <paramname="httpRequestMethod"> HTTP请求方式 </param>
/// <returns></returns>
internal static string ReadJsonDataToString( string url, string httpRequestMethod)
{
try
{
WebResponsewr = BasicAuthorizationRequest(url,httpRequestMethod).GetResponse();
string content = null ;
StreamreceieveStream = wr.GetResponseStream();
StreamReaderreader = new StreamReader(receieveStream,Encoding.UTF8);
content = reader.ReadToEnd();
reader.Close();
receieveStream.Close();
wr.Close();
return content;
}
catch (Exceptione)
{
return null ;
}
}
/// <summary>
/// 将JSON格式字符串反序列化为Status集合对象
/// </summary>
/// <paramname="url"> 同ReadJsonDataToString() </param>
/// <paramname="httpRequestMethod"> HTTP请求方式 </param>
/// <returns></returns>
internal static List < Status > DeserializeJsonToListObject( string url, string httpRequestMethod)
{
try
{
List < Status > listObj;
MemoryStreamstream = new MemoryStream();
DataContractJsonSerializerser = new DataContractJsonSerializer( typeof (List < Status > ));
StreamWriterwr = new StreamWriter(stream);
wr.Write(ReadJsonDataToString(url,httpRequestMethod));
wr.Flush();
stream.Position = 0 ;
Objectobj = ser.ReadObject(stream);
listObj = (List < Status > )obj;
wr.Close();
stream.Close();
return listObj;
}
catch (Exceptione)
{
return null ;
}
}
3、发表围脖
下面的方法还是写在ReadDataBySinaAPI这个类里。
发表围脖分为两种情况:
(1)纯文本 ===》statuses/update发布一条微博信息
代码 /// <summary>
/// 发表一条微博
/// </summary>
/// <paramname="url"> APIURL http://api./statuses/update.json </param>
/// <paramname="data"> AppKey和微博内容"source=123456&status="+微博内容; </param>
/// <paramname="httpRequestMethod"> HTTP请求方式 </param>
/// <returns></returns>
internal static bool PostBlog( string url, string data, string httpRequestMethod)
{
try
{
HttpWebRequesthttpRequest = BasicAuthorizationRequest(url,httpRequestMethod) as HttpWebRequest;
httpRequest.ContentType = " application/x-www-form-urlencoded " ;
Encodingencoding = Encoding.UTF8; // System.Text.Encoding.ASCII
byte []bytesToPost = encoding.GetBytes(data); // System.Web.HttpUtility.UrlEncode(data)
httpRequest.ContentLength = bytesToPost.Length;
StreamrequestStream = httpRequest.GetRequestStream();
requestStream.Write(bytesToPost, 0 ,bytesToPost.Length);
requestStream.Close();
return true ;
}
catch
{
return false ;
}
}
(2)带图片 ===》statuses/upload上传图片并发布一条微博信息
代码 /// <summary>
/// 发布带图片的微博
/// ref: http://sarlmolapple.is-/posts/22435.html
/// </summary>
/// <paramname="url"> 微博API </param>
/// <paramname="data"> 包含微博内容和图片的集合 </param>
/// <paramname="httpRequestMethod"> HTTP请求方式 </param>
/// <returns></returns>
internal static bool PostBlogWithPic( string url,List < string > data, string httpRequestMethod)
{
try
{
HttpWebRequesthttpRequest = 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);
StringBuildercontents = 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;
StreamrequestStream = httpRequest.GetRequestStream();
requestStream.Write(bytesToPost, 0 ,bytesToPost.Length);
requestStream.Write(bytesToPost2, 0 ,bytesToPost2.Length);
requestStream.Close();
return true ;
}
catch (Exceptione)
{
return false ;
}
}
这里要注意的一点是iso-8859-1这种编码方式不支持中文,所以,把图片内容和非图片内容分开处理了。
(3)验证输入字数不能超过140字符
// 计算字符串长度,中文及全角算1,其它算0.5
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>
/// 设置工作内存,降低内存占用
/// </summary>
/// <paramname="maxWorkingSet"> 内存中允许的进程的最大工作集大小(以字节为单位) </param>
public static void SetWorkingSet( int maxWorkingSet)
{
System.Diagnostics.Process.GetCurrentProcess().MaxWorkingSet = (IntPtr)maxWorkingSet;
}
HTTP请求方式,API中有说明,所以上面没有一一指出。
可参考资料:
CredentialCache:/zh-cn/library/.credentialcache(v=VS.90).aspx
WebRequest:/zh-cn/library/.webrequest(v=VS.90).aspx
NetworkCredential:/zh-cn/library/workcredential(v=VS.90).aspx
Basic Access Authentication:/wiki/Basic_access_authentication;Basic:基本;digest:简要
枝枝节节的还有不少内容,不一一说了,稍后我提供源码,有兴趣的可以看下,一起讨论。
最后,围脖=微博,呵呵。
先提供编译后的,供测试:/Files/Ferry/MBPCClient/微博客户端.rar
C#源码下载