File: System\ServiceModel\Dispatcher\HelpPage.cs
Project: ndp\cdf\src\NetFx35\System.ServiceModel.Web\System.ServiceModel.Web.csproj (System.ServiceModel.Web)
//-----------------------------------------------------------------------------
// 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<string, OperationHelpInformation> operationInfoDictionary;
        NameValueCache<string> operationPageCache;
        NameValueCache<string> helpPageCache;
 
        public HelpPage(WebHttpBehavior behavior, ContractDescription description)
        {
            this.operationInfoDictionary = new Dictionary<string, OperationHelpInformation>();
            this.operationPageCache = new NameValueCache<string>();
            this.helpPageCache = new NameValueCache<string>();
            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<KeyValuePair<UriTemplate, object>> GetOperationTemplatePairs()
        {
            return new KeyValuePair<UriTemplate, object>[]
            {
                new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationListHelpPageUriTemplate), HelpMethodName),
                new KeyValuePair<UriTemplate, object>(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<MessagePartDescription> 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<XmlQualifiedName, Type> knownTypes = new Dictionary<XmlQualifiedName, Type>();
                    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<Type> listTypes = new List<Type>(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;
                }
            }
        }
    }
}