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