# [C# 网络编程系列]专题十一:实现一个基于FTP协议的程序——文件上传下载器
**引言:**
在这个专题将为大家揭开下FTP这个协议的面纱,其实学习知识和生活中的例子都是很相通的,就拿这个专题来说,要了解FTP协议然后根据FTP协议实现一个文件下载器,就和和追MM是差不多的过程的,相信大家追MM都有自己的经验的,我感觉大部分的过程肯定是——第一步: 先通过工作关系或者朋友关系等认识MM(认识FTP协议,知道FTP协议的是什么) ; 第二步: 当然了解MM有兴趣爱好了(了解FTP协议有哪些命令和工作过程)第三步:如果对方是你的菜的话,那当然要采取追求的了(就好比用了解到的FTP协议来实现一个文件上传下载器)。不过追MM好像对我来说还是比较难的了, 所以还是言归正传了,还是好好的学习我的代码吧,回到本章的内容——FTP的协议。
(注:最近想好好改进下文章的幽默程度,所以文章中会尽量以有趣的生活中的例子来表述网络编程的知识,希望可以让大家在学习知识的同时也可以获得乐趣,如果有什么地方理解不准确的还望大家多多指出。)
**一、FTP协议的自我介绍**
我们在上学的时候,同学们第一次开学的时候老师一般会组织大学到讲台上进行自我介绍,让同学都互相认识,同样,如果对于没有接触过FTP协议的朋友来说,FTP协议的自我介绍当然也是不可避免的了,这样我们才能进一步去了解FTP协议 “这位同学”了,之后才能和他成为好朋友,或者是好基友了。下面就开始FTP协议同学的自我介绍了, 大家热烈欢迎。
FTP 协议同学: 大家好, 我的名字叫FTP,FTP是文件传输协议(File Transfer Protocol,FTP),我的工作就是负责将文件从一台计算机传输到另外一台计算机,并且我还可以保证传输的可靠性。我的工作流程可以通过下面的一张图来表达:
![](https://box.kancloud.cn/2016-01-23_56a2eb3874cd6.png)
从图中大家应该可以明白我的工作过程了吧,我的工作过程是典型的C/S模式——我的客户端(在本章实现的文件上传下载器属于客户端)首先发起与我的服务器连接连接,告诉我的服务器说“我现在想和你聊聊天”, 然后我的服务器收到这个请求后给出回答——“聊天,当然可以了,我批准了”,客户端收到这个信息后,就可以服务器之间就建立一条马路或者是通道,然后我的客户端好还想进一步了解下我的服务器,在发出一个说“我想要下载你上面的东西 或者是 我想上传一些文件到你那里,想让你帮我保管下,这样我可以随处都可以从你那里得到我上传的资料的”, 我的服务器收到请求后,如果允许客户端这么做的话就会回答说 “可以啊”(就像我们追女生一样,建立好关系后,当然就要表白了,此时我们就说“我很喜欢你之类的话”,然后等待MM的回答,“可以啊” 这个答案都是我们希望听到的答案的),我的客户端听到后非常开心,马上选择自己需要上传的文件或者想从服务器下载的文件找到,上传或者下载该文件的。 我还要补充一点,在访问我的FTP服务器之前必须登录,这样我的服务器才认识你,才可能会搭理你的,登录时就需要客户端提供一个用户名和密码,提供了正确的用户名和密码后就可以和我的服务器进行聊天 和请求上传或下载我服务器上的文件了; 然而我的某些服务器提供了一种匿名的方式,我的客户端不需要提供用户名和密码就可以进行聊天了,其实匿名的方式和我聊天的本质是:提供服务的公司或机构在我的服务器上建立一个公用的账户,方便那些没有提供用户名和密码的客户端与我聊天。 上面就是我的自我介绍了,谢谢大家。
**二、.Net 为实现我的客户端提供了些什么?**
可以说微软真是一位雷锋叔叔的,因为在他的.Net类库中提供了很多类库供我们使用,当然为实现我的客户端也提供了一些类的支持的, 现在就看看这位好人帮我们提供了哪些类来对实现一个FTP客户端程序的支持的。
这位好人通过命名空间System.Net下的FtpWebRequest类和FtpWebResponse类提供对实现FTP客户端的支持。
**2.1 FtpWebRequest类**
该类是WebRequest类的派生类,FTPWebRequest类用于向服务器发出请求,告诉服务器说“我想和你聊天",如果要获得FtpWebRequest的一个实例,则需要使用**Create**方法来创建实例,对于该类如何使用我在这里也就不一一列出来的, 大家可以查看MSDN的相关文档来了解方法的使用,并且在本专题实现的程序中也会有所介绍的,下面给出MSDN中的一个链接的:
[http://msdn.microsoft.com/zh-cn/library/8exfzxft.aspx](http://msdn.microsoft.com/zh-cn/library/8exfzxft.aspx)
**2.2 FtpWebResponse类**
FTP客户端既然发话了,服务器当然也要有所表示的了, 不要哑巴一样不说话的,总要给个答复的,FtpWebResponse类就负责封装FTP服务器对客户端请求的回答的一个类。FTP客户端通过**GetResponse**方法来获得FtpWebResponse类的对象的,如果服务器回答说“我们可以聊天的”,这样就说明他们俩就可以互相沟通了,就好比追MM的时候你问MM说“可以给电话号码给我吗?”,然后MM对你也有好感就告诉你一个号码后,得到MM的号码也就和MM建立了沟通的通道了,就好比服务器回答“我们可以聊天的”。之后客户端和服务器就可以进行进一步的沟通(上传文件到服务器或者要求服务器给些文件给客户端),之后的过程就好比你可以通过电话号码和MM进一步的交流,知道MM的有些什么性格和爱好的。下面提供一个MSDN中该类的使用链接,这里我就不一一介绍他的成员了,大家可以到MSDN中查看的,上面每个属性和方法都有一个比较好的解释,并且大家也可以通过下面实现的FTP客户端程序进一步了解该类的使用:
[http://msdn.microsoft.com/zh-cn/library/system.net.ftpwebresponse.aspx](http://msdn.microsoft.com/zh-cn/library/system.net.ftpwebresponse.aspx)
**三、如何实现一个FTP客户端程序?——看完下面的介绍你就会知道了**
通过FTP协议的自我介绍部分大家应该可以明白了FTP协议的工作过程的,然而一个FTP客户端程序就是基于FTP协议的文件上传下载器,通过这个程序大家可以对FTP服务器上的资料进行浏览、上传和下载等操作的。
程序中主要模块的代码:
登录模块:
View Code
```
#region 登录模块的实现
// 登录服务器事件
private void btnlogin_Click(object sender, EventArgs e)
{
if (tbxServerIp.Text == string.Empty)
{
MessageBox.Show("请先填写服务器IP地址", "提示");
return;
}
ftpUristring = "ftp://" + tbxServerIp.Text;
networkCredential = new NetworkCredential(tbxUsername.Text, tbxPassword.Text);
if (ShowFtpFileAndDirectory() == true)
{
btnlogin.Enabled = false;
btnlogout.Enabled = true;
lstbxFtpResources.Enabled = true;
lstbxFtpState.Enabled = true;
tbxServerIp.Enabled = false;
if (chkbxAnonymous.Checked == false)
{
tbxUsername.Enabled = false;
tbxPassword.Enabled = false;
chkbxAnonymous.Enabled = false;
}
else
{
chkbxAnonymous.Enabled = false;
}
tbxloginmessage.Text = "登录成功";
btnUpload.Enabled = true;
btndownload.Enabled = true;
btnDelete.Enabled = true;
}
else
{
lstbxFtpState.Enabled = true;
tbxloginmessage.Text = "登录失败";
}
}
// 显示资源列表
private bool ShowFtpFileAndDirectory()
{
lstbxFtpResources.Items.Clear();
string uri = string.Empty;
if (currentDir == "/")
{
uri = ftpUristring;
}
else
{
uri = ftpUristring + currentDir;
}
string[] urifield = uri.Split(' ');
uri = urifield[0];
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.ListDirectoryDetails);
// 获得服务器返回的响应信息
FtpWebResponse response = GetFtpResponse(request);
if (response == null)
{
return false;
}
lstbxFtpState.Items.Add("连接成功,服务器返回的是:"+response.StatusCode+" "+response.StatusDescription);
// 读取网络流数据
Stream stream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(stream,Encoding.Default);
lstbxFtpState.Items.Add("获取响应流....");
string s = streamReader.ReadToEnd();
streamReader.Close();
stream.Close();
response.Close();
lstbxFtpState.Items.Add("传输完成");
// 处理并显示文件目录列表
string[] ftpdir = s.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
lstbxFtpResources.Items.Add("↑返回上层目录");
int length = 0;
for (int i = 0; i < ftpdir.Length; i++)
{
if (ftpdir[i].EndsWith("."))
{
length = ftpdir[i].Length - 2;
break;
}
}
for (int i = 0; i < ftpdir.Length; i++)
{
s = ftpdir[i];
int index = s.LastIndexOf('\t');
if (index == -1)
{
if (length < s.Length)
{
index = length;
}
else
{
continue;
}
}
string name = s.Substring(index + 1);
if (name == "." || name == "..")
{
continue;
}
// 判断是否为目录,在名称前加"目录"来表示
if (s[0] == 'd' || (s.ToLower()).Contains("<dir>"))
{
string[] namefield = name.Split(' ');
int namefieldlength = namefield.Length;
string dirname;
dirname = namefield[namefieldlength - 1];
// 对齐
dirname = dirname.PadRight(34,' ');
name = dirname;
// 显示目录
lstbxFtpResources.Items.Add("[目录]" + name);
}
}
for (int i = 0; i < ftpdir.Length; i++)
{
s = ftpdir[i];
int index = s.LastIndexOf('\t');
if (index == -1)
{
if (length < s.Length)
{
index = length;
}
else
{
continue;
}
}
string name = s.Substring(index + 1);
if (name == "." || name == "..")
{
continue;
}
// 判断是否为文件
if (!(s[0] == 'd' || (s.ToLower()).Contains("<dir>")))
{
string[] namefield = name.Split(' ');
int namefieldlength = namefield.Length;
string filename;
filename = namefield[namefieldlength - 1];
// 对齐
filename = filename.PadRight(34, ' ');
name = filename;
// 显示文件
lstbxFtpResources.Items.Add(name);
}
}
return true;
}
// 注销事件
private void btnlogout_Click(object sender, EventArgs e)
{
btnlogin.Enabled = true;
btnlogout.Enabled = false;
tbxServerIp.Enabled = true;
tbxServerIp.SelectAll();
tbxServerIp.Focus();
chkbxAnonymous.Enabled = true;
if (chkbxAnonymous.Checked == false)
{
tbxUsername.Enabled = true;
tbxPassword.Enabled = true;
}
tbxloginmessage.Text = "你已经退出了。";
lstbxFtpResources.Items.Clear();
lstbxFtpResources.Enabled = false;
lstbxFtpState.Items.Clear();
lstbxFtpState.Enabled = false;
btnUpload.Enabled = false;
btndownload.Enabled = false;
btnDelete.Enabled = false;
}
#endregion
```
对FTP服务器操作模块(本程序中实现下载、上传和删除的功能):
View Code
```
#region 对文件的操作模块实现
// 上传文件到服务器事件
private void btnUpload_Click(object sender, EventArgs e)
{
// 选择要上传的文件
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.FileName = openFileDialog.FileNames.ToString();
openFileDialog.Filter = "所有文件(*.*)|*.*";
if (openFileDialog.ShowDialog() != DialogResult.OK)
{
return;
}
FileInfo fileinfo = new FileInfo(openFileDialog.FileName);
try
{
string uri = GetUriString(fileinfo.Name);
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.UploadFile);
request.ContentLength = fileinfo.Length;
int buflength = 8196;
byte[] buffer = new byte[buflength];
FileStream filestream = fileinfo.OpenRead();
Stream responseStream = request.GetRequestStream();
lstbxFtpState.Items.Add("打开上传流,文件上传中...");
int contenlength = filestream.Read(buffer, 0, buflength);
while (contenlength != 0)
{
responseStream.Write(buffer, 0, contenlength);
contenlength = filestream.Read(buffer, 0, buflength);
}
responseStream.Close();
filestream.Close();
FtpWebResponse response = GetFtpResponse(request);
if (response == null)
{
lstbxFtpState.Items.Add("服务器未响应...");
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
return;
}
lstbxFtpState.Items.Add("上传完毕,服务器返回:" + response.StatusCode + " " + response.StatusDescription);
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
MessageBox.Show("上传成功!");
// 上传成功后,立即刷新服务器目录列表
ShowFtpFileAndDirectory();
}
catch (WebException ex)
{
lstbxFtpState.Items.Add("上传发生错误,返回信息为:" + ex.Status);
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
MessageBox.Show(ex.Message, "上传失败");
}
}
private string GetUriString(string filename)
{
string uri = string.Empty;
if (currentDir.EndsWith("/"))
{
uri = ftpUristring + currentDir + filename;
}
else
{
uri = ftpUristring + currentDir + "/" + filename;
}
return uri;
}
// 从服务器上下载文件到本地事件
private void btndownload_Click(object sender, EventArgs e)
{
string fileName = GetSelectedFile();
if (fileName.Length == 0)
{
MessageBox.Show("请选择要下载的文件!","提示");
return;
}
// 选择保存文件的位置
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.FileName = fileName;
saveFileDialog.Filter = "所有文件(*.*)|(*.*)";
if (saveFileDialog.ShowDialog() != DialogResult.OK)
{
return;
}
string filePath = saveFileDialog.FileName;
try
{
string uri = GetUriString(fileName);
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DownloadFile);
FtpWebResponse response = GetFtpResponse(request);
if (response == null)
{
lstbxFtpState.Items.Add("服务器未响应...");
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
return;
}
Stream responseStream = response.GetResponseStream();
FileStream filestream = File.Create(filePath);
int buflength = 8196;
byte[] buffer = new byte[buflength];
int bytesRead =1;
lstbxFtpState.Items.Add("打开下载通道,文件下载中...");
while (bytesRead != 0)
{
bytesRead = responseStream.Read(buffer, 0, buflength);
filestream.Write(buffer, 0, bytesRead);
}
responseStream.Close();
filestream.Close();
lstbxFtpState.Items.Add("下载完毕,服务器返回:" + response.StatusCode + " " + response.StatusDescription);
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
MessageBox.Show("下载完成!");
}
catch (WebException ex)
{
lstbxFtpState.Items.Add("发生错误,返回状态为:" + ex.Status);
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
MessageBox.Show(ex.Message, "下载失败");
}
}
// 获得选择的文件
// 如果选择的是目录或者是返回上层目录,则返回null
private string GetSelectedFile()
{
string filename = string.Empty;
if (!(lstbxFtpResources.SelectedIndex == -1 || lstbxFtpResources.SelectedItem.ToString().Substring(0, 4) == "[目录]"))
{
string[] namefield = lstbxFtpResources.SelectedItem.ToString().Split(' ');
filename = namefield[0];
}
return filename;
}
// 删除服务器文件事件
private void btnDelete_Click(object sender, EventArgs e)
{
string filename = GetSelectedFile();
if (filename.Length == 0)
{
MessageBox.Show("请选择要删除的文件!", "提示");
return;
}
try
{
string uri = GetUriString(filename);
if (MessageBox.Show("确定要删除文件 " + filename + " 吗?", "确认文件删除", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.DeleteFile);
FtpWebResponse response = GetFtpResponse(request);
if (response == null)
{
lstbxFtpState.Items.Add("服务器未响应...");
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
return;
}
lstbxFtpState.Items.Add("文件删除成功,服务器返回:" + response.StatusCode + " " + response.StatusDescription);
ShowFtpFileAndDirectory();
}
else
{
return;
}
}
catch (WebException ex)
{
lstbxFtpState.Items.Add("发生错误,返回状态为:" + ex.Status);
lstbxFtpState.TopIndex = lstbxFtpState.Items.Count - 1;
MessageBox.Show(ex.Message, "删除失败");
}
}
#endregion
```
由于程序的演示效果需要结合下一专题介绍的FTP服务器,具体的演示效果大家可以查看——专题十二:实现一个简单的FTP服务器,下面就列出程序的主界面截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb3884d1f.png)
**四、小结**
这个专题的介绍就到这里的,在下一个专题将和大家介绍下如何实现一个FTP服务器,这样再加上本专题制作的FTP文件上传下载器就可以形成一个完整的软件套件,自己实现FTP文件上传下载器访问自己实现的FTP服务器将会让大家觉得很很有趣的, 想赶快体验下这样的一种乐趣吗?那就赶快下载本专题的源码来亲身体验下吧。通过希望通过本专题让大家对FTP协议不再陌生,并且做Asp.net开发的朋友,文件的上传和下载是一个公共模块的,然后Asp.net中的文件上传和下载只是通过浏览器向HTTP服务器发送HTTP命令,来告诉HTTP服务器说“我想和你对话”,“我想要你上面的某某文件”以及“我想上传一个文件到你的上面去”等等的对话,这个系列完成之后,我也会和大家总结下网络编程的知识的。
最后提供下源码下载地址:[http://files.cnblogs.com/zhili/FTPUpDownloader.zip](http://files.cnblogs.com/zhili/FTPUpDownloader.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结