1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.ServiceModel.Dispatcher
6 {
7     using System;
8     using System.Collections;
9     using System.Collections.Generic;
10     using System.IO;
11     using System.Linq;
12     using System.Net;
13     using System.Runtime;
14     using System.Runtime.Serialization;
15     using System.ServiceModel.Channels;
16     using System.ServiceModel.Description;
17     using System.ServiceModel.Syndication;
18     using System.ServiceModel.Web;
19     using System.Web;
20     using System.Xml;
21     using System.Xml.Linq;
22     using System.Xml.Schema;
23     using System.Xml.Serialization;
24 
25     class HelpPage
26     {
27         public const string OperationListHelpPageUriTemplate = "help";
28         public const string OperationHelpPageUriTemplate = "help/operations/{operation}";
29         const string HelpMethodName = "GetHelpPage";
30         const string HelpOperationMethodName = "GetOperationHelpPage";
31 
32         DateTime startupTime = DateTime.UtcNow;
33 
34         Dictionary<string, OperationHelpInformation> operationInfoDictionary;
35         NameValueCache<string> operationPageCache;
36         NameValueCache<string> helpPageCache;
37 
HelpPage(WebHttpBehavior behavior, ContractDescription description)38         public HelpPage(WebHttpBehavior behavior, ContractDescription description)
39         {
40             this.operationInfoDictionary = new Dictionary<string, OperationHelpInformation>();
41             this.operationPageCache = new NameValueCache<string>();
42             this.helpPageCache = new NameValueCache<string>();
43             foreach (OperationDescription od in description.Operations)
44             {
45                 operationInfoDictionary.Add(od.Name, new OperationHelpInformation(behavior, od));
46             }
47         }
48 
GetHelpPage()49         Message GetHelpPage()
50         {
51             Uri baseUri = UriTemplate.RewriteUri(OperationContext.Current.Channel.LocalAddress.Uri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]);
52             string helpPage = this.helpPageCache.Lookup(baseUri.Authority);
53             if (String.IsNullOrEmpty(helpPage))
54             {
55                 helpPage = HelpHtmlBuilder.CreateHelpPage(baseUri, operationInfoDictionary.Values).ToString();
56                 if (HttpContext.Current == null)
57                 {
58                     this.helpPageCache.AddOrUpdate(baseUri.Authority, helpPage);
59                 }
60             }
61             return WebOperationContext.Current.CreateTextResponse(helpPage, "text/html");
62         }
63 
GetOperationHelpPage(string operation)64         Message GetOperationHelpPage(string operation)
65         {
66             Uri requestUri = UriTemplate.RewriteUri(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]);
67             string helpPage = this.operationPageCache.Lookup(requestUri.AbsoluteUri);
68             if (String.IsNullOrEmpty(helpPage))
69             {
70                 OperationHelpInformation operationInfo;
71                 if (this.operationInfoDictionary.TryGetValue(operation, out operationInfo))
72                 {
73                     Uri baseUri = UriTemplate.RewriteUri(OperationContext.Current.Channel.LocalAddress.Uri, WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Host]);
74                     helpPage = HelpHtmlBuilder.CreateOperationHelpPage(baseUri, operationInfo).ToString();
75                     if (HttpContext.Current == null)
76                     {
77                         this.operationPageCache.AddOrUpdate(requestUri.AbsoluteUri, helpPage);
78                     }
79                 }
80                 else
81                 {
82                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.NotFound));
83                 }
84             }
85             return WebOperationContext.Current.CreateTextResponse(helpPage, "text/html");
86         }
87 
GetOperationTemplatePairs()88         public static IEnumerable<KeyValuePair<UriTemplate, object>> GetOperationTemplatePairs()
89         {
90             return new KeyValuePair<UriTemplate, object>[]
91             {
92                 new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationListHelpPageUriTemplate), HelpMethodName),
93                 new KeyValuePair<UriTemplate, object>(new UriTemplate(OperationHelpPageUriTemplate), HelpOperationMethodName)
94             };
95         }
96 
Invoke(UriTemplateMatch match)97         public object Invoke(UriTemplateMatch match)
98         {
99             if (HttpContext.Current != null)
100             {
101                 HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
102                 HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.MaxValue);
103                 HttpContext.Current.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidationCallback), this.startupTime);
104                 HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
105             }
106             switch ((string)match.Data)
107             {
108                 case HelpMethodName:
109                     return GetHelpPage();
110                 case HelpOperationMethodName:
111                     return GetOperationHelpPage(match.BoundVariables["operation"]);
112                 default:
113                     return null;
114             }
115         }
116 
CacheValidationCallback(HttpContext context, object state, ref HttpValidationStatus result)117         void CacheValidationCallback(HttpContext context, object state, ref HttpValidationStatus result)
118         {
119             if (((DateTime)state) == this.startupTime)
120             {
121                 result = HttpValidationStatus.Valid;
122             }
123             else
124             {
125                 result = HttpValidationStatus.Invalid;
126             }
127         }
128     }
129 
130     class OperationHelpInformation
131     {
132         OperationDescription od;
133         WebHttpBehavior behavior;
134         MessageHelpInformation request;
135         MessageHelpInformation response;
136 
OperationHelpInformation(WebHttpBehavior behavior, OperationDescription od)137         internal OperationHelpInformation(WebHttpBehavior behavior, OperationDescription od)
138         {
139             this.od = od;
140             this.behavior = behavior;
141         }
142 
143         public string Name
144         {
145             get
146             {
147                 return od.Name;
148             }
149         }
150 
151         public string UriTemplate
152         {
153             get
154             {
155                 return UriTemplateClientFormatter.GetUTStringOrDefault(od);
156             }
157         }
158 
159         public string Method
160         {
161             get
162             {
163                 return WebHttpBehavior.GetWebMethod(od);
164             }
165         }
166 
167         public string Description
168         {
169             get
170             {
171                 return WebHttpBehavior.GetDescription(od);
172             }
173         }
174 
175         public string JavascriptCallbackParameterName
176         {
177             get
178             {
179                 if (this.Response.SupportsJson && this.Method == WebHttpBehavior.GET)
180                 {
181                     return behavior.JavascriptCallbackParameterName;
182                 }
183                 return null;
184             }
185         }
186 
187         public WebMessageBodyStyle BodyStyle
188         {
189             get
190             {
191                 return behavior.GetBodyStyle(od);
192             }
193         }
194 
195         public MessageHelpInformation Request
196         {
197             get
198             {
199                 if (this.request == null)
200                 {
201                     this.request = new MessageHelpInformation(od, true, GetRequestBodyType(od, this.UriTemplate),
202                         this.BodyStyle == WebMessageBodyStyle.WrappedRequest || this.BodyStyle == WebMessageBodyStyle.Wrapped);
203                 }
204                 return this.request;
205             }
206         }
207 
208         public MessageHelpInformation Response
209         {
210             get
211             {
212                 if (this.response == null)
213                 {
214                     this.response = new MessageHelpInformation(od, false, GetResponseBodyType(od),
215                         this.BodyStyle == WebMessageBodyStyle.WrappedResponse || this.BodyStyle == WebMessageBodyStyle.Wrapped);
216                 }
217                 return this.response;
218             }
219         }
220 
GetResponseBodyType(OperationDescription od)221         static Type GetResponseBodyType(OperationDescription od)
222         {
223             if (WebHttpBehavior.IsUntypedMessage(od.Messages[1]))
224             {
225                 return typeof(Message);
226             }
227             else if (WebHttpBehavior.IsTypedMessage(od.Messages[1]))
228             {
229                 return od.Messages[1].MessageType;
230             }
231             else if (od.Messages[1].Body.Parts.Count > 0)
232             {
233                 // If it is more than 0 the response is wrapped and not supported
234                 return null;
235             }
236             else
237             {
238                 return (od.Messages[1].Body.ReturnValue.Type);
239             }
240         }
241 
GetRequestBodyType(OperationDescription od, string uriTemplate)242         static Type GetRequestBodyType(OperationDescription od, string uriTemplate)
243         {
244             if (od.Behaviors.Contains(typeof(WebGetAttribute)))
245             {
246                 return typeof(void);
247             }
248             else if (WebHttpBehavior.IsUntypedMessage(od.Messages[0]))
249             {
250                 return typeof(Message);
251             }
252             else if (WebHttpBehavior.IsTypedMessage(od.Messages[0]))
253             {
254                 return od.Messages[0].MessageType;
255             }
256             else
257             {
258                 UriTemplate template = new UriTemplate(uriTemplate);
259                 IEnumerable<MessagePartDescription> parts =
260                     from part in od.Messages[0].Body.Parts
261                     where !template.PathSegmentVariableNames.Contains(part.Name.ToUpperInvariant()) && !template.QueryValueVariableNames.Contains(part.Name.ToUpperInvariant())
262                     select part;
263 
264                 if (parts.Count() == 1)
265                 {
266                     return parts.First().Type;
267                 }
268                 else if (parts.Count() == 0)
269                 {
270                     return typeof(void);
271                 }
272                 else
273                 {
274                     // The request is wrapped and not supported
275                     return null;
276                 }
277             }
278         }
279     }
280 
281     class MessageHelpInformation
282     {
283         public string BodyDescription { get; private set; }
284         public string FormatString { get; private set; }
285         public Type Type { get; private set; }
286         public bool SupportsJson { get; private set; }
287         public XmlSchemaSet SchemaSet { get; private set; }
288         public XmlSchema Schema { get; private set; }
289         public XElement XmlExample { get; private set; }
290         public XElement JsonExample { get; private set; }
291 
MessageHelpInformation(OperationDescription od, bool isRequest, Type type, bool wrapped)292         internal MessageHelpInformation(OperationDescription od, bool isRequest, Type type, bool wrapped)
293         {
294             this.Type = type;
295             this.SupportsJson = WebHttpBehavior.SupportsJsonFormat(od);
296             string direction = isRequest ? SR2.GetString(SR2.HelpPageRequest) : SR2.GetString(SR2.HelpPageResponse);
297 
298             if (wrapped && !typeof(void).Equals(type))
299             {
300                 this.BodyDescription = SR2.GetString(SR2.HelpPageBodyIsWrapped, direction);
301                 this.FormatString = SR2.GetString(SR2.HelpPageUnknown);
302             }
303             else if (typeof(void).Equals(type))
304             {
305                 this.BodyDescription = SR2.GetString(SR2.HelpPageBodyIsEmpty, direction);
306                 this.FormatString = SR2.GetString(SR2.HelpPageNA);
307             }
308             else if (typeof(Message).IsAssignableFrom(type))
309             {
310                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsMessage, direction);
311                 this.FormatString = SR2.GetString(SR2.HelpPageUnknown);
312             }
313             else if (typeof(Stream).IsAssignableFrom(type))
314             {
315                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsStream, direction);
316                 this.FormatString = SR2.GetString(SR2.HelpPageUnknown);
317             }
318             else if (typeof(Atom10FeedFormatter).IsAssignableFrom(type))
319             {
320                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtom10Feed, direction);
321                 this.FormatString = WebMessageFormat.Xml.ToString();
322             }
323             else if (typeof(Atom10ItemFormatter).IsAssignableFrom(type))
324             {
325                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtom10Entry, direction);
326                 this.FormatString = WebMessageFormat.Xml.ToString();
327             }
328             else if (typeof(AtomPub10ServiceDocumentFormatter).IsAssignableFrom(type))
329             {
330                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtomPubServiceDocument, direction);
331                 this.FormatString = WebMessageFormat.Xml.ToString();
332             }
333             else if (typeof(AtomPub10CategoriesDocumentFormatter).IsAssignableFrom(type))
334             {
335                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsAtomPubCategoriesDocument, direction);
336                 this.FormatString = WebMessageFormat.Xml.ToString();
337             }
338             else if (typeof(Rss20FeedFormatter).IsAssignableFrom(type))
339             {
340                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsRSS20Feed, direction);
341                 this.FormatString = WebMessageFormat.Xml.ToString();
342             }
343             else if (typeof(SyndicationFeedFormatter).IsAssignableFrom(type))
344             {
345                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsSyndication, direction);
346                 this.FormatString = WebMessageFormat.Xml.ToString();
347             }
348             else if (typeof(XElement).IsAssignableFrom(type) || typeof(XmlElement).IsAssignableFrom(type))
349             {
350                 this.BodyDescription = SR2.GetString(SR2.HelpPageIsXML, direction);
351                 this.FormatString = WebMessageFormat.Xml.ToString();
352             }
353             else
354             {
355                 try
356                 {
357                     bool usesXmlSerializer = od.Behaviors.Contains(typeof(XmlSerializerOperationBehavior));
358                     XmlQualifiedName name;
359                     this.SchemaSet = new XmlSchemaSet();
360                     IDictionary<XmlQualifiedName, Type> knownTypes = new Dictionary<XmlQualifiedName, Type>();
361                     if (usesXmlSerializer)
362                     {
363                         XmlReflectionImporter importer = new XmlReflectionImporter();
364                         XmlTypeMapping typeMapping = importer.ImportTypeMapping(this.Type);
365                         name = new XmlQualifiedName(typeMapping.ElementName, typeMapping.Namespace);
366                         XmlSchemas schemas = new XmlSchemas();
367                         XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
368                         exporter.ExportTypeMapping(typeMapping);
369                         foreach (XmlSchema schema in schemas)
370                         {
371                             this.SchemaSet.Add(schema);
372                         }
373                     }
374                     else
375                     {
376                         XsdDataContractExporter exporter = new XsdDataContractExporter();
377                         List<Type> listTypes = new List<Type>(od.KnownTypes);
378                         bool isQueryable;
379                         Type dataContractType = DataContractSerializerOperationFormatter.GetSubstituteDataContractType(this.Type, out isQueryable);
380                         listTypes.Add(dataContractType);
381                         exporter.Export(listTypes);
382                         if (!exporter.CanExport(dataContractType))
383                         {
384                             this.BodyDescription = SR2.GetString(SR2.HelpPageCouldNotGenerateSchema);
385                             this.FormatString = SR2.GetString(SR2.HelpPageUnknown);
386                             return;
387                         }
388                         name = exporter.GetRootElementName(dataContractType);
389                         DataContract typeDataContract = DataContract.GetDataContract(dataContractType);
390                         if (typeDataContract.KnownDataContracts != null)
391                         {
392                             foreach (XmlQualifiedName dataContractName in typeDataContract.KnownDataContracts.Keys)
393                             {
394                                 knownTypes.Add(dataContractName, typeDataContract.KnownDataContracts[dataContractName].UnderlyingType);
395                             }
396                         }
397                         foreach (Type knownType in od.KnownTypes)
398                         {
399                             XmlQualifiedName knownTypeName = exporter.GetSchemaTypeName(knownType);
400                             if (!knownTypes.ContainsKey(knownTypeName))
401                             {
402                                 knownTypes.Add(knownTypeName, knownType);
403                             }
404                         }
405 
406                         foreach (XmlSchema schema in exporter.Schemas.Schemas())
407                         {
408                             this.SchemaSet.Add(schema);
409                         }
410                     }
411                     this.SchemaSet.Compile();
412 
413                     XmlWriterSettings settings = new XmlWriterSettings
414                     {
415                         CloseOutput = false,
416                         Indent = true,
417                     };
418 
419                     if (this.SupportsJson)
420                     {
421                         XDocument exampleDocument = new XDocument();
422                         using (XmlWriter writer = XmlWriter.Create(exampleDocument.CreateWriter(), settings))
423                         {
424                             HelpExampleGenerator.GenerateJsonSample(this.SchemaSet, name, writer, knownTypes);
425                         }
426                         this.JsonExample = exampleDocument.Root;
427                     }
428 
429                     if (name.Namespace != "http://schemas.microsoft.com/2003/10/Serialization/")
430                     {
431                         foreach (XmlSchema schema in this.SchemaSet.Schemas(name.Namespace))
432                         {
433                             this.Schema = schema;
434 
435                         }
436                     }
437 
438                     XDocument XmlExampleDocument = new XDocument();
439                     using (XmlWriter writer = XmlWriter.Create(XmlExampleDocument.CreateWriter(), settings))
440                     {
441                         HelpExampleGenerator.GenerateXmlSample(this.SchemaSet, name, writer);
442                     }
443                     this.XmlExample = XmlExampleDocument.Root;
444 
445                 }
446                 catch (Exception e)
447                 {
448                     if (Fx.IsFatal(e))
449                     {
450                         throw;
451                     }
452                     this.BodyDescription = SR2.GetString(SR2.HelpPageCouldNotGenerateSchema);
453                     this.FormatString = SR2.GetString(SR2.HelpPageUnknown);
454                     this.Schema = null;
455                     this.JsonExample = null;
456                     this.XmlExample = null;
457                 }
458             }
459         }
460     }
461 }
462