5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Ankit Jain <JAnkit@novell.com>
7 // Martin Baulig <martin.baulig@xamarin.com>
9 // Copyright (C) 2005 Novell, Inc. http://www.novell.com
10 // Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
33 using System.Collections.Generic;
35 using System.ServiceModel;
36 using System.ServiceModel.Channels;
37 using System.Web.Services.Description;
39 using System.Xml.Schema;
40 using System.Xml.Serialization;
41 using System.Runtime.Serialization;
43 using SMBinding = System.ServiceModel.Channels.Binding;
44 using SMMessage = System.ServiceModel.Channels.Message;
46 using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
47 using WSBinding = System.Web.Services.Description.Binding;
48 using WSMessage = System.Web.Services.Description.Message;
49 using QName = System.Xml.XmlQualifiedName;
51 namespace System.ServiceModel.Description
54 public class WsdlExporter : MetadataExporter
56 class ContractExportMap
58 public ContractExportMap (QName qname, ContractDescription contract, List<IWsdlExportExtension> results)
65 public QName QName { get; private set; }
66 public ContractDescription Contract { get; private set; }
67 public List<IWsdlExportExtension> Results { get; private set; }
70 class EndpointExportMap
72 public EndpointExportMap (string name, ServiceEndpoint endpoint)
78 public string Name { get; private set; }
79 public ServiceEndpoint Endpoint { get; private set; }
83 ServiceDescriptionCollection wsdl_colln;
84 XsdDataContractExporter xsd_exporter;
85 Dictionary<ContractDescription, ContractExportMap> exported_contracts;
86 List<EndpointExportMap> exported_endpoints;
88 public override MetadataSet GetGeneratedMetadata ()
93 metadata = new MetadataSet ();
94 foreach (WSServiceDescription sd in GeneratedWsdlDocuments)
95 metadata.MetadataSections.Add (
96 MetadataSection.CreateFromServiceDescription (sd));
98 foreach (XmlSchema xs in GeneratedXmlSchemas.Schemas ())
99 if (xs.TargetNamespace != XmlSchema.Namespace)
100 metadata.MetadataSections.Add (
101 MetadataSection.CreateFromSchema (xs));
106 public override void ExportContract (ContractDescription contract)
108 ExportContractInternal (contract);
111 ContractExportMap ExportContractInternal (ContractDescription contract)
113 if (ExportedContracts.ContainsKey (contract))
114 return ExportedContracts [contract];
116 QName qname = new QName (contract.Name, contract.Namespace);
117 if (ExportedContracts.Any (m => m.Value.QName == qname))
118 throw new ArgumentException (String.Format (
119 "A ContractDescription with Namespace : {0} and Name : {1} has already been exported.",
120 contract.Namespace, contract.Name));
122 WSServiceDescription sd = GetServiceDescription (contract.Namespace);
124 List<IWsdlExportExtension> extensions = new List<IWsdlExportExtension> ();
125 foreach (IWsdlExportExtension extn in contract.Behaviors.FindAll<IWsdlExportExtension> ())
126 extensions.Add (extn);
128 XmlDocument xdoc = new XmlDocument ();
130 PortType ws_port = new PortType ();
131 ws_port.Name = contract.Name;
133 foreach (OperationDescription sm_op in contract.Operations) {
134 Operation ws_op = new Operation ();
135 ws_op.Name = sm_op.Name;
137 foreach (MessageDescription sm_md in sm_op.Messages) {
139 OperationMessage ws_opmsg;
140 WSMessage ws_msg = new WSMessage ();
141 MessagePart ws_msgpart;
142 if (sm_md.Direction == MessageDirection.Input) {
143 ws_opmsg = new OperationInput ();
144 ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "InputMessage");
145 ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name, sd.TargetNamespace);
147 ws_opmsg = new OperationOutput ();
148 ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "OutputMessage");
149 ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name + "Response", sd.TargetNamespace);
151 ws_msg.Parts.Add (ws_msgpart);
156 XmlAttribute attr = xdoc.CreateAttribute ("wsaw", "Action", "http://www.w3.org/2006/05/addressing/wsdl");
157 attr.Value = sm_md.Action;
158 ws_opmsg.ExtensibleAttributes = new XmlAttribute [] { attr };
160 //FIXME: Set .Input & .Output
162 ws_opmsg.Message = new QName (ws_msg.Name, sd.TargetNamespace);
163 ws_op.Messages.Add (ws_opmsg);
164 sd.Messages.Add (ws_msg);
167 ws_port.Operations.Add (ws_op);
169 foreach (IWsdlExportExtension extn in sm_op.Behaviors.FindAll<IWsdlExportExtension> ())
170 extensions.Add (extn);
173 //Add Imports for <types
174 XmlSchema xs_import = new XmlSchema ();
175 xs_import.TargetNamespace = String.Concat (
177 contract.Namespace.EndsWith ("/") ? "" : "/",
179 foreach (XmlSchema schema in GeneratedXmlSchemas.Schemas ()) {
180 XmlSchemaImport imp = new XmlSchemaImport ();
181 imp.Namespace = schema.TargetNamespace;
182 xs_import.Includes.Add (imp);
184 sd.Types.Schemas.Add (xs_import);
186 sd.PortTypes.Add (ws_port);
187 var map = new ContractExportMap (qname, contract, extensions);
188 ExportedContracts.Add (contract, map);
190 WsdlContractConversionContext context = new WsdlContractConversionContext (contract, ws_port);
191 foreach (IWsdlExportExtension extn in extensions)
192 extn.ExportContract (this, context);
197 public override void ExportEndpoint (ServiceEndpoint endpoint)
199 ExportEndpoint_Internal (endpoint);
202 EndpointExportMap ExportEndpoint_Internal (ServiceEndpoint endpoint)
204 var map = ExportedEndpoints.FirstOrDefault (m => m.Endpoint == endpoint);
209 var baseName = String.Concat (endpoint.Binding.Name, "_", endpoint.Contract.Name);
211 while (ExportedEndpoints.Exists (m => m.Name == name))
212 name = String.Concat (baseName, (++index).ToString ());
214 map = new EndpointExportMap (name, endpoint);
215 ExportedEndpoints.Add (map);
217 var contract = ExportContractInternal (endpoint.Contract);
220 WSServiceDescription sd = GetServiceDescription ("http://tempuri.org/");
221 if (sd.TargetNamespace != endpoint.Contract.Namespace) {
222 sd.Namespaces.Add ("i0", endpoint.Contract.Namespace);
225 Import import = new Import ();
226 import.Namespace = endpoint.Contract.Namespace;
228 sd.Imports.Add (import);
231 if (endpoint.Binding == null)
232 throw new ArgumentException (String.Format (
233 "Binding for ServiceEndpoint named '{0}' is null",
236 var extensions = new List<IWsdlExportExtension> ();
237 var extensionTypes = new Dictionary<Type, IWsdlExportExtension> ();
238 if (contract.Results != null) {
239 foreach (var extension in contract.Results) {
240 var type = extension.GetType ();
241 if (extensionTypes.ContainsKey (type))
243 extensionTypes.Add (type, extension);
244 extensions.Add (extension);
248 var bindingElements = endpoint.Binding.CreateBindingElements ();
249 foreach (var element in bindingElements) {
250 var extension = element as IWsdlExportExtension;
251 if (extension == null)
253 var type = extension.GetType ();
254 if (extensionTypes.ContainsKey (type))
256 extensionTypes.Add (type, extension);
257 extensions.Add (extension);
261 WSBinding ws_binding = new WSBinding ();
264 ws_binding.Name = name;
267 ws_binding.Type = new QName (endpoint.Contract.Name, endpoint.Contract.Namespace);
268 sd.Bindings.Add (ws_binding);
271 foreach (OperationDescription sm_op in endpoint.Contract.Operations) {
272 var op_binding = CreateOperationBinding (endpoint, sm_op);
273 ws_binding.Operations.Add (op_binding);
277 Port ws_port = ExportService (sd, ws_binding, endpoint.Address);
279 //Call IWsdlExportExtension.ExportEndpoint
280 WsdlContractConversionContext contract_context = new WsdlContractConversionContext (
281 endpoint.Contract, sd.PortTypes [endpoint.Contract.Name]);
282 WsdlEndpointConversionContext endpoint_context = new WsdlEndpointConversionContext (
283 contract_context, endpoint, ws_port, ws_binding);
285 foreach (var extension in extensions) {
287 extension.ExportEndpoint (this, endpoint_context);
288 } catch (Exception ex) {
289 var error = AddError (
290 "Failed to export endpoint '{0}': wsdl exporter '{1}' " +
291 "threw an exception: {2}", endpoint.Name, extension.GetType (), ex);
292 throw new MetadataExportException (error, ex);
297 ExportPolicy (endpoint, ws_binding);
298 } catch (MetadataExportException) {
300 } catch (Exception ex) {
301 var error = AddError (
302 "Failed to export endpoint '{0}': unhandled exception " +
303 "while exporting policy: {1}", endpoint.Name, ex);
304 throw new MetadataExportException (error, ex);
310 OperationBinding CreateOperationBinding (ServiceEndpoint endpoint, OperationDescription sm_op)
312 OperationBinding op_binding = new OperationBinding ();
313 op_binding.Name = sm_op.Name;
315 foreach (MessageDescription sm_md in sm_op.Messages) {
316 if (sm_md.Direction == MessageDirection.Input) {
318 CreateInputBinding (endpoint, op_binding, sm_md);
321 CreateOutputBinding (endpoint, op_binding, sm_md);
328 void CreateInputBinding (ServiceEndpoint endpoint, OperationBinding op_binding,
329 MessageDescription sm_md)
331 var in_binding = new InputBinding ();
332 op_binding.Input = in_binding;
334 var message_version = endpoint.Binding.MessageVersion ?? MessageVersion.None;
335 if (message_version == MessageVersion.None)
338 SoapBodyBinding soap_body_binding;
339 SoapOperationBinding soap_operation_binding;
340 if (message_version.Envelope == EnvelopeVersion.Soap11) {
341 soap_body_binding = new SoapBodyBinding ();
342 soap_operation_binding = new SoapOperationBinding ();
343 } else if (message_version.Envelope == EnvelopeVersion.Soap12) {
344 soap_body_binding = new Soap12BodyBinding ();
345 soap_operation_binding = new Soap12OperationBinding ();
347 throw new InvalidOperationException ();
350 soap_body_binding.Use = SoapBindingUse.Literal;
351 in_binding.Extensions.Add (soap_body_binding);
354 //<operation > <soap:operation soapAction .. >
355 soap_operation_binding.SoapAction = sm_md.Action;
356 soap_operation_binding.Style = SoapBindingStyle.Document;
357 op_binding.Extensions.Add (soap_operation_binding);
360 void CreateOutputBinding (ServiceEndpoint endpoint, OperationBinding op_binding,
361 MessageDescription sm_md)
363 var out_binding = new OutputBinding ();
364 op_binding.Output = out_binding;
366 var message_version = endpoint.Binding.MessageVersion ?? MessageVersion.None;
367 if (message_version == MessageVersion.None)
370 SoapBodyBinding soap_body_binding;
371 if (message_version.Envelope == EnvelopeVersion.Soap11) {
372 soap_body_binding = new SoapBodyBinding ();
373 } else if (message_version.Envelope == EnvelopeVersion.Soap12) {
374 soap_body_binding = new Soap12BodyBinding ();
376 throw new InvalidOperationException ();
379 soap_body_binding.Use = SoapBindingUse.Literal;
380 out_binding.Extensions.Add (soap_body_binding);
383 Port ExportService (WSServiceDescription sd, WSBinding ws_binding, EndpointAddress address)
388 Service ws_svc = GetService (sd, "service");
391 Port ws_port = new Port ();
392 ws_port.Name = ws_binding.Name;
393 ws_port.Binding = new QName (ws_binding.Name, sd.TargetNamespace);
395 ws_svc.Ports.Add (ws_port);
400 Service GetService (WSServiceDescription sd, string name)
402 Service svc = sd.Services [name];
406 svc = new Service ();
408 sd.Services.Add (svc);
413 WSServiceDescription GetServiceDescription (string ns)
415 foreach (WSServiceDescription sd in GeneratedWsdlDocuments) {
416 if (sd.TargetNamespace == ns)
420 WSServiceDescription ret = new WSServiceDescription ();
421 ret.TargetNamespace = ns;
422 ret.Namespaces = GetNamespaces (ns);
423 GeneratedWsdlDocuments.Add (ret);
430 public ServiceDescriptionCollection GeneratedWsdlDocuments {
432 if (wsdl_colln == null)
433 wsdl_colln = new ServiceDescriptionCollection ();
438 public XmlSchemaSet GeneratedXmlSchemas {
439 get { return XsdExporter.Schemas; }
442 public void ExportEndpoints (
443 IEnumerable<ServiceEndpoint> endpoints,
444 XmlQualifiedName wsdlServiceQName)
446 if (endpoints == null)
447 throw new ArgumentNullException ("endpoints");
448 if (wsdlServiceQName == null)
449 throw new ArgumentNullException ("wsdlServiceQName");
451 foreach (ServiceEndpoint ep in endpoints) {
452 if (ep.Contract.Name == ServiceMetadataBehavior.MexContractName)
459 XsdDataContractExporter XsdExporter {
461 if (xsd_exporter == null)
462 xsd_exporter = new XsdDataContractExporter ();
467 Dictionary<ContractDescription, ContractExportMap> ExportedContracts {
469 if (exported_contracts == null)
470 exported_contracts = new Dictionary<ContractDescription, ContractExportMap> ();
471 return exported_contracts;
475 List<EndpointExportMap> ExportedEndpoints {
477 if (exported_endpoints == null)
478 exported_endpoints = new List<EndpointExportMap> ();
479 return exported_endpoints;
483 XmlSerializerNamespaces GetNamespaces (string target_namespace)
485 XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces ();
487 namespaces.Add ("soap", "http://schemas.xmlsoap.org/wsdl/soap/");
488 namespaces.Add ("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
489 namespaces.Add ("soapenc", "http://schemas.xmlsoap.org/soap/encoding/");
490 namespaces.Add ("tns", target_namespace);
491 namespaces.Add ("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
492 namespaces.Add ("wsp", "http://schemas.xmlsoap.org/ws/2004/09/policy");
493 namespaces.Add ("wsap", "http://schemas.xmlsoap.org/ws/2004/08/addressing/policy");
494 namespaces.Add ("msc", "http://schemas.microsoft.com/ws/2005/12/wsdl/contract");
495 namespaces.Add ("wsaw", "http://www.w3.org/2006/05/addressing/wsdl");
496 namespaces.Add ("soap12", "http://schemas.xmlsoap.org/wsdl/soap12/");
497 namespaces.Add ("wsa10", "http://www.w3.org/2005/08/addressing");
498 namespaces.Add ("wsdl", "http://schemas.xmlsoap.org/wsdl/");
503 MessagePart ExportMessageBodyDescription (MessageBodyDescription msgbody, string name, string ns)
505 MessagePart msgpart = new MessagePart ();
506 string part_name = IsTypeMessage (msgbody);
508 if (part_name != null) {
509 msgpart.Name = part_name;
510 msgpart.Type = ExportTypeMessage (); //FIXME: Cache this
512 msgpart.Name = "parameters";
513 msgpart.Element = ExportParameters (msgbody, name, ns);
518 /* Sets the @name if the param or return type is SMMessage */
519 string IsTypeMessage (MessageBodyDescription msgbody)
521 MessagePartDescription part = null;
523 if (msgbody.Parts.Count == 0)
524 part = msgbody.ReturnValue;
525 else if (msgbody.Parts.Count == 1)
526 part = msgbody.Parts [0];
528 if (part != null && (part.Type.FullName == typeof (SMMessage).FullName))
534 QName ExportParameters (MessageBodyDescription msgbody, string name, string ns)
536 XmlSchema xs = GetSchema (ns);
537 //FIXME: Extract to a HasElement method ?
538 foreach (XmlSchemaObject o in xs.Items) {
539 XmlSchemaElement e = o as XmlSchemaElement;
544 throw new InvalidOperationException (String.Format (
545 "Message element named '{0}:{1}' has already been exported.",
549 //Create the element for "parameters"
550 XmlSchemaElement schema_element = new XmlSchemaElement ();
551 schema_element.Name = name;
553 XmlSchemaComplexType complex_type = new XmlSchemaComplexType ();
554 //Generate Sequence representing the message/parameters
555 //FIXME: MessageContractAttribute
557 XmlSchemaSequence sequence = new XmlSchemaSequence ();
558 XmlSchemaElement element = null;
560 if (msgbody.ReturnValue == null) {
562 foreach (MessagePartDescription part in msgbody.Parts) {
563 if (part.Type == null)
564 //FIXME: Eg. when WsdlImporter is used to import a wsdl
565 throw new NotImplementedException ();
567 element = GetSchemaElementForPart (part, xs);
568 sequence.Items.Add (element);
572 if (msgbody.ReturnValue.Type != typeof (void)) {
573 element = GetSchemaElementForPart (msgbody.ReturnValue, xs);
574 sequence.Items.Add (element);
578 complex_type.Particle = sequence;
579 schema_element.SchemaType = complex_type;
581 xs.Items.Add (schema_element);
582 GeneratedXmlSchemas.Reprocess (xs);
584 return new QName (schema_element.Name, xs.TargetNamespace);
587 //Exports <xs:type for SMMessage
588 //FIXME: complex type for this can be made static
589 QName ExportTypeMessage ()
591 XmlSchema xs = GetSchema ("http://schemas.microsoft.com/Message");
592 QName qname = new QName ("MessageBody", xs.TargetNamespace);
594 foreach (XmlSchemaObject o in xs.Items) {
595 XmlSchemaComplexType ct = o as XmlSchemaComplexType;
599 if (ct.Name == "MessageBody")
604 XmlSchemaComplexType complex_type = new XmlSchemaComplexType ();
605 complex_type.Name = "MessageBody";
606 XmlSchemaSequence sequence = new XmlSchemaSequence ();
608 XmlSchemaAny any = new XmlSchemaAny ();
610 any.MaxOccursString = "unbounded";
611 any.Namespace = "##any";
613 sequence.Items.Add (any);
614 complex_type.Particle = sequence;
616 xs.Items.Add (complex_type);
617 GeneratedXmlSchemas.Reprocess (xs);
622 XmlSchemaElement GetSchemaElementForPart (MessagePartDescription part, XmlSchema schema)
624 XmlSchemaElement element = new XmlSchemaElement ();
626 element.Name = part.Name;
627 XsdExporter.Export (part.Type);
628 element.SchemaTypeName = XsdExporter.GetSchemaTypeName (part.Type);
629 AddImport (schema, element.SchemaTypeName.Namespace);
631 //FIXME: nillable, minOccurs
632 if (XsdExporter.GetSchemaType (part.Type) is XmlSchemaComplexType ||
633 part.Type == typeof (string))
634 element.IsNillable = true;
635 element.MinOccurs = 0;
640 //FIXME: Replace with a dictionary ?
641 void AddImport (XmlSchema schema, string ns)
643 if (ns == XmlSchema.Namespace || schema.TargetNamespace == ns)
646 foreach (XmlSchemaObject o in schema.Includes) {
647 XmlSchemaImport import = o as XmlSchemaImport;
650 if (import.Namespace == ns)
654 if (ns == string.Empty)
657 XmlSchemaImport imp = new XmlSchemaImport ();
659 schema.Includes.Add (imp);
662 XmlSchema GetSchema (string ns)
664 ICollection colln = GeneratedXmlSchemas.Schemas (ns);
665 if (colln.Count > 0) {
667 throw new Exception ("More than 1 schema found for ns = " + ns);
669 foreach (object o in colln)
670 return (o as XmlSchema);
673 XmlSchema schema = new XmlSchema ();
674 schema.TargetNamespace = ns;
675 schema.ElementFormDefault = XmlSchemaForm.Qualified;
676 GeneratedXmlSchemas.Add (schema);
681 PolicyConversionContext ExportPolicy (ServiceEndpoint endpoint, WSBinding binding)
683 var context = new CustomPolicyConversionContext (endpoint);
685 var elements = endpoint.Binding.CreateBindingElements ();
686 foreach (var element in elements) {
687 var exporter = element as IPolicyExportExtension;
688 if (exporter == null)
692 exporter.ExportPolicy (this, context);
693 } catch (Exception ex) {
694 var error = AddError (
695 "Failed to export endpoint '{0}': policy exporter " +
696 "'{1}' threw an exception: {2}", endpoint.Name,
697 element.GetType (), ex);
698 throw new MetadataExportException (error, ex);
702 var assertions = context.GetBindingAssertions ();
703 if (assertions.Count == 0)
706 var doc = new XmlDocument ();
707 var policy = doc.CreateElement ("wsp", "Policy", PolicyImportHelper.PolicyNS);
708 doc.AppendChild (policy);
710 var exactlyOne = doc.CreateElement ("wsp", "ExactlyOne", PolicyImportHelper.PolicyNS);
711 var all = doc.CreateElement ("wsp", "All", PolicyImportHelper.PolicyNS);
713 policy.AppendChild (exactlyOne);
714 exactlyOne.AppendChild (all);
716 foreach (var assertion in assertions) {
717 var imported = doc.ImportNode (assertion, true);
718 all.AppendChild (imported);
721 binding.Extensions.Add (policy);