PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.ServiceModel/System.ServiceModel.Description/ServiceMetadataExtension.cs

https://bitbucket.org/steenlund/mono-2.6.7-for-amiga
C# | 401 lines | 288 code | 58 blank | 55 comment | 62 complexity | 04489cb2aebaeec0873b76a379b44c18 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, LGPL-2.1
  1. //
  2. // ServiceMetadataExtension.cs
  3. //
  4. // Author:
  5. // Atsushi Enomoto <atsushi@ximian.com>
  6. // Ankit Jain <jankit@novell.com>
  7. // Gonzalo Paniagua Javier (gonzalo@novell.com)
  8. //
  9. // Copyright (C) 2005 Novell, Inc. http://www.novell.com
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System;
  31. using System.Collections;
  32. using System.Collections.Generic;
  33. using System.Collections.ObjectModel;
  34. using System.Collections.Specialized;
  35. using System.IO;
  36. using System.Linq;
  37. using System.ServiceModel.Channels;
  38. using System.ServiceModel.Dispatcher;
  39. using System.Web;
  40. using System.Web.Services;
  41. using System.Web.Services.Description;
  42. using System.Xml;
  43. using System.Xml.Schema;
  44. using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
  45. using WSMessage = System.Web.Services.Description.Message;
  46. using SMMessage = System.ServiceModel.Channels.Message;
  47. using WCFBinding = System.ServiceModel.Channels.Binding;
  48. namespace System.ServiceModel.Description
  49. {
  50. public class ServiceMetadataExtension : IExtension<ServiceHostBase>
  51. {
  52. const string ServiceMetadataBehaviorHttpGetBinding = "ServiceMetadataBehaviorHttpGetBinding";
  53. MetadataSet metadata;
  54. ServiceHostBase owner;
  55. Dictionary<Uri, ChannelDispatcher> dispatchers;
  56. HttpGetWsdl instance;
  57. public ServiceMetadataExtension ()
  58. {
  59. }
  60. [MonoTODO]
  61. public MetadataSet Metadata {
  62. get {
  63. if (metadata == null) {
  64. MetadataExporter exporter = new WsdlExporter ();
  65. foreach (ServiceEndpoint ep in owner.Description.Endpoints) {
  66. if (ep.Contract.Name == ServiceMetadataBehavior.MexContractName)
  67. continue;
  68. exporter.ExportEndpoint (ep);
  69. }
  70. metadata = exporter.GetGeneratedMetadata ();
  71. }
  72. return metadata;
  73. }
  74. }
  75. internal ServiceHostBase Owner {
  76. get { return owner; }
  77. }
  78. internal static ServiceMetadataExtension EnsureServiceMetadataExtension (ServiceHostBase serviceHostBase) {
  79. ServiceMetadataExtension sme = serviceHostBase.Extensions.Find<ServiceMetadataExtension> ();
  80. if (sme == null) {
  81. sme = new ServiceMetadataExtension ();
  82. serviceHostBase.Extensions.Add (sme);
  83. }
  84. return sme;
  85. }
  86. // FIXME: distinguish HTTP and HTTPS in the Url properties.
  87. // FIXME: reject such ServiceDescription that has no HTTP(S) binding.
  88. // FIXME: it should not use the binding that is used in the ServiceEndpoint. For example, WSDL results have to be given as text, not binary.
  89. // FIXME: if the ServiceDescription has a base address (e.g. http://localhost:8080) and HttpGetUrl is empty, it returns UnknownDestination while it is expected to return the HTTP help page.
  90. internal void EnsureChannelDispatcher (bool isMex, string scheme, Uri uri, WCFBinding binding)
  91. {
  92. if (isMex)
  93. instance.WsdlUrl = uri;
  94. else
  95. instance.HelpUrl = uri;
  96. if (dispatchers == null)
  97. dispatchers = new Dictionary<Uri, ChannelDispatcher> ();
  98. else if (dispatchers.ContainsKey (uri)) {
  99. var info = dispatchers [uri].Listener.GetProperty<MetadataPublishingInfo> ();
  100. if (isMex)
  101. info.SupportsMex = true;
  102. else
  103. info.SupportsHelp = true;
  104. return;
  105. }
  106. if (binding == null) {
  107. switch (scheme) {
  108. case "http":
  109. binding = MetadataExchangeBindings.CreateMexHttpBinding ();
  110. break;
  111. case "https":
  112. binding = MetadataExchangeBindings.CreateMexHttpsBinding ();
  113. break;
  114. case "net.tcp":
  115. binding = MetadataExchangeBindings.CreateMexTcpBinding ();
  116. break;
  117. case "net.pipe":
  118. binding = MetadataExchangeBindings.CreateMexNamedPipeBinding ();
  119. break;
  120. }
  121. }
  122. CustomBinding cb = new CustomBinding (binding) { Name = ServiceMetadataBehaviorHttpGetBinding };
  123. cb.Elements.Find<MessageEncodingBindingElement> ().MessageVersion = MessageVersion.None;
  124. ServiceEndpoint se = new ServiceEndpoint (ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract)), cb, new EndpointAddress (uri))
  125. {
  126. ListenUri = uri,
  127. };
  128. var channelDispatcher = new DispatcherBuilder ().BuildChannelDispatcher (owner.Description.ServiceType, se, new BindingParameterCollection ());
  129. // add HttpGetWsdl to indicate that the ChannelDispatcher is for mex or help.
  130. var listener = channelDispatcher.Listener as ChannelListenerBase;
  131. if (listener != null)
  132. listener.Properties.Add (new MetadataPublishingInfo () { SupportsMex = isMex, SupportsHelp = !isMex, Instance = instance });
  133. channelDispatcher.Endpoints [0].DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider (new InstanceContext (owner, instance));
  134. dispatchers.Add (uri, channelDispatcher);
  135. owner.ChannelDispatchers.Add (channelDispatcher);
  136. }
  137. void IExtension<ServiceHostBase>.Attach (ServiceHostBase owner)
  138. {
  139. this.owner = owner;
  140. instance = new HttpGetWsdl (this);
  141. }
  142. void IExtension<ServiceHostBase>.Detach (ServiceHostBase owner)
  143. {
  144. this.owner = null;
  145. }
  146. }
  147. [ServiceContract (Namespace = "http://schemas.microsoft.com/2006/04/http/metadata")]
  148. interface IHttpGetHelpPageAndMetadataContract
  149. {
  150. [OperationContract (Action = "*", ReplyAction = "*")]
  151. SMMessage Get (SMMessage req);
  152. }
  153. // It is used to identify which page to serve when a channel dispatcher
  154. // has a listener to an relatively empty URI (conflicting with the
  155. // target service endpoint)
  156. //
  157. // Can't be enum as it is for GetProperty<T> ().
  158. internal class MetadataPublishingInfo
  159. {
  160. public bool SupportsMex { get; set; }
  161. public bool SupportsHelp { get; set; }
  162. public HttpGetWsdl Instance { get; set; }
  163. }
  164. class HttpGetWsdl : IHttpGetHelpPageAndMetadataContract
  165. {
  166. ServiceMetadataExtension ext;
  167. bool initialized;
  168. Dictionary <string,WSServiceDescription> wsdl_documents =
  169. new Dictionary<string, WSServiceDescription> ();
  170. Dictionary <string, XmlSchema> schemas =
  171. new Dictionary<string, XmlSchema> ();
  172. public HttpGetWsdl (ServiceMetadataExtension ext)
  173. {
  174. this.ext = ext;
  175. }
  176. public Uri HelpUrl { get; set; }
  177. public Uri WsdlUrl { get; set; }
  178. void EnsureMetadata ()
  179. {
  180. if (!initialized) {
  181. GetMetadata ();
  182. initialized = true;
  183. }
  184. }
  185. public SMMessage Get (SMMessage req)
  186. {
  187. EnsureMetadata ();
  188. HttpRequestMessageProperty prop = (HttpRequestMessageProperty) req.Properties [HttpRequestMessageProperty.Name];
  189. NameValueCollection query_string = CreateQueryString (prop.QueryString);
  190. if (query_string == null || query_string.AllKeys.Length != 1) {
  191. if (HelpUrl != null && Uri.Compare (req.Headers.To, HelpUrl, UriComponents.HttpRequestUrl ^ UriComponents.Query, UriFormat.UriEscaped, StringComparison.Ordinal) == 0)
  192. return CreateHelpPage (req);
  193. WSServiceDescription w = GetWsdl ("wsdl");
  194. if (w != null)
  195. return CreateWsdlMessage (w);
  196. }
  197. if (query_string [null] == "wsdl") {
  198. WSServiceDescription wsdl = GetWsdl ("wsdl");
  199. if (wsdl != null)
  200. return CreateWsdlMessage (wsdl);
  201. } else if (query_string ["wsdl"] != null) {
  202. WSServiceDescription wsdl = GetWsdl (query_string ["wsdl"]);
  203. if (wsdl != null)
  204. return CreateWsdlMessage (wsdl);
  205. } else if (query_string ["xsd"] != null) {
  206. XmlSchema schema = GetXmlSchema (query_string ["xsd"]);
  207. if (schema != null) {
  208. //FIXME: Is this the correct way?
  209. MemoryStream ms = new MemoryStream ();
  210. schema.Write (ms);
  211. ms.Seek (0, SeekOrigin.Begin);
  212. SMMessage ret = SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
  213. return ret;
  214. }
  215. }
  216. return CreateHelpPage (req);
  217. }
  218. /* Code from HttpListenerRequest */
  219. NameValueCollection CreateQueryString (string query)
  220. {
  221. NameValueCollection query_string = new NameValueCollection ();
  222. if (query == null || query.Length == 0)
  223. return null;
  224. string [] components = query.Split ('&');
  225. foreach (string kv in components) {
  226. int pos = kv.IndexOf ('=');
  227. if (pos == -1) {
  228. query_string.Add (null, HttpUtility.UrlDecode (kv));
  229. } else {
  230. string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
  231. string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
  232. query_string.Add (key, val);
  233. }
  234. }
  235. return query_string;
  236. }
  237. // It is returned for ServiceDebugBehavior.Http(s)HelpPageUrl.
  238. // They may be empty, and for such case the help page URL is
  239. // simply the service endpoint URL (foobar.svc).
  240. //
  241. // Note that if there is also ServiceMetadataBehavior that
  242. // lacks Http(s)GetUrl, then it is also mapped to the same
  243. // URL, but it requires "?wsdl" parameter and .NET somehow
  244. // differentiates those requests.
  245. //
  246. // If both Http(s)HelpPageUrl and Http(s)GetUrl exist, then
  247. // requests to the service endpoint URL (foobar.svc) results
  248. // in an xml output with empty string (non-WF XML error).
  249. SMMessage CreateHelpPage (SMMessage request)
  250. {
  251. var helpBody = ext.Owner.Description.Behaviors.Find<ServiceMetadataBehavior> () != null ?
  252. String.Format (@"
  253. <p>To create client proxy source, run:</p>
  254. <p><code>svcutil <a href='{0}'>{0}</a></code></p>
  255. <!-- FIXME: add client proxy usage (that required decent ServiceContractGenerator implementation, so I leave it yet.) -->
  256. ", new Uri (WsdlUrl.ToString () + "?wsdl")) : // this Uri.ctor() is nasty, but there is no other way to add "?wsdl" (!!)
  257. String.Format (@"
  258. <p>Service metadata publishing for {0} is not enabled. Service administrators can enable it by adding &lt;serviceMetadata&gt; element in the host configuration (web.config in ASP.NET), or ServiceMetadataBehavior object to the Behaviors collection of the service host's ServiceDescription.</p>", ext.Owner.Description.Name);
  259. var html = String.Format (@"
  260. <html>
  261. <head>
  262. <title>Service {0}</title>
  263. </head>
  264. <body>
  265. {1}
  266. </body>
  267. </html>", ext.Owner.Description.Name, helpBody);
  268. var m = SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (new StringReader (html)));
  269. var rp = new HttpResponseMessageProperty ();
  270. rp.Headers ["Content-Type"] = "text/html";
  271. m.Properties.Add (HttpResponseMessageProperty.Name, rp);
  272. return m;
  273. }
  274. SMMessage CreateWsdlMessage (WSServiceDescription wsdl)
  275. {
  276. MemoryStream ms = new MemoryStream ();
  277. XmlWriter xw = XmlWriter.Create (ms);
  278. WSServiceDescription.Serializer.Serialize (xw, wsdl);
  279. ms.Seek (0, SeekOrigin.Begin);
  280. return SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
  281. }
  282. void GetMetadata ()
  283. {
  284. if (WsdlUrl == null)
  285. return;
  286. MetadataSet metadata = ext.Metadata;
  287. int xs_i = 0, wsdl_i = 0;
  288. //Dictionary keyed by namespace
  289. StringDictionary wsdl_strings = new StringDictionary ();
  290. StringDictionary xsd_strings = new StringDictionary ();
  291. foreach (MetadataSection section in metadata.MetadataSections) {
  292. string key;
  293. XmlSchema xs = section.Metadata as XmlSchema;
  294. if (xs != null) {
  295. key = String.Format ("xsd{0}", xs_i ++);
  296. schemas [key] = xs;
  297. xsd_strings [xs.TargetNamespace] = key;
  298. continue;
  299. }
  300. WSServiceDescription wsdl = section.Metadata as WSServiceDescription;
  301. if (wsdl == null)
  302. continue;
  303. //if (wsdl.TargetNamespace == "http://tempuri.org/")
  304. if (wsdl.Services.Count > 0)
  305. key = "wsdl";
  306. else
  307. key = String.Format ("wsdl{0}", wsdl_i ++);
  308. wsdl_documents [key] = wsdl;
  309. wsdl_strings [wsdl.TargetNamespace] = key;
  310. }
  311. string base_url = WsdlUrl.ToString ();
  312. foreach (WSServiceDescription wsdl in wsdl_documents.Values) {
  313. foreach (Import import in wsdl.Imports) {
  314. if (!String.IsNullOrEmpty (import.Location))
  315. continue;
  316. import.Location = String.Format ("{0}?wsdl={1}", base_url, wsdl_strings [import.Namespace]);
  317. }
  318. foreach (XmlSchema schema in wsdl.Types.Schemas) {
  319. foreach (XmlSchemaObject obj in schema.Includes) {
  320. XmlSchemaImport imp = obj as XmlSchemaImport;
  321. if (imp == null || imp.SchemaLocation != null)
  322. continue;
  323. imp.SchemaLocation = String.Format ("{0}?xsd={1}", base_url, xsd_strings [imp.Namespace]);
  324. }
  325. }
  326. }
  327. }
  328. WSServiceDescription GetWsdl (string which)
  329. {
  330. EnsureMetadata ();
  331. WSServiceDescription wsdl;
  332. wsdl_documents.TryGetValue (which, out wsdl);
  333. return wsdl;
  334. }
  335. XmlSchema GetXmlSchema (string which)
  336. {
  337. EnsureMetadata ();
  338. XmlSchema schema;
  339. schemas.TryGetValue (which, out schema);
  340. return schema;
  341. }
  342. }
  343. }