WCF 聊天室程序代码详细讲解教程(幽灵湖)

作者:hzqghost.cnblogs.com

本例使用了 Nikola Paljetak 的聊天示例,原始下载 http://www.ms.phy.hr/wcfchat/

解决方案

ChatService 服务端主要的三个文件:App.config,ChatService.cs,Program.cs

FormChatClient 客户端主要二个文件:App.config,ChatForm.cs

以下为这五个文件的全部代码及讲解,因为打算放在一篇文章里,所以本文会很长。发由本教程目的并不仅仅让初学者了解怎么开发一个聊天室。而是要通过这个例子

加深对C#及WCF一些实用特性的了解。

1 Service App.config

<xml version="1.0" encoding="utf-8" >
<configuration>
  <appSettings>
    <!--提供服务的通信协议、地址、端口、目录-->
    <!--通信协议:net.tcp 、http 、-->
    <add key="addr" value="net.tcp://localhost:22222/chatservice" />
  </appSettings>
  <system.serviceModel>
    <services>
      <!--服务名 = <命名空间>.<程序集名称>-->
      <!--behaviorConfiguration 性能配置自定一个名称,<serviceBehaviors> 下的项对应此名称-->
      <service name="NikeSoftChat.ChatService" behaviorConfiguration="MyBehavior">

        <!--终节点-->
        <!--binding 绑定类型,NetTcpBinding、WSDualHttpBinding、WSHttpBindingBase、BasicHttpBinding、NetNamedPipeBinding、NetPeerTcpBinding、MsmqBindingBase、NetPeerTcpBinding、WebHttpBinding、MailBindingBase、CustomBinding-->
        <!--DuplexBinding 双工-->
        <!--使用契约:<命名空间>.<接口名称>-->
        <endpoint address=""
                  binding="netTcpBinding"
                  bindingConfiguration="DuplexBinding"
                  contract="NikeSoftChat.IChat" />
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior  name="MyBehavior">
          <!--会话最大数量-->
          <serviceThrottling maxConcurrentSessions="10000" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <netTcpBinding>
        <!--双工,超时设置-->
        <binding name="DuplexBinding" sendTimeout="00:00:01">
          <!--可靠会话-->
          <reliableSession enabled="true" />
          <!--安全模式-->
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

2 Service ChatService.cs

// Copyright (C) 2006 by Nikola Paljetak

using System;
using System.Collections;
using System.Collections.Generic;
using System.ServiceModel;

namespace NikeSoftChat
{
    //服务契约
    // SessionMode.Required  允许Session会话
    // 双工协定时的回调协定类型为IChatCallback接口)
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
    interface IChat
    {
        //服务操作
        // IsOneWay = false      等待服务器完成对方法处理
        // IsInitiating = true   启动Session会话
        // IsTerminating = false 设置服务器发送回复后不关闭会话
        [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
        string[] Join(string name);

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
        void Say(string msg);

        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
        void Whisper(string to, string msg);

        //服务操作
        // IsOneWay = true       不等待服务器完成对方法处理
        // IsInitiating = false  不启动Session会话
        // IsTerminating = true  关闭会话,在服务器发送回复后
        [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
        void Leave();
    }

    interface IChatCallback
    {
        [OperationContract(IsOneWay = true)]
        void Receive(string senderName, string message);

        [OperationContract(IsOneWay = true)]
        void ReceiveWhisper(string senderName, string message);

        [OperationContract(IsOneWay = true)]
        void UserEnter(string name);

        [OperationContract(IsOneWay = true)]
        void UserLeave(string name);
    }

    //定义一个客户端动作的枚举
    public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper };

    //定义一个本例的事件消息类
    public class ChatEventArgs : EventArgs
    {
        public MessageType msgType;
        public string name;
        public string message;
    }


    // InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象
    // ConcurrencyMode.Multiple       异步的多线程实例
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ChatService : IChat //继承IChat接口或者说IChat的实现类
    {
        //定义一个静态对象用于线程部份代码块的锁定,用于lock操作
        private static Object syncObj = new Object();

        //创建一个IChatCallback 回调接口实例,接口成员始终是公共的,所有没有访问修饰符
        IChatCallback callback = null;

        //定义一个委托
        public delegate void ChatEventHandler(object sender, ChatEventArgs e);
        //定义一个静态的委托事件
        public static event ChatEventHandler ChatEvent;

        //创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类
        static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();

        //当用客户的昵称
        private string name;
        //创建委托(ChatEventHandler)的一个空实例
        private ChatEventHandler myEventHandler = null;

        //成员进入聊天室
        public string[] Join(string name)
        {
            bool userAdded = false;
            //用MyEventHandler方法,实例化委托(ChatEventHandler)
            myEventHandler = new ChatEventHandler(MyEventHandler);

            //锁定,保持lock块中的代码段始终只有一个线程在调用,原因是ConcurrencyMode.Multiple 为异步的多线程实例,存在并发竞争问题
            //如果不锁定,则静态成员字典chatters.ContainsKey(name) 的结果将会不确定,原因是每个线程都可以访问到它。以下凡是chatters 的操作匀加锁
            //使用lock多个线程同时请示时,没有操作权的将会在线程池中等待至有操作权的线程执完成。lock 方法存在影响吞吐量的问题
            lock (syncObj) 
            {
                //如果请求的昵称在成员字典中不存在并不空
                if (!chatters.ContainsKey(name) && name != "" && name != null)
                {
                    this.name = name; //记录当前线程昵称
                    chatters.Add(name, MyEventHandler);//加入到成员字典key 为当前昵称,MyEventHandler 当前的委托调用
                    userAdded = true;
                }
            }

            if (userAdded)
            {
                //获取当前操作客户端实例的通道给IChatCallback接口的实例callback,
                //此通道是一个定义为IChatCallback类型的泛类型
                //通道的类型是事先服务契约协定好的双工机制(见IChat前的ServiceContract)
                callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();

                //实例化事件消息类ChatEventArgs,并对其赋值
                ChatEventArgs e = new ChatEventArgs();
                e.msgType = MessageType.UserEnter;
                e.name = name;

                //发送广播信息
                BroadcastMessage(e);
                //加入到多路广播委托的调用列表中,下面这条如果和上面一条位置互换,那么会收到自己进入聊天室的广播信息。
                ChatEvent += myEventHandler;

                //以下代码返回当前进入聊天室成员的称列表
                string[] list = new string[chatters.Count];
                lock (syncObj)
                {
                    chatters.Keys.CopyTo(list, 0);//从成员字典索引0 开始复制chatters成员字典的key 值到list 字符串数组
                }
                return list;
            }
            else
            {
                //当昵称重复或为空是,如果客户端做了为空检测,则可直接认为是名称重复,当前要在没有异常的情况下。
                return null;
            }
        }

        //聊天室通信
        public void Say(string msg)
        {
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.Receive;
            e.name = this.name;
            e.message = msg;
            BroadcastMessage(e);
        }

        //私有对话
        public void Whisper(string to, string msg)
        {
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.ReceiveWhisper;
            e.name = this.name;
            e.message = msg;
            try
            {
                //创建一个临时委托实例
                ChatEventHandler chatterTo;
                lock (syncObj)
                {
                    //查找成员字典中,找到要接收者的委托调用
                    chatterTo = chatters[to];
                }
                //异步方式调用接收者的委托调用
                chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
            }
            catch (KeyNotFoundException)
            {
                //访问集合中元素的键与集合中的任何键都不匹配时所引发的异常
            }
        }

        //成员离开聊天室
        public void Leave()
        {
            if (this.name == null)
                return;

            //删除成员字典中的当前会话的成员,及删除多路广播委托的调用列表中的当前调用
            //name 和myEventHandler 的生存周期是在当前会话中一直存在的,参考Session 周期
            lock (syncObj)
            {
                chatters.Remove(this.name);
            }
            ChatEvent -= myEventHandler;
            ChatEventArgs e = new ChatEventArgs();
            e.msgType = MessageType.UserLeave;
            e.name = this.name;
            this.name = null;
            BroadcastMessage(e);
        }

        //回调
        //根据客户端动作通知对应客户端执行对应的操作
        private void MyEventHandler(object sender, ChatEventArgs e)
        {
            try
            {
                switch (e.msgType)
                {
                    case MessageType.Receive:
                        callback.Receive(e.name, e.message);
                        break;
                    case MessageType.ReceiveWhisper:
                        callback.ReceiveWhisper(e.name, e.message);
                        break;
                    case MessageType.UserEnter:
                        callback.UserEnter(e.name);
                        break;
                    case MessageType.UserLeave:
                        callback.UserLeave(e.name);
                        break;
                }
            }
            catch //异常退出,或超时,或session过期
            {
                Leave();
            }
        }

        //发送广播信息
        //要点:根据上下文理解: 1 广播什么(what),2 为谁广播(who),3“谁”从哪来(where),4 如何来的(how)
        private void BroadcastMessage(ChatEventArgs e)
        {
            //创建回调委托事件实例ChatEvent的一个副本,之所以用副本是因为ChatEvent处于多线程并状态(?此处不知理解是否正确,因为我理解后面的handler 是一个引用,自相矛盾了)
            ChatEventHandler temp = ChatEvent;

            if (temp != null)
            {
                //GetInvocationList方法,按照调用顺序返回“多路广播委托(MulticastDelegate)”的调用列表
                foreach (ChatEventHandler handler in temp.GetInvocationList())
                {
                    //异步方式调用多路广播委托的调用列表中的ChatEventHandler 
                    //BeginInvoke方法异步调用,即不等等执行,详细说明则是:公共语言运行库(CLR) 将对请求进行排队并立即返回到调用方。将对来自线程池的线程调用该目标方法。
                    //EndAsync 为线程异步调用完成的回调方法,EndAsync 接收并操持着线程异步调用的操作状态,可通过此结果找到调用者,如此例handler,handler是一个委托实例的引用
                    //         此状态为调用者(委托)的事件声明类型此例为public event ChatEventHandler ChatEvent; 中的ChatEventHandler
                    //最后一个参数:包含的对象的状态信息,传递给委托;
                    handler.BeginInvoke(this, e,EndAsync, null);
                }
            }
        }

        //广播中线程调用完成的回调方法
        //功能:清除异常多路广播委托的调用列表中异常对象(空对象)
        private void EndAsync(IAsyncResult ar)
        {
            ChatEventHandler d = null;

            try
            {
                //封装异步委托上的异步操作结果
                System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar;

                //asres.AsyncDelegate 获取在异步调用asres 的委托对象,asres 来自对ar 的AsyncResult 封装,ar 来自线程异步调用的操作状态
                d = ((ChatEventHandler)asres.AsyncDelegate);

                //EndInvoke 返回由异步操作ar 生成的结果Object
                d.EndInvoke(ar);
            }
            catch
            {
                ChatEvent -= d;
            }
        }

    }
}

3 Service Program.cs

// Copyright (C) 2006 by Nikola Paljetak

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Configuration;

namespace NikeSoftChat
{
    class Program
    {
        static void Main(string[] args)
        {

            Uri uri = new Uri(ConfigurationManager.AppSettings["addr"]);//获取配置
            ServiceHost host = new ServiceHost(typeof(NikeSoftChat.ChatService), uri);
            host.Open();
            Console.WriteLine("Chat service listen on endpoint {0}", uri.ToString());
            Console.WriteLine("Press ENTER to stop chat service...");
            Console.ReadLine();
            host.Abort();
            host.Close(); 

        }

       
    }
}

4 Client  App.config

<xml version="1.0" encoding="utf-8">
<!--这里的说明可以完全参考 service 的 app.config -->
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name=""
                address="net.tcp://localhost:22222/chatservice"
                binding="netTcpBinding"
                bindingConfiguration="DuplexBinding"
                contract="IChat" />
    </client>
    <bindings>
      <netTcpBinding>
        <!--这里的sendTimeout比服务端的要多出4秒,因为服务端不参入具体通信,它只是提供服务-->
        <binding name="DuplexBinding" sendTimeout="00:00:05" >
          <reliableSession enabled="true" />
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

5 Client ChatForm.cs

// Copyright (C) 2006 by Nikola Paljetak

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ServiceModel;


namespace NikeSoftChat
{
    public partial class ChatForm : Form, IChatCallback
    {
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
        private const int WM_VSCROLL = 0x115;
        private const int SB_BOTTOM = 7;

        private int lastSelectedIndex = -1;

        private ChatProxy proxy;//代理
        private string myNick;  //当前我的昵称

        private PleaseWaitDialog pwDlg; //状态窗口(显示等待与错误提示)
        private delegate void HandleDelegate(string[] list);//委托
        private delegate void HandleErrorDelegate();//委托

        public ChatForm()
        {
            InitializeComponent();
            ShowConnectMenuItem(true);
        }

        private void connectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            lstChatters.Items.Clear();
            NickDialog nickDlg = new NickDialog();
            if (nickDlg.ShowDialog() == DialogResult.OK)
            {
                myNick = nickDlg.txtNick.Text;// 得到键入的当前昵称
                nickDlg.Close();
            }

            txtMessage.Focus();
            Application.DoEvents(); //强制处理当前队列的所有windows 消息,以名影响后面的通信,winForm程序要注意一下UI 线程的问题
            //获取服务实例的上下文,为指定主机承载的服务初始化
            InstanceContext site = new InstanceContext(this);
            proxy = new ChatProxy(site); //初始服务实例

            //BeginJoin 是svcutil工具自动生成的(如果你用工具的话,当然你也可以自己写),还有一个EndJoin 也是。
            //为什么会生成这两个我们并没有在服务契约中定义的接口呢?原因是服务契约中Join 接口定义了IsOneWay = false
            //IsOneWay = false 则我们在配置中绑定的是duplex 双工(双通道),指操作返回应答信息。
            //duplex 双工并不会等待调用服务方法完成,而是立即返回。单工方式则为Request/Reply 本例中没有涉及
            //自动生成BeginJoin 会比我们的Join 多两个参数,一个用来当BeginJoin请求在服务方完成后本地回调的方法,另一个获取作为BeginInvoke 方法调用的最后一个参数而提供的对象。
            //BeginJoin 会请求服务方执行Join 方法。当Join代码执行完毕,触发回调方法OnEndJoin
            IAsyncResult iar = proxy.BeginJoin(myNick, new AsyncCallback(OnEndJoin),null);
            pwDlg = new PleaseWaitDialog();
            pwDlg.ShowDialog();

        }

        private void OnEndJoin(IAsyncResult iar)
        {
            try
            {
                //EndJoin 请求服务方返回Join 执行后的返回值
                //iar 异步调用的操作状态
                //返回聊天室当前在线成员列表
                string[] list = proxy.EndJoin(iar);
                HandleEndJoin(list);

            }
            catch (Exception e)
            {
                HandleEndJoinError();
            }

        }

        private void HandleEndJoinError()
        {
            //判断状态提示窗口是否在同一个线程内
            if (pwDlg.InvokeRequired)
                //对当前线程调用目标方法,此例调用本身
                pwDlg.Invoke(new HandleErrorDelegate(HandleEndJoinError));
            else
            {
                pwDlg.ShowError("Error: Cannot connect to chat!");
                ExitChatSession();
            }
        }


        //生成在线成员列表,当参数list为空时则表示当前昵称在在线成员列表中已存在
        private void HandleEndJoin(string[] list)
        {
            //状态提示窗口是否运行在同一个线程
            if (pwDlg.InvokeRequired)
                pwDlg.Invoke(new HandleDelegate(HandleEndJoin), new object[] { list });
            else
            {

                if (list == null)
                {
                    pwDlg.ShowError("Error: Username already exist!");
                    ExitChatSession();
                }
                else
                {
                    pwDlg.Close();
                    ShowConnectMenuItem(false);
                    foreach (string name in list)
                    {
                        lstChatters.Items.Add(name);
                    }
                    AppendText("Connected at " + DateTime.Now.ToString() + " with user name " + myNick + Environment.NewLine);
                }
            }
        }
        //发送信息
        private void SayAndClear(string to, string msg, bool pvt)
        {
            if (msg != "")
            {
                try
                {
                    CommunicationState cs = proxy.State;
                    //pvt 公聊还是私聊
                    if (!pvt)
                        proxy.Say(msg); //发送信息
                    else
                        proxy.Whisper(to, msg);//对指定者发送信息

                    txtMessage.Text = "";
                }
                catch
                {
                    AbortProxyAndUpdateUI();
                    AppendText("Disconnected at " + DateTime.Now.ToString() + Environment.NewLine);
                    Error("Error: Connection to chat server lost!");
                }
            }
        }


        private void Error(string errMessage)
        {
            MessageBox.Show(errMessage, "Connection error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            ExitChatSession();
        }

        private void btnSay_Click(object sender, EventArgs e)
        {
            SayAndClear("", txtMessage.Text, false);
            txtMessage.Focus();
        }

        private void btnWhisper_Click(object sender, EventArgs e)
        {
            if (txtMessage.Text == "")
                return;

            object to = lstChatters.SelectedItem;
            if (to != null)
            {
                string receiverName = (string)to;
                AppendText("Whisper to " + receiverName + ": " + txtMessage.Text + Environment.NewLine);
                SayAndClear(receiverName, txtMessage.Text, true);
                txtMessage.Focus();
            }
        }

        private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ExitChatSession();
            btnWhisper.Enabled = false;
            AppendText("Disconnected at " + DateTime.Now.ToString() + Environment.NewLine);
        }

        #region IChatCallback 实现接口成员

        //接收公聊时
        public void Receive(string senderName, string message)
        {
            AppendText(senderName + ": " + message + Environment.NewLine);
        }
        //接收私聊时
        public void ReceiveWhisper(string senderName, string message)
        {
            AppendText(senderName + " whisper: " + message + Environment.NewLine);
        }
        //其他人进入聊天室时
        public void UserEnter(string name)
        {
            AppendText("User " + name + " enter at " + DateTime.Now.ToString() + Environment.NewLine);
            lstChatters.Items.Add(name);
        }
        //其他人退出聊天室时
        public void UserLeave(string name)
        {
            AppendText("User " + name + " leave at " + DateTime.Now.ToString() + Environment.NewLine);
            lstChatters.Items.Remove(name);
            AdjustWhisperButton();
        }

        #endregion

        private void AppendText(string text)
        {
            txtChatText.Text += text;
            //txtChatText 滚动条始终定位于最下面
            SendMessage(txtChatText.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
        }

        //菜单项"连接"与"断开"的显/隐控制()
        private void ShowConnectMenuItem(bool show)
        {
            connectToolStripMenuItem.Enabled = show;
            disconnectToolStripMenuItem.Enabled = btnSay.Enabled = !show;
        }

        private void txtMessage_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter && btnSay.Enabled)
            {
                SayAndClear("", txtMessage.Text, false);
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ExitChatSession();
            ExitApplication();
        }

        private void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            ExitChatSession();
            ExitApplication();
        }

        //退出聊天室会话
        private void ExitChatSession()
        {
            try
            {
                //离开通知
                proxy.Leave();
            }
            catch { }
            finally
            {              
                AbortProxyAndUpdateUI();
            }
        }

        //中断代理并更新UI
        private void AbortProxyAndUpdateUI()
        {
            if (proxy != null)
            {
                proxy.Abort();
                proxy.Close();
                proxy = null;
            }
            ShowConnectMenuItem(true);
        }


        private void ExitApplication()
        {
            Application.Exit();
        }

        private void txtMessage_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == 13)
            {
                e.Handled = true;
                btnSay.PerformClick();
            }
        }

        private void lstChatters_SelectedIndexChanged(object sender, EventArgs e)
        {
            AdjustWhisperButton();
        }

        private void AdjustWhisperButton()
        {
            if (lstChatters.SelectedIndex == lastSelectedIndex)
            {
                lstChatters.SelectedIndex = -1;
                lastSelectedIndex = -1;
                btnWhisper.Enabled = false;
            }
            else
            {
                btnWhisper.Enabled = true;
                lastSelectedIndex = lstChatters.SelectedIndex;
            }
            txtMessage.Focus();
        }

        private void ChatForm_Resize(object sender, EventArgs e)
        {
            //txtChatText 滚动条始终定位于最下面
            SendMessage(txtChatText.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
        }


    }
}

转载于:https://www.cnblogs.com/hzqghost/archive/2009/08/13/1544841.html

WCF全面解析(套装上下册)》由蒋金楠所著,是作者多年潜心研究WCF技术的心血之作,也是这些年来从事WCF开发的经验总结。书如其名,此书涵盖了WCF几乎所有的知识点,并对其底层框架进行了“庖丁解牛”式的剖析,力求将WCF的整个运行机制完整而清晰地呈现在读者面前。 《WCF全面解析(套装上下册)》上册的前四章在对WCF进行总体介绍的基础上,对构成终结点的三要素(地址、绑定和契约)进行了系统说明;随后的两章则着重剖析序列化和消息编码在WCF中的实现;第7、8章讲述了在服务寄宿和操作调用过程中,WCF的服务端和客户端框架分别为我们做了什么;第9、10章将介绍的重点落在实例化、会话和REST服务上面;在最后一章中采用WCF构建了一个具体的电子商务网站VM,它将指导你如何将理论应用于实践。 《WCF全面解析(套装上下册)》的下册主要涉及一些所谓的“高级”话题,主要包括如何在分布式环境中处理异常(第1章);元数据的导入与导出、发布与获取如何实现(第2章);如何利用WCF对事务的支持将分布式事务引入服务(第3章);如何利用并发与限流机制提高服务的吞吐量和可用性(第4章);如何利用可靠会话机制确保消息的“使命必达”(第5章);如何利用队列服务提供离线通信的支持(第6章);第7、8章主要涉及安全的相关内容,包括传输安全、授权与审核;第9章全景展示WCF服务端和客户端的运行时框架,以及在此基础上的所有扩展可能;最后一章为你带来WCF4.0几个独立的新特性。 编辑推荐 《WCF全面解析(套装上下册)》不仅适合尚未接触过WCF,希望尽快入门并进行深入研究的开发人员使用,同样也适合对WCF有一定了解的开发设计人员和架构师阅读。相信不同层次的读者都能从此书中找到自己希望了解的部分。 《WCF全面解析(套装上下册)》的内容不仅适合尚未接触过WCF,希望尽快入门并进行深入研究的开发人员,同样适合对WCF具有一定了解的开发设计人员和架构师。相信不同层次的读者都能从本书中找到自己希望了解的部分。阅读本书的读者需要对.NET,包括对C#和.NET Framework具有一定的了解。如果读者具备了DCOM、Enterprise Library Service、.NET Remoting、Web Service、MSMQ及SOA相关的基础,对阅读此书尽快掌握WCF将大有裨益。 名人推荐我经历了COM时代,一直把Don Box的《COMM本质论》奉为我的指路明灯。能把SOA机理和WCF这种特定厂商实现的技术讲得如《COM本质论》一样完美透彻的,那必属Artech这本经过自己深研、实践而著的心血结晶——《WCF全面解析》。如果你想成为SOA和WCF方面的专家,那么这本书就是你的最好法宝。想想你作为专家而获得的回报,那么你对这本书购买所付出的,简直是太值了。 ——《走出软件作坊》 作者 明源软件CTO 阿朱 首先,金楠是—位工作在一线的优秀的WCF技术人员,这符合我对阅读技术图书的第一个要求和期待。其次,金楠的写作文笔、专业责任也给人以充分信任,这在金楠的文字中读者可以体会。这本《WCF全面解析》全面剖析了构建WCF应用所需要的各方面技术,剥丝抽茧,由浅入深,也是我非常欣赏的技术讲述方式。我相信《WCF全面解析》—书是搞WCF朋友的案头必备。 ——祝成科技与Boolan.com创始人.NET技术专家 李建忠 知识全面、论述准确、逻辑严密是本书的特点。这是一本各层次开发人员都可以从中受益的书:对于初、中级开发人员,它可以帮助你获得WCF全方位的知识,系统地梳理WCF的知识结构,提升动手实践能力;对于高级开发人员,它既可以有效弥补你WCF相关知识中的盲点,又可以让你在自己熟悉的知识点上领略作者的看法和理解。 ——资深架构师 曲春雨 作者简介蒋金楠,网名Artech,现就职于某知名软件公司担任高级软件顾问。连续5届微软MVP(最有价值专家),同时也是少数的双料MVP(Solutions Architecture+Connected System)之一。国内较早接触WCF的人之一,2007年2月起在个人博客(http://www.cnblogs.com/artech)上发表超过两百篇深入介绍WCF的文章,成为了目前国内WCF在线资料的主要来源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值