# [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),我的工作就是負責將文件從一臺計算機傳輸到另外一臺計算機,并且我還可以保證傳輸的可靠性。我的工作流程可以通過下面的一張圖來表達:

從圖中大家應該可以明白我的工作過程了吧,我的工作過程是典型的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服務器,下面就列出程序的主界面截圖:

**四、小結**
這個專題的介紹就到這里的,在下一個專題將和大家介紹下如何實現一個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 領域驅動設計實戰系列總結