2 // ServiceMetadataExtension.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Ankit Jain <jankit@novell.com>
7 // Gonzalo Paniagua Javier (gonzalo@novell.com)
9 // Copyright (C) 2005 Novell, Inc. http://www.novell.com
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Collections.ObjectModel;
34 using System.Collections.Specialized;
37 using System.ServiceModel.Channels;
38 using System.ServiceModel.Dispatcher;
40 using System.Web.Services;
41 using System.Web.Services.Description;
43 using System.Xml.Schema;
45 using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
46 using WSMessage = System.Web.Services.Description.Message;
47 using SMMessage = System.ServiceModel.Channels.Message;
48 using WCFBinding = System.ServiceModel.Channels.Binding;
50 namespace System.ServiceModel.Description
52 public class ServiceMetadataExtension : IExtension<ServiceHostBase>
54 const string ServiceMetadataBehaviorHttpGetBinding = "ServiceMetadataBehaviorHttpGetBinding";
57 ServiceHostBase owner;
58 Dictionary<Uri, ChannelDispatcher> dispatchers;
61 public ServiceMetadataExtension ()
65 internal HttpGetWsdl Instance {
66 get { return instance; }
70 public MetadataSet Metadata {
72 if (metadata == null) {
73 var exporter = new WsdlExporter ();
74 exporter.ExportEndpoints (owner.Description.Endpoints, new XmlQualifiedName (owner.Description.Name, owner.Description.Namespace));
75 metadata = exporter.GetGeneratedMetadata ();
81 internal ServiceHostBase Owner {
85 internal static ServiceMetadataExtension EnsureServiceMetadataExtension (ServiceHostBase serviceHostBase) {
86 ServiceMetadataExtension sme = serviceHostBase.Extensions.Find<ServiceMetadataExtension> ();
88 sme = new ServiceMetadataExtension ();
89 serviceHostBase.Extensions.Add (sme);
94 // FIXME: distinguish HTTP and HTTPS in the Url properties.
95 // FIXME: reject such ServiceDescription that has no HTTP(S) binding.
96 // 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.
97 // 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.
98 internal void EnsureChannelDispatcher (bool isMex, string scheme, Uri uri, WCFBinding binding)
101 instance.WsdlUrl = uri;
103 instance.HelpUrl = uri;
105 if (dispatchers == null)
106 dispatchers = new Dictionary<Uri, ChannelDispatcher> ();
107 else if (dispatchers.ContainsKey (uri))
108 return; // already exists (e.g. reached here for wsdl while help is already filled on the same URI.)
110 if (binding == null) {
113 binding = MetadataExchangeBindings.CreateMexHttpBinding ();
116 binding = MetadataExchangeBindings.CreateMexHttpsBinding ();
119 binding = MetadataExchangeBindings.CreateMexTcpBinding ();
122 binding = MetadataExchangeBindings.CreateMexNamedPipeBinding ();
127 CustomBinding cb = new CustomBinding (binding) { Name = ServiceMetadataBehaviorHttpGetBinding };
128 cb.Elements.Find<MessageEncodingBindingElement> ().MessageVersion = MessageVersion.None;
130 ServiceEndpoint se = new ServiceEndpoint (ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract)), cb, new EndpointAddress (uri))
135 var channelDispatcher = new DispatcherBuilder (Owner).BuildChannelDispatcher (owner.Description.ServiceType, se, new BindingParameterCollection ());
136 channelDispatcher.MessageVersion = MessageVersion.None; // it has no MessageVersion.
137 channelDispatcher.IsMex = true;
138 channelDispatcher.Endpoints [0].DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider (new InstanceContext (owner, instance));
140 dispatchers.Add (uri, channelDispatcher);
141 owner.ChannelDispatchers.Add (channelDispatcher);
144 void IExtension<ServiceHostBase>.Attach (ServiceHostBase owner)
147 instance = new HttpGetWsdl (this);
150 void IExtension<ServiceHostBase>.Detach (ServiceHostBase owner)
156 [ServiceContract (Namespace = "http://schemas.microsoft.com/2006/04/http/metadata")]
157 interface IHttpGetHelpPageAndMetadataContract
159 [OperationContract (Action = "*", ReplyAction = "*")]
160 SMMessage Get (SMMessage req);
163 class HttpGetWsdl : IHttpGetHelpPageAndMetadataContract
165 ServiceMetadataExtension ext;
168 Dictionary <string,WSServiceDescription> wsdl_documents =
169 new Dictionary<string, WSServiceDescription> ();
170 Dictionary <string, XmlSchema> schemas =
171 new Dictionary<string, XmlSchema> ();
173 public HttpGetWsdl (ServiceMetadataExtension ext)
178 public Uri HelpUrl { get; set; }
179 public Uri WsdlUrl { get; set; }
181 void EnsureMetadata ()
189 public SMMessage Get (SMMessage req)
193 HttpRequestMessageProperty prop = (HttpRequestMessageProperty) req.Properties [HttpRequestMessageProperty.Name];
195 NameValueCollection query_string = CreateQueryString (prop.QueryString);
196 if (query_string == null || query_string.AllKeys.Length != 1) {
197 if (HelpUrl != null && Uri.Compare (req.Headers.To, HelpUrl, UriComponents.HttpRequestUrl ^ UriComponents.Query, UriFormat.UriEscaped, StringComparison.Ordinal) == 0)
198 return CreateHelpPage (req);
199 WSServiceDescription w = GetWsdl ("wsdl");
201 return CreateWsdlMessage (w);
204 if (String.Compare (query_string [null], "wsdl", StringComparison.OrdinalIgnoreCase) == 0) {
205 WSServiceDescription wsdl = GetWsdl ("wsdl");
207 return CreateWsdlMessage (wsdl);
208 } else if (query_string ["wsdl"] != null) {
209 WSServiceDescription wsdl = GetWsdl (query_string ["wsdl"]);
211 return CreateWsdlMessage (wsdl);
212 } else if (query_string ["xsd"] != null) {
213 XmlSchema schema = GetXmlSchema (query_string ["xsd"]);
214 if (schema != null) {
215 //FIXME: Is this the correct way?
216 MemoryStream ms = new MemoryStream ();
219 ms.Seek (0, SeekOrigin.Begin);
220 SMMessage ret = SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
226 return CreateHelpPage (req);
229 /* Code from HttpListenerRequest */
230 NameValueCollection CreateQueryString (string query)
232 NameValueCollection query_string = new NameValueCollection ();
233 if (query == null || query.Length == 0)
236 string [] components = query.Split ('&');
237 foreach (string kv in components) {
238 int pos = kv.IndexOf ('=');
240 query_string.Add (null, HttpUtility.UrlDecode (kv));
242 string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
243 string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
245 query_string.Add (key, val);
252 // It is returned for ServiceDebugBehavior.Http(s)HelpPageUrl.
253 // They may be empty, and for such case the help page URL is
254 // simply the service endpoint URL (foobar.svc).
256 // Note that if there is also ServiceMetadataBehavior that
257 // lacks Http(s)GetUrl, then it is also mapped to the same
258 // URL, but it requires "?wsdl" parameter and .NET somehow
259 // differentiates those requests.
261 // If both Http(s)HelpPageUrl and Http(s)GetUrl exist, then
262 // requests to the service endpoint URL (foobar.svc) results
263 // in an xml output with empty string (non-WF XML error).
265 SMMessage CreateHelpPage (SMMessage request)
267 var helpBody = ext.Owner.Description.Behaviors.Find<ServiceMetadataBehavior> () != null ?
269 <p>To create client proxy source, run:</p>
270 <p><code>svcutil <a href='{0}'>{0}</a></code></p>
271 <!-- FIXME: add client proxy usage (that required decent ServiceContractGenerator implementation, so I leave it yet.) -->
272 ", new Uri (WsdlUrl.ToString () + "?wsdl")) : // this Uri.ctor() is nasty, but there is no other way to add "?wsdl" (!!)
274 <p>Service metadata publishing for {0} is not enabled. Service administrators can enable it by adding <serviceMetadata> 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);
276 var html = String.Format (@"
279 <title>Service {0}</title>
284 </html>", ext.Owner.Description.Name, helpBody);
286 var m = SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (new StringReader (html)));
287 var rp = new HttpResponseMessageProperty ();
288 rp.Headers ["Content-Type"] = "text/html";
289 m.Properties.Add (HttpResponseMessageProperty.Name, rp);
293 SMMessage CreateWsdlMessage (WSServiceDescription wsdl)
295 MemoryStream ms = new MemoryStream ();
296 XmlWriter xw = XmlWriter.Create (ms);
298 WSServiceDescription.Serializer.Serialize (xw, wsdl);
299 ms.Seek (0, SeekOrigin.Begin);
300 return SMMessage.CreateMessage (MessageVersion.None, "", XmlReader.Create (ms));
308 MetadataSet metadata = ext.Metadata;
309 int xs_i = 0, wsdl_i = 0;
311 //Dictionary keyed by namespace
312 StringDictionary wsdl_strings = new StringDictionary ();
313 StringDictionary xsd_strings = new StringDictionary ();
315 foreach (MetadataSection section in metadata.MetadataSections) {
318 XmlSchema xs = section.Metadata as XmlSchema;
320 key = String.Format ("xsd{0}", xs_i ++);
322 xsd_strings [xs.TargetNamespace] = key;
326 WSServiceDescription wsdl = section.Metadata as WSServiceDescription;
330 //if (wsdl.TargetNamespace == "http://tempuri.org/")
331 if (wsdl.Services.Count > 0)
334 key = String.Format ("wsdl{0}", wsdl_i ++);
336 wsdl_documents [key] = wsdl;
337 wsdl_strings [wsdl.TargetNamespace] = key;
340 string base_url = WsdlUrl.ToString ();
341 foreach (WSServiceDescription wsdl in wsdl_documents.Values) {
342 foreach (Import import in wsdl.Imports) {
343 if (!String.IsNullOrEmpty (import.Location))
346 import.Location = String.Format ("{0}?wsdl={1}", base_url, wsdl_strings [import.Namespace]);
349 foreach (XmlSchema schema in wsdl.Types.Schemas) {
350 foreach (XmlSchemaObject obj in schema.Includes) {
351 XmlSchemaImport imp = obj as XmlSchemaImport;
352 if (imp == null || imp.SchemaLocation != null)
355 imp.SchemaLocation = String.Format ("{0}?xsd={1}", base_url, xsd_strings [imp.Namespace]);
362 WSServiceDescription GetWsdl (string which)
365 WSServiceDescription wsdl;
366 wsdl_documents.TryGetValue (which, out wsdl);
370 XmlSchema GetXmlSchema (string which)
374 schemas.TryGetValue (which, out schema);