Silverlight探索记录--用WCF和Silverlight建立"准"聊天室(1)
使用Silverlight构建互联网应用是再方便不过的了.WCF服务更是将以前与服务器的复杂沟通简化到像调用类库一样简单.
项目一放下就没什么事做.写个小东西拿出来分享~
我毕竟年龄小,写的东西没有什么技术含量,大家见谅了.先谢谢了.
-------------------------------------还是我,那个没资历,没资格学习Silverlight的人 => 回应昨天文章里进行人身攻击的那些过来人 ----------------------------------------------------------
初衷:
实在不想再像以前一样使用SOCKET编程了,只是觉得用过了,但没有用到Silverlight最顺手的地方.Silverlight为了安全,把Socket能访问的端口限制在了几个上,总觉得很不爽.想想Silverlight总是可以和Server用WCF无缝集成的吧,就用WCF服务和Silverlight写个"准"聊天室.
WCF不具备操作客户端的能力,所以就没有C/S结构的设计了,变成C请求S,S回应的形式
思路:
类似留言板但不需要远程存储的一个程序。否则容易出现并发访问的问题。--也更不需要WCF了,直接POST好了。。
首先做了简单的WCF端设计
主要具备以下几个函数
Login
GetUserList
Logoff
SendMessage
GetMessage
还有两个DataContract类
Message --存储一条消息的相关信息
UserIdentify --提供用户验证策略 --这里面没有实现,但是由于留出了这个类,后面的再开发会很容易。
在SL项目里面 添加一个WCF服务,然后加入下面的代码
[ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class ChatServer { static Dictionary<string, List<Message>> MessageList = new Dictionary<string, List<Message>>(); static Dictionary<string,UserIdentify> UserList = new Dictionary<string,UserIdentify>(); [OperationContract] public UserIdentify Login(string UserName) { UserIdentify UI = new UserIdentify(); UI.UserName = UserName; if (UserList.Keys.Contains(UserName)) { return null; } else { UserList.Add(UserName,UI); return UI; } } [OperationContract] public List<string> GetUserList(UserIdentify YourIdentify) { if (UserList.Keys.Contains(YourIdentify.User_Name)) { return new List<string>(UserList.Keys); } else { return null; } } [OperationContract] public UserIdentify Logoff(UserIdentify UI) { //Clean Data_List if (UserList.Keys.Contains(UI.User_Name)) { UserList.Remove(UI.User_Name); MessageList.Remove(UI.User_Name); return null; } else { return null; } } [OperationContract] public Message SendMessage(UserIdentify Sender,string TargetName,string MessageBody) { if (UserList.Keys.Contains(TargetName)) { Message M = new Message(); M.SentTime = DateTime.Now.ToShortTimeString(); M.To = UserList[TargetName]; M.From = Sender; M.MessageBody = MessageBody; if (!(MessageList.Keys.Contains(M.To.User_Name))) { MessageList.Add(M.To.User_Name, new List<Message>()); } MessageList[M.To.User_Name].Add(M); return M; } else { return null; } } [OperationContract] public List<Message> GetMessage(UserIdentify Receiver) { if (MessageList.Keys.Contains(Receiver.User_Name)) { //T-COPY, Clear=> List<Message> Temp = new List<Message>(MessageList[Receiver.User_Name]); //Clear. MessageList.Remove(Receiver.User_Name); //Post-// return Temp; } else { return null; } } } [DataContract, ServiceKnownType(typeof(UserIdentify))] public class UserIdentify { //构建该类,可以为后期添加密码和GUID做准备 [DataMember] internal string UserName; [DataMember] public string User_Name { get { return UserName; } set { } } } [DataContract, ServiceKnownType(typeof(Message))] public class Message { [DataMember] public UserIdentify From { get; set; } [DataMember] public string MessageBody { get; set; } [DataMember] public string Senttime { get { return SentTime; } set { } } [DataMember] internal UserIdentify To; [DataMember] internal string SentTime; } |
代码很简单,也很少。不多作解释了。(如果需要解释就在评论里吼一句,下一篇加注释)
服务端搞定之后,在SL客户端添加服务引用。
然后写一下Page.XAML的界面——当然,很简单了。我不太喜欢Blend,但是手写效果确实不太好。。
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="*"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBox x:Name="Text_UserName" Grid.Row="0" Text="User"/> <TextBox x:Name="Text_MsgBody" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3" Foreground="White" Background="Black" Text="MsgBody"/> <Button x:Name="Btn_Login" Grid.Row="1" Content="Login" Click="B_Click"/> <Button x:Name="Btn_Send" Grid.Row="3" Content="Send" Click="SendMsg"/> <ListBox x:Name="List_UserList" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="1" Background="SlateBlue"/> <TextBox x:Name="Text_Messages" IsReadOnly="True" Grid.Column="1" Grid.ColumnSpan="3" Grid.RowSpan="3" Background="Black" Foreground="White"/> </Grid> |
效果很难看,但是够用了---具体的把它变成能用的东西要到下次了
下一步是Page.Xaml.cs的代码。看起来有点冗杂,但其实都是Async惹的祸。
直接贴过来了
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using WCFChat.ChatServerRef; using System.ServiceModel; namespace WCFChat { public partial class Page : UserControl { public Page() { InitializeComponent(); CSC = new WCFChat.ChatServerRef.ChatServerClient(new BasicHttpBinding(),new EndpointAddress("http://192.168.1.102/Chat/ChatServer.svc")); CSC.LoginCompleted += new EventHandler<WCFChat.ChatServerRef.LoginCompletedEventArgs>(CSC_LoginCompleted); CSC.LogoffCompleted += new EventHandler<LogoffCompletedEventArgs>(CSC_LogoffCompleted); CSC.GetUserListCompleted += new EventHandler<GetUserListCompletedEventArgs>(CSC_GetUserListCompleted); CSC.SendMessageCompleted += new EventHandler<SendMessageCompletedEventArgs>(CSC_SendMessageCompleted); CSC.GetMessageCompleted += new EventHandler<GetMessageCompletedEventArgs>(CSC_GetMessageCompleted); Refresher = new Storyboard(); Refresher.Stop(); Refresher.Completed += new EventHandler(LetusRefresh); Refresher.Duration = new Duration(new TimeSpan(0, 0, 0, 1)); Refresher.AutoReverse = true; } public ChatServerRef.ChatServerClient CSC; Storyboard Refresher; bool IsOnLine; public UserIdentify UI; private void B_Click(object sender, RoutedEventArgs e) { if (!IsOnLine) { CSC.LoginAsync(Text_UserName.Text); } else { CSC.LogoffAsync(UI); } } void CSC_LoginCompleted(object sender, WCFChat.ChatServerRef.LoginCompletedEventArgs e) { if (e.Result == null) { Btn_Login.Content = "Failed"; IsOnLine = false; Text_UserName.IsReadOnly = false; UI = null; } else { Text_UserName.IsReadOnly = true; Btn_Login.Content = "LogOff"; IsOnLine = true; UI = e.Result; RefreshNameList(); Refresher.Begin(); } } void CSC_LogoffCompleted(object sender, LogoffCompletedEventArgs e) { if (e.Result == null) { //Finished Btn_Login.Content = "Login"; IsOnLine = false; Text_UserName.IsReadOnly = false; UI = null; Refresher.Stop(); } else { Btn_Login.Content = "Failed"; } } void CSC_GetUserListCompleted(object sender, GetUserListCompletedEventArgs e) { if (e.Result != null) { List_UserList.Items.Clear(); foreach (string Name in e.Result) { ListBoxItem LBI = new ListBoxItem(); LBI.Content = Name; List_UserList.Items.Add(LBI); } } } void CSC_SendMessageCompleted(object sender, SendMessageCompletedEventArgs e) { if (e.Result == null) { OutputLine("Error Sending Msg!"); } else { OutputLine(e.Result.SentTime); OutputLine(string.Format("I said to {0}: {1}", e.Result.To.UserName, e.Result.MessageBody)); } } void CSC_GetMessageCompleted(object sender, GetMessageCompletedEventArgs e) { if (e.Result != null) { if (e.Result.Count != 0) { foreach (Message M in e.Result) { OutputLine(M.SentTime); OutputLine(string.Format("{0} said to me: {1}", M.From.UserName, M.MessageBody)); } } } } private void RefreshNameList() { CSC.GetUserListAsync(UI); } private void SendMsg(object sender, RoutedEventArgs e) { if (Btn_Send.Tag != null) { CSC.SendMessageAsync(UI, Btn_Send.Tag.ToString(), Text_MsgBody.Text); } } private void OutputLine(string Line) { Text_Messages.Text += "\n" + Line; Text_Messages.Select(Text_Messages.Text.Length - 1, 0); } private void LetusRefresh(object sender, EventArgs e) { CSC.GetMessageAsync(UI); CSC.GetUserListAsync(UI); Refresher.Begin(); } private void List_UserList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count > 0) { Btn_Send.Tag = (e.AddedItems[e.AddedItems.Count - 1] as ListBoxItem).Content.ToString(); ; Btn_Send.Content = "To " + Btn_Send.Tag.ToString(); } } } } |
对于这里的广大高手来说,这点东西很易懂...:)所以偷懒不搞注释了 :) (小朋友能力有限..还要上新东方..英文课...注释没时间了)
好啦,一切搞定,当然要注意了,我上面的服务绑定是绑定在我家的局域网IP上,大家要自己修改哦
比如修改成你们的部署目录:
http://localhost/...svc 就好了
这个东西Bug很多,比如说下线的时候需要手动下线。当然,我回去完善下,把心跳信息加上就好了~
上面的Refresher就是个Timer,可以通过就该她的更新时间来减少网络开销。
就这样了。我是小朋友,我是新手。没有技术含量 只是分享罢了~代码写得不好见谅 :)
--------------------------------------------------------------------------------------------------------------------------------
最后的最后:我觉得 CNBLOG是个好地方,我觉得我应该有资格来共享我的小经验吧 :)
忘记了:项目打包下载:
https://files.cnblogs.com/struggle-luan/WCFChat.rar
关于演示的问题,实在没有办法。。我没有服务器,其他虚拟主机不支持WCF,不知大虾们都是怎么做的。。