//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Description { using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Runtime; using System.Runtime.Diagnostics; using System.ServiceModel.Channels; using System.ServiceModel.Diagnostics; using System.ServiceModel.Dispatcher; using System.Xml; public class ServiceMetadataBehavior : IServiceBehavior { public const string MexContractName = "IMetadataExchange"; internal const string MexContractNamespace = "http://schemas.microsoft.com/2006/04/mex"; static readonly Uri emptyUri = new Uri(String.Empty, UriKind.Relative); bool httpGetEnabled = false; bool httpsGetEnabled = false; Uri httpGetUrl; Uri httpsGetUrl; Binding httpGetBinding; Binding httpsGetBinding; Uri externalMetadataLocation = null; MetadataExporter metadataExporter = null; static ContractDescription mexContract = null; static object thisLock = new object(); public bool HttpGetEnabled { get { return this.httpGetEnabled; } set { this.httpGetEnabled = value; } } [TypeConverter(typeof(UriTypeConverter))] public Uri HttpGetUrl { get { return this.httpGetUrl; } set { if (value != null && value.IsAbsoluteUri && value.Scheme != Uri.UriSchemeHttp) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxServiceMetadataBehaviorUrlMustBeHttpOrRelative, "HttpGetUrl", Uri.UriSchemeHttp, value.ToString(), value.Scheme)); } this.httpGetUrl = value; } } public bool HttpsGetEnabled { get { return this.httpsGetEnabled; } set { this.httpsGetEnabled = value; } } [TypeConverter(typeof(UriTypeConverter))] public Uri HttpsGetUrl { get { return this.httpsGetUrl; } set { if (value != null && value.IsAbsoluteUri && value.Scheme != Uri.UriSchemeHttps) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxServiceMetadataBehaviorUrlMustBeHttpOrRelative, "HttpsGetUrl", Uri.UriSchemeHttps, value.ToString(), value.Scheme)); } this.httpsGetUrl = value; } } public Binding HttpGetBinding { get { return this.httpGetBinding; } set { if (value != null) { if (!value.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxBindingSchemeDoesNotMatch, value.Scheme, value.GetType().ToString(), Uri.UriSchemeHttp)); } CustomBinding customBinding = new CustomBinding(value); TextMessageEncodingBindingElement textMessageEncodingBindingElement = customBinding.Elements.Find(); if (textMessageEncodingBindingElement != null && !textMessageEncodingBindingElement.MessageVersion.IsMatch(MessageVersion.None)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxIncorrectMessageVersion, textMessageEncodingBindingElement.MessageVersion.ToString(), MessageVersion.None.ToString())); } HttpTransportBindingElement httpTransportBindingElement = customBinding.Elements.Find(); if (httpTransportBindingElement != null) { httpTransportBindingElement.Method = "GET"; } this.httpGetBinding = customBinding; } } } public Binding HttpsGetBinding { get { return this.httpsGetBinding; } set { if (value != null) { if (!value.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxBindingSchemeDoesNotMatch, value.Scheme, value.GetType().ToString(), Uri.UriSchemeHttps)); } CustomBinding customBinding = new CustomBinding(value); TextMessageEncodingBindingElement textMessageEncodingBindingElement = customBinding.Elements.Find(); if (textMessageEncodingBindingElement != null && !textMessageEncodingBindingElement.MessageVersion.IsMatch(MessageVersion.None)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.SFxIncorrectMessageVersion, textMessageEncodingBindingElement.MessageVersion.ToString(), MessageVersion.None.ToString())); } HttpsTransportBindingElement httpsTransportBindingElement = customBinding.Elements.Find(); if (httpsTransportBindingElement != null) { httpsTransportBindingElement.Method = "GET"; } this.httpsGetBinding = customBinding; } } } [TypeConverter(typeof(UriTypeConverter))] public Uri ExternalMetadataLocation { get { return this.externalMetadataLocation; } set { if (value != null && value.IsAbsoluteUri && !(value.Scheme == Uri.UriSchemeHttp || value.Scheme == Uri.UriSchemeHttps)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ExternalMetadataLocation", SR.GetString(SR.SFxBadMetadataLocationUri, value.OriginalString, value.Scheme)); } this.externalMetadataLocation = value; } } public MetadataExporter MetadataExporter { get { if (this.metadataExporter == null) this.metadataExporter = new WsdlExporter(); return this.metadataExporter; } set { this.metadataExporter = value; } } static internal ContractDescription MexContract { get { EnsureMexContractDescription(); return ServiceMetadataBehavior.mexContract; } } void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { } void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection parameters) { } void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase) { if (description == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("description"); if (serviceHostBase == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serviceHostBase"); ApplyBehavior(description, serviceHostBase); } void ApplyBehavior(ServiceDescription description, ServiceHostBase host) { ServiceMetadataExtension mex = ServiceMetadataExtension.EnsureServiceMetadataExtension(description, host); SetExtensionProperties(description, host, mex); CustomizeMetadataEndpoints(description, host, mex); CreateHttpGetEndpoints(description, host, mex); } private void CreateHttpGetEndpoints(ServiceDescription description, ServiceHostBase host, ServiceMetadataExtension mex) { bool httpDispatcherEnabled = false; bool httpsDispatcherEnabled = false; if (this.httpGetEnabled) { httpDispatcherEnabled = EnsureGetDispatcher(host, mex, this.httpGetUrl, Uri.UriSchemeHttp); } if (this.httpsGetEnabled) { httpsDispatcherEnabled = EnsureGetDispatcher(host, mex, this.httpsGetUrl, Uri.UriSchemeHttps); } if (!httpDispatcherEnabled && !httpsDispatcherEnabled) { if (this.httpGetEnabled) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxServiceMetadataBehaviorNoHttpBaseAddress))); } if (this.httpsGetEnabled) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxServiceMetadataBehaviorNoHttpsBaseAddress))); } } } static bool EnsureGetDispatcher(ServiceHostBase host, ServiceMetadataExtension mex, Uri url, string scheme) { Uri address = host.GetVia(scheme, url == null ? new Uri(string.Empty, UriKind.Relative) : url); if (address != null) { ChannelDispatcher channelDispatcher = mex.EnsureGetDispatcher(address, false /* isServiceDebugBehavior */); ((ServiceMetadataExtension.HttpGetImpl)channelDispatcher.Endpoints[0].DispatchRuntime.SingletonInstanceContext.UserObject).GetWsdlEnabled = true; return true; } return false; } void SetExtensionProperties(ServiceDescription description, ServiceHostBase host, ServiceMetadataExtension mex) { mex.ExternalMetadataLocation = this.ExternalMetadataLocation; mex.Initializer = new MetadataExtensionInitializer(this, description, host); mex.HttpGetEnabled = this.httpGetEnabled; mex.HttpsGetEnabled = this.httpsGetEnabled; mex.HttpGetUrl = host.GetVia(Uri.UriSchemeHttp, this.httpGetUrl == null ? new Uri(string.Empty, UriKind.Relative) : this.httpGetUrl); mex.HttpsGetUrl = host.GetVia(Uri.UriSchemeHttps, this.httpsGetUrl == null ? new Uri(string.Empty, UriKind.Relative) : this.httpsGetUrl); mex.HttpGetBinding = this.httpGetBinding; mex.HttpsGetBinding = this.httpsGetBinding; UseRequestHeadersForMetadataAddressBehavior dynamicUpdateBehavior = description.Behaviors.Find(); if (dynamicUpdateBehavior != null) { mex.UpdateAddressDynamically = true; mex.UpdatePortsByScheme = new Dictionary(dynamicUpdateBehavior.DefaultPortsByScheme); } foreach (ChannelDispatcherBase dispatcherBase in host.ChannelDispatchers) { ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher; if (dispatcher != null && IsMetadataTransferDispatcher(description, dispatcher)) { mex.MexEnabled = true; mex.MexUrl = dispatcher.Listener.Uri; if (dynamicUpdateBehavior != null) { foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints) { if (!endpointDispatcher.AddressFilterSetExplicit) { endpointDispatcher.AddressFilter = new MatchAllMessageFilter(); } } } break; } } } private static void CustomizeMetadataEndpoints(ServiceDescription description, ServiceHostBase host, ServiceMetadataExtension mex) { for (int i = 0; i < host.ChannelDispatchers.Count; i++) { ChannelDispatcher channelDispatcher = host.ChannelDispatchers[i] as ChannelDispatcher; if (channelDispatcher != null && ServiceMetadataBehavior.IsMetadataTransferDispatcher(description, channelDispatcher)) { if (channelDispatcher.Endpoints.Count != 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(SR.GetString(SR.SFxServiceMetadataBehaviorInstancingError, channelDispatcher.Listener.Uri, channelDispatcher.CreateContractListString()))); } DispatchRuntime dispatcher = channelDispatcher.Endpoints[0].DispatchRuntime; // set instancing dispatcher.InstanceContextProvider = InstanceContextProviderBase.GetProviderForMode(InstanceContextMode.Single, dispatcher); bool isListeningOnHttps = channelDispatcher.Listener.Uri.Scheme == Uri.UriSchemeHttps; Uri listenUri = channelDispatcher.Listener.Uri; ServiceMetadataExtension.WSMexImpl impl = new ServiceMetadataExtension.WSMexImpl(mex, isListeningOnHttps, listenUri); dispatcher.SingletonInstanceContext = new InstanceContext(host, impl, false); } } } static EndpointDispatcher GetListenerByID(SynchronizedCollection channelDispatchers, string id) { for (int i = 0; i < channelDispatchers.Count; ++i) { ChannelDispatcher channelDispatcher = channelDispatchers[i] as ChannelDispatcher; if (channelDispatcher != null) { for (int j = 0; j < channelDispatcher.Endpoints.Count; ++j) { EndpointDispatcher endpointDispatcher = channelDispatcher.Endpoints[j]; if (endpointDispatcher.Id == id) return endpointDispatcher; } } } return null; } internal static bool IsMetadataDispatcher(ServiceDescription description, ChannelDispatcher channelDispatcher) { foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { if (IsMetadataTransferDispatcher(description, channelDispatcher) || IsHttpGetMetadataDispatcher(description, channelDispatcher)) return true; } return false; } static bool IsMetadataTransferDispatcher(ServiceDescription description, ChannelDispatcher channelDispatcher) { if (BehaviorMissingObjectNullOrServiceImplements(description, channelDispatcher)) return false; foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { if (endpointDispatcher.ContractName == ServiceMetadataBehavior.MexContractName && endpointDispatcher.ContractNamespace == ServiceMetadataBehavior.MexContractNamespace) return true; } return false; } private static bool BehaviorMissingObjectNullOrServiceImplements(ServiceDescription description, object obj) { if (obj == null) return true; if (description.Behaviors != null && description.Behaviors.Find() == null) return true; if (description.ServiceType != null && description.ServiceType.GetInterface(typeof(IMetadataExchange).Name) != null) return true; return false; } internal static bool IsHttpGetMetadataDispatcher(ServiceDescription description, ChannelDispatcher channelDispatcher) { if (description.Behaviors.Find() == null) return false; foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { if (endpointDispatcher.ContractName == ServiceMetadataExtension.HttpGetImpl.ContractName && endpointDispatcher.ContractNamespace == ServiceMetadataExtension.HttpGetImpl.ContractNamespace) return true; } return false; } internal static bool IsMetadataEndpoint(ServiceDescription description, ServiceEndpoint endpoint) { if (BehaviorMissingObjectNullOrServiceImplements(description, endpoint)) return false; return IsMetadataEndpoint(endpoint); } static bool IsMetadataEndpoint(ServiceEndpoint endpoint) { return (endpoint.Contract.Name == ServiceMetadataBehavior.MexContractName && endpoint.Contract.Namespace == ServiceMetadataBehavior.MexContractNamespace); } internal static bool IsMetadataImplementedType(ServiceDescription description, Type type) { if (BehaviorMissingObjectNullOrServiceImplements(description, type)) return false; return type == typeof(IMetadataExchange); } internal static bool IsMetadataImplementedType(Type type) { return type == typeof(IMetadataExchange); } internal void AddImplementedContracts(ServiceHostBase.ServiceAndBehaviorsContractResolver resolver) { if (!resolver.BehaviorContracts.ContainsKey(MexContractName)) { resolver.BehaviorContracts.Add(MexContractName, ServiceMetadataBehavior.MexContract); } } static void EnsureMexContractDescription() { if (ServiceMetadataBehavior.mexContract == null) { lock (thisLock) { if (ServiceMetadataBehavior.mexContract == null) { ServiceMetadataBehavior.mexContract = CreateMexContract(); } } } } static ContractDescription CreateMexContract() { ContractDescription mexContract = ContractDescription.GetContract(typeof(IMetadataExchange)); foreach (OperationDescription operation in mexContract.Operations) { operation.Behaviors.Find().Impersonation = ImpersonationOption.Allowed; } mexContract.Behaviors.Add(new ServiceMetadataContractBehavior(true)); return mexContract; } internal class MetadataExtensionInitializer { ServiceMetadataBehavior behavior; ServiceDescription description; ServiceHostBase host; Exception metadataGenerationException = null; internal MetadataExtensionInitializer(ServiceMetadataBehavior behavior, ServiceDescription description, ServiceHostBase host) { this.behavior = behavior; this.description = description; this.host = host; } internal MetadataSet GenerateMetadata() { if (this.behavior.ExternalMetadataLocation == null || this.behavior.ExternalMetadataLocation.ToString() == string.Empty) { if (this.metadataGenerationException != null) throw this.metadataGenerationException; try { MetadataExporter exporter = this.behavior.MetadataExporter; XmlQualifiedName serviceName = new XmlQualifiedName(this.description.Name, this.description.Namespace); Collection exportedEndpoints = new Collection(); foreach (ServiceEndpoint endpoint in this.description.Endpoints) { ServiceMetadataContractBehavior contractBehavior = endpoint.Contract.Behaviors.Find(); // if contract behavior exists, generate metadata when the behavior allows metadata generation // if contract behavior doesn't exist, generate metadata only for non system endpoints if ((contractBehavior != null && !contractBehavior.MetadataGenerationDisabled) || (contractBehavior == null && !endpoint.IsSystemEndpoint)) { EndpointAddress address = null; EndpointDispatcher endpointDispatcher = GetListenerByID(this.host.ChannelDispatchers, endpoint.Id); if (endpointDispatcher != null) { address = endpointDispatcher.EndpointAddress; } ServiceEndpoint exportedEndpoint = new ServiceEndpoint(endpoint.Contract); exportedEndpoint.Binding = endpoint.Binding; exportedEndpoint.Name = endpoint.Name; exportedEndpoint.Address = address; foreach (IEndpointBehavior behavior in endpoint.Behaviors) { exportedEndpoint.Behaviors.Add(behavior); } exportedEndpoints.Add(exportedEndpoint); } } WsdlExporter wsdlExporter = exporter as WsdlExporter; if (wsdlExporter != null) { // Pass the BindingParameterCollection into the ExportEndpoints method so that the binding parameters can be using to export WSDL correctly. // The binding parameters are used in BuildChannelListener, during which they can modify the configuration of the channel in ways that might have to // be communicated in the WSDL. For example, in the case of Multi-Auth, the AuthenticationSchemesBindingParameter is used during BuildChannelListener // to set the AuthenticationSchemes supported by the virtual directory on the HttpTransportBindingElement. These authentication schemes also need // to be in the WSDL, so that clients know what authentication schemes are supported by the service. (see CSDMain #180381) Fx.Assert(this.host != null, "ServiceHostBase field on MetadataExtensionInitializer should never be null."); wsdlExporter.ExportEndpoints(exportedEndpoints, serviceName, this.host.GetBindingParameters(exportedEndpoints)); } else { foreach (ServiceEndpoint endpoint in exportedEndpoints) { exporter.ExportEndpoint(endpoint); } } if (exporter.Errors.Count > 0 && DiagnosticUtility.ShouldTraceWarning) { TraceWsdlExportErrors(exporter); } return exporter.GetGeneratedMetadata(); } catch (Exception e) { this.metadataGenerationException = e; throw; } } return null; } static void TraceWsdlExportErrors(MetadataExporter exporter) { foreach (MetadataConversionError error in exporter.Errors) { if (DiagnosticUtility.ShouldTraceWarning) { Hashtable h = new Hashtable(2) { { "IsWarning", error.IsWarning }, { "Message", error.Message } }; TraceUtility.TraceEvent(TraceEventType.Warning, TraceCode.WsmexNonCriticalWsdlExportError, SR.GetString(SR.TraceCodeWsmexNonCriticalWsdlExportError), new DictionaryTraceRecord(h), null, null); } } } } } }