最近在為公司實施做了一個工具,Silverlight部署早已是輕車熟路, 但對於非技術人員來說卻很是頭疼的一件事,當到現場實施碰到客戶情況也各不相同, 急需一個類似系統備份的"一鍵工具"快速實現應用程式部署和資料庫進行關聯. 網上關於這方面資源也比較混亂,其中對於IIS的編程影響因素很多,操作技巧上加以細化小結.
A:版本問題
這是你在進行編程前必須要要考慮的一個實際問題.因為IIS版本不同對應編程上基本上完全不同方式來進行的.先瞭解一下Iss版本在作業系統個具體要求.
ISS6.0時代主要以using System.DirectoryServices空間下的DirectoryEntry 對象作為編程訪問一個主要載體.但隨著ISS7.0發布.NET的Web程式由ISS6.0開始逐漸過渡到 7.0版本.而且在編程式控制制ISS上新添加的Microsoft.Web.Administration名稱空間, 提出多個用於管理 操作 和訪問ISS的對象, 使操作ISS的編程更加簡潔 高效.
B:我們要做什麼
實際使用者作業環境中主要以Windows XP/2000/2003作業系統為主, 這就導致了ISS版本主要在5.0/5.1/6.0之間,所以本次示範的代碼編程執行個體都是ISS7.0版本以下(不包含Iss7.0).
為了達到示範目的,我們現在需求是這樣的: 把硬碟上Silverlight應用程式成功部署到本地機器ISS上. 並支援通過區域網路訪問. 需求很簡單吧就是一句話, 其實當進入實際編程時因為客戶的區域網路環境是不可預知的, 這也就導致影響部署Silverlight程式到ISS上諸多未知因素. 所以要在編程中使Silverlight應用程式部署成功,我們必須利用編程除了控制ISS外還要控制其他主要影響ISS因素.
C:進入編程
既然提出需求我們大概確定一下解決思路:
在預設網站下建立一個虛擬目錄承載Silverlight 應用程式, 建立的虛擬目錄運行在獨立應用程式集區中, 支援本地區域網路匿名訪問其實就是對存取權限控制, 為了直接使更改的應用生效需要多次重新啟動ISS的服務, 這就需要對ISS服務進行編程式控制制.
<1>ISS版本的擷取
在進入客戶環境前我們先檢測下是否存在安裝了ISS,以及ISS版本擷取提示,目前擷取ISS版本的方式主要有兩種,第一種方法是通過遍曆DirectoryEntry實體目錄 第二種方式是通過擷取註冊表的ISS修改版本值[經測試這種方式擷取版本不穩定].
public static void GetIssVersionByDri(string domainname) 2: { 3: try 4: { 5: if (string.IsNullOrEmpty(domainname)) 6: { 7: //如果為空白 則預設為本地機器 8: domainname = "LOCALHOST"; 9: } 10: DirectoryEntry getEntity=new DirectoryEntry("IIS://" + domainname + "/W3SVC/INFO"); 11: string Version=getEntity.Properties["MajorIISVersionNumber"].Value.ToString(); 12: MessageBox.Show(Version); 13: } 14: catch (Exception se) 15: { 16: //說明一點:IIS5.0中沒有(int)entry.Properties["MajorIISVersionNumber"].Value;屬性,將拋出異常 證明版本為 5.0 17: MessageBox.Show("擷取ISS的版本是發生異常資訊:"+se.Message); 18: } 19: }
通過系統註冊表擷取ISS版本的值:[測試發現不穩定]
1: public static string GetIssVersion() 2: { 3: //RegistryKey表示 Windows 註冊表中的項級節點.此類是註冊表封裝 4: string issversion = string.Empty; 5: RegistryKey getkey = Registry.LocalMachine.OpenSubKey("software\\microsoft\\inetstp"); 6: if (getkey != null) 7: { 8: issversion= Convert.ToInt32(getkey.GetValue("majorversion", -1)).ToString(); 9: MessageBox.Show(issversion.ToString()); 10: } 11: return issversion; 12: }
<2>建立虛擬目錄
每個Internet服務可以從多個目錄中發布,通過以通用命名慣例 (UNC) 名、使用者名稱及用於存取權限的密碼指定目錄,可將每個目錄定位在本地磁碟機或網路上,虛擬目錄可以一個宿主程式,這可發布的操作目錄即為虛擬目錄.再來看看在ISS6.0中建立的虛擬目錄的步驟和控制的屬性.
建立虛擬目錄:
1: /// <summary> 2: /// 添加一個虛擬目錄 3: /// </summary> 4: public void CreateVirtualDir(string virtualdirname, string logicDir) 5: { 6: //如果存在重複 就直接刪除虛擬目錄 7: if (IsExitesVirtualDir(virtualdirname)) 8: DeleteVirtualDir(virtualdirname); 9: 10: DirectoryEntry rootEntry; 11: rootEntry = new DirectoryEntry("IIS://localhost/W3SVC/1/root"); 12: 13: DirectoryEntry newVirDir; 14: newVirDir = rootEntry.Children.Add(virtualdirname, "IIsWebVirtualDir"); 15: newVirDir.Invoke("AppCreate", true); 16: 17: newVirDir.CommitChanges(); 18: rootEntry.CommitChanges(); 19: 20: newVirDir.Properties["AnonymousPasswordSync"][0] = true; 21: newVirDir.Properties["Path"][0] = logicDir;//+ @"virtualdirentry\virtualname\"; 22: 23: //設定的連接埠綁定資料 24: //_newVirDir.Properties["ServerBindings"].Value =AppEntitys.WebAppInfor.HostIp+AppEntitys.WebAppInfor.HostProt+AppEntitys.WebAppInfor.AppDesc; 25: 26: //設定起始預設頁: 27: newVirDir.Properties["EnableDefaultDoc"][0] = true; 28: newVirDir.Properties["DefaultDoc"][0] = "Default.aspx"; 29: 30: //_newVirDir 31: newVirDir.CommitChanges(); 32: }
這個方法在建立時主要有兩個參數 一個是建立虛擬目錄名稱 另外一個要部署Silverlight應用程式實體路徑.在擷取根節目錄時需要制定ISS的路徑. ISS的路徑格式如:IIS://ComputerName/Service/Website/Directory
找到根目錄後添加建立虛擬目錄. 制定參數為Schema-指每個結點的類型:IIsVirtualDir:——虛擬目錄 IIsWebDir :——普通目錄, 添加完成後調用ADSI中的"AppCreate"方法將目錄真正建立.建立完成後通過根目錄和新目錄提交儲存.
<3>目錄屬性設定
在DirectoryEntity虛擬目錄屬性可以說是非常多的,當時在編程時我為了區分屬性間區別做了一個方法去遍曆整個屬性集合PropertyCollection. 然後把常用重要的屬性跳出來進入賦值設定. 因為關於虛擬目錄的屬性在MSDN上的API中並沒有直接提到, 特別是對虛擬目錄存取權限控制非常重要屬性等 .先說明局部的常用的屬性:
例如修改程式起始頁:
1: //設定起始預設頁: 2: newVirDir.Properties["EnableDefaultDoc"][0] = true; 3: newVirDir.Properties["DefaultDoc"][0] = "Default.aspx,Index.Html,index.asp"; 4: newVirDir.CommitChanges();
DefaultDoc選項可以支援多個但注意選擇性排序.一般修改目錄屬性後都選通過CommitChanges()方法提交儲存, 但有時你會發現我明明修改屬性 卻沒有儲存生效. 這是因為ISS中部分屬性設定需要重新啟動ISS服務才會生效.這個時候我們需要對ISS服務進行控制.
<4>ISS服務控制
關於ISS服務控制微軟提供一個命名空間System.ServiceProcess 提供能夠快速控制項目本地系統服務API.對於ISS服務控制我們最常用的是重新啟動使當前設定生效. 在設定服務時我先找到服務名稱,開啟電腦管理 在服務和應用程式目錄下開啟服務.
我們能看到ISS服務命名是:ISSAdmin 我們通過編碼來控制:啟動ISS.
1: //擷取IIS Serivcer控制聲明 . 參數為Server在系統標識該服務的簡稱,. 2: ServiceController getservicecon = new ServiceController("IISADMIN"); 3: getservicecon.Start();
重啟/暫停/停止ISS服務:
1: if (getservicecon.Status == ServiceControllerStatus.Running) 2: { 3: //停止服務 4: getservicecon.Stop(); 5: //暫停服務 6: getservicecon.Pause(); 7: //重啟服務 8: //Process提供對本地和遠程進程的訪問並使您能夠啟動和停止本地系統進程 9: //利用Start方法調用:啟動(或重用)此 Process 組件的 StartInfo 屬性指定的進程資源,並將其與該組件關聯 10: //如果啟動了進程資源,則為 true;如果沒有啟動新的進程資源(例如,如果重用了現有進程),則為 false 11: //通過指定文檔或應用程式檔案的名稱來啟動進程資源,並將資源與新的 Process 組件關聯 12: Process.Start("iisreset"); 13: }
如上就是簡單利用編程有效控制ISS服務運行狀態.
<5>應用程式集區建立與控制
ISS應用程式集區是將一個或多個應用程式連結到一個或多個背景工作處理序集合的配置。因為應用程式集區中的應用程式與其他應用程式被背景工作處理序邊界分隔,所以某個應用程式集區中的應用程式不會受到其他應用程式集區中應用程式所產生的問題的影響, 當我們建立一個應用程式時 有時用戶端環境我們無法清除預知, 為了盡量減少影響ISS設定外在因素, 我們把建立虛擬目錄放到一個獨立應用程式集區中.
當建立一個虛擬目錄後,也同時建立一個程式池 把虛擬目錄放到其中:
1: /// <summary> 2: /// 建立程式池後關聯相應應用程式及虛擬目錄 3: /// </summary> 4: public static void SetAppToPool(string appname) 5: { 6: //擷取目錄 7: DirectoryEntry getdir = new DirectoryEntry("IIS://localhost/W3SVC"); 8: foreach (DirectoryEntry getentity in getdir.Children) 9: { 10: if (getentity.SchemaClassName.Equals("IIsWebServer")) 11: { 12: //設定應用程式程式池 先獲得應用程式 在設定應用程式程式池 13: //第一次測試根目錄 14: foreach (DirectoryEntry getchild in getentity.Children) 15: { 16: if (getchild.SchemaClassName.Equals("IIsWebVirtualDir")) 17: { 18: //找到指定的虛擬目錄. 19: foreach (DirectoryEntry getsite in getchild.Children) 20: { 21: if (getsite.Name.Equals(appname)) 22: { 23: //【測試成功通過】 24: getsite.Properties["AppPoolId"].Value = appname; 25: getsite.CommitChanges(); 26: } 27: } 28: } 29: } 30: } 31: } 32: }
思路如下: 首先獲得ISS的根目錄即ISS://localhost/W3SVC. 擷取成功後通過SchemaClassName擷取節點類型.ISSWebServer普通目錄 ISSWebVirturalDir虛擬目錄. 通過虛擬目錄的Name唯一名稱擷取指定虛擬目錄, 在通過屬性參數AppPoolId設定附屬的應用程式集區的名稱. 然後提交儲存.
其實通過應用我們可以清晰看到ISS中各個目錄之間的關聯關係.上面是修改一個已經儲存在應用程式集區,如何建立:
1: public static bool CreateAppPool(string metabasePath, string appPoolName, string Username, string Password) 2: { 3: bool issucess = false; 4: try 5: { 6: if (metabasePath.EndsWith("/W3SVC/AppPools")) 7: { 8: if (MyIISHelper.AppPoolExist(appPoolName)) 9: { 10: //已經存在 先刪除這個AppPool 在建立 11: //MessageBox.Show("當前以網站名稱命名的程式池已經存在!"); 12: DeleteRepPool(appPoolName); 13: } 14: 15: //建立一個新程式池 16: DirectoryEntry newpool; 17: DirectoryEntry apppools = new DirectoryEntry(metabasePath); 18: newpool = apppools.Children.Add(appPoolName, "IIsApplicationPool"); 19: 20: //設定屬性 訪問使用者名稱和密碼 一般採取預設 21: newpool.Properties["WAMUserName"][0] = Username; 22: newpool.Properties["WAMUserPass"][0] = Password; 23: newpool.Properties["AppPoolIdentityType"][0] = "3"; 24: newpool.CommitChanges(); 25: } 26: return issucess; 27: } 28: catch// (Exception ex) 29: { 30: return false; 31: } 32: }
基本上和建立一個DirectoryEntity雷同但注意指定的Schome類型是IIsApplicationPool用來標識建立的是一個應用程式集區.當然建立可以刪除 刪除方式如下:
1: DirectoryEntry appPool = new DirectoryEntry("IIS://localhost/W3SVC/AppPools"); 2: foreach (DirectoryEntry getdir in appPool.Children) 3: { 4: if (getdir.Name.Equals(appname)) 5: { 6: getdir.DeleteTree();//刪除 7: } 8: }
關於應用程式集區如果不做任何設定則在ISS中會被DefaultPool中添加, 但有時預設程式池設定環境不一定能夠滿足當前程式需求,例如最常見的關於.NET版本的控制. 其中對3.0和3.5版本應用設定需要重新註冊3.5 .NET FrameWork.部分組件這就涉及到一個.NET版本問題.
<6>.NET版本問題
在進行ISS控制中當然也是遇到各種各樣的問題, 一方面因為測試XP 2003系統內容不同, 需要控制因素過多,當然其中值得一提就是關於整個應用程式集區的.NET版本問題.我們先來看看多版本下ISS中設定:
如果我們的應用程式使用3.0或3.5版本 則在應用程式集區的沒有對應的.NET版本.這時應用程式運行會提示一個Http錯誤404.17 notFound:
其實ISS在處理應用程式集區於.NET版本進行映射時,Net3.0\3.5沒帶處理常式aspnet_isapi.dll,所以IIS中指定網站適用架構時,這也是為什麼我們在ISS映射時看不到.Net3.0\3.5版本. 最直接的方法重新註冊.NET 3.5 由.NET 2.0版本託管: 這時我們需要執行一個Cmd命令:
從新把應用程式集區.net版本切換成.net 2.0.同樣實現.net 3.0/3.5託管.
<7>使用環境以及測試條件
如上應用程式和代碼均在Windows 7./XP2/Windows Server 2003上通過. 對應的ISS版本範圍從ISS5.0/5.1到Iss 6.0/7.0. 包含ISS7.0. 篇幅有限,實際中關於ISS的操作還有很多細節問題不能面面俱到,我只是挑了其中我認為基礎或幾位重要幾個問題著重來寫, 實際中關於ISS還有其他諸多因素, 所以對於這方面控制編程 只能折中的有目的性選擇自己想做的效果. 切不可貪大求全. 因為這本身控制過程就是複雜的過程. 客戶環境也不能諸如統一.