1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
4 #pragma warning disable 1634, 1691
5 namespace System.ServiceModel.Description
8 using System.Diagnostics;
9 using System.Globalization;
11 using System.Runtime.Serialization;
12 using System.Runtime.Serialization.Json;
13 using System.Security;
14 using System.ServiceModel;
15 using System.ServiceModel.Activation;
16 using System.ServiceModel.Channels;
17 using System.ServiceModel.Dispatcher;
18 using System.ServiceModel.Web;
21 public sealed class WebScriptEnablingBehavior : WebHttpBehavior
23 static readonly DataContractJsonSerializer jsonFaultSerializer = new DataContractJsonSerializer(typeof(JsonFaultDetail));
24 static readonly WebMessageBodyStyle webScriptBodyStyle = WebMessageBodyStyle.WrappedRequest;
25 static readonly WebMessageFormat webScriptDefaultMessageFormat = WebMessageFormat.Json;
26 const int MaxMetadataEndpointBufferSize = 2048;
27 WebMessageFormat requestMessageFormat = webScriptDefaultMessageFormat;
28 WebMessageFormat responseMessageFormat = webScriptDefaultMessageFormat;
30 public WebScriptEnablingBehavior()
34 public override WebMessageBodyStyle DefaultBodyStyle
38 return webScriptBodyStyle;
42 if (value != webScriptBodyStyle)
44 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.BodyStyleNotSupportedByWebScript, value, this.GetType().Name, webScriptBodyStyle)));
49 public override WebMessageFormat DefaultOutgoingRequestFormat
53 return this.requestMessageFormat;
57 if (!WebMessageFormatHelper.IsDefined(value))
59 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
61 this.requestMessageFormat = value;
65 public override WebMessageFormat DefaultOutgoingResponseFormat
69 return this.responseMessageFormat;
73 if (!WebMessageFormatHelper.IsDefined(value))
75 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
77 this.responseMessageFormat = value;
81 public override bool HelpEnabled
91 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.HelpPageNotSupportedInScripts)));
96 public override bool AutomaticFormatSelectionEnabled
106 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.AutomaticFormatSelectionNotSupportedInScripts)));
111 public override bool FaultExceptionEnabled
121 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.FaultExceptionEnabledNotSupportedInScripts)));
126 public override void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
128 base.ApplyClientBehavior(endpoint, clientRuntime);
129 #pragma warning disable 56506 // Microsoft, clientRuntime.MessageInspectors is never null
130 clientRuntime.MessageInspectors.Add(new JsonClientMessageInspector());
131 #pragma warning restore 56506
134 public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
136 base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
140 AddMetadataEndpoint(endpoint, endpointDispatcher, false); // debugMode
141 AddMetadataEndpoint(endpoint, endpointDispatcher, true); // debugMode
143 catch (XmlException exception)
145 // Microsoft, need to reference this resource string although fix for 13332 was removed
146 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.InvalidXmlCharactersInNameUsedWithPOSTMethod, string.Empty, string.Empty, string.Empty), exception));
150 public override void Validate(ServiceEndpoint endpoint)
152 base.Validate(endpoint);
154 #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null
155 foreach (OperationDescription operation in endpoint.Contract.Operations)
156 #pragma warning restore 56506
158 if (operation.Behaviors.Find<XmlSerializerOperationBehavior>() != null)
160 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
161 SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(XmlSerializerFormatAttribute).Name, this.GetType().ToString())));
163 string method = WebHttpBehavior.GetWebMethod(operation);
164 if (method != WebHttpBehavior.GET
165 && method != WebHttpBehavior.POST)
167 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
168 SR2.GetString(SR2.WebScriptInvalidHttpRequestMethod, operation.Name,
169 endpoint.Contract.Name, method, this.GetType().ToString())));
171 WebGetAttribute webGetAttribute = operation.Behaviors.Find<WebGetAttribute>();
172 if (webGetAttribute != null && webGetAttribute.UriTemplate != null)
174 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
175 SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(UriTemplate).Name, this.GetType().ToString())));
177 WebInvokeAttribute webInvokeAttribute = operation.Behaviors.Find<WebInvokeAttribute>();
178 if (webInvokeAttribute != null && webInvokeAttribute.UriTemplate != null)
180 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
181 SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(UriTemplate).Name, this.GetType().ToString())));
183 WebMessageBodyStyle bodyStyle = GetBodyStyle(operation);
184 if (bodyStyle != webScriptBodyStyle)
186 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.BodyStyleNotSupportedByWebScript, bodyStyle, this.GetType().Name, webScriptBodyStyle)));
189 foreach (MessageDescription messageDescription in operation.Messages)
191 if (!messageDescription.IsTypedMessage &&
192 (messageDescription.Direction == MessageDirection.Output) &&
193 (messageDescription.Body.Parts.Count > 0))
195 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
196 SR2.GetString(SR2.WebScriptOutRefOperationsNotSupported, operation.Name,
197 endpoint.Contract.Name)));
203 internal override DataContractJsonSerializerOperationFormatter CreateDataContractJsonSerializerOperationFormatter(OperationDescription od, DataContractSerializerOperationBehavior dcsob, bool isWrapped)
205 return new DataContractJsonSerializerOperationFormatter(od, dcsob.MaxItemsInObjectGraph, dcsob.IgnoreExtensionDataObject, dcsob.DataContractSurrogate, isWrapped, true, this.JavascriptCallbackParameterName);
208 internal override string GetWmiTypeName()
210 return "WebScriptEnablingBehavior";
213 internal override bool UseBareReplyFormatter(WebMessageBodyStyle style, OperationDescription operationDescription, WebMessageFormat responseFormat, out Type parameterType)
215 if (responseFormat == WebMessageFormat.Json)
217 parameterType = null;
220 return base.UseBareReplyFormatter(style, operationDescription, responseFormat, out parameterType);
223 protected override void AddClientErrorInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
225 clientRuntime.MessageInspectors.Add(new JsonClientMessageInspector());
228 protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
230 if (endpointDispatcher.ChannelDispatcher == null)
232 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(
233 "endpointDispatcher", SR2.GetString(SR2.ChannelDispatcherMustBePresent));
235 #pragma warning disable 56506 // Microsoft, endpointDispatcher.ChannelDispatcher.ErrorHandlers never null
236 endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler(endpoint, endpointDispatcher.ChannelDispatcher.IncludeExceptionDetailInFaults));
237 #pragma warning restore 56506
240 protected override QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
242 return new JsonQueryStringConverter(operationDescription);
245 void AddMetadataEndpoint(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher, bool debugMode)
247 Uri baseAddress = endpoint.Address.Uri;
248 if (baseAddress == null)
253 ServiceHostBase host = endpointDispatcher.ChannelDispatcher.Host;
255 UriBuilder builder = new UriBuilder(baseAddress);
256 builder.Path += builder.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase)
257 ? (WebScriptClientGenerator.GetMetadataEndpointSuffix(debugMode))
258 : ("/" + WebScriptClientGenerator.GetMetadataEndpointSuffix(debugMode));
259 EndpointAddress metadataAddress = new EndpointAddress(builder.Uri);
261 foreach (ServiceEndpoint serviceEndpoint in host.Description.Endpoints)
263 if (EndpointAddress.UriEquals(serviceEndpoint.Address.Uri, metadataAddress.Uri, true, false))// ignoreCase // includeHostNameInComparison
265 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
266 new InvalidOperationException(SR2.GetString(SR2.JsonNoEndpointAtMetadataAddress, this.GetType().ToString(), serviceEndpoint.Address, serviceEndpoint.Name, host.Description.Name)));
270 HttpTransportBindingElement transportBindingElement;
271 HttpTransportBindingElement existingTransportBindingElement = endpoint.Binding.CreateBindingElements().Find<HttpTransportBindingElement>();
273 if (existingTransportBindingElement != null)
275 transportBindingElement = (HttpTransportBindingElement)existingTransportBindingElement.Clone();
279 if (baseAddress.Scheme == "https")
281 transportBindingElement = new HttpsTransportBindingElement();
285 transportBindingElement = new HttpTransportBindingElement();
289 transportBindingElement.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
290 transportBindingElement.TransferMode = TransferMode.Buffered;
291 transportBindingElement.MaxBufferSize = MaxMetadataEndpointBufferSize;
292 transportBindingElement.MaxReceivedMessageSize = MaxMetadataEndpointBufferSize;
293 Binding metadataBinding = new CustomBinding(
294 new WebScriptMetadataMessageEncodingBindingElement(),
295 transportBindingElement);
296 BindingParameterCollection parameters = host.GetBindingParameters(endpoint);
298 // build endpoint dispatcher
299 ContractDescription metadataContract = ContractDescription.GetContract(typeof(ServiceMetadataExtension.IHttpGetMetadata));
300 OperationDescription metadataOperation = metadataContract.Operations[0];
301 EndpointDispatcher metadataEndpointDispatcher = new EndpointDispatcher(metadataAddress, metadataContract.Name, metadataContract.Namespace);
302 DispatchOperation dispatchOperation = new DispatchOperation(metadataEndpointDispatcher.DispatchRuntime, metadataOperation.Name, metadataOperation.Messages[0].Action, metadataOperation.Messages[1].Action);
303 dispatchOperation.Formatter = new WebScriptMetadataFormatter();
304 dispatchOperation.Invoker = new SyncMethodInvoker(metadataOperation.SyncMethod);
305 metadataEndpointDispatcher.DispatchRuntime.Operations.Add(dispatchOperation);
306 metadataEndpointDispatcher.DispatchRuntime.SingletonInstanceContext = new InstanceContext(host, new WebScriptClientGenerator(endpoint, debugMode, !String.IsNullOrEmpty(this.JavascriptCallbackParameterName)));
307 metadataEndpointDispatcher.DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider(metadataEndpointDispatcher.DispatchRuntime);
309 // build channel dispatcher
310 IChannelListener<IReplyChannel> listener = null;
311 if (metadataBinding.CanBuildChannelListener<IReplyChannel>(parameters))
313 listener = metadataBinding.BuildChannelListener<IReplyChannel>(metadataAddress.Uri, parameters);
315 ChannelDispatcher metadataChannelDispatcher = new ChannelDispatcher(listener);
316 metadataChannelDispatcher.MessageVersion = MessageVersion.None;
317 metadataChannelDispatcher.Endpoints.Add(metadataEndpointDispatcher);
319 host.ChannelDispatchers.Add(metadataChannelDispatcher);
322 class JsonClientMessageInspector : WebFaultClientMessageInspector
324 public override void AfterReceiveReply(ref Message reply, object correlationState)
326 bool callBase = true;
329 object responseProperty = reply.Properties[HttpResponseMessageProperty.Name];
330 if (responseProperty != null)
332 if (((HttpResponseMessageProperty)responseProperty).Headers[JsonGlobals.jsonerrorString] == JsonGlobals.trueString)
335 XmlDictionaryReader reader = reply.GetReaderAtBodyContents();
336 JsonFaultDetail faultDetail = jsonFaultSerializer.ReadObject(reader) as JsonFaultDetail;
337 FaultCode faultCode = new FaultCode(FaultCodeConstants.Codes.InternalServiceFault, FaultCodeConstants.Namespaces.NetDispatch);
338 faultCode = FaultCode.CreateReceiverFaultCode(faultCode);
339 if (faultDetail != null)
341 if (faultDetail.ExceptionDetail != null)
343 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
344 new FaultException<ExceptionDetail>(faultDetail.ExceptionDetail, faultDetail.Message, faultCode));
348 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
349 new FaultException(MessageFault.CreateFault(faultCode, faultDetail.Message)));
354 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
355 new FaultException(MessageFault.CreateFault(faultCode,
356 System.ServiceModel.SR.GetString(System.ServiceModel.SR.SFxInternalServerError))));
363 base.AfterReceiveReply(ref reply, correlationState);
368 class JsonErrorHandler : IErrorHandler
370 bool includeExceptionDetailInFaults;
371 string outgoingContentType;
373 public JsonErrorHandler(ServiceEndpoint endpoint, bool includeExceptionDetailInFaults)
375 WebMessageEncodingBindingElement webMEBE = endpoint.Binding.CreateBindingElements().Find<WebMessageEncodingBindingElement>();
376 outgoingContentType = JsonMessageEncoderFactory.GetContentType(webMEBE);
377 this.includeExceptionDetailInFaults = includeExceptionDetailInFaults;
380 public bool HandleError(Exception error)
385 public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
387 HttpResponseMessageProperty responseProperty;
390 FaultCode code = new FaultCode(FaultCodeConstants.Codes.InternalServiceFault, FaultCodeConstants.Namespaces.NetDispatch);
391 code = FaultCode.CreateReceiverFaultCode(code);
392 string action = FaultCodeConstants.Actions.NetDispatcher;
394 MessageFault innerFault;
395 innerFault = MessageFault.CreateFault(code, new FaultReason(error.Message, CultureInfo.CurrentCulture), new ExceptionDetail(error));
396 fault = Message.CreateMessage(version, action, new JsonFaultBodyWriter(innerFault, this.includeExceptionDetailInFaults));
398 responseProperty = new HttpResponseMessageProperty();
399 fault.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
403 MessageFault innerFault = MessageFault.CreateFault(fault, TransportDefaults.MaxFaultSize);
404 Message newMessage = Message.CreateMessage(version, fault.Headers.Action, new JsonFaultBodyWriter(innerFault, this.includeExceptionDetailInFaults));
405 newMessage.Headers.To = fault.Headers.To;
406 newMessage.Properties.CopyProperties(fault.Properties);
408 object property = null;
409 if (newMessage.Properties.TryGetValue(HttpResponseMessageProperty.Name, out property))
411 responseProperty = (HttpResponseMessageProperty)property;
415 responseProperty = new HttpResponseMessageProperty();
416 newMessage.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
422 responseProperty.Headers.Add(HttpResponseHeader.ContentType, outgoingContentType);
423 responseProperty.Headers.Add(JsonGlobals.jsonerrorString, JsonGlobals.trueString);
424 responseProperty.StatusCode = System.Net.HttpStatusCode.InternalServerError;
426 object bodyFormatPropertyObject;
427 if (fault.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatPropertyObject))
429 WebBodyFormatMessageProperty bodyFormatProperty = bodyFormatPropertyObject as WebBodyFormatMessageProperty;
430 if ((bodyFormatProperty == null) ||
431 (bodyFormatProperty.Format != WebContentFormat.Json))
433 fault.Properties[WebBodyFormatMessageProperty.Name] = WebBodyFormatMessageProperty.JsonProperty;
438 fault.Properties.Add(WebBodyFormatMessageProperty.Name, WebBodyFormatMessageProperty.JsonProperty);
442 class JsonFaultBodyWriter : BodyWriter
444 JsonFaultDetail faultDetail;
446 public JsonFaultBodyWriter(MessageFault fault, bool includeExceptionDetailInFaults)
449 faultDetail = new JsonFaultDetail();
450 if (includeExceptionDetailInFaults)
452 faultDetail.Message = fault.Reason.ToString();
457 ExceptionDetail originalFaultDetail = fault.GetDetail<ExceptionDetail>();
458 faultDetail.StackTrace = originalFaultDetail.StackTrace;
459 faultDetail.ExceptionType = originalFaultDetail.Type;
460 faultDetail.ExceptionDetail = originalFaultDetail;
462 catch (SerializationException exception)
464 System.ServiceModel.DiagnosticUtility.TraceHandledException(exception, TraceEventType.Information);
465 // A SerializationException will be thrown if the detail isn't of type ExceptionDetail
466 // In that case, we want to just move on.
468 catch (SecurityException exception)
470 System.ServiceModel.DiagnosticUtility.TraceHandledException(exception, TraceEventType.Information);
471 // A SecurityException will be thrown if the detail can't be obtained in partial trust
472 // (This is guaranteed to happen unless there's an Assert for MemberAccessPermission, since ExceptionDetail
473 // has DataMembers that have private setters.)
474 // In that case, we want to just move on.
480 faultDetail.Message = System.ServiceModel.SR.GetString(System.ServiceModel.SR.SFxInternalServerError);
484 protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
486 jsonFaultSerializer.WriteObject(writer, faultDetail);