PageRenderTime 65ms CodeModel.GetById 19ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 1ms

/WCFWebApi/src/Microsoft.ApplicationServer.Http/Microsoft/ApplicationServer/Http/Dispatcher/HelpPage.cs

#
C# | 1645 lines | 1483 code | 138 blank | 24 comment | 241 complexity | 9f683ae33460c35c409f396d04142923 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1// <copyright>
  2//   Copyright (c) Microsoft Corporation.  All rights reserved.
  3// </copyright>
  4
  5namespace Microsoft.ApplicationServer.Http.Dispatcher
  6{
  7    using System;
  8    using System.Collections;
  9    using System.Collections.Generic;
 10    using System.Diagnostics.CodeAnalysis;
 11    using System.Globalization;
 12    using System.IO;
 13    using System.Linq;
 14    using System.Net;
 15    using System.Net.Http;
 16    using System.Net.Http.Formatting;
 17    using System.Runtime.Serialization;
 18    using System.Runtime.Serialization.Json;
 19    using System.ServiceModel.Channels;
 20    using System.ServiceModel.Description;
 21    using System.ServiceModel.Dispatcher;
 22    using System.ServiceModel.Syndication;
 23    using System.ServiceModel.Web;
 24    using System.Text;
 25    using System.Web;
 26    using System.Xml;
 27    using System.Xml.Linq;
 28    using System.Xml.Schema;
 29    using System.Xml.Serialization;
 30    using Microsoft.Server.Common;
 31    using Microsoft.ApplicationServer.Http.Description;
 32    using Microsoft.Runtime.Serialization;
 33    using Microsoft.Runtime.Serialization.Json;
 34
 35    internal class HelpPage
 36    {
 37        public const string OperationListHelpPageUriTemplate = "help";
 38        public const string OperationHelpPageUriTemplateBase = "help/operations/";
 39        public const string OperationHelpPageUriTemplate = "help/operations/{operation}";
 40        public const string HelpMethodName = "GetHelpPage";
 41        public const string HelpOperationMethodName = "GetOperationHelpPage";
 42
 43        private static readonly Dictionary<string, string> ConstantValues = new Dictionary<string, string>()
 44        {
 45            { "anyURI", "http://www.example.com/" },
 46            { "base64Binary", "QmFzZSA2NCBTdHJlYW0=" },
 47            { "boolean", "true" },
 48            { "byte", "127" },
 49            { "date", "1999-05-31" },
 50            { "decimal", "12678967.543233" },
 51            { "double", "1.26743233E+15" },
 52            { "duration", "P428DT10H30M12.3S" },
 53            { "ENTITY", "NCNameString" },
 54            { "float", "1.26743237E+15" },
 55            { "gDay", "---31" },
 56            { "gMonth", "--05" },
 57            { "gMonthDay", "--05-31" },
 58            { "guid", "1627aea5-8e0a-4371-9022-9b504344e724" },
 59            { "gYear", "1999" },
 60            { "gYearMonth", "1999-02" },
 61            { "hexBinary", "GpM7" },
 62            { "ID", "NCNameString" },
 63            { "IDREF", "NCNameString" },
 64            { "IDREFS", "NCNameString" },
 65            { "int", "2147483647" },
 66            { "integer", "2147483647" },
 67            { "language", "Http.SR.HelpExampleGeneratorLanguage" },
 68            { "long", "9223372036854775807" },
 69            { "Name", "Name" },
 70            { "NCName", "NCNameString" },
 71            { "negativeInteger", "-12678967543233" },
 72            { "NMTOKEN", Http.SR.HelpExampleGeneratorStringContent },
 73            { "NMTOKENS", Http.SR.HelpExampleGeneratorStringContent },
 74            { "nonNegativeInteger", "+2147483647" },
 75            { "nonPositiveInteger", "-12678967543233" },
 76            { "normalizedString", Http.SR.HelpExampleGeneratorStringContent },
 77            { "NOTATION", "namespace:Name" },
 78            { "positiveInteger", "+2147483647" },
 79            { "QName", "namespace:Name" },
 80            { "short", "32767" },
 81            { "string", Http.SR.HelpExampleGeneratorStringContent },
 82            { "time", "13:20:00.000, 13:20:00.000-05:00" },
 83            { "token", Http.SR.HelpExampleGeneratorStringContent },
 84            { "unsignedByte", "255" },
 85            { "unsignedInt", "4294967295" },
 86            { "unsignedLong", "18446744073709551615" },
 87            { "unsignedShort", "65535" },
 88        };
 89
 90        private static readonly Encoding utf8Encoding = new UTF8Encoding(true);
 91        private DateTime startupTime = DateTime.UtcNow;
 92        private Uri baseAddress;
 93        private Dictionary<string, OperationHelpInformation> operationInfoDictionary;
 94        private NameValueCache<string> operationPageCache;
 95        private NameValueCache<string> helpPageCache;
 96
 97        public HelpPage(Uri baseAddress, string javascriptCallbackParameterName, ContractDescription description)
 98        {
 99            Fx.Assert(baseAddress != null, "The 'baseAddress' parameter should not be null.");
100
101            this.baseAddress = baseAddress;
102
103            this.operationInfoDictionary = new Dictionary<string, OperationHelpInformation>();
104            this.operationPageCache = new NameValueCache<string>();
105            this.helpPageCache = new NameValueCache<string>();
106
107            foreach (OperationDescription od in description.Operations)
108            {
109                HttpOperationDescription httpOperationDescription = od.ToHttpOperationDescription();
110                operationInfoDictionary.Add(
111                    httpOperationDescription.Name,
112                    new OperationHelpInformation(javascriptCallbackParameterName, WebMessageBodyStyle.Bare, httpOperationDescription));
113            }
114        }
115
116        public static OperationDescription[] AddHelpOperations(ContractDescription contractDescription, DispatchRuntime dispatchRuntime)
117        {
118            Fx.Assert(contractDescription != null, "The 'contractDescription' parameter should not be null.");
119            Fx.Assert(dispatchRuntime != null, "The 'dispatchRuntime' parameter should not be null.");
120
121            Uri baseAddress = dispatchRuntime.EndpointDispatcher.EndpointAddress.Uri;
122            HelpPage helpPage = new HelpPage(baseAddress, null, contractDescription);
123
124            HttpOperationDescription helpPageOperation = new HttpOperationDescription(HelpPage.HelpMethodName, contractDescription);
125            helpPageOperation.Behaviors.Add(new WebGetAttribute() { UriTemplate = HelpPage.OperationListHelpPageUriTemplate });
126            helpPageOperation.InputParameters.Add(HttpParameter.RequestMessage);
127            helpPageOperation.ReturnValue = HttpParameter.ResponseMessage;
128
129            HttpOperationDescription operationhelpPageOperation = new HttpOperationDescription(HelpPage.HelpOperationMethodName, contractDescription);
130            operationhelpPageOperation.Behaviors.Add(new WebGetAttribute() { UriTemplate = HelpPage.OperationHelpPageUriTemplate });
131            operationhelpPageOperation.InputParameters.Add(HttpParameter.RequestMessage);
132            operationhelpPageOperation.InputParameters.Add(new HttpParameter("operation", TypeHelper.StringType));
133            operationhelpPageOperation.ReturnValue = HttpParameter.ResponseMessage;
134
135            dispatchRuntime.Operations.Add(
136                new DispatchOperation(dispatchRuntime, HelpPage.HelpMethodName, null, null) { Invoker = helpPage.GetHelpPageInvoker(true) });
137            dispatchRuntime.Operations.Add(
138                new DispatchOperation(dispatchRuntime, HelpPage.HelpOperationMethodName, null, null) { Invoker = helpPage.GetHelpPageInvoker(false) });
139
140            return new OperationDescription[] { helpPageOperation.ToOperationDescription(), operationhelpPageOperation.ToOperationDescription() };
141        }
142
143        public static IEnumerable<KeyValuePair<UriTemplate, object>> GetOperationTemplatePairs()
144        {
145            return new KeyValuePair<UriTemplate, object>[]
146            {
147                new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationListHelpPageUriTemplate), HelpMethodName),
148                new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationHelpPageUriTemplate), HelpOperationMethodName)
149            };
150        }
151
152        public IOperationInvoker GetHelpPageInvoker(bool invokerForOperationListHelpPage)
153        {
154            return new HttpHelpOperationInvoker(this, invokerForOperationListHelpPage);
155        }
156
157        public void InvokeHelpPage(HttpResponseMessage response)
158        {
159            ApplyCaching();
160
161            Uri baseUri = this.baseAddress.GetHostNormalizedUri(response.RequestMessage);
162
163            string helpPage = this.helpPageCache.Lookup(baseUri.Authority);
164
165            if (string.IsNullOrEmpty(helpPage))
166            {
167                helpPage = HelpHtmlPageBuilder.CreateHelpPage(baseUri, operationInfoDictionary.Values).ToString();
168                if (HttpContext.Current == null)
169                {
170                    this.helpPageCache.AddOrUpdate(baseUri.Authority, helpPage);
171                }
172            }
173
174            response.Content = new ActionOfStreamContent(
175                stream =>
176                {
177                    byte[] preamble = utf8Encoding.GetPreamble();
178                    stream.Write(preamble, 0, preamble.Length);
179                    byte[] bytes = utf8Encoding.GetBytes(helpPage);
180                    stream.Write(bytes, 0, bytes.Length);
181                });
182
183            response.Content.Headers.ContentType = MediaTypeConstants.HtmlMediaType;
184        }
185
186        public void InvokeOperationHelpPage(string operationName, HttpResponseMessage response)
187        {
188            ApplyCaching();
189
190            Uri requestUri = response.RequestMessage.RequestUri.GetHostNormalizedUri(response.RequestMessage);
191            string helpPage = this.operationPageCache.Lookup(requestUri.AbsoluteUri);
192
193            if (string.IsNullOrEmpty(helpPage))
194            {
195                OperationHelpInformation operationInfo;
196                if (this.operationInfoDictionary.TryGetValue(operationName, out operationInfo))
197                {
198                    Uri baseUri = this.baseAddress.GetHostNormalizedUri(response.RequestMessage);
199                    helpPage = HelpHtmlPageBuilder.CreateOperationHelpPage(baseUri, operationInfo).ToString();
200                    if (HttpContext.Current == null)
201                    {
202                        this.operationPageCache.AddOrUpdate(requestUri.AbsoluteUri, helpPage);
203                    }
204                }
205                else
206                {
207                    throw Fx.Exception.AsError(new WebFaultException(HttpStatusCode.NotFound));
208                }
209            }
210
211            response.Content = new ActionOfStreamContent(
212                stream =>
213                {
214                    byte[] preamble = utf8Encoding.GetPreamble();
215                    stream.Write(preamble, 0, preamble.Length);
216                    byte[] bytes = utf8Encoding.GetBytes(helpPage);
217                    stream.Write(bytes, 0, bytes.Length);
218                });
219            response.Content.Headers.ContentType = MediaTypeConstants.HtmlMediaType;
220        }
221
222        private void ApplyCaching()
223        {
224            if (HttpContext.Current != null)
225            {
226                HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
227                HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.MaxValue);
228                HttpContext.Current.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidationCallback), this.startupTime);
229                HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
230            }
231        }
232
233        private void CacheValidationCallback(HttpContext context, object state, ref HttpValidationStatus result)
234        {
235            if (((DateTime)state) == this.startupTime)
236            {
237                result = HttpValidationStatus.Valid;
238            }
239            else
240            {
241                result = HttpValidationStatus.Invalid;
242            }
243        }
244
245        private class NameValueCache<T>
246        {
247            // The NameValueCache implements a structure that uses a dictionary to map objects to
248            // indices of an array of cache entries.  This allows us to store the cache entries in 
249            // the order in which they were added to the cache, and yet still lookup any cache entry.
250            // The eviction policy of the cache is to evict the least-recently-added cache entry.  
251            // Using a pointer to the next available cache entry in the array, we can always be sure 
252            // that the given entry is the oldest entry. 
253            private Hashtable cache;
254            private string[] currentKeys;
255            private int nextAvailableCacheIndex;
256            private object cachelock;
257            internal const int maxNumberofEntriesInCache = 16;
258
259            public NameValueCache()
260                : this(maxNumberofEntriesInCache)
261            {
262            }
263
264            public NameValueCache(int maxCacheEntries)
265            {
266                cache = new Hashtable();
267                currentKeys = new string[maxCacheEntries];
268                cachelock = new object();
269            }
270
271            public T Lookup(string key)
272            {
273                return (T)cache[key];
274            }
275
276            public void AddOrUpdate(string key, T value)
277            {
278                lock (this.cachelock)
279                {
280                    if (!cache.ContainsKey(key))
281                    {
282                        if (!String.IsNullOrEmpty(currentKeys[nextAvailableCacheIndex]))
283                        {
284                            cache.Remove(currentKeys[nextAvailableCacheIndex]);
285                        }
286                        currentKeys[nextAvailableCacheIndex] = key;
287                        nextAvailableCacheIndex = ++nextAvailableCacheIndex % currentKeys.Length;
288                    }
289                    cache[key] = value;
290                }
291            }
292        }
293
294        private class OperationHelpInformation
295        {
296            HttpOperationDescription httpOperationDescription;
297            MessageHelpInformation request;
298            MessageHelpInformation response;
299            string javascriptCallbackParameterName;
300            WebMessageBodyStyle bodyStyle;
301
302            internal OperationHelpInformation(string javascriptCallbackParameterName, WebMessageBodyStyle bodyStyle, HttpOperationDescription httpOperationDescription)
303            {
304                this.httpOperationDescription = httpOperationDescription;
305                this.javascriptCallbackParameterName = javascriptCallbackParameterName;
306                this.bodyStyle = bodyStyle;
307            }
308
309            public string Name
310            {
311                get
312                {
313                    return httpOperationDescription.Name;
314                }
315            }
316
317            public string UriTemplate
318            {
319                get
320                {
321                    return httpOperationDescription.GetUriTemplate().ToString();
322                }
323            }
324
325            public string Method
326            {
327                get
328                {
329                    return httpOperationDescription.GetHttpMethod().Method;
330                }
331            }
332
333            public string Description
334            {
335                get
336                {
337                    return httpOperationDescription.GetDescription();
338                }
339            }
340
341            public string JavascriptCallbackParameterName
342            {
343                get
344                {
345                    if (this.Response.SupportsJson && string.Equals(this.Method, HttpMethod.Get.ToString()))
346                    {
347                        return this.javascriptCallbackParameterName;
348                    }
349                    return null;
350                }
351            }
352
353            public WebMessageBodyStyle BodyStyle
354            {
355                get
356                {
357                    return this.bodyStyle;
358                }
359            }
360
361            public MessageHelpInformation Request
362            {
363                get
364                {
365                    if (this.request == null)
366                    {
367                        this.request = new MessageHelpInformation(httpOperationDescription, true, GetRequestBodyType(httpOperationDescription, this.UriTemplate),
368                            this.BodyStyle == WebMessageBodyStyle.WrappedRequest || this.BodyStyle == WebMessageBodyStyle.Wrapped);
369                    }
370                    return this.request;
371                }
372            }
373
374            public MessageHelpInformation Response
375            {
376                get
377                {
378                    if (this.response == null)
379                    {
380                        this.response = new MessageHelpInformation(httpOperationDescription, false, GetResponseBodyType(httpOperationDescription),
381                            this.BodyStyle == WebMessageBodyStyle.WrappedResponse || this.BodyStyle == WebMessageBodyStyle.Wrapped);
382                    }
383                    return this.response;
384                }
385            }
386
387            public static Type GetResponseBodyType(HttpOperationDescription description)
388            {
389                if (description.OutputParameters.Count > 0)
390                {
391                    // If it is more than 0 the response is wrapped and not supported
392                    return null;
393                }
394
395                HttpParameter returnDescription = description.ReturnValue;
396
397                return returnDescription == null ? null : returnDescription.ParameterType;
398            }
399
400            public static Type GetRequestBodyType(HttpOperationDescription description, string uriTemplate)
401            {
402                if (description.Behaviors.Contains(typeof(WebGetAttribute)))
403                {
404                    return typeof(void);
405                }
406
407                UriTemplate template = new UriTemplate(uriTemplate);
408                IEnumerable<HttpParameter> parameterDescriptions =
409                    from parameterDescription in description.InputParameters
410                    where !template.PathSegmentVariableNames.Contains(parameterDescription.Name.ToUpperInvariant()) && !template.QueryValueVariableNames.Contains(parameterDescription.Name.ToUpperInvariant())
411                    select parameterDescription;
412
413                HttpParameter[] matchingParameters = parameterDescriptions.ToArray();
414
415                // No match == void
416                // One match == the body type
417                // Multiple matches == the request is wrapped and not supported
418                return matchingParameters.Length == 0
419                        ? typeof(void)
420                        : (matchingParameters.Length == 1)
421                            ? matchingParameters[0].ParameterType
422                            : null;
423            }
424        }
425
426        private class MessageHelpInformation
427        {
428            private static readonly Type typeOfIQueryable = typeof(IQueryable);
429            private static readonly Type typeOfIQueryableGeneric = typeof(IQueryable<>);
430            private static readonly Type typeOfIEnumerable = typeof(IEnumerable);
431            private static readonly Type typeOfIEnumerableGeneric = typeof(IEnumerable<>);
432
433            public string BodyDescription { get; private set; }
434            public string FormatString { get; private set; }
435            public Type Type { get; private set; }
436            public bool SupportsJson { get; private set; }
437            public XmlSchemaSet SchemaSet { get; private set; }
438            public XmlSchema Schema { get; private set; }
439            public XElement XmlExample { get; private set; }
440            public XElement JsonExample { get; private set; }
441
442            [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This class requires the evaluation of many types.")]
443            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This class requires the evaluation of many types.")]
444            [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is handled.")]
445            internal MessageHelpInformation(HttpOperationDescription description, bool isRequest, Type type, bool wrapped)
446            {
447                this.Type = type;
448                this.SupportsJson = true;
449                string direction = isRequest ? Http.SR.HelpPageRequest : Http.SR.HelpPageResponse;
450
451                if (wrapped && !typeof(void).Equals(type))
452                {
453                    this.BodyDescription = Http.SR.HelpPageBodyIsWrapped(direction);
454                    this.FormatString = Http.SR.HelpPageUnknown;
455                }
456                else if (typeof(void).Equals(type))
457                {
458                    this.BodyDescription = Http.SR.HelpPageBodyIsEmpty(direction);
459                    this.FormatString = Http.SR.HelpPageNA;
460                }
461                else if (typeof(Message).IsAssignableFrom(type))
462                {
463                    this.BodyDescription = Http.SR.HelpPageIsMessage(direction);
464                    this.FormatString = Http.SR.HelpPageUnknown;
465                }
466                else if (typeof(Stream).IsAssignableFrom(type))
467                {
468                    this.BodyDescription = Http.SR.HelpPageIsStream(direction);
469                    this.FormatString = Http.SR.HelpPageUnknown;
470                }
471                else if (typeof(Atom10FeedFormatter).IsAssignableFrom(type))
472                {
473                    this.BodyDescription = Http.SR.HelpPageIsAtom10Feed(direction);
474                    this.FormatString = WebMessageFormat.Xml.ToString();
475                }
476                else if (typeof(Atom10ItemFormatter).IsAssignableFrom(type))
477                {
478                    this.BodyDescription = Http.SR.HelpPageIsAtom10Entry(direction);
479                    this.FormatString = WebMessageFormat.Xml.ToString();
480                }
481                else if (typeof(AtomPub10ServiceDocumentFormatter).IsAssignableFrom(type))
482                {
483                    this.BodyDescription = Http.SR.HelpPageIsAtomPubServiceDocument(direction);
484                    this.FormatString = WebMessageFormat.Xml.ToString();
485                }
486                else if (typeof(AtomPub10CategoriesDocumentFormatter).IsAssignableFrom(type))
487                {
488                    this.BodyDescription = Http.SR.HelpPageIsAtomPubCategoriesDocument(direction);
489                    this.FormatString = WebMessageFormat.Xml.ToString();
490                }
491                else if (typeof(Rss20FeedFormatter).IsAssignableFrom(type))
492                {
493                    this.BodyDescription = Http.SR.HelpPageIsRSS20Feed(direction);
494                    this.FormatString = WebMessageFormat.Xml.ToString();
495                }
496                else if (typeof(SyndicationFeedFormatter).IsAssignableFrom(type))
497                {
498                    this.BodyDescription = Http.SR.HelpPageIsSyndication(direction);
499                    this.FormatString = WebMessageFormat.Xml.ToString();
500                }
501                else if (typeof(XElement).IsAssignableFrom(type) || typeof(XmlElement).IsAssignableFrom(type))
502                {
503                    this.BodyDescription = Http.SR.HelpPageIsXML(direction);
504                    this.FormatString = WebMessageFormat.Xml.ToString();
505                }
506                else
507                {
508                    try
509                    {
510                        bool usesXmlSerializer = HttpOperationHandlerFactory.DetermineSerializerFormat(description) != XmlMediaTypeSerializerFormat.DataContractSerializer;
511
512                        XmlQualifiedName name;
513                        this.SchemaSet = new XmlSchemaSet();
514                        IDictionary<XmlQualifiedName, Type> knownTypes = new Dictionary<XmlQualifiedName, Type>();
515                        if (usesXmlSerializer)
516                        {
517                            XmlReflectionImporter importer = new XmlReflectionImporter();
518                            XmlTypeMapping typeMapping = importer.ImportTypeMapping(this.Type);
519                            name = new XmlQualifiedName(typeMapping.ElementName, typeMapping.Namespace);
520                            XmlSchemas schemas = new XmlSchemas();
521                            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
522                            exporter.ExportTypeMapping(typeMapping);
523                            foreach (XmlSchema schema in schemas)
524                            {
525                                this.SchemaSet.Add(schema);
526                            }
527                        }
528                        else
529                        {
530                            XsdDataContractExporter exporter = new XsdDataContractExporter();
531                            List<Type> listTypes = new List<Type>(description.KnownTypes);
532                            bool isQueryable;
533                            Type dataContractType = GetSubstituteDataContractType(this.Type, out isQueryable);
534
535                            listTypes.Add(dataContractType);
536                            exporter.Export(listTypes);
537                            if (!exporter.CanExport(dataContractType))
538                            {
539                                this.BodyDescription = Http.SR.HelpPageCouldNotGenerateSchema;
540                                this.FormatString = Http.SR.HelpPageUnknown;
541                                return;
542                            }
543                            name = exporter.GetRootElementName(dataContractType);
544                            DataContract typeDataContract = DataContract.GetDataContract(dataContractType);
545                            if (typeDataContract.KnownDataContracts != null)
546                            {
547                                foreach (XmlQualifiedName dataContractName in typeDataContract.KnownDataContracts.Keys)
548                                {
549                                    knownTypes.Add(dataContractName, typeDataContract.KnownDataContracts[dataContractName].UnderlyingType);
550                                }
551                            }
552                            foreach (Type knownType in description.KnownTypes)
553                            {
554                                XmlQualifiedName knownTypeName = exporter.GetSchemaTypeName(knownType);
555                                if (!knownTypes.ContainsKey(knownTypeName))
556                                {
557                                    knownTypes.Add(knownTypeName, knownType);
558                                }
559                            }
560
561                            foreach (XmlSchema schema in exporter.Schemas.Schemas())
562                            {
563                                this.SchemaSet.Add(schema);
564                            }
565                        }
566                        this.SchemaSet.Compile();
567
568                        XmlWriterSettings settings = new XmlWriterSettings
569                        {
570                            CloseOutput = false,
571                            Indent = true,
572                        };
573
574                        if (this.SupportsJson)
575                        {
576                            XDocument exampleDocument = new XDocument();
577                            using (XmlWriter writer = XmlWriter.Create(exampleDocument.CreateWriter(), settings))
578                            {
579                                HelpExampleGenerator.GenerateJsonSample(this.SchemaSet, name, writer, knownTypes);
580                            }
581                            this.JsonExample = exampleDocument.Root;
582                        }
583
584                        if (name.Namespace != "http://schemas.microsoft.com/2003/10/Serialization/")
585                        {
586                            foreach (XmlSchema schema in this.SchemaSet.Schemas(name.Namespace))
587                            {
588                                this.Schema = schema;
589
590                            }
591                        }
592
593                        XDocument XmlExampleDocument = new XDocument();
594                        using (XmlWriter writer = XmlWriter.Create(XmlExampleDocument.CreateWriter(), settings))
595                        {
596                            HelpExampleGenerator.GenerateXmlSample(this.SchemaSet, name, writer);
597                        }
598                        this.XmlExample = XmlExampleDocument.Root;
599
600                    }
601                    catch (Exception)
602                    {
603                        this.BodyDescription = Http.SR.HelpPageCouldNotGenerateSchema;
604                        this.FormatString = Http.SR.HelpPageUnknown;
605                        this.Schema = null;
606                        this.JsonExample = null;
607                        this.XmlExample = null;
608                    }
609                }
610            }
611
612            private static Type GetSubstituteDataContractType(Type type, out bool isQueryable)
613            {
614                if (type == typeOfIQueryable)
615                {
616                    isQueryable = true;
617                    return typeOfIEnumerable;
618                }
619
620                if (type.IsGenericType &&
621                    type.GetGenericTypeDefinition() == typeOfIQueryableGeneric)
622                {
623                    isQueryable = true;
624                    return typeOfIEnumerableGeneric.MakeGenericType(type.GetGenericArguments());
625                }
626
627                isQueryable = false;
628                return type;
629            }
630        }
631
632        private class HelpHtmlPageBuilder : HtmlPageBuilder
633        {
634            internal const string HelpOperationPageUrl = "help/operations/{0}";
635
636            private HelpHtmlPageBuilder()
637            { 
638            }
639
640            public static XDocument CreateHelpPage(Uri baseUri, IEnumerable<OperationHelpInformation> operations)
641            {
642                XDocument document = CreateBaseDocument(Http.SR.HelpPageOperationsAt(baseUri));
643                XElement table = new XElement(HtmlTableElementName,
644                        new XElement(HtmlTrElementName,
645                            new XElement(HtmlThElementName, Http.SR.HelpPageUri),
646                            new XElement(HtmlThElementName, Http.SR.HelpPageMethod),
647                            new XElement(HtmlThElementName, Http.SR.HelpPageDescription)));
648
649                string lastOperation = null;
650                XElement firstTr = null;
651                int rowspan = 0;
652                foreach (OperationHelpInformation operationHelpInfo in operations.OrderBy(o => FilterQueryVariables(o.UriTemplate)))
653                {
654                    string operationUri = FilterQueryVariables(operationHelpInfo.UriTemplate);
655                    string description = operationHelpInfo.Description;
656                    if (String.IsNullOrEmpty(description))
657                    {
658                        description = Http.SR.HelpPageDefaultDescription(BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate));
659                    }
660                    XElement tr = new XElement(HtmlTrElementName,
661                        new XElement(HtmlTdElementName, new XAttribute(HtmlTitleAttributeName, BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate)),
662                            new XElement(HtmlAElementName,
663                                new XAttribute(HtmlRelAttributeName, HtmlOperationClass),
664                                new XAttribute(HtmlHrefAttributeName, String.Format(CultureInfo.InvariantCulture, HelpOperationPageUrl, operationHelpInfo.Name)), operationHelpInfo.Method)),
665                        new XElement(HtmlTdElementName, description));
666                    table.Add(tr);
667                    if (operationUri != lastOperation)
668                    {
669                        XElement td = new XElement(HtmlTdElementName, operationUri == lastOperation ? String.Empty : operationUri);
670                        tr.AddFirst(td);
671                        if (firstTr != null && rowspan > 1)
672                        {
673                            firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
674                        }
675                        firstTr = tr;
676                        rowspan = 0;
677                        lastOperation = operationUri;
678                    }
679                    ++rowspan;
680                }
681                if (firstTr != null && rowspan > 1)
682                {
683                    firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
684                }
685                document.Descendants(HtmlBodyElementName).First().Add(new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
686                    new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), Http.SR.HelpPageOperationsAt(baseUri)),
687                    new XElement(HtmlPElementName, Http.SR.HelpPageStaticText),
688                    table));
689                return document;
690            }
691
692            public static XDocument CreateOperationHelpPage(Uri baseUri, OperationHelpInformation operationInfo)
693            {
694                XDocument document = CreateBaseDocument(Http.SR.HelpPageReferenceFor(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)));
695                XElement table = new XElement(HtmlTableElementName,
696                    new XElement(HtmlTrElementName,
697                        new XElement(HtmlThElementName, Http.SR.HelpPageMessageDirection),
698                        new XElement(HtmlThElementName, Http.SR.HelpPageFormat),
699                        new XElement(HtmlThElementName, Http.SR.HelpPageBody)));
700
701                RenderMessageInformation(table, operationInfo, true);
702                RenderMessageInformation(table, operationInfo, false);
703
704                XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
705                    new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), Http.SR.HelpPageReferenceFor(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate))),
706                    new XElement(HtmlPElementName, operationInfo.Description),
707                    XElement.Parse(Http.SR.HelpPageOperationUri(HttpUtility.HtmlEncode(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)))),
708                    XElement.Parse(Http.SR.HelpPageOperationMethod(HttpUtility.HtmlEncode(operationInfo.Method))));
709                if (!String.IsNullOrEmpty(operationInfo.JavascriptCallbackParameterName))
710                {
711                    div.Add(XElement.Parse(Http.SR.HelpPageCallbackText(HttpUtility.HtmlEncode(operationInfo.JavascriptCallbackParameterName))), table);
712                }
713                else
714                {
715                    div.Add(table);
716                }
717                document.Descendants(HtmlBodyElementName).First().Add(div);
718
719                CreateOperationSamples(document.Descendants(HtmlDivElementName).First(), operationInfo);
720
721                return document;
722            }
723
724            private static string FilterQueryVariables(string uriTemplate)
725            {
726                int variablesIndex = uriTemplate.IndexOf('?');
727                if (variablesIndex > 0)
728                {
729                    return uriTemplate.Substring(0, variablesIndex);
730                }
731                return uriTemplate;
732            }
733
734            private static void RenderMessageInformation(XElement table, OperationHelpInformation operationInfo, bool isRequest)
735            {
736                MessageHelpInformation info = isRequest ? operationInfo.Request : operationInfo.Response;
737                string direction = isRequest ? Http.SR.HelpPageRequest : Http.SR.HelpPageResponse;
738
739                if (info.BodyDescription != null)
740                {
741                    table.Add(new XElement(HtmlTrElementName,
742                        new XElement(HtmlTdElementName, direction),
743                        new XElement(HtmlTdElementName, info.FormatString),
744                        new XElement(HtmlTdElementName, info.BodyDescription)));
745                }
746                else
747                {
748                    if (info.XmlExample != null || info.Schema != null)
749                    {
750                        XElement contentTd;
751                        table.Add(new XElement(HtmlTrElementName,
752                            new XElement(HtmlTdElementName, direction),
753                            new XElement(HtmlTdElementName, "Xml"),
754                            contentTd = new XElement(HtmlTdElementName)));
755
756                        if (info.XmlExample != null)
757                        {
758                            contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestXmlId : HtmlResponseXmlId)), Http.SR.HelpPageExample));
759                            if (info.Schema != null)
760                            {
761                                contentTd.Add(",");
762                            }
763                        }
764                        if (info.Schema != null)
765                        {
766                            contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestSchemaId : HtmlResponseSchemaId)), Http.SR.HelpPageSchema));
767                        }
768                    }
769                    if (info.JsonExample != null)
770                    {
771                        table.Add(new XElement(HtmlTrElementName,
772                            new XElement(HtmlTdElementName, direction),
773                            new XElement(HtmlTdElementName, "Json"),
774                            new XElement(HtmlTdElementName,
775                                new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestJsonId : HtmlResponseJsonId)), Http.SR.HelpPageExample))));
776                    }
777                }
778            }
779
780            private static void CreateOperationSamples(XElement element, OperationHelpInformation operationInfo)
781            {
782                if (operationInfo.Request.XmlExample != null)
783                {
784                    element.Add(GenerateSampleXml(operationInfo.Request.XmlExample, Http.SR.HelpPageXmlRequest, HtmlRequestXmlId));
785                }
786                if (operationInfo.Request.JsonExample != null)
787                {
788                    element.Add(AddSampleJson(operationInfo.Request.JsonExample, Http.SR.HelpPageJsonRequest, HtmlRequestJsonId));
789                }
790                if (operationInfo.Response.XmlExample != null)
791                {
792                    element.Add(GenerateSampleXml(operationInfo.Response.XmlExample, Http.SR.HelpPageXmlResponse, HtmlResponseXmlId));
793                }
794                if (operationInfo.Response.JsonExample != null)
795                {
796                    element.Add(AddSampleJson(operationInfo.Response.JsonExample, Http.SR.HelpPageJsonResponse, HtmlResponseJsonId));
797                }
798
799                if (operationInfo.Request.Schema != null)
800                {
801                    element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Request.Schema), Http.SR.HelpPageRequestSchema, HtmlRequestSchemaId));
802                    int count = 0;
803                    foreach (XmlSchema schema in operationInfo.Request.SchemaSet.Schemas())
804                    {
805                        if (schema.TargetNamespace != operationInfo.Request.Schema.TargetNamespace)
806                        {
807                            element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? Http.SR.HelpPageAdditionalRequestSchema : null, HtmlRequestSchemaId));
808                        }
809                    }
810                }
811                if (operationInfo.Response.Schema != null)
812                {
813                    element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Response.Schema), Http.SR.HelpPageResponseSchema, HtmlResponseSchemaId));
814                    int count = 0;
815                    foreach (XmlSchema schema in operationInfo.Response.SchemaSet.Schemas())
816                    {
817                        if (schema.TargetNamespace != operationInfo.Response.Schema.TargetNamespace)
818                        {
819                            element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? Http.SR.HelpPageAdditionalResponseSchema : null, HtmlResponseSchemaId));
820                        }
821                    }
822                }
823            }
824
825            private static XElement XmlSchemaToXElement(XmlSchema schema)
826            {
827                XmlWriterSettings settings = new XmlWriterSettings
828                {
829                    CloseOutput = false,
830                    Indent = true,
831                };
832
833                XDocument schemaDocument = new XDocument();
834
835                using (XmlWriter writer = XmlWriter.Create(schemaDocument.CreateWriter(), settings))
836                {
837                    schema.Write(writer);
838                }
839                return schemaDocument.Root;
840            }
841
842            private static XElement AddSample(object content, string title, string label)
843            {
844                if (String.IsNullOrEmpty(title))
845                {
846                    return new XElement(HtmlPElementName,
847                        new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
848                }
849                else
850                {
851                    return new XElement(HtmlPElementName,
852                        new XElement(HtmlAElementName, new XAttribute(HtmlNameAttributeName, "#" + label), title),
853                        new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
854                }
855            }
856
857            private static XElement GenerateSampleXml(XElement content, string title, string label)
858            {
859                StringBuilder sample = new StringBuilder();
860                using (XmlWriter writer = XmlTextWriter.Create(sample, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
861                {
862                    content.WriteTo(writer);
863                }
864                return AddSample(sample.ToString(), title, label);
865            }
866
867            [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "dispose is idempotent.")]
868            private static XElement AddSampleJson(XElement content, string title, string label)
869            {
870                StringBuilder sample = new StringBuilder();
871                using (MemoryStream stream = new MemoryStream())
872                {
873                    using (XmlDictionaryWriter writer =
874                        JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.Unicode, false))
875                    {
876                        content.WriteTo(writer);
877                    }
878
879                    stream.Position = 0;
880                    sample.Append(new StreamReader(stream, Encoding.Unicode).ReadToEnd());
881                }
882                int depth = 0;
883                bool inString = false;
884                for (int i = 0; i < sample.Length; ++i)
885                {
886                    if (sample[i] == '"')
887                    {
888                        inString = !inString;
889                    }
890                    else if (sample[i] == '{')
891                    {
892                        sample.Insert(i + 1, "\n" + new String('\t', ++depth));
893                        i += depth + 1;
894                    }
895                    else if (sample[i] == ',' && !inString)
896                    {
897                        sample.Insert(i + 1, "\n" + new String('\t', depth));
898                    }
899                    else if (sample[i] == '}' && depth > 0)
900                    {
901                        sample.Insert(i, "\n" + new String('\t', --depth));
902                        i += depth + 1;
903                    }
904                }
905                return AddSample(sample.ToString(), title, label);
906            }
907
908            private static string BuildFullUriTemplate(Uri baseUri, string uriTemplate)
909            {
910                UriTemplate template = new UriTemplate(uriTemplate);
911                Uri result = template.BindByPosition(baseUri, template.PathSegmentVariableNames.Concat(template.QueryValueVariableNames).Select(name => "{" + name + "}").ToArray());
912                return result.ToString();
913            }
914        }
915
916        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This type requires the evaluation of many types.")]
917        private static class HelpExampleGenerator
918        {
919            private const int MaxDepthLevel = 256;
920            private const string XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";
921            private const string XmlNamespacePrefix = "xmlns";
922            private const string XmlSchemaInstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance";
923            private const string XmlSchemaInstanceNil = "nil";
924            private const string XmlSchemaInstanceType = "type";
925
926            private static Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>> XmlObjectHandler = new Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>>
927            {                
928                { typeof(XmlSchemaComplexContent), ContentHandler },
929                { typeof(XmlSchemaSimpleContent), ContentHandler },
930                { typeof(XmlSchemaSimpleTypeRestriction), SimpleTypeRestrictionHandler },                
931                { typeof(XmlSchemaChoice), ChoiceHandler },
932                // Nothing to do, inheritance is resolved by Schema compilation process                
933                { typeof(XmlSchemaComplexContentExtension), EmptyHandler },
934                { typeof(XmlSchemaSimpleContentExtension), EmptyHandler },
935                // No need to generate XML for these objects
936                { typeof(XmlSchemaAny), EmptyHandler },
937                { typeof(XmlSchemaAnyAttribute), EmptyHandler },
938                { typeof(XmlSchemaAnnotated), EmptyHandler },
939                { typeof(XmlSchema), EmptyHandler },
940                // The following schema objects are not handled            
941                { typeof(XmlSchemaAttributeGroup), ErrorHandler },
942                { typeof(XmlSchemaAttributeGroupRef), ErrorHandler },
943                { typeof(XmlSchemaComplexContentRestriction), ErrorHandler },
944                { typeof(XmlSchemaSimpleContentRestriction), ErrorHandler },
945                // Enumerations are supported by the GenerateContentForSimpleType
946                { typeof(XmlSchemaEnumerationFacet), EmptyHandler },
947                { typeof(XmlSchemaMaxExclusiveFacet), ErrorHandler },
948                { typeof(XmlSchemaMaxInclusiveFacet), ErrorHandler },
949                { typeof(XmlSchemaMinExclusiveFacet), ErrorHandler },
950                { typeof(XmlSchemaMinInclusiveFacet), ErrorHandler },
951                { typeof(XmlSchemaNumericFacet), ErrorHandler },
952                { typeof(XmlSchemaFractionDigitsFacet), ErrorHandler },
953                { typeof(XmlSchemaLengthFacet), ErrorHandler },
954                { typeof(XmlSchemaMaxLengthFacet), ErrorHandler },
955                { typeof(XmlSchemaMinLengthFacet), ErrorHandler },
956                { typeof(XmlSchemaTotalDigitsFacet), ErrorHandler },
957                { typeof(XmlSchemaPatternFacet), ErrorHandler },
958                { typeof(XmlSchemaWhiteSpaceFacet), ErrorHandler },
959                { typeof(XmlSchemaGroup), ErrorHandler },
960                { typeof(XmlSchemaIdentityConstraint), ErrorHandler },
961                { typeof(XmlSchemaKey), ErrorHandler },
962                { typeof(XmlSchemaKeyref), ErrorHandler },
963                { typeof(XmlSchemaUnique), ErrorHandler },
964                { typeof(XmlSchemaNotation), ErrorHandler },
965                { typeof(XmlSchemaAll), ErrorHandler },
966                { typeof(XmlSchemaGroupRef), ErrorHandler },
967                { typeof(XmlSchemaSimpleTypeUnion), ErrorHandler },
968                { typeof(XmlSchemaSimpleTypeList), XmlSimpleTypeListHandler },
969                { typeof(XmlSchemaXPath), ErrorHandler },
970                { typeof(XmlSchemaAttribute), XmlAttributeHandler },
971                { typeof(XmlSchemaElement), XmlElementHandler },
972                { typeof(XmlSchemaComplexType), XmlComplexTypeHandler },
973                { typeof(XmlSchemaSequence), XmlSequenceHandler },
974                { typeof(XmlSchemaSimpleType), XmlSimpleTypeHandler },
975            };
976
977            private static Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>> JsonObjectHandler = new Dictionary<Type, Action<XmlSchemaObject, HelpExampleGeneratorContext>>
978            {                
979                { typeof(XmlSchemaComplexContent), ContentHandler },
980                { typeof(XmlSchemaSimpleContent), ContentHandler },
981                { typeof(XmlSchemaSimpleTypeRestriction), SimpleTypeRestrictionHandler },                
982                { typeof(XmlSchemaChoice), ChoiceHandler },
983                // Nothing to do, inheritance is resolved by Schema compilation process                
984                { typeof(XmlSchemaComplexContentExtension), EmptyHandler },
985                { typeof(XmlSchemaSimpleContentExtension), EmptyHandler },
986                // No need to generate XML for these objects
987        

Large files files are truncated, but you can click here to view the full file