/// <summary> /// Serialize the request for the operation /// </summary> public void DeserializeRequest(EndpointOperation operation, RestRequestMessage request, object[] parameters) { try { var httpRequest = RestOperationContext.Current.IncomingRequest; string contentTypeHeader = httpRequest.Headers["Content-Type"]; ContentType contentType = null; if (!String.IsNullOrEmpty(contentTypeHeader)) { contentType = new ContentType(contentTypeHeader); } for (int pNumber = 0; pNumber < parameters.Length; pNumber++) { var parm = operation.Description.InvokeMethod.GetParameters()[pNumber]; // TODO: Look for MessageFormatAttribute for override // Simple parameter if (parameters[pNumber] != null) { continue; // dispatcher already populated } else { switch (contentType.MediaType) { case "application/xml": if (!this.m_serializers.TryGetValue(parm.ParameterType, out XmlSerializer serializer)) { serializer = new XmlSerializer(parm.ParameterType); this.m_serializers.TryAdd(parm.ParameterType, serializer); } var requestObject = serializer.Deserialize(request.Body); parameters[pNumber] = requestObject; break; case "application/json": using (var sr = new StreamReader(request.Body)) { JsonSerializer jsz = new JsonSerializer() { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.All }; jsz.Converters.Add(new StringEnumConverter()); var dserType = parm.ParameterType; parameters[pNumber] = jsz.Deserialize(sr, dserType); } break; case "application/octet-stream": parameters[pNumber] = request.Body; break; case "application/x-www-form-urlencoded": NameValueCollection nvc = new NameValueCollection(); using (var sr = new StreamReader(request.Body)) { var ptext = sr.ReadToEnd(); var parms = ptext.Split('&'); foreach (var p in parms) { var parmData = p.Split('='); parmData[1] += new string('=', parmData.Length - 2); nvc.Add(WebUtility.UrlDecode(parmData[0]), WebUtility.UrlDecode(parmData[1])); } } parameters[pNumber] = nvc; break; default: throw new InvalidOperationException("Invalid request format"); } } } } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); throw; } }
/// <summary> /// Creats a new operation dispatcher /// </summary> public OperationDispatcher(EndpointOperation endpointOperation) { this.m_endpointOperation = endpointOperation; var match = this.m_templateParser.Match(endpointOperation.Description.UriTemplate); var regexBuilder = new StringBuilder("^"); this.DispatchFormatter = new DefaultDispatchFormatter(); Type[] parmTypes = endpointOperation.Description.InvokeMethod.GetParameters().Select(o => o.ParameterType).ToArray(); m_regexGroupNames = new string[parmTypes.Length]; int parmCount = 0; while (match.Success) { switch (match.Groups[1].Value[0]) { case '{': // parameter if (parmTypes.Length < parmCount) { throw new InvalidOperationException($"REST method accepts {parmTypes.Length} parameters but route specifies more"); } m_regexGroupNames[parmCount] = match.Groups[1].Value; var ptype = parmTypes[parmCount++]; switch (ptype.Name.ToLowerInvariant()) { case "string": if (match.Groups[1].Value.StartsWith("{*")) // match all { m_regexGroupNames[parmCount - 1] = "{" + m_regexGroupNames[parmCount - 1].Substring(2); regexBuilder.Append(@"(.*?)"); } else { regexBuilder.Append(@"([A-Za-z0-9_\-%\.\~\\]*?)"); } break; case "int32": regexBuilder.Append("(\\d*?)"); break; case "guid": regexBuilder.Append("([a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12})"); break; default: throw new InvalidOperationException($"Cannot use parameter type {ptype.Name} on route"); } break; case '*': // anything regexBuilder.Append(".*"); break; default: regexBuilder.Append(match.Groups[1].Value.Replace("$", "\\$")); break; } match = match.NextMatch(); } if (regexBuilder[1] == '/') // starting / is optional { regexBuilder.Insert(2, "?"); } if (regexBuilder[regexBuilder.Length - 1] == '/') // ending with /? is optional { regexBuilder.Append("?"); } regexBuilder.Append("$"); this.m_regexGroupNames = this.m_regexGroupNames.Where(o => o != null).ToArray(); this.m_traceSource.TraceEvent(TraceEventType.Verbose, 0, "Operation {0} will be bound to {1}", endpointOperation.Description.InvokeMethod, regexBuilder); this.m_dispatchRegex = new Regex(regexBuilder.ToString(), RegexOptions.IgnoreCase); }