//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Dispatcher { using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Runtime; using System.Runtime.Serialization; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Syndication; using System.ServiceModel.Web; using System.Web; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using System.Xml.Serialization; class HelpPage { public const string OperationListHelpPageUriTemplate = "help"; public const string OperationHelpPageUriTemplate = "help/operations/{operation}"; const string HelpMethodName = "GetHelpPage"; const string HelpOperationMethodName = "GetOperationHelpPage"; DateTime startupTime = DateTime.UtcNow; Dictionary operationInfoDictionary; NameValueCache operationPageCache; NameValueCache helpPageCache; public HelpPage(WebHttpBehavior behavior, ContractDescription description) { this.operationInfoDictionary = new Dictionary(); this.operationPageCache = new NameValueCache(); this.helpPageCache = new NameValueCache(); foreach (OperationDescription od in description.Operations) { operationInfoDictionary.Add(od.Name, new OperationHelpInformation(behavior, od)); } } Message GetHelpPage() { Uri baseUri = UriTemplate.RewriteUri(OperationContext.Current.Channel.LocalAddress.Uri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]); string helpPage = this.helpPageCache.Lookup(baseUri.Authority); if (String.IsNullOrEmpty(helpPage)) { helpPage = HelpHtmlBuilder.CreateHelpPage(baseUri, operationInfoDictionary.Values).ToString(); if (HttpContext.Current == null) { this.helpPageCache.AddOrUpdate(baseUri.Authority, helpPage); } } return WebOperationContext.Current.CreateTextResponse(helpPage, "text/html"); } Message GetOperationHelpPage(string operation) { Uri requestUri = UriTemplate.RewriteUri(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]); string helpPage = this.operationPageCache.Lookup(requestUri.AbsoluteUri); if (String.IsNullOrEmpty(helpPage)) { OperationHelpInformation operationInfo; if (this.operationInfoDictionary.TryGetValue(operation, out operationInfo)) { Uri baseUri = UriTemplate.RewriteUri(OperationContext.Current.Channel.LocalAddress.Uri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]); helpPage = HelpHtmlBuilder.CreateOperationHelpPage(baseUri, operationInfo).ToString(); if (HttpContext.Current == null) { this.operationPageCache.AddOrUpdate(requestUri.AbsoluteUri, helpPage); } } else { throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.NotFound)); } } return WebOperationContext.Current.CreateTextResponse(helpPage, "text/html"); } public static IEnumerable> GetOperationTemplatePairs() { return new KeyValuePair[] { new KeyValuePair(new UriTemplate(OperationListHelpPageUriTemplate), HelpMethodName), new KeyValuePair(new UriTemplate(OperationHelpPageUriTemplate), HelpOperationMethodName) }; } public object Invoke(UriTemplateMatch match) { if (HttpContext.Current != null) { HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public); HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.MaxValue); HttpContext.Current.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidationCallback), this.startupTime); HttpContext.Current.Response.Cache.SetValidUntilExpires(true); } switch ((string)match.Data) { case HelpMethodName: return GetHelpPage(); case HelpOperationMethodName: return GetOperationHelpPage(match.BoundVariables["operation"]); default: return null; } } void CacheValidationCallback(HttpContext context, object state, ref HttpValidationStatus result) { if (((DateTime)state) == this.startupTime) { result = HttpValidationStatus.Valid; } else { result = HttpValidationStatus.Invalid; } } } class OperationHelpInformation { OperationDescription od; WebHttpBehavior behavior; MessageHelpInformation request; MessageHelpInformation response; internal OperationHelpInformation(WebHttpBehavior behavior, OperationDescription od) { this.od = od; this.behavior = behavior; } public string Name { get { return od.Name; } } public string UriTemplate { get { return UriTemplateClientFormatter.GetUTStringOrDefault(od); } } public string Method { get { return WebHttpBehavior.GetWebMethod(od); } } public string Description { get { return WebHttpBehavior.GetDescription(od); } } public string JavascriptCallbackParameterName { get { if (this.Response.SupportsJson && this.Method == WebHttpBehavior.GET) { return behavior.JavascriptCallbackParameterName; } return null; } } public WebMessageBodyStyle BodyStyle { get { return behavior.GetBodyStyle(od); } } public MessageHelpInformation Request { get { if (this.request == null) { this.request = new MessageHelpInformation(od, true, GetRequestBodyType(od, this.UriTemplate), this.BodyStyle == WebMessageBodyStyle.WrappedRequest || this.BodyStyle == WebMessageBodyStyle.Wrapped); } return this.request; } } public MessageHelpInformation Response { get { if (this.response == null) { this.response = new MessageHelpInformation(od, false, GetResponseBodyType(od), this.BodyStyle == WebMessageBodyStyle.WrappedResponse || this.BodyStyle == WebMessageBodyStyle.Wrapped); } return this.response; } } static Type GetResponseBodyType(OperationDescription od) { if (WebHttpBehavior.IsUntypedMessage(od.Messages[1])) { return typeof(Message); } else if (WebHttpBehavior.IsTypedMessage(od.Messages[1])) { return od.Messages[1].MessageType; } else if (od.Messages[1].Body.Parts.Count > 0) { // If it is more than 0 the response is wrapped and not supported return null; } else { return (od.Messages[1].Body.ReturnValue.Type); } } static Type GetRequestBodyType(OperationDescription od, string uriTemplate) { if (od.Behaviors.Contains(typeof(WebGetAttribute))) { return typeof(void); } else if (WebHttpBehavior.IsUntypedMessage(od.Messages[0])) { return typeof(Message); } else if (WebHttpBehavior.IsTypedMessage(od.Messages[0])) { return od.Messages[0].MessageType; } else { UriTemplate template = new UriTemplate(uriTemplate); IEnumerable parts = from part in od.Messages[0].Body.Parts where !template.PathSegmentVariableNames.Contains(part.Name.ToUpperInvariant()) && !template.QueryValueVariableNames.Contains(part.Name.ToUpperInvariant()) select part; if (parts.Count() == 1) { return parts.First().Type; } else if (parts.Count() == 0) { return typeof(void); } else { // The request is wrapped and not supported return null; } } } } class MessageHelpInformation { public string BodyDescription { get; private set; } public string FormatString { get; private set; } public Type Type { get; private set; } public bool SupportsJson { get; private set; } public XmlSchemaSet SchemaSet { get; private set; } public XmlSchema Schema { get; private set; } public XElement XmlExample { get; private set; } public XElement JsonExample { get; private set; } internal MessageHelpInformation(OperationDescription od, bool isRequest, Type type, bool wrapped) { this.Type = type; this.SupportsJson = WebHttpBehavior.SupportsJsonFormat(od); string direction = isRequest ? SR2.GetString(SR2.HelpPageRequest) : SR2.GetString(SR2.HelpPageResponse); if (wrapped && !typeof(void).Equals(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageBodyIsWrapped, direction); this.FormatString = SR2.GetString(SR2.HelpPageUnknown); } else if (typeof(void).Equals(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageBodyIsEmpty, direction); this.FormatString = SR2.GetString(SR2.HelpPageNA); } else if (typeof(Message).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsMessage, direction); this.FormatString = SR2.GetString(SR2.HelpPageUnknown); } else if (typeof(Stream).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsStream, direction); this.FormatString = SR2.GetString(SR2.HelpPageUnknown); } else if (typeof(Atom10FeedFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtom10Feed, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(Atom10ItemFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtom10Entry, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(AtomPub10ServiceDocumentFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtomPubServiceDocument, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(AtomPub10CategoriesDocumentFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtomPubCategoriesDocument, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(Rss20FeedFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsRSS20Feed, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(SyndicationFeedFormatter).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsSyndication, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else if (typeof(XElement).IsAssignableFrom(type) || typeof(XmlElement).IsAssignableFrom(type)) { this.BodyDescription = SR2.GetString(SR2.HelpPageIsXML, direction); this.FormatString = WebMessageFormat.Xml.ToString(); } else { try { bool usesXmlSerializer = od.Behaviors.Contains(typeof(XmlSerializerOperationBehavior)); XmlQualifiedName name; this.SchemaSet = new XmlSchemaSet(); IDictionary knownTypes = new Dictionary(); if (usesXmlSerializer) { XmlReflectionImporter importer = new XmlReflectionImporter(); XmlTypeMapping typeMapping = importer.ImportTypeMapping(this.Type); name = new XmlQualifiedName(typeMapping.ElementName, typeMapping.Namespace); XmlSchemas schemas = new XmlSchemas(); XmlSchemaExporter exporter = new XmlSchemaExporter(schemas); exporter.ExportTypeMapping(typeMapping); foreach (XmlSchema schema in schemas) { this.SchemaSet.Add(schema); } } else { XsdDataContractExporter exporter = new XsdDataContractExporter(); List listTypes = new List(od.KnownTypes); bool isQueryable; Type dataContractType = DataContractSerializerOperationFormatter.GetSubstituteDataContractType(this.Type, out isQueryable); listTypes.Add(dataContractType); exporter.Export(listTypes); if (!exporter.CanExport(dataContractType)) { this.BodyDescription = SR2.GetString(SR2.HelpPageCouldNotGenerateSchema); this.FormatString = SR2.GetString(SR2.HelpPageUnknown); return; } name = exporter.GetRootElementName(dataContractType); DataContract typeDataContract = DataContract.GetDataContract(dataContractType); if (typeDataContract.KnownDataContracts != null) { foreach (XmlQualifiedName dataContractName in typeDataContract.KnownDataContracts.Keys) { knownTypes.Add(dataContractName, typeDataContract.KnownDataContracts[dataContractName].UnderlyingType); } } foreach (Type knownType in od.KnownTypes) { XmlQualifiedName knownTypeName = exporter.GetSchemaTypeName(knownType); if (!knownTypes.ContainsKey(knownTypeName)) { knownTypes.Add(knownTypeName, knownType); } } foreach (XmlSchema schema in exporter.Schemas.Schemas()) { this.SchemaSet.Add(schema); } } this.SchemaSet.Compile(); XmlWriterSettings settings = new XmlWriterSettings { CloseOutput = false, Indent = true, }; if (this.SupportsJson) { XDocument exampleDocument = new XDocument(); using (XmlWriter writer = XmlWriter.Create(exampleDocument.CreateWriter(), settings)) { HelpExampleGenerator.GenerateJsonSample(this.SchemaSet, name, writer, knownTypes); } this.JsonExample = exampleDocument.Root; } if (name.Namespace != "http://schemas.microsoft.com/2003/10/Serialization/") { foreach (XmlSchema schema in this.SchemaSet.Schemas(name.Namespace)) { this.Schema = schema; } } XDocument XmlExampleDocument = new XDocument(); using (XmlWriter writer = XmlWriter.Create(XmlExampleDocument.CreateWriter(), settings)) { HelpExampleGenerator.GenerateXmlSample(this.SchemaSet, name, writer); } this.XmlExample = XmlExampleDocument.Root; } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.BodyDescription = SR2.GetString(SR2.HelpPageCouldNotGenerateSchema); this.FormatString = SR2.GetString(SR2.HelpPageUnknown); this.Schema = null; this.JsonExample = null; this.XmlExample = null; } } } } }