Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.ServiceModel.Web / System / ServiceModel / Dispatcher / WebHttpDispatchOperationSelector.cs
blobdba20ddce99f808170670dee3fd92cccabd2ac40
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
4 #pragma warning disable 1634, 1691
5 namespace System.ServiceModel.Dispatcher
7 using System;
8 using System.Collections.Generic;
9 using System.Collections.ObjectModel;
10 using System.Diagnostics;
11 using System.Diagnostics.CodeAnalysis;
12 using System.Runtime;
13 using System.ServiceModel;
14 using System.ServiceModel.Activation;
15 using System.ServiceModel.Channels;
16 using System.ServiceModel.Description;
17 using System.ServiceModel.Diagnostics;
18 using System.ServiceModel.Web;
19 using System.Net;
21 public class WebHttpDispatchOperationSelector : IDispatchOperationSelector
23 public const string HttpOperationSelectorUriMatchedPropertyName = "UriMatched";
24 internal const string HttpOperationSelectorDataPropertyName = "HttpOperationSelectorData";
26 //
27 public const string HttpOperationNamePropertyName = "HttpOperationName";
28 internal const string redirectOperationName = ""; // always unhandled invoker
29 internal const string RedirectPropertyName = "WebHttpRedirect";
31 string catchAllOperationName = ""; // user UT=* Method=* operation, else unhandled invoker
33 Dictionary<string, UriTemplateTable> methodSpecificTables; // indexed by the http method name
34 UriTemplateTable wildcardTable; // this is one of the methodSpecificTables, special-cased for faster access
35 Dictionary<string, UriTemplate> templates;
37 UriTemplateTable helpUriTable;
39 public WebHttpDispatchOperationSelector(ServiceEndpoint endpoint)
41 if (endpoint == null)
43 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");
45 if (endpoint.Address == null)
47 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
48 SR2.GetString(SR2.EndpointAddressCannotBeNull)));
50 #pragma warning disable 56506 // Microsoft, endpoint.Address.Uri is never null
51 Uri baseUri = endpoint.Address.Uri;
52 this.methodSpecificTables = new Dictionary<string, UriTemplateTable>();
53 this.templates = new Dictionary<string, UriTemplate>();
54 #pragma warning restore 56506
56 WebHttpBehavior webHttpBehavior = endpoint.Behaviors.Find<WebHttpBehavior>();
57 if (webHttpBehavior != null && webHttpBehavior.HelpEnabled)
59 this.helpUriTable = new UriTemplateTable(endpoint.ListenUri, HelpPage.GetOperationTemplatePairs());
62 Dictionary<WCFKey, string> alreadyHaves = new Dictionary<WCFKey, string>();
64 #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null
65 foreach (OperationDescription od in endpoint.Contract.Operations)
66 #pragma warning restore 56506
68 // ignore callback operations
69 if (od.Messages[0].Direction == MessageDirection.Input)
71 string method = WebHttpBehavior.GetWebMethod(od);
72 string path = UriTemplateClientFormatter.GetUTStringOrDefault(od);
74 //
76 if (UriTemplateHelpers.IsWildcardPath(path) && (method == WebHttpBehavior.WildcardMethod))
78 if (this.catchAllOperationName != "")
80 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
81 new InvalidOperationException(
82 SR2.GetString(SR2.MultipleOperationsInContractWithPathMethod,
83 endpoint.Contract.Name, path, method)));
85 this.catchAllOperationName = od.Name;
87 UriTemplate ut = new UriTemplate(path);
88 WCFKey wcfKey = new WCFKey(ut, method);
89 if (alreadyHaves.ContainsKey(wcfKey))
91 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
92 new InvalidOperationException(
93 SR2.GetString(SR2.MultipleOperationsInContractWithPathMethod,
94 endpoint.Contract.Name, path, method)));
96 alreadyHaves.Add(wcfKey, od.Name);
98 UriTemplateTable methodSpecificTable;
99 if (!methodSpecificTables.TryGetValue(method, out methodSpecificTable))
101 methodSpecificTable = new UriTemplateTable(baseUri);
102 methodSpecificTables.Add(method, methodSpecificTable);
105 methodSpecificTable.KeyValuePairs.Add(new KeyValuePair<UriTemplate, object>(ut, od.Name));
106 this.templates.Add(od.Name, ut);
110 if (this.methodSpecificTables.Count == 0)
112 this.methodSpecificTables = null;
114 else
116 // freeze all the tables because they should not be modified after this point
117 foreach (UriTemplateTable table in this.methodSpecificTables.Values)
119 table.MakeReadOnly(true /* allowDuplicateEquivalentUriTemplates */);
122 if (!methodSpecificTables.TryGetValue(WebHttpBehavior.WildcardMethod, out wildcardTable))
124 wildcardTable = null;
129 protected WebHttpDispatchOperationSelector()
133 public virtual UriTemplate GetUriTemplate(string operationName)
135 if (operationName == null)
137 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("operationName");
139 UriTemplate result;
140 if (!this.templates.TryGetValue(operationName, out result))
142 return null;
144 else
146 return result;
150 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This method is defined by the IDispatchOperationSelector interface")]
151 public string SelectOperation(ref Message message)
153 if (message == null)
155 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
157 bool uriMatched;
158 string result = this.SelectOperation(ref message, out uriMatched);
159 #pragma warning disable 56506 // Microsoft, Message.Properties is never null
160 message.Properties.Add(HttpOperationSelectorUriMatchedPropertyName, uriMatched);
161 #pragma warning restore 56506
162 if (result != null)
164 message.Properties.Add(HttpOperationNamePropertyName, result);
165 if (DiagnosticUtility.ShouldTraceInformation)
167 #pragma warning disable 56506 // Microsoft, Message.Headers is never null
168 TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.WebRequestMatchesOperation, SR2.GetString(SR2.TraceCodeWebRequestMatchesOperation, message.Headers.To, result));
169 #pragma warning restore 56506
172 return result;
175 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This method is like that defined by the IDispatchOperationSelector interface")]
176 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "This API needs to return multiple things")]
177 protected virtual string SelectOperation(ref Message message, out bool uriMatched)
179 if (message == null)
181 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
183 uriMatched = false;
184 if (this.methodSpecificTables == null)
186 return this.catchAllOperationName;
189 #pragma warning disable 56506 // Microsoft, message.Properties is never null
190 if (!message.Properties.ContainsKey(HttpRequestMessageProperty.Name))
192 return this.catchAllOperationName;
194 HttpRequestMessageProperty prop = message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
195 if (prop == null)
197 return this.catchAllOperationName;
199 string method = prop.Method;
201 Uri to = message.Headers.To;
202 #pragma warning restore 56506
204 if (to == null)
206 return this.catchAllOperationName;
209 if (this.helpUriTable != null)
211 UriTemplateMatch match = this.helpUriTable.MatchSingle(to);
212 if (match != null)
214 uriMatched = true;
215 AddUriTemplateMatch(match, prop, message);
216 if (method == WebHttpBehavior.GET)
218 return HelpOperationInvoker.OperationName;
220 message.Properties.Add(WebHttpDispatchOperationSelector.HttpOperationSelectorDataPropertyName,
221 new WebHttpDispatchOperationSelectorData() { AllowedMethods = new List<string>() { WebHttpBehavior.GET } });
222 return this.catchAllOperationName;
226 UriTemplateTable methodSpecificTable;
227 bool methodMatchesExactly = methodSpecificTables.TryGetValue(method, out methodSpecificTable);
228 if (methodMatchesExactly)
230 string operationName;
231 uriMatched = CanUriMatch(methodSpecificTable, to, prop, message, out operationName);
232 if (uriMatched)
234 return operationName;
238 if (wildcardTable != null)
240 string operationName;
241 uriMatched = CanUriMatch(wildcardTable, to, prop, message, out operationName);
242 if (uriMatched)
244 return operationName;
248 if (ShouldRedirectToUriWithSlashAtTheEnd(methodSpecificTable, message, to))
250 return redirectOperationName;
253 // the {method, uri} pair does not match anything the service supports.
254 // we know at this point that we'll return some kind of error code, but we
255 // should go through all methods for the uri to see if any method is supported
256 // so that that information could be returned to the user as well
258 List<string> allowedMethods = null;
259 foreach (KeyValuePair<string, UriTemplateTable> pair in methodSpecificTables)
261 if (pair.Key == method || pair.Key == WebHttpBehavior.WildcardMethod)
263 // the uri must not match the uri template
264 continue;
266 UriTemplateTable table = pair.Value;
267 if (table.MatchSingle(to) != null)
269 if (allowedMethods == null)
271 allowedMethods = new List<string>();
276 if (!allowedMethods.Contains(pair.Key))
278 allowedMethods.Add(pair.Key);
283 if (allowedMethods != null)
285 uriMatched = true;
286 message.Properties.Add(WebHttpDispatchOperationSelector.HttpOperationSelectorDataPropertyName,
287 new WebHttpDispatchOperationSelectorData() { AllowedMethods = allowedMethods });
289 return catchAllOperationName;
292 bool CanUriMatch(UriTemplateTable methodSpecificTable, Uri to, HttpRequestMessageProperty prop, Message message, out string operationName)
294 operationName = null;
295 UriTemplateMatch result = methodSpecificTable.MatchSingle(to);
297 if (result != null)
299 operationName = result.Data as string;
300 Fx.Assert(operationName != null, "bad result");
301 AddUriTemplateMatch(result, prop, message);
302 return true;
304 return false;
307 void AddUriTemplateMatch(UriTemplateMatch match, HttpRequestMessageProperty requestProp, Message message)
309 match.SetBaseUri(match.BaseUri, requestProp);
310 message.Properties.Add(IncomingWebRequestContext.UriTemplateMatchResultsPropertyName, match);
313 bool ShouldRedirectToUriWithSlashAtTheEnd(UriTemplateTable methodSpecificTable, Message message, Uri to)
315 UriBuilder ub = new UriBuilder(to);
316 if (ub.Path.EndsWith("/", StringComparison.Ordinal))
318 return false;
321 ub.Path = ub.Path + "/";
322 Uri originalPlusSlash = ub.Uri;
324 bool result = false;
325 if (methodSpecificTable != null && methodSpecificTable.MatchSingle(originalPlusSlash) != null)
327 // as an optimization, we check the table that matched the request's method
328 // first, as it is more probable that a hit happens there
329 result = true;
331 else
333 // back-compat:
334 // we will redirect as long as there is any method
335 // - not necessary the one the user is looking for -
336 // that matches the uri with a slash at the end
338 foreach (KeyValuePair<string, UriTemplateTable> pair in methodSpecificTables)
340 UriTemplateTable table = pair.Value;
341 if (table != methodSpecificTable && table.MatchSingle(originalPlusSlash) != null)
343 result = true;
344 break;
349 if (result)
351 string hostAndPort = GetAuthority(message);
352 originalPlusSlash = UriTemplate.RewriteUri(ub.Uri, hostAndPort);
353 message.Properties.Add(RedirectPropertyName, originalPlusSlash);
355 return result;
358 static string GetAuthority(Message message)
360 HttpRequestMessageProperty requestProperty;
361 string hostName = null;
362 if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out requestProperty))
364 hostName = requestProperty.Headers[HttpRequestHeader.Host];
365 if (!string.IsNullOrEmpty(hostName))
367 return hostName;
370 IAspNetMessageProperty aspNetMessageProperty = AspNetEnvironment.Current.GetHostingProperty(message);
371 if (aspNetMessageProperty != null)
373 hostName = aspNetMessageProperty.OriginalRequestUri.Authority;
375 return hostName;
378 // to enforce that no two ops have same UriTemplate & Method
379 class WCFKey
381 string method;
382 UriTemplate uriTemplate;
383 public WCFKey(UriTemplate uriTemplate, string method)
385 this.uriTemplate = uriTemplate;
386 this.method = method;
388 public override bool Equals(object obj)
390 WCFKey other = obj as WCFKey;
391 if (other == null)
393 return false;
395 return this.uriTemplate.IsEquivalentTo(other.uriTemplate) && this.method == other.method;
397 public override int GetHashCode()
399 return UriTemplateEquivalenceComparer.Instance.GetHashCode(this.uriTemplate);