ASP.NET WebService(二)_三種存取方式介紹_webmethod屬性大致介紹
很久以前曾整理過一篇xml webservice部落格文章
https://coolmandiary.blogspot.com/2017/11/web-service.html
可以得知用於應用程式整合、B2B整合和跨防火牆的通訊
如今因為又看到其他文章跟在實際專案中再次見面
因此想再加深補充實務上的經驗分享當筆記
在此之前先溫顧一下
在.NET WebService中共分為三種存取途徑
1.EndPoint (SOAP): 也就是比較常在市面上基礎教授書籍中講的URI location
會直接條列出web service所包含的一系列方法及方法描述、Class描述
是XML based的message template,是Browser跟WebService之間溝通收發的橋樑。
實際案例:
http://www.webxml.com.cn/WebServices/WeatherWS.asmx
2.Disco(Web Service Discovery):用於指向web服務位置
Disco 的用途就像電話簿和搜尋引擎網站一樣,提供資訊分類以及尋找的服務,讓我們能方便迅速找到所需的 Web Services。
其運作原理是,當開發人員將一個 Web Service 設計完成之後,可以將它登錄到一個集中的地方,其他人就可以向這個集中地查詢找到需要的服務。這個登錄-查詢的機制只要就是依靠 UDDI(Universal Description, Discovery and Integration)來達成。
(備註:在不知道哪裡有你需要的 Web Services情境下起到作用。)
實際案例:
http://www.webxml.com.cn/WebServices/WeatherWS.asmx?disco
3.WSDL(Web Services Description Language):
用於描述webservice服務定義的方法、屬性、Binding的協定、Port、URI
當在網路上找到一個 Web Service,如何知道怎樣使用?有哪些服務、方法可以呼叫?
要傳遞對應哪些參數?
(備註:是在你已經確定要使用某個 Web Service 並且知道其網址的情形下才有用)
實際案例:
http://www.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl
將XML、SOAP、UDDI、WSDL核心元素組合再一起即形成所謂的「WebService架構」
WebService架構三角色
服務提供者
服務要求者
介於上述兩者之間的中介者
1.服務提供者向UDDI 伺服器註冊所隸屬Web services 的URL
2.服務要求者經由UDDI伺服器查詢Web services位址和相應資訊
3.服務要求者向UDDI取得服務提供者之Web services位址
4.經由所取之Web services位址下載描述相關資訊的WSDL描述文件
5.服務要求者透過SOAP協定對服務提供者提出資料存取的要求
6.服務提供者把資料封裝於SOAP訊息當中,再回傳給服務要求者
在較淺的操作上或許我們覺得應該就是一個URL Request操作
實際底層之間做了很多事情
Web based System
透過瀏覽器下載整篇網頁讀取
瀏覽器先確認User要得URL在去向DNS詢問服務端IP,DNS返回IP後瀏覽器建立一TCP離線到IP位址的port,瀏覽器發送一個要求存取網頁檔的網頁,Server回應網頁內容,TCP斷連線,瀏覽器呈現出圖文訊息。(這段就是每天上網的重複動作)
WebService based System
開發好的webservice部屬到Server端,並向服務中介者登錄資訊至UDDI註冊資料庫中。
服務要求者到UDDI註冊資料庫去搜尋所需服務,之後取得Web Services一系列資訊即可使用該服務。
============================================================(我是分隔線)
這就是一個Web Service的專案
那我們可能透過他來做一個對應資料請求回傳(可能是string , DataSet , DataTable....)
通常方法修飾詞有[WebMethod]
然後是public存取權限
這裡做一個確認是哪一家銀行的信用卡method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Services; namespace WSApp { /// <summary> ///MyService 的摘要描述 /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // 若要允許使用 ASP.NET AJAX 從指令碼呼叫此 Web 服務,請取消註解下列一行。 // [System.Web.Script.Services.ScriptService] public class MyService : System.Web.Services.WebService { [WebMethod] public string HelloWorld() { return "Hello World"; } [WebMethod(Description ="This method help to identify which bank's credit card.")] public string IdentifyCreditCardBank(string CardNumber) { string strResult = ""; if (string.IsNullOrEmpty(CardNumber.Trim())) strResult = "Fail Input"; string strCardNum = CardNumber.Trim(); if (strCardNum.StartsWith("4")) { strResult = "VisaCard"; } else if (strCardNum.StartsWith("5") && (int.Parse(strCardNum.Substring(0, 2)) >= 51 && int.Parse(strCardNum.Substring(0, 2)) <= 55)) { strResult = "MasterCard"; } else if ((strCardNum.StartsWith("1") && strCardNum.Substring(0, 4).Equals("1800")) || (strCardNum.StartsWith("2") && strCardNum.Substring(0, 4).Equals("2131")) || (strCardNum.StartsWith("3") && (int.Parse(strCardNum.Substring(0, 3)) >= 300 && int.Parse(strCardNum.Substring(0, 3)) <= 399)) ) { strResult = "JCBCard"; } else { strResult = "Not support yet"; } return strResult; } } } |
規則(以下測試案例是隨機產生的):
Visa Card:
第一碼為 4。
4044537864494662
4293940793434437
4858017259516044
Master Card:
第一碼為 5,且前二碼介於 51 和 55 中間。
5175693365527506
5275378199506277
5386213365906256
5426594312829903
JCB Card:
第一碼為 1,且前四碼為 1800。
第一碼為 2,且前四碼為 2131。
第一碼為 3,且前三碼介於 300和399之間。
3569012648156737
3559223794235868
3539056167512231
在此可以看到多寫了一段
[WebMethod(Description ="This method help to identify which bank's credit card.")]
https://docs.microsoft.com/zh-tw/dotnet/api/system.web.services.webmethodattribute.description?view=netframework-4.8
這個是加上一個WebMethod屬性中的"功能描述",有點像函數前的註解。
其餘還有很多WebMethod屬性
webmethod屬性大致介紹
常見的WebMethod屬性有這六種
1.Description:
Specifies the value of the description element under each operation element within each type definition within the ASP.NETgenerated WSDL document.
2.EnableSession:
Specifies whether the ASP.NET session state services will be available for the implementation of the method.
3.TransactionOption:
Specifies the transactional support that should be provided for the implementation of the method.
The method can serve only as the root of a transaction and cannot participate in the caller’s transaction.
4.BufferResponse:Specifies whether the response to the client should be buffered.
5.CacheDuration:
Specifies the amount of time, in seconds, that a response will be cached in memory by the Web server for a given response. The default is 0.
6.MessageName:
Specifies the name of the method exposed by the Web service. Specifically, it sets the name of the element within the body of the SOAP message that contains the parameters as well as the suffix of the SOAP action. It also specifies the prefix of the names of the message, input, and output elements within the ASP.NET-generated WSDL document.
[WebMethod(EnableSession=true)]
https://docs.microsoft.com/zh-tw/dotnet/api/system.web.services.webmethodattribute.enablesession?view=netframework-4.8
是否啟動session機制,主要通過cookie完成的,預設為false。
來個實例(累加1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | [WebMethod(EnableSession =true)] public int AccumulateWithSession() { int CountNum = 0; if (HttpContext.Current.Session["Count"] == null) { CountNum = 1; } else { CountNum = (int)HttpContext.Current.Session["Count"] + 1; } HttpContext.Current.Session["Count"] = CountNum; return CountNum; } [WebMethod] public int AccumulateWithoutSession() { int CountNum = 0; if (HttpContext.Current.Session["Count"] == null) { CountNum = 1; } else { CountNum = (int)HttpContext.Current.Session["Count"] + 1; } HttpContext.Current.Session["Count"] = CountNum; return CountNum; } |
會發現有添加Session property為true的 session可以直接使用並持續累加(一直刷新網頁呼叫webservice),而沒有的則是會報null的錯。
如果Session property設置爲 true,則client訪問WS時,可以不賦值Session變量,有默認值。
如果Session property設置爲 false,則client訪問WS時,必須要先對Session變量賦值,否則報錯。
[WebMethod(BufferResponse:=False)]
https://docs.microsoft.com/zh-tw/dotnet/api/system.web.services.webmethodattribute.bufferresponse?view=netframework-4.8
是否等到響應被完全緩沖完,才發送信息給請求端。預設為true要等完。
全被緩沖完才被發送的!(若資料量小預設值即可,若可能容易很大就要看是否調整為false)
比如很大份文字檔的parser處理或讀檔IO
1 2 3 4 | [WebMethod(BufferResponse=false)] public string GetTextFile() { // return large amount of data } |
將設定 BufferResponse 為 true 時,會將 XML Web Service 方法的回應序列化為記憶體緩衝區,直到回應完全序列化或緩衝區已滿為止。 一旦回應經過緩衝處理,就會透過網路傳回 XML Web Service 用戶端。 當 BufferResponse 為時 false ,XML Web Service 方法的回應會在序列化時傳送回用戶端。 一般來說,您只想要將設定 BufferResponse 為 false (如果已知 XML Web Service 方法會將大量資料傳回給用戶端)。 對於較少量的資料,XML Web Service 效能會優於 BufferResponse true 。
當 BufferResponse 為時 false ,會停用 XML Web Service 方法的 SOAP 延伸模組。
[WebMethod(CacheDuration:=60)]
https://docs.microsoft.com/zh-tw/dotnet/api/system.web.services.webmethodattribute.cacheduration?view=netframework-4.8
在.NET中有支援輸出緩存,client端每一次的請求如果有重複就存入server memory中,使webservice不需要執行多遍整批重新search的問題,能直接透過緩存提高訪問效率,
而CacheDuration就是指定緩存時間的屬性。
預設值為0也就是disable
1 2 3 4 | [WebMethod(CacheDuration=60)] public string GetSomeDetails() { // return large amount of data } |
[WebMethod(TransactionOption=(TransactionOption))]
https://docs.microsoft.com/zh-tw/dotnet/api/system.web.services.webmethodattribute.transactionoption?view=netframework-4.8
XML Web Service 方法的交易支援。 預設值為 Disabled。
以下是其選項
如果 XML Web Service 方法擲回或未攔截到例外狀況,則會自動中止交易。 如果沒有發生任何例外狀況,則會自動認可交易,除非方法明確地呼叫 SetAbort
。
[WebMethod(MessageName="函數功能描述")]
在WebService中我們可以同名異式也就是多載一些方法
而此時可以透過MessageName來作區別
TestCode:
1 2 3 4 5 6 7 8 9 10 | [WebMethod] public string GetTime() { return DateTime.Now.ToString("T"); } [WebMethod(MessageName="GetTimeFormat")] public string GetTime(string format) { return DateTime.Now.ToString(format); } |
WebService創建與發佈方式
通常在vs中開發網站應用會分成用
WebSite跟WebApp
若是用WebSite要創建Webservice
右鍵加入新增項目選擇Visual C#找有點像羽毛球符號有ASMX字眼的就是了
預設會將實際要寫後端程式放置於App_Code目錄下
右鍵加入新增項目
要在Web大項下找
一樣找有點像羽毛球符號有ASMX字眼
預設則是跟一般寫ASPX一樣放在一起
只是副檔名改為ASMX
這裡示範一個如何發布
寫好一個計算面積的方法後
對專案右鍵發布
當點完發布完成後就會看到目錄有生成
編譯好的dll , asmx , asax等檔案
開啟IIS新建一網站選擇才剛發布的目錄
三種存取方式介紹
方式1.靜態調用WebService
前提:已知webservice路徑,則可以直接添加引用。
缺點:如果發布的WebService URL改變,那麼就要重新添加WebService的引用。
如果是既有的WebService發生了改變(擴充方法 增減方法參數...),也要更新現有的服務引用!
又分為Service Reference 和 Web Reference兩種
靜態調用的第一種.Service Reference
(調用端必須要.net3.0以上)
在引用上我們針對新建的WebApp示範
右鍵添加 服務參考
這裡會發現跟剛剛port不同因為預設是抓同一方案的
這裡我們模擬已經部屬在遠端IIS Server上的版本
(比較符合實際較常見的跨部門調用服務的情況)
輸入已部屬到IIS上版本的URL後按移至做Refresh
修改一下Namespace
會連動程式裡面引用時的撰寫所以命名盡可能有意義不用預設的
那在引用過程主要是先實體化
WebService的本地代理間接做method調用
執行結果圖
實際專案目錄下共有這幾種檔案被產生(於添加服務參考時候)
在專案中的外觀特徵:
通常會在網站專案App_WebReferences的目錄下看到
Web參考的檔案,如.wsdl、xsd、disco、discomap..等檔案類型,
則表示應用程式中有使用Web服務有關的檔案或文件。
靜態調用的第二種.Web Reference
vs2010之前版本(2005,2008,2010)的方式(比較早期的方式)
若調用端(Client端)屬於用比較早期的.net framework 比如1.多、2.多版就要使用此方式
此方法不支援WCF的引用因為WCF是在.net3.0之後的產物
後期都推薦改用服務參考(.net 3.0以上支援)方式了
一樣是在右鍵服務參考
只是要點左下角進階
在URL輸入提供 Web Service 位址 , 點選旁邊箭頭按鈕
http://localhost:1458/MyAppWebService.asmx
改一下參考名稱
最終調用Service Proxy法
執行結果圖
Web Reference 跟 Service Reference主要差別?
Web Reference只支援SOAP Web Service,和.net 1.0 ~2.0支援度較好
Service Reference則是可支援到WCF 可支援到更高的.net 版本(3.0以上)
Web Reference是由wsdl.exe產生Client 端Proxy程式的
而產生的的proxy class是繼承自System.Web.Services.Protocols.SoapHttpClientProtocol
(PS:WCF不是直接繼承自System.Web.Services.Protocols.SoapHttpClientProtocol)
其proxy class可以指定要訪問的URL屬性
Service Reference是由svcutil.exe產生Client 端Proxy程式的
其proxy class沒有URL屬性可指定,而是在Client端配置文件app.config中指定endpoint來訪問指定的url。
結論:
若是有舊系統相容性的考量就用Web Reference若有要導入WCF技術或是全新的專案就統一改採用Service Reference!
方式2.藉由wsdl文件來產生webservice 的.cs文件
倘若遇到接手專案維護情況
往往會有意想不到的問題
程式碼給不全若只有wsdl檔案時候
則可以透過visual studio開發人員命令提示字元
開啟方式
來產生proxy 的cs檔
在此我們拿剛剛的wsdl來示範
輸入格式
wsdl wsdl檔案位置 /out:程式檔名.cs
wsdl webservice url.asmx?wsdl /out:程式檔名.cs
wsdl D:\webservice_release\wsdl_tmp\MyAppWebService.wsdl /out:MyAppWebService.cs
wsdl http://localhost:1458/MyAppWebService.asmx?wsdl /out:MyAppWebService2.cs
建議cd 到特定目錄避免目錄權限不足
那此時就會產生一c#程式檔案
可以直接摳到專案去用也可以再產生dll來間接調用
產生dll指令
csc /t:library cs檔案
csc /t:library D:\webservice_release\wsdl_tmp\MyAppWebService.cs
而這種方式創建的就是上一點講的
Web Reference引用方式的一種
預設產生是SOAP版本1.1,若要改到1.2版則可以透過如下指令來指定
wsdl D:\webservice_release\wsdl_tmp\MyAppWebService.wsdl /protocol:SOAP12 /out:MyAppWebService.cs
方式3.動態調用Web Service
若我們無法在一開始得知Web Service URL或者URL有浮動可能則我們此時就要改用動態方式來對Web Service進行存取了。
此外這種方式也可提升後續維護不用Service一更新又要重新引用等等
所謂動態調用
事實上也就是將原先透過指令或是懶人版的藉由IDE印用方式
改全部透過手寫程式來進行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | private string DynamicServiceCall_GetArea(string len,string width) { string res = ""; // 1. 使用 WebClient 下载 WSDL 。 WebClient web = new WebClient(); Stream stream = web.OpenRead("http://localhost:1458/MyAppWebService.asmx?WSDL"); // 2. 建立 WSDL 檔案。 ServiceDescription description = ServiceDescription.Read(stream); // 3. 產生Client端Proxy Class。 ServiceDescriptionImporter importer = new ServiceDescriptionImporter(); importer.ProtocolName = "Soap"; // 指定訪問的Protoccol。 importer.Style = ServiceDescriptionImportStyle.Client; //Client端 Proxy 的建立。 importer.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync; importer.AddServiceDescription(description, null, null); // 添加 WSDL 檔案。 // 4. 使用 CodeDom 去Compile出Client端Proxy程式。 CodeNamespace nmspace = new CodeNamespace(); // 加入namespace CodeCompileUnit unit = new CodeCompileUnit(); unit.Namespaces.Add(nmspace); ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit); CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"); CompilerParameters parameter = new CompilerParameters(); parameter.GenerateExecutable = false; parameter.GenerateInMemory = true; parameter.ReferencedAssemblies.Add("System.dll"); parameter.ReferencedAssemblies.Add("System.XML.dll"); parameter.ReferencedAssemblies.Add("System.Web.Services.dll"); parameter.ReferencedAssemblies.Add("System.Data.dll"); CompilerResults result = provider.CompileAssemblyFromDom(parameter, unit); // 5. 使用 Reflection 調用 WebService。 if (!result.Errors.HasErrors) { Assembly asm = result.CompiledAssembly; Type t = asm.GetType("MyAppWebService"); // 如果在前面有設置Namespace則需要記得將Mamespace天加到Class之前。 object o = Activator.CreateInstance(t); MethodInfo method = t.GetMethod("Area"); //res = method.Invoke(o, new object[] { "4.5", "8" }).ToString(); res = method.Invoke(o, new object[] { len, width }).ToString(); //method.Invoke(o, null); //MethodInfo method = t.GetMethod("HelloWorld"); //Console.WriteLine(method.Invoke(o, null)); } return res; } |
以下是上述完整的(所有存取方式)程式範例aspx.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using System.Web.Services.Description; using System.Web.UI; using System.Web.UI.WebControls; using System.CodeDom; using System.CodeDom.Compiler; using System.Xml.Serialization; using System.Reflection; namespace WebApplication1 { public partial class WebForm1 : System.Web.UI.Page { //服務參考 MyTestService.MyAppWebServiceSoapClient myTestWs = new MyTestService.MyAppWebServiceSoapClient(); //Web 參考 MyTestWebRefService.MyAppWebService MyAppWebService = new MyTestWebRefService.MyAppWebService(); //引用wsdl和csc編譯的dll MyAppWebService myAppService = new MyAppWebService(); protected void Page_Load(object sender, EventArgs e) { } protected void btnCalculate_Click(object sender, EventArgs e) { //服務參考 //lblResult.Text = myTestWs.Area(txtLen.Text, txtWidth.Text).ToString(); //Web 參考 //lblResult.Text = MyAppWebService.Area(txtLen.Text, txtWidth.Text).ToString(); //引用wsdl和csc編譯的dll //lblResult.Text = myAppService.Area(txtLen.Text, txtWidth.Text).ToString(); //動態調用WS lblResult.Text = DynamicServiceCall_GetArea(txtLen.Text, txtWidth.Text); } private string DynamicServiceCall_GetArea(string len,string width) { string res = ""; // 1. 使用 WebClient 下载 WSDL 。 WebClient web = new WebClient(); Stream stream = web.OpenRead("http://localhost:1458/MyAppWebService.asmx?WSDL"); // 2. 建立 WSDL 檔案。 ServiceDescription description = ServiceDescription.Read(stream); // 3. 產生Client端Proxy Class。 ServiceDescriptionImporter importer = new ServiceDescriptionImporter(); importer.ProtocolName = "Soap"; // 指定訪問的Protoccol。 importer.Style = ServiceDescriptionImportStyle.Client; //Client端 Proxy 的建立。 importer.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync; importer.AddServiceDescription(description, null, null); // 添加 WSDL 檔案。 // 4. 使用 CodeDom 去Compile出Client端Proxy程式。 CodeNamespace nmspace = new CodeNamespace(); // 加入namespace CodeCompileUnit unit = new CodeCompileUnit(); unit.Namespaces.Add(nmspace); ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit); CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"); CompilerParameters parameter = new CompilerParameters(); parameter.GenerateExecutable = false; parameter.GenerateInMemory = true; parameter.ReferencedAssemblies.Add("System.dll"); parameter.ReferencedAssemblies.Add("System.XML.dll"); parameter.ReferencedAssemblies.Add("System.Web.Services.dll"); parameter.ReferencedAssemblies.Add("System.Data.dll"); CompilerResults result = provider.CompileAssemblyFromDom(parameter, unit); // 5. 使用 Reflection 調用 WebService。 if (!result.Errors.HasErrors) { Assembly asm = result.CompiledAssembly; Type t = asm.GetType("MyAppWebService"); // 如果在前面有設置Namespace則需要記得將Mamespace天加到Class之前。 object o = Activator.CreateInstance(t); MethodInfo method = t.GetMethod("Area"); //res = method.Invoke(o, new object[] { "4.5", "8" }).ToString(); res = method.Invoke(o, new object[] { len, width }).ToString(); //method.Invoke(o, null); //MethodInfo method = t.GetMethod("HelloWorld"); //Console.WriteLine(method.Invoke(o, null)); } return res; } } } |
Ref:
信用卡冷知識―起源、卡別、卡號怎麼來?
Dummy Card Generator 電子商務支付平台開發測試用信用卡卡號產生器
http://ir.lib.pccu.edu.tw/retrieve/62719/%E5%91%82%E5%AD%B8%E9%8A%98.pdf
XML Web Service 开发 第1章 XML Web Service 第2章 XML Web Service 体系结构
https://slidesplayer.com/slide/15513577/
.Net下WebMethod屬性
when to use EnableSession = true i.e. [WebMethod(EnableSession = true)] in Web Service
[轉]在ASP.NET Web Services 中使用Session
[WebMethod(EnableSession = true)]
What happened to Add Web Reference in Visual Studio 2019?
can't find "Add Web Reference" on menu
C# - VS2019 WebService创建与发布,并部署到Windows Server 2012R
SOAP WebService 加入服務參考(Web參考)
XML Web Service
C# Webservice開發及使用
Web Service入門 #2,呼叫現成的Web Service 給我的程式來用
service references and web references
Web Reference和Service Reference的区别
將wsdl轉成web service
C# 调用WebService的3种方式 :直接调用、根据wsdl生成webservice的.cs文件及生成dll调用、动态调用
C#调用WebService
C#动态调用webservice
Web Service学习笔记:动态调用WebService
作者已經移除這則留言。
回覆刪除作者已經移除這則留言。
回覆刪除想請問" res = method.Invoke(o, new object[] { len, width }).ToString();"這個寫法, 只取得第一行的資料... 有可能是什麼原因造成的呢? 再麻煩解惑了,謝謝!
回覆刪除