/// <summary> /// Called after an inbound message has been received but before the message is dispatched to the intended operation. /// </summary> /// <param name="request">The request message.</param> /// <param name="channel">The incoming channel.</param> /// <param name="instanceContext">The current service instance.</param> /// <returns>The object used to correlate state. This object is passed back in the <see cref="M:System.ServiceModel.Dispatcher.IDispatchMessageInspector.BeforeSendReply(System.ServiceModel.Channels.Message@,System.Object)" /> method.</returns> public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { if (!request.Properties.ContainsKey("Via")) { return(request); // Nothing much we can do here } if (!request.Properties.ContainsKey("httpRequest")) { return(request); // Same here } var httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty; if (httpRequest == null) { return(request); } var httpMethod = httpRequest.Method.ToUpper(); var uri = request.Properties["Via"] as Uri; if (uri == null) { return(request); // Still nothing much we can do } var url = uri.AbsoluteUri; var urlFragment = url; if (urlFragment.ToLower().StartsWith(_rootUrlLower)) { urlFragment = urlFragment.Substring(_rootUrlLower.Length); } var operationInfo = RestHelper.GetMethodNameFromUrlFragmentAndContract(urlFragment, httpMethod, _contractType); var urlParameters = RestHelper.GetUrlParametersFromUrlFragmentAndContract(urlFragment, httpMethod, _contractType); if (httpMethod == "GET") { // TODO: Support GET if at all possible throw new Exception("REST-GET operations are not currently supported in the chosen hosting environment. Please use a different HTTP Verb, or host in a different environment (such as WebApi). We hope to add this feature in a future version."); // This is a REST GET operation. Therefore, there is no posted message. Instead, we have to decode the input parameters from the URL var parameters = operationInfo.GetParameters(); if (parameters.Length != 1) { throw new NotSupportedException("Only service methods/operations with a single input parameter can be mapped to REST-GET operations. Method " + operationInfo.Name + " has " + parameters.Length + " parameters. Consider changing the method to have a single object with multiple properties instead."); } var parameterType = parameters[0].ParameterType; var parameterInstance = Activator.CreateInstance(parameterType); foreach (var propertyName in urlParameters.Keys) { var urlProperty = parameterType.GetProperty(propertyName); if (urlProperty == null) { continue; } urlProperty.SetValue(parameterInstance, urlParameters[propertyName], null); } // Seralize the object back into a new request message // TODO: We only need to do this for methods OTHER than GET var format = GetMessageContentFormat(request); switch (format) { case WebContentFormat.Xml: var xmlStream = new MemoryStream(); var xmlSerializer = new DataContractSerializer(parameterInstance.GetType()); xmlSerializer.WriteObject(xmlStream, parameterInstance); var xmlReader = XmlDictionaryReader.CreateTextReader(StreamHelper.ToArray(xmlStream), XmlDictionaryReaderQuotas.Max); var newXmlMessage = Message.CreateMessage(xmlReader, int.MaxValue, request.Version); newXmlMessage.Properties.CopyProperties(request.Properties); newXmlMessage.Headers.CopyHeadersFrom(request.Headers); if (format == WebContentFormat.Default) { if (newXmlMessage.Properties.ContainsKey(WebBodyFormatMessageProperty.Name)) { newXmlMessage.Properties.Remove(WebBodyFormatMessageProperty.Name); } newXmlMessage.Properties.Add(WebBodyFormatMessageProperty.Name, WebContentFormat.Xml); } request = newXmlMessage; break; case WebContentFormat.Default: case WebContentFormat.Json: var jsonStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(parameterInstance.GetType()); serializer.WriteObject(jsonStream, parameterInstance); var jsonReader = JsonReaderWriterFactory.CreateJsonReader(StreamHelper.ToArray(jsonStream), XmlDictionaryReaderQuotas.Max); var newMessage = Message.CreateMessage(jsonReader, int.MaxValue, request.Version); newMessage.Properties.CopyProperties(request.Properties); newMessage.Headers.CopyHeadersFrom(request.Headers); if (format == WebContentFormat.Default) { if (newMessage.Properties.ContainsKey(WebBodyFormatMessageProperty.Name)) { newMessage.Properties.Remove(WebBodyFormatMessageProperty.Name); } newMessage.Properties.Add(WebBodyFormatMessageProperty.Name, WebContentFormat.Json); } request = newMessage; break; default: throw new NotSupportedException("Mesage format " + format.ToString() + " is not supported form REST/JSON operations"); } } return(null); }
/// <summary> /// Inspects the URL fragment, trims the method name (if appropriate) and returns the remaining parameters as a dictionary /// of correlating property names and their values /// </summary> /// <param name="urlFragment">The URL fragment.</param> /// <param name="httpMethod">The HTTP method.</param> /// <param name="contractType">Service contract types.</param> /// <returns>Dictionary of property values</returns> public static Dictionary <string, object> GetUrlParametersFromUrlFragmentAndContract(string urlFragment, string httpMethod, Type contractType) { if (urlFragment.StartsWith("/")) { urlFragment = urlFragment.Substring(1); } var firstParameter = string.Empty; if (urlFragment.IndexOf("/", StringComparison.Ordinal) > -1) { firstParameter = urlFragment.Substring(0, urlFragment.IndexOf("/", StringComparison.Ordinal)); } else if (!string.IsNullOrEmpty(urlFragment)) { firstParameter = urlFragment; } var methods = ObjectHelper.GetAllMethodsForInterface(contractType); MethodInfo foundMethod = null; foreach (var method in methods) { var restAttribute = GetRestAttribute(method); var httpMethodForMethod = restAttribute.Method.ToString().ToUpper(); if (!string.Equals(httpMethod, httpMethodForMethod, StringComparison.OrdinalIgnoreCase)) { continue; } var methodName = method.Name; if (!string.IsNullOrEmpty(restAttribute.Name)) { methodName = restAttribute.Name; } if (!string.Equals(methodName, firstParameter, StringComparison.OrdinalIgnoreCase)) { continue; } urlFragment = urlFragment.Substring(methodName.Length); if (urlFragment.StartsWith("/")) { urlFragment = urlFragment.Substring(1); } foundMethod = method; break; // We found our methoid } if (foundMethod == null) // We haven't found our method yet. If there is a default method (a method with an empty REST name) that matches the HTTP method, we will use that instead { foreach (var method in methods) { var restAttribute = GetRestAttribute(method); if (restAttribute.Name != "") { continue; } var httpMethodForMethod = restAttribute.Method.ToString().ToUpper(); if (!string.Equals(httpMethod, httpMethodForMethod, StringComparison.OrdinalIgnoreCase)) { continue; } foundMethod = method; break; // We found our methoid } } if (foundMethod == null) { return(new Dictionary <string, object>()); // We didn't find a match, therefore, we can't map anything } var foundMethodParameters = foundMethod.GetParameters(); if (foundMethodParameters.Length != 1) { return(new Dictionary <string, object>()); // The method signature has multiple parameters, so we can't handle it (Note: Other code in the chain will probably throw an exception about it, so we just return out here as we do not want duplicate exceptions) } var firstParameterType = foundMethodParameters[0].ParameterType; // Ready to extract the parameters var inlineParameterString = string.Empty; var namedParameterString = string.Empty; var separatorPosition = urlFragment.IndexOf("?", StringComparison.Ordinal); if (separatorPosition > -1) { inlineParameterString = urlFragment.Substring(0, separatorPosition); namedParameterString = urlFragment.Substring(separatorPosition + 1); } else { if (urlFragment.IndexOf("=", StringComparison.Ordinal) > -1) { namedParameterString = urlFragment; } else { inlineParameterString = urlFragment; } } var dictionary = new Dictionary <string, object>(); // Parsing the inline parameters if (!string.IsNullOrEmpty(inlineParameterString)) { var inlineParameters = inlineParameterString.Split('/'); var inlineProperties = RestHelper.GetOrderedInlinePropertyList(firstParameterType); for (var propertyCounter = 0; propertyCounter < inlineParameters.Length; propertyCounter++) { if (propertyCounter >= inlineProperties.Count) { break; // We overshot the available parameters for some reason } var parameterString = HttpHelper.UrlDecode(inlineParameters[propertyCounter]); var parameterValue = ConvertValue(parameterString, inlineProperties[propertyCounter].PropertyType); dictionary.Add(inlineProperties[propertyCounter].Name, parameterValue); } } // Parsing the named parameters if (!string.IsNullOrEmpty(namedParameterString)) { var parameterElements = namedParameterString.Split('&'); foreach (var parameterElement in parameterElements) { var parameterNameValuePair = parameterElement.Split('='); if (parameterNameValuePair.Length != 2) { continue; } var currentProperty = firstParameterType.GetProperty(parameterNameValuePair[0]); if (currentProperty == null) { continue; } var currentPropertyString = HttpHelper.UrlDecode(parameterNameValuePair[1]); var currentPropertyValue = ConvertValue(currentPropertyString, currentProperty.PropertyType); dictionary.Add(parameterNameValuePair[0], currentPropertyValue); } } return(dictionary); }