//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ #pragma warning disable 1634, 1691 namespace System.ServiceModel.Description { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.ServiceModel; using System.ServiceModel.Administration; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher; using System.ServiceModel.Web; public class WebHttpBehavior : IEndpointBehavior, IWmiInstanceProvider { internal const string GET = "GET"; internal const string POST = "POST"; internal const string WildcardAction = "*"; internal const string WildcardMethod = "*"; internal static readonly string defaultStreamContentType = "application/octet-stream"; internal static readonly string defaultCallbackParameterName = "callback"; const string AddressPropertyName = "Address"; WebMessageBodyStyle defaultBodyStyle; WebMessageFormat defaultOutgoingReplyFormat; WebMessageFormat defaultOutgoingRequestFormat; XmlSerializerOperationBehavior.Reflector reflector; UnwrappedTypesXmlSerializerManager xmlSerializerManager; public WebHttpBehavior() { defaultOutgoingRequestFormat = WebMessageFormat.Xml; defaultOutgoingReplyFormat = WebMessageFormat.Xml; this.defaultBodyStyle = WebMessageBodyStyle.Bare; xmlSerializerManager = new UnwrappedTypesXmlSerializerManager(); } internal delegate void Effect(); public virtual WebMessageBodyStyle DefaultBodyStyle { get { return this.defaultBodyStyle; } set { if (!WebMessageBodyStyleHelper.IsDefined(value)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value")); } this.defaultBodyStyle = value; } } public virtual WebMessageFormat DefaultOutgoingRequestFormat { get { return this.defaultOutgoingRequestFormat; } set { if (!WebMessageFormatHelper.IsDefined(value)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value")); } this.defaultOutgoingRequestFormat = value; } } public virtual WebMessageFormat DefaultOutgoingResponseFormat { get { return this.defaultOutgoingReplyFormat; } set { if (!WebMessageFormatHelper.IsDefined(value)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value")); } this.defaultOutgoingReplyFormat = value; } } public virtual bool HelpEnabled { get; set; } public virtual bool AutomaticFormatSelectionEnabled { get; set; } public virtual bool FaultExceptionEnabled { get; set; } internal Uri HelpUri { get; set; } protected internal string JavascriptCallbackParameterName { get; set; } public virtual void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { // do nothing } public virtual void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { if (endpoint == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint"); } if (clientRuntime == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("clientRuntime"); } WebMessageEncodingBindingElement webEncodingBindingElement = endpoint.Binding.CreateBindingElements().Find(); if (webEncodingBindingElement != null && webEncodingBindingElement.CrossDomainScriptAccessEnabled) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.CrossDomainJavascriptNotsupported)); } #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null this.reflector = new XmlSerializerOperationBehavior.Reflector(endpoint.Contract.Namespace, null); foreach (OperationDescription od in endpoint.Contract.Operations) #pragma warning restore 56506 { #pragma warning disable 56506 // Microsoft, clientRuntime.Operations is never null if (clientRuntime.Operations.Contains(od.Name)) #pragma warning restore 56506 { ClientOperation cop = clientRuntime.Operations[od.Name]; IClientMessageFormatter requestClient = GetRequestClientFormatter(od, endpoint); IClientMessageFormatter replyClient = GetReplyClientFormatter(od, endpoint); cop.Formatter = new CompositeClientFormatter(requestClient, replyClient); cop.SerializeRequest = true; cop.DeserializeReply = od.Messages.Count > 1 && !IsUntypedMessage(od.Messages[1]); } } AddClientErrorInspector(endpoint, clientRuntime); } public virtual void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { if (endpoint == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint"); } if (endpointDispatcher == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointDispatcher"); } WebMessageEncodingBindingElement webEncodingBindingElement = endpoint.Binding.CreateBindingElements().Find(); if (webEncodingBindingElement != null && webEncodingBindingElement.CrossDomainScriptAccessEnabled) { ISecurityCapabilities securityCapabilities = endpoint.Binding.GetProperty(new BindingParameterCollection()); if (securityCapabilities.SupportsClientAuthentication) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.CrossDomainJavascriptAuthNotSupported)); } if (endpoint.Contract.Behaviors.Contains(typeof(JavascriptCallbackBehaviorAttribute))) { JavascriptCallbackBehaviorAttribute behavior = endpoint.Contract.Behaviors[typeof(JavascriptCallbackBehaviorAttribute)] as JavascriptCallbackBehaviorAttribute; this.JavascriptCallbackParameterName = behavior.UrlParameterName; } else { this.JavascriptCallbackParameterName = defaultCallbackParameterName; } endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new JavascriptCallbackMessageInspector(this.JavascriptCallbackParameterName)); } if (this.HelpEnabled) { this.HelpUri = new UriTemplate(HelpPage.OperationListHelpPageUriTemplate).BindByPosition(endpoint.ListenUri); } #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null this.reflector = new XmlSerializerOperationBehavior.Reflector(endpoint.Contract.Namespace, null); #pragma warning restore 56506 // endpoint filter endpointDispatcher.AddressFilter = new PrefixEndpointAddressMessageFilter(endpoint.Address); endpointDispatcher.ContractFilter = new MatchAllMessageFilter(); // operation selector #pragma warning disable 56506 // Microsoft, endpointDispatcher.DispatchRuntime is never null endpointDispatcher.DispatchRuntime.OperationSelector = this.GetOperationSelector(endpoint); #pragma warning restore 56506 // unhandled operation string actionStarOperationName = null; #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null foreach (OperationDescription od in endpoint.Contract.Operations) #pragma warning restore 56506 { if (od.Messages[0].Direction == MessageDirection.Input && od.Messages[0].Action == WildcardAction) { actionStarOperationName = od.Name; break; } } if (actionStarOperationName != null) { // WCF v1 installs any Action="*" op into UnhandledDispatchOperation, but WebHttpBehavior // doesn't want this, so we 'move' that operation back into normal set of operations #pragma warning disable 56506 // Microsoft, endpointDispatcher.DispatchRuntime.{Operations,UnhandledDispatchOperation} is never null endpointDispatcher.DispatchRuntime.Operations.Add( endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation); #pragma warning restore 56506 } FormatSelectingMessageInspector formatSelectingMessageInspector = null; string xmlContentType = null; string jsonContentType = null; if (webEncodingBindingElement != null) { XmlFormatMapping xmlFormatMapping = new XmlFormatMapping(webEncodingBindingElement.WriteEncoding, webEncodingBindingElement.ContentTypeMapper); JsonFormatMapping jsonFormatMapping = new JsonFormatMapping(webEncodingBindingElement.WriteEncoding, webEncodingBindingElement.ContentTypeMapper); xmlContentType = xmlFormatMapping.DefaultContentType.ToString(); jsonContentType = jsonFormatMapping.DefaultContentType.ToString(); if (AutomaticFormatSelectionEnabled) { formatSelectingMessageInspector = new FormatSelectingMessageInspector(this, new List { xmlFormatMapping, jsonFormatMapping }); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(formatSelectingMessageInspector); } } else { xmlContentType = TextMessageEncoderFactory.GetContentType(XmlFormatMapping.defaultMediaType, TextEncoderDefaults.Encoding); jsonContentType = JsonMessageEncoderFactory.GetContentType(null); } #pragma warning disable 56506 // Microsoft, endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation is never null // always install UnhandledDispatchOperation (WebHttpDispatchOperationSelector may choose not to use it) endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = new DispatchOperation(endpointDispatcher.DispatchRuntime, "*", WildcardAction, WildcardAction); endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.DeserializeRequest = false; endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.SerializeReply = false; endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.Invoker = new HttpUnhandledOperationInvoker { HelpUri = this.HelpUri }; #pragma warning restore 56506 // install formatters and parameter inspectors foreach (OperationDescription od in endpoint.Contract.Operations) { DispatchOperation dop = null; #pragma warning disable 56506 // Microsoft, endpointDispatcher.DispatchRuntime, DispatchRuntime.Operations are never null if (endpointDispatcher.DispatchRuntime.Operations.Contains(od.Name)) #pragma warning restore 56506 { dop = endpointDispatcher.DispatchRuntime.Operations[od.Name]; } #pragma warning disable 56506 // Microsoft, endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation is never null else if (endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.Name == od.Name) { dop = endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation; } #pragma warning restore 56506 if (dop != null) { IDispatchMessageFormatter requestDispatch = GetRequestDispatchFormatter(od, endpoint); IDispatchMessageFormatter replyDispatch = GetReplyDispatchFormatter(od, endpoint); MultiplexingDispatchMessageFormatter replyDispatchAsMultiplexing = replyDispatch as MultiplexingDispatchMessageFormatter; if (replyDispatchAsMultiplexing != null) { // here we are adding all default content types, despite the fact that // some of the formatters in MultiplexingDispatchMessageFormatter might not be present // i.e. the JSON formatter replyDispatchAsMultiplexing.DefaultContentTypes.Add(WebMessageFormat.Xml, xmlContentType); replyDispatchAsMultiplexing.DefaultContentTypes.Add(WebMessageFormat.Json, jsonContentType); if (formatSelectingMessageInspector != null) { formatSelectingMessageInspector.RegisterOperation(od.Name, replyDispatchAsMultiplexing); } } dop.Formatter = new CompositeDispatchFormatter(requestDispatch, replyDispatch); dop.FaultFormatter = new WebFaultFormatter(dop.FaultFormatter); dop.DeserializeRequest = (requestDispatch != null); dop.SerializeReply = od.Messages.Count > 1 && (replyDispatch != null); } } if (this.HelpEnabled) { HelpPage helpPage = new HelpPage(this, endpoint.Contract); DispatchOperation dispatchOperation = new DispatchOperation(endpointDispatcher.DispatchRuntime, HelpOperationInvoker.OperationName, null, null) { DeserializeRequest = false, SerializeReply = false, Invoker = new HelpOperationInvoker(helpPage, endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation.Invoker), }; endpointDispatcher.DispatchRuntime.Operations.Add(dispatchOperation); } AddServerErrorHandlers(endpoint, endpointDispatcher); } internal virtual Dictionary GetWmiProperties() { Dictionary result = new Dictionary(); result.Add("DefaultBodyStyle", this.DefaultBodyStyle.ToString()); result.Add("DefaultOutgoingRequestFormat", this.DefaultOutgoingRequestFormat.ToString()); result.Add("DefaultOutgoingResponseFormat", this.DefaultOutgoingResponseFormat.ToString()); return result; } internal virtual string GetWmiTypeName() { return "WebHttpBehavior"; } void IWmiInstanceProvider.FillInstance(IWmiInstance wmiInstance) { if (wmiInstance == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("wmiInstance"); } Dictionary properties = this.GetWmiProperties(); foreach (string key in properties.Keys) { wmiInstance.SetProperty(key, properties[key]); } } string IWmiInstanceProvider.GetInstanceType() { return GetWmiTypeName(); } public virtual void Validate(ServiceEndpoint endpoint) { if (endpoint == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint"); } ValidateNoMessageHeadersPresent(endpoint); ValidateBinding(endpoint); ValidateContract(endpoint); } void ValidateNoMessageHeadersPresent(ServiceEndpoint endpoint) { if (endpoint == null || endpoint.Address == null) { return; } EndpointAddress address = endpoint.Address; if (address.Headers.Count > 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.WebHttpServiceEndpointCannotHaveMessageHeaders, address))); } } protected virtual void ValidateBinding(ServiceEndpoint endpoint) { ValidateIsWebHttpBinding(endpoint, this.GetType().ToString()); } internal static string GetWebMethod(OperationDescription od) { WebGetAttribute wga = od.Behaviors.Find(); WebInvokeAttribute wia = od.Behaviors.Find(); EnsureOk(wga, wia, od); if (wga != null) { return GET; } else if (wia != null) { return wia.Method ?? POST; } else { return POST; } } internal static string GetWebUriTemplate(OperationDescription od) { // return exactly what is on the attribute WebGetAttribute wga = od.Behaviors.Find(); WebInvokeAttribute wia = od.Behaviors.Find(); EnsureOk(wga, wia, od); if (wga != null) { return wga.UriTemplate; } else if (wia != null) { return wia.UriTemplate; } else { return null; } } internal static string GetDescription(OperationDescription od) { object[] attributes = null; if (od.SyncMethod != null) { attributes = od.SyncMethod.GetCustomAttributes(typeof(DescriptionAttribute), true); } else if (od.BeginMethod != null) { attributes = od.BeginMethod.GetCustomAttributes(typeof(DescriptionAttribute), true); } if (attributes != null && attributes.Length > 0) { return ((DescriptionAttribute)attributes[0]).Description; } else { return String.Empty; } } internal static bool IsTypedMessage(MessageDescription message) { return (message != null && message.MessageType != null); } internal static bool IsUntypedMessage(MessageDescription message) { if (message == null) { return false; } return (message.Body.ReturnValue != null && message.Body.Parts.Count == 0 && message.Body.ReturnValue.Type == typeof(Message)) || (message.Body.ReturnValue == null && message.Body.Parts.Count == 1 && message.Body.Parts[0].Type == typeof(Message)); } internal static MessageDescription MakeDummyMessageDescription(MessageDirection direction) { MessageDescription messageDescription = new MessageDescription("urn:dummyAction", direction); return messageDescription; } internal static bool SupportsJsonFormat(OperationDescription od) { // if the type is XmlSerializable, then we cannot create a json serializer for it DataContractSerializerOperationBehavior dcsob = od.Behaviors.Find(); return (dcsob != null); } internal static void ValidateIsWebHttpBinding(ServiceEndpoint serviceEndpoint, string behaviorName) { Binding binding = serviceEndpoint.Binding; if (binding.Scheme != "http" && binding.Scheme != "https") { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.WCFBindingCannotBeUsedWithUriOperationSelectorBehaviorBadScheme, serviceEndpoint.Contract.Name, behaviorName))); } if (binding.MessageVersion != MessageVersion.None) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.WCFBindingCannotBeUsedWithUriOperationSelectorBehaviorBadMessageVersion, serviceEndpoint.Address.Uri.AbsoluteUri, behaviorName))); } TransportBindingElement transportBindingElement = binding.CreateBindingElements().Find(); if (transportBindingElement != null && !transportBindingElement.ManualAddressing) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.ManualAddressingCannotBeFalseWithTransportBindingElement, serviceEndpoint.Address.Uri.AbsoluteUri, behaviorName, transportBindingElement.GetType().Name))); } } internal WebMessageBodyStyle GetBodyStyle(OperationDescription od) { WebGetAttribute wga = od.Behaviors.Find(); WebInvokeAttribute wia = od.Behaviors.Find(); EnsureOk(wga, wia, od); if (wga != null) { return wga.GetBodyStyleOrDefault(this.DefaultBodyStyle); } else if (wia != null) { return wia.GetBodyStyleOrDefault(this.DefaultBodyStyle); } else { return this.DefaultBodyStyle; } } internal IClientMessageFormatter GetDefaultClientFormatter(OperationDescription od, bool useJson, bool isWrapped) { DataContractSerializerOperationBehavior dcsob = od.Behaviors.Find(); if (useJson) { if (dcsob == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.JsonFormatRequiresDataContract, od.Name, od.DeclaringContract.Name, od.DeclaringContract.Namespace))); } return CreateDataContractJsonSerializerOperationFormatter(od, dcsob, isWrapped); } else { ClientRuntime clientRuntime = new ClientRuntime("name", ""); ClientOperation cop = new ClientOperation(clientRuntime, "dummyClient", "urn:dummy"); cop.Formatter = null; if (dcsob != null) { (dcsob as IOperationBehavior).ApplyClientBehavior(od, cop); return cop.Formatter; } XmlSerializerOperationBehavior xsob = od.Behaviors.Find(); if (xsob != null) { xsob = new XmlSerializerOperationBehavior(od, xsob.XmlSerializerFormatAttribute, this.reflector); (xsob as IOperationBehavior).ApplyClientBehavior(od, cop); return cop.Formatter; } } return null; } protected virtual void AddClientErrorInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { if (!this.FaultExceptionEnabled) { clientRuntime.MessageInspectors.Add(new WebFaultClientMessageInspector()); } else { clientRuntime.MessageVersionNoneFaultsEnabled = true; } } protected virtual void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { if (!this.FaultExceptionEnabled) { WebErrorHandler errorHandler = new WebErrorHandler(this, endpoint.Contract, endpointDispatcher.DispatchRuntime.ChannelDispatcher.IncludeExceptionDetailInFaults); endpointDispatcher.DispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(errorHandler); } } protected virtual WebHttpDispatchOperationSelector GetOperationSelector(ServiceEndpoint endpoint) { return new WebHttpDispatchOperationSelector(endpoint); } protected virtual QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription) { return new QueryStringConverter(); } protected virtual IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) { if (operationDescription.Messages.Count < 2) { return null; } ValidateBodyParameters(operationDescription, false); Type type; if (TryGetStreamParameterType(operationDescription.Messages[1], operationDescription, false, out type)) { return new HttpStreamFormatter(operationDescription); } if (IsUntypedMessage(operationDescription.Messages[1])) { return new MessagePassthroughFormatter(); } WebMessageBodyStyle style = GetBodyStyle(operationDescription); Type parameterType; if (UseBareReplyFormatter(style, operationDescription, GetResponseFormat(operationDescription), out parameterType)) { return SingleBodyParameterMessageFormatter.CreateXmlAndJsonClientFormatter(operationDescription, parameterType, false, this.xmlSerializerManager); } else { MessageDescription temp = operationDescription.Messages[0]; operationDescription.Messages[0] = MakeDummyMessageDescription(MessageDirection.Input); IClientMessageFormatter result; result = GetDefaultXmlAndJsonClientFormatter(operationDescription, !IsBareResponse(style)); operationDescription.Messages[0] = temp; return result; } } internal virtual bool UseBareReplyFormatter(WebMessageBodyStyle style, OperationDescription operationDescription, WebMessageFormat responseFormat, out Type parameterType) { parameterType = null; return IsBareResponse(style) && TryGetNonMessageParameterType(operationDescription.Messages[1], operationDescription, false, out parameterType); } protected virtual IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) { if (operationDescription.Messages.Count < 2) { return null; } ValidateBodyParameters(operationDescription, false); WebMessageFormat responseFormat = GetResponseFormat(operationDescription); // Determine if we should add a json formatter; If the ResponseFormat is json, we always add the json formatter even if the // operation is XmlSerializerFormat because the formatter constructor throws the exception: "json not valid with XmlSerializerFormat" [Microsoft] bool useJson = (responseFormat == WebMessageFormat.Json || SupportsJsonFormat(operationDescription)); IDispatchMessageFormatter innerFormatter; Type type; if (TryGetStreamParameterType(operationDescription.Messages[1], operationDescription, false, out type)) { innerFormatter = new ContentTypeSettingDispatchMessageFormatter(defaultStreamContentType, new HttpStreamFormatter(operationDescription)); } else if (IsUntypedMessage(operationDescription.Messages[1])) { innerFormatter = new MessagePassthroughFormatter(); } else { Type parameterType; WebMessageBodyStyle style = GetBodyStyle(operationDescription); Dictionary formatters = new Dictionary(); if (UseBareReplyFormatter(style, operationDescription, responseFormat, out parameterType)) { formatters.Add(WebMessageFormat.Xml, SingleBodyParameterMessageFormatter.CreateDispatchFormatter(operationDescription, parameterType, false, false, this.xmlSerializerManager, null)); if (useJson) { formatters.Add(WebMessageFormat.Json, SingleBodyParameterMessageFormatter.CreateDispatchFormatter(operationDescription, parameterType, false, true, this.xmlSerializerManager, this.JavascriptCallbackParameterName)); } } else { MessageDescription temp = operationDescription.Messages[0]; operationDescription.Messages[0] = MakeDummyMessageDescription(MessageDirection.Input); formatters.Add(WebMessageFormat.Xml, GetDefaultDispatchFormatter(operationDescription, false, !IsBareResponse(style))); if (useJson) { formatters.Add(WebMessageFormat.Json, GetDefaultDispatchFormatter(operationDescription, true, !IsBareResponse(style))); } operationDescription.Messages[0] = temp; } innerFormatter = new MultiplexingDispatchMessageFormatter(formatters, responseFormat); } return innerFormatter; } protected virtual IClientMessageFormatter GetRequestClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) { WebMessageFormat requestFormat = GetRequestFormat(operationDescription); bool useJson = (requestFormat == WebMessageFormat.Json); WebMessageEncodingBindingElement webEncoding = (useJson) ? endpoint.Binding.CreateBindingElements().Find() : null; IClientMessageFormatter innerFormatter = null; // get some validation errors by creating "throwAway" formatter // validate that endpoint.Address is not null before accessing the endpoint.Address.Uri. This is to avoid throwing a NullRefException while constructing a UriTemplateClientFormatter if (endpoint.Address == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.ServiceEndpointMustHaveNonNullAddress, typeof(ServiceEndpoint), typeof(ChannelFactory), typeof(WebHttpEndpoint), AddressPropertyName, typeof(ServiceEndpoint)))); } UriTemplateClientFormatter throwAway = new UriTemplateClientFormatter(operationDescription, null, GetQueryStringConverter(operationDescription), endpoint.Address.Uri, false, endpoint.Contract.Name); int numUriVariables = throwAway.pathMapping.Count + throwAway.queryMapping.Count; bool isStream = false; HideReplyMessage(operationDescription, delegate() { WebMessageBodyStyle style = GetBodyStyle(operationDescription); bool isUntypedWhenUriParamsNotConsidered = false; Effect doBodyFormatter = delegate() { if (numUriVariables != 0) { EnsureNotUntypedMessageNorMessageContract(operationDescription); } // get body formatter ValidateBodyParameters(operationDescription, true); IClientMessageFormatter baseFormatter; Type parameterType; if (TryGetStreamParameterType(operationDescription.Messages[0], operationDescription, true, out parameterType)) { isStream = true; baseFormatter = new HttpStreamFormatter(operationDescription); } else if (UseBareRequestFormatter(style, operationDescription, out parameterType)) { baseFormatter = SingleBodyParameterMessageFormatter.CreateClientFormatter(operationDescription, parameterType, true, useJson, this.xmlSerializerManager); } else { baseFormatter = GetDefaultClientFormatter(operationDescription, useJson, !IsBareRequest(style)); } innerFormatter = baseFormatter; isUntypedWhenUriParamsNotConsidered = IsUntypedMessage(operationDescription.Messages[0]); }; if (numUriVariables == 0) { if (IsUntypedMessage(operationDescription.Messages[0])) { ValidateBodyParameters(operationDescription, true); innerFormatter = new MessagePassthroughFormatter(); isUntypedWhenUriParamsNotConsidered = true; } else if (IsTypedMessage(operationDescription.Messages[0])) { ValidateBodyParameters(operationDescription, true); innerFormatter = GetDefaultClientFormatter(operationDescription, useJson, !IsBareRequest(style)); } else { doBodyFormatter(); } } else { HideRequestUriTemplateParameters(operationDescription, throwAway, delegate() { CloneMessageDescriptionsBeforeActing(operationDescription, delegate() { doBodyFormatter(); }); }); } innerFormatter = new UriTemplateClientFormatter(operationDescription, innerFormatter, GetQueryStringConverter(operationDescription), endpoint.Address.Uri, isUntypedWhenUriParamsNotConsidered, endpoint.Contract.Name); }); string defaultContentType = GetDefaultContentType(isStream, useJson, webEncoding); if (!string.IsNullOrEmpty(defaultContentType)) { innerFormatter = new ContentTypeSettingClientMessageFormatter(defaultContentType, innerFormatter); } return innerFormatter; } protected virtual IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint) { IDispatchMessageFormatter result = null; // get some validation errors by creating "throwAway" formatter UriTemplateDispatchFormatter throwAway = new UriTemplateDispatchFormatter(operationDescription, null, GetQueryStringConverter(operationDescription), endpoint.Contract.Name, endpoint.Address.Uri); int numUriVariables = throwAway.pathMapping.Count + throwAway.queryMapping.Count; HideReplyMessage(operationDescription, delegate() { WebMessageBodyStyle style = GetBodyStyle(operationDescription); Effect doBodyFormatter = delegate() { if (numUriVariables != 0) { EnsureNotUntypedMessageNorMessageContract(operationDescription); } // get body formatter ValidateBodyParameters(operationDescription, true); Type type; if (TryGetStreamParameterType(operationDescription.Messages[0], operationDescription, true, out type)) { result = new HttpStreamFormatter(operationDescription); } else { Type parameterType; if (UseBareRequestFormatter(style, operationDescription, out parameterType)) { result = SingleBodyParameterMessageFormatter.CreateXmlAndJsonDispatchFormatter(operationDescription, parameterType, true, this.xmlSerializerManager, this.JavascriptCallbackParameterName); } else { result = GetDefaultXmlAndJsonDispatchFormatter(operationDescription, !IsBareRequest(style)); } } }; if (numUriVariables == 0) { if (IsUntypedMessage(operationDescription.Messages[0])) { ValidateBodyParameters(operationDescription, true); result = new MessagePassthroughFormatter(); } else if (IsTypedMessage(operationDescription.Messages[0])) { ValidateBodyParameters(operationDescription, true); result = GetDefaultXmlAndJsonDispatchFormatter(operationDescription, !IsBareRequest(style)); } else { doBodyFormatter(); } } else { HideRequestUriTemplateParameters(operationDescription, throwAway, delegate() { CloneMessageDescriptionsBeforeActing(operationDescription, delegate() { doBodyFormatter(); }); }); } result = new UriTemplateDispatchFormatter(operationDescription, result, GetQueryStringConverter(operationDescription), endpoint.Contract.Name, endpoint.Address.Uri); }); return result; } static void CloneMessageDescriptionsBeforeActing(OperationDescription operationDescription, Effect effect) { MessageDescription originalRequest = operationDescription.Messages[0]; bool thereIsAReply = operationDescription.Messages.Count > 1; MessageDescription originalReply = thereIsAReply ? operationDescription.Messages[1] : null; operationDescription.Messages[0] = originalRequest.Clone(); if (thereIsAReply) { operationDescription.Messages[1] = originalReply.Clone(); } effect(); operationDescription.Messages[0] = originalRequest; if (thereIsAReply) { operationDescription.Messages[1] = originalReply; } } internal virtual bool UseBareRequestFormatter(WebMessageBodyStyle style, OperationDescription operationDescription, out Type parameterType) { parameterType = null; return IsBareRequest(style) && TryGetNonMessageParameterType(operationDescription.Messages[0], operationDescription, true, out parameterType); } static Collection CloneParts(MessageDescription md) { MessagePartDescriptionCollection bodyParameters = md.Body.Parts; Collection bodyParametersClone = new Collection(); for (int i = 0; i < bodyParameters.Count; ++i) { MessagePartDescription copy = bodyParameters[i].Clone(); bodyParametersClone.Add(copy); } return bodyParametersClone; } static void EnsureNotUntypedMessageNorMessageContract(OperationDescription operationDescription) { // Called when there are UriTemplate parameters. UT does not compose with Message // or MessageContract because the SOAP and REST programming models must be uniform here. bool isUnadornedWebGet = false; if (GetWebMethod(operationDescription) == GET && GetWebUriTemplate(operationDescription) == null) { isUnadornedWebGet = true; } if (IsTypedMessage(operationDescription.Messages[0])) { if (isUnadornedWebGet) { // WebGet will give you UriTemplate parameters by default. // We need a special error message for this case to prevent confusion. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.GETCannotHaveMCParameter, operationDescription.Name, operationDescription.DeclaringContract.Name, operationDescription.Messages[0].MessageType.Name))); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString( SR2.UTParamsDoNotComposeWithMessageContract, operationDescription.Name, operationDescription.DeclaringContract.Name))); } } if (IsUntypedMessage(operationDescription.Messages[0])) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString( SR2.UTParamsDoNotComposeWithMessage, operationDescription.Name, operationDescription.DeclaringContract.Name))); } } static void EnsureOk(WebGetAttribute wga, WebInvokeAttribute wia, OperationDescription od) { if (wga != null && wia != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.MultipleWebAttributes, od.Name, od.DeclaringContract.Name))); } } static void HideReplyMessage(OperationDescription operationDescription, Effect effect) { MessageDescription temp = null; if (operationDescription.Messages.Count > 1) { temp = operationDescription.Messages[1]; operationDescription.Messages[1] = MakeDummyMessageDescription(MessageDirection.Output); } effect(); if (operationDescription.Messages.Count > 1) { operationDescription.Messages[1] = temp; } } static void HideRequestUriTemplateParameters(OperationDescription operationDescription, UriTemplateClientFormatter throwAway, Effect effect) { HideRequestUriTemplateParameters(operationDescription, throwAway.pathMapping, throwAway.queryMapping, effect); } internal static void HideRequestUriTemplateParameters(OperationDescription operationDescription, UriTemplateDispatchFormatter throwAway, Effect effect) { HideRequestUriTemplateParameters(operationDescription, throwAway.pathMapping, throwAway.queryMapping, effect); } static void HideRequestUriTemplateParameters(OperationDescription operationDescription, Dictionary pathMapping, Dictionary> queryMapping, Effect effect) { // mutate description to hide UriTemplate parameters Collection originalParts = CloneParts(operationDescription.Messages[0]); Collection parts = CloneParts(operationDescription.Messages[0]); operationDescription.Messages[0].Body.Parts.Clear(); int newIndex = 0; for (int i = 0; i < parts.Count; ++i) { if (!pathMapping.ContainsKey(i) && !queryMapping.ContainsKey(i)) { operationDescription.Messages[0].Body.Parts.Add(parts[i]); parts[i].Index = newIndex++; } } effect(); // unmutate description operationDescription.Messages[0].Body.Parts.Clear(); for (int i = 0; i < originalParts.Count; ++i) { operationDescription.Messages[0].Body.Parts.Add(originalParts[i]); } } static bool IsBareRequest(WebMessageBodyStyle style) { return (style == WebMessageBodyStyle.Bare || style == WebMessageBodyStyle.WrappedResponse); } static bool IsBareResponse(WebMessageBodyStyle style) { return (style == WebMessageBodyStyle.Bare || style == WebMessageBodyStyle.WrappedRequest); } internal static bool TryGetNonMessageParameterType(MessageDescription message, OperationDescription declaringOperation, bool isRequest, out Type type) { type = null; if (message == null) { return true; } if (IsTypedMessage(message) || IsUntypedMessage(message)) { return false; } if (isRequest) { if (message.Body.Parts.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.AtMostOneRequestBodyParameterAllowedForUnwrappedMessages, declaringOperation.Name, declaringOperation.DeclaringContract.Name))); } if (message.Body.Parts.Count == 1 && message.Body.Parts[0].Type != typeof(void)) { type = message.Body.Parts[0].Type; } return true; } else { if (message.Body.Parts.Count > 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.OnlyReturnValueBodyParameterAllowedForUnwrappedMessages, declaringOperation.Name, declaringOperation.DeclaringContract.Name))); } if (message.Body.ReturnValue != null && message.Body.ReturnValue.Type != typeof(void)) { type = message.Body.ReturnValue.Type; } return true; } } static bool TryGetStreamParameterType(MessageDescription message, OperationDescription declaringOperation, bool isRequest, out Type type) { type = null; if (message == null || IsTypedMessage(message) || IsUntypedMessage(message)) { return false; } if (isRequest) { bool hasStream = false; for (int i = 0; i < message.Body.Parts.Count; ++i) { if (typeof(Stream) == message.Body.Parts[i].Type) { type = message.Body.Parts[i].Type; hasStream = true; break; } } if (hasStream && message.Body.Parts.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR2.GetString(SR2.AtMostOneRequestBodyParameterAllowedForStream, declaringOperation.Name, declaringOperation.DeclaringContract.Name))); } return hasStream; } else { // validate that the stream is not an out or ref param for (int i = 0; i < message.Body.Parts.Count; ++i) { if (typeof(Stream) == message.Body.Parts[i].Type) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR2.GetString(SR2.NoOutOrRefStreamParametersAllowed, message.Body.Parts[i].Name, declaringOperation.Name, declaringOperation.DeclaringContract.Name))); } } if (message.Body.ReturnValue != null && typeof(Stream) == message.Body.ReturnValue.Type) { // validate that there are no out or ref params if (message.Body.Parts.Count > 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR2.GetString(SR2.NoOutOrRefParametersAllowedWithStreamResult, declaringOperation.Name, declaringOperation.DeclaringContract.Name))); } type = message.Body.ReturnValue.Type; return true; } else { return false; } } } static void ValidateAtMostOneStreamParameter(OperationDescription operation, bool request) { Type dummy; if (request) { TryGetStreamParameterType(operation.Messages[0], operation, true, out dummy); } else { if (operation.Messages.Count > 1) { TryGetStreamParameterType(operation.Messages[1], operation, false, out dummy); } } } string GetDefaultContentType(bool isStream, bool useJson, WebMessageEncodingBindingElement webEncoding) { if (isStream) { return defaultStreamContentType; } else if (useJson) { return JsonMessageEncoderFactory.GetContentType(webEncoding); } else { return null; } } IDispatchMessageFormatter GetDefaultDispatchFormatter(OperationDescription od, bool useJson, bool isWrapped) { DataContractSerializerOperationBehavior dcsob = od.Behaviors.Find(); if (useJson) { if (dcsob == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.JsonFormatRequiresDataContract, od.Name, od.DeclaringContract.Name, od.DeclaringContract.Namespace))); } return CreateDataContractJsonSerializerOperationFormatter(od, dcsob, isWrapped); } else { EndpointDispatcher dummyED = new EndpointDispatcher(new EndpointAddress("http://localhost/"), "name", ""); DispatchRuntime dispatchRuntime = dummyED.DispatchRuntime; DispatchOperation dop = new DispatchOperation(dispatchRuntime, "dummyDispatch", "urn:dummy"); dop.Formatter = null; if (dcsob != null) { (dcsob as IOperationBehavior).ApplyDispatchBehavior(od, dop); return dop.Formatter; } XmlSerializerOperationBehavior xsob = od.Behaviors.Find(); if (xsob != null) { xsob = new XmlSerializerOperationBehavior(od, xsob.XmlSerializerFormatAttribute, this.reflector); (xsob as IOperationBehavior).ApplyDispatchBehavior(od, dop); return dop.Formatter; } } return null; } internal virtual DataContractJsonSerializerOperationFormatter CreateDataContractJsonSerializerOperationFormatter(OperationDescription od, DataContractSerializerOperationBehavior dcsob, bool isWrapped) { return new DataContractJsonSerializerOperationFormatter(od, dcsob.MaxItemsInObjectGraph, dcsob.IgnoreExtensionDataObject, dcsob.DataContractSurrogate, isWrapped, false, JavascriptCallbackParameterName); } IClientMessageFormatter GetDefaultXmlAndJsonClientFormatter(OperationDescription od, bool isWrapped) { IClientMessageFormatter xmlFormatter = GetDefaultClientFormatter(od, false, isWrapped); if (!SupportsJsonFormat(od)) { return xmlFormatter; } IClientMessageFormatter jsonFormatter = GetDefaultClientFormatter(od, true, isWrapped); Dictionary map = new Dictionary(); map.Add(WebContentFormat.Xml, xmlFormatter); map.Add(WebContentFormat.Json, jsonFormatter); // In case there is no format property, the default formatter to use is XML return new DemultiplexingClientMessageFormatter(map, xmlFormatter); } IDispatchMessageFormatter GetDefaultXmlAndJsonDispatchFormatter(OperationDescription od, bool isWrapped) { IDispatchMessageFormatter xmlFormatter = GetDefaultDispatchFormatter(od, false, isWrapped); if (!SupportsJsonFormat(od)) { return xmlFormatter; } IDispatchMessageFormatter jsonFormatter = GetDefaultDispatchFormatter(od, true, isWrapped); Dictionary map = new Dictionary(); map.Add(WebContentFormat.Xml, xmlFormatter); map.Add(WebContentFormat.Json, jsonFormatter); return new DemultiplexingDispatchMessageFormatter(map, xmlFormatter); } internal WebMessageFormat GetRequestFormat(OperationDescription od) { WebGetAttribute wga = od.Behaviors.Find(); WebInvokeAttribute wia = od.Behaviors.Find(); EnsureOk(wga, wia, od); if (wga != null) { return wga.IsRequestFormatSetExplicitly ? wga.RequestFormat : this.DefaultOutgoingRequestFormat; } else if (wia != null) { return wia.IsRequestFormatSetExplicitly ? wia.RequestFormat : this.DefaultOutgoingRequestFormat; } else { return this.DefaultOutgoingRequestFormat; } } internal WebMessageFormat GetResponseFormat(OperationDescription od) { WebGetAttribute wga = od.Behaviors.Find(); WebInvokeAttribute wia = od.Behaviors.Find(); EnsureOk(wga, wia, od); if (wga != null) { return wga.IsResponseFormatSetExplicitly ? wga.ResponseFormat : this.DefaultOutgoingResponseFormat; } else if (wia != null) { return wia.IsResponseFormatSetExplicitly ? wia.ResponseFormat : this.DefaultOutgoingResponseFormat; } else { return this.DefaultOutgoingResponseFormat; } } void ValidateBodyParameters(OperationDescription operation, bool request) { string method = GetWebMethod(operation); if (request) { ValidateGETHasNoBody(operation, method); } // validate that if bare is chosen for request/response, then at most 1 parameter is possible ValidateBodyStyle(operation, request); // validate if the request or response body is a stream, no other body parameters // can be specified ValidateAtMostOneStreamParameter(operation, request); } void ValidateBodyStyle(OperationDescription operation, bool request) { WebMessageBodyStyle style = GetBodyStyle(operation); Type dummy; if (request && IsBareRequest(style)) { TryGetNonMessageParameterType(operation.Messages[0], operation, true, out dummy); } if (!request && operation.Messages.Count > 1 && IsBareResponse(style)) { TryGetNonMessageParameterType(operation.Messages[1], operation, false, out dummy); } } void ValidateGETHasNoBody(OperationDescription operation, string method) { if (method == GET) { if (!IsUntypedMessage(operation.Messages[0]) && operation.Messages[0].Body.Parts.Count != 0) { if (!IsTypedMessage(operation.Messages[0])) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.GETCannotHaveBody, operation.Name, operation.DeclaringContract.Name, operation.Messages[0].Body.Parts[0].Name))); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.GETCannotHaveMCParameter, operation.Name, operation.DeclaringContract.Name, operation.Messages[0].MessageType.Name))); } } } } void ValidateContract(ServiceEndpoint endpoint) { foreach (OperationDescription od in endpoint.Contract.Operations) { ValidateNoOperationHasEncodedXmlSerializer(od); ValidateNoMessageContractHeaders(od.Messages[0], od.Name, endpoint.Contract.Name); ValidateNoBareMessageContractWithMultipleParts(od.Messages[0], od.Name, endpoint.Contract.Name); ValidateNoMessageContractWithStream(od.Messages[0], od.Name, endpoint.Contract.Name); if (od.Messages.Count > 1) { ValidateNoMessageContractHeaders(od.Messages[1], od.Name, endpoint.Contract.Name); ValidateNoBareMessageContractWithMultipleParts(od.Messages[1], od.Name, endpoint.Contract.Name); ValidateNoMessageContractWithStream(od.Messages[1], od.Name, endpoint.Contract.Name); } } } internal static bool IsXmlSerializerFaultFormat(OperationDescription operationDescription) { XmlSerializerOperationBehavior xsob = operationDescription.Behaviors.Find(); return (xsob != null && xsob.XmlSerializerFormatAttribute.SupportFaults); } void ValidateNoMessageContractWithStream(MessageDescription md, string opName, string contractName) { if (IsTypedMessage(md)) { foreach (MessagePartDescription description in md.Body.Parts) { if (description.Type == typeof(Stream)) { throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException(System.ServiceModel.SR2.GetString(System.ServiceModel.SR2.StreamBodyMemberNotSupported, this.GetType().ToString(), contractName, opName, md.MessageType.ToString(), description.Name))); } } } } void ValidateNoOperationHasEncodedXmlSerializer(OperationDescription od) { XmlSerializerOperationBehavior xsob = od.Behaviors.Find(); if (xsob != null && (xsob.XmlSerializerFormatAttribute.Style == OperationFormatStyle.Rpc || xsob.XmlSerializerFormatAttribute.IsEncoded)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.RpcEncodedNotSupportedForNoneMessageVersion, od.Name, od.DeclaringContract.Name, od.DeclaringContract.Namespace))); } } void ValidateNoBareMessageContractWithMultipleParts(MessageDescription md, string opName, string contractName) { if (IsTypedMessage(md) && md.Body.WrapperName == null) { if (md.Body.Parts.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.InvalidMessageContractWithoutWrapperName, opName, contractName, md.MessageType))); } if (md.Body.Parts.Count == 1 && md.Body.Parts[0].Multiple) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(SR2.MCAtMostOneRequestBodyParameterAllowedForUnwrappedMessages, opName, contractName, md.MessageType))); } } } void ValidateNoMessageContractHeaders(MessageDescription md, string opName, string contractName) { if (md.Headers.Count != 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.InvalidMethodWithSOAPHeaders, opName, contractName))); } } internal class MessagePassthroughFormatter : IClientMessageFormatter, IDispatchMessageFormatter { public object DeserializeReply(Message message, object[] parameters) { return message; } public void DeserializeRequest(Message message, object[] parameters) { parameters[0] = message; } public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result) { return result as Message; } public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) { return parameters[0] as Message; } } static internal JavascriptCallbackResponseMessageProperty TrySetupJavascriptCallback(string callbackParameterName) { JavascriptCallbackResponseMessageProperty javascriptProperty = null; if (!String.IsNullOrEmpty(callbackParameterName) && !OperationContext.Current.OutgoingMessageProperties.TryGetValue(JavascriptCallbackResponseMessageProperty.Name, out javascriptProperty)) { UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch; if (match != null && match.QueryParameters.AllKeys.Contains(callbackParameterName)) { string callbackName = match.QueryParameters[callbackParameterName]; if (!String.IsNullOrEmpty(callbackName)) { javascriptProperty = new JavascriptCallbackResponseMessageProperty { CallbackFunctionName = callbackName }; OperationContext.Current.OutgoingMessageProperties.Add(JavascriptCallbackResponseMessageProperty.Name, javascriptProperty); } } } return javascriptProperty; } } }