update portable projects

This commit is contained in:
Luke Pulverenti
2016-11-11 14:55:12 -05:00
parent f8b8de13b7
commit 406e6cb813
100 changed files with 12629 additions and 213 deletions

View File

@@ -0,0 +1,27 @@
using System;
namespace ServiceStack.Host
{
/// <summary>
/// Context to capture IService action
/// </summary>
public class ActionContext
{
public const string AnyAction = "ANY";
public string Id { get; set; }
public ActionInvokerFn ServiceAction { get; set; }
public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; }
public static string Key(Type serviceType, string method, string requestDtoName)
{
return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName;
}
public static string AnyKey(Type serviceType, string requestDtoName)
{
return Key(serviceType, AnyAction, requestDtoName);
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using MediaBrowser.Model.Services;
namespace ServiceStack.Host
{
public class ContentTypes
{
public static ContentTypes Instance = new ContentTypes();
public void SerializeToStream(IRequest req, object response, Stream responseStream)
{
var contentType = req.ResponseContentType;
var serializer = GetResponseSerializer(contentType);
if (serializer == null)
throw new NotSupportedException("ContentType not supported: " + contentType);
var httpRes = new HttpResponseStreamWrapper(responseStream, req)
{
Dto = req.Response.Dto
};
serializer(req, response, httpRes);
}
public Action<IRequest, object, IResponse> GetResponseSerializer(string contentType)
{
var serializer = GetStreamSerializer(contentType);
if (serializer == null) return null;
return (httpReq, dto, httpRes) => serializer(httpReq, dto, httpRes.OutputStream);
}
public Action<IRequest, object, Stream> GetStreamSerializer(string contentType)
{
switch (GetRealContentType(contentType))
{
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
return (r, o, s) => ServiceStackHost.Instance.SerializeToXml(o, s);
case "application/json":
case "text/json":
return (r, o, s) => ServiceStackHost.Instance.SerializeToJson(o, s);
}
return null;
}
public Func<Type, Stream, object> GetStreamDeserializer(string contentType)
{
switch (GetRealContentType(contentType))
{
case "application/xml":
case "text/xml":
case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
return ServiceStackHost.Instance.DeserializeXml;
case "application/json":
case "text/json":
return ServiceStackHost.Instance.DeserializeJson;
}
return null;
}
private static string GetRealContentType(string contentType)
{
return contentType == null
? null
: contentType.Split(';')[0].ToLower().Trim();
}
}
}

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using MediaBrowser.Model.Services;
namespace ServiceStack.Host
{
public class HttpResponseStreamWrapper : IHttpResponse
{
private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false);
public HttpResponseStreamWrapper(Stream stream, IRequest request)
{
this.OutputStream = stream;
this.Request = request;
this.Headers = new Dictionary<string, string>();
this.Items = new Dictionary<string, object>();
}
public Dictionary<string, string> Headers { get; set; }
public object OriginalResponse
{
get { return null; }
}
public IRequest Request { get; private set; }
public int StatusCode { set; get; }
public string StatusDescription { set; get; }
public string ContentType { get; set; }
public void AddHeader(string name, string value)
{
this.Headers[name] = value;
}
public string GetHeader(string name)
{
return this.Headers[name];
}
public void Redirect(string url)
{
this.Headers["Location"] = url;
}
public Stream OutputStream { get; private set; }
public object Dto { get; set; }
public void Write(string text)
{
var bytes = UTF8EncodingWithoutBom.GetBytes(text);
OutputStream.Write(bytes, 0, bytes.Length);
}
public bool UseBufferedStream { get; set; }
public void Close()
{
if (IsClosed) return;
OutputStream.Dispose();
IsClosed = true;
}
public void End()
{
Close();
}
public void Flush()
{
OutputStream.Flush();
}
public bool IsClosed { get; private set; }
public void SetContentLength(long contentLength) {}
public bool KeepAlive { get; set; }
public Dictionary<string, object> Items { get; private set; }
public void SetCookie(Cookie cookie)
{
}
public void ClearCookies()
{
}
}
}

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
namespace ServiceStack.Host
{
public class RestHandler
{
public string RequestName { get; set; }
public async Task<object> HandleResponseAsync(object response)
{
var taskResponse = response as Task;
if (taskResponse == null)
{
return response;
}
await taskResponse.ConfigureAwait(false);
var taskResult = ServiceStackHost.Instance.GetTaskResult(taskResponse, RequestName);
var taskResults = taskResult as Task[];
if (taskResults == null)
{
var subTask = taskResult as Task;
if (subTask != null)
taskResult = ServiceStackHost.Instance.GetTaskResult(subTask, RequestName);
return taskResult;
}
if (taskResults.Length == 0)
{
return new object[] { };
}
var firstResponse = ServiceStackHost.Instance.GetTaskResult(taskResults[0], RequestName);
var batchedResponses = firstResponse != null
? (object[])Array.CreateInstance(firstResponse.GetType(), taskResults.Length)
: new object[taskResults.Length];
batchedResponses[0] = firstResponse;
for (var i = 1; i < taskResults.Length; i++)
{
batchedResponses[i] = ServiceStackHost.Instance.GetTaskResult(taskResults[i], RequestName);
}
return batchedResponses;
}
protected static object CreateContentTypeRequest(IRequest httpReq, Type requestType, string contentType)
{
if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0)
{
var deserializer = ContentTypes.Instance.GetStreamDeserializer(contentType);
if (deserializer != null)
{
return deserializer(requestType, httpReq.InputStream);
}
}
return ServiceStackHost.Instance.CreateInstance(requestType); //Return an empty DTO, even for empty request bodies
}
protected static object GetCustomRequestFromBinder(IRequest httpReq, Type requestType)
{
Func<IRequest, object> requestFactoryFn;
ServiceStackHost.Instance.ServiceController.RequestTypeFactoryMap.TryGetValue(
requestType, out requestFactoryFn);
return requestFactoryFn != null ? requestFactoryFn(httpReq) : null;
}
public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType)
{
pathInfo = GetSanitizedPathInfo(pathInfo, out contentType);
return ServiceStackHost.Instance.ServiceController.GetRestPathForRequest(httpMethod, pathInfo);
}
public static string GetSanitizedPathInfo(string pathInfo, out string contentType)
{
contentType = null;
var pos = pathInfo.LastIndexOf('.');
if (pos >= 0)
{
var format = pathInfo.Substring(pos + 1);
contentType = GetFormatContentType(format);
if (contentType != null)
{
pathInfo = pathInfo.Substring(0, pos);
}
}
return pathInfo;
}
private static string GetFormatContentType(string format)
{
//built-in formats
if (format == "json")
return "application/json";
if (format == "xml")
return "application/xml";
return null;
}
public RestPath GetRestPath(string httpMethod, string pathInfo)
{
if (this.RestPath == null)
{
string contentType;
this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out contentType);
if (contentType != null)
ResponseContentType = contentType;
}
return this.RestPath;
}
public RestPath RestPath { get; set; }
// Set from SSHHF.GetHandlerForPathInfo()
public string ResponseContentType { get; set; }
public async Task ProcessRequestAsync(IRequest httpReq, IResponse httpRes, string operationName)
{
var appHost = ServiceStackHost.Instance;
var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo);
if (restPath == null)
{
throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo);
}
httpReq.SetRoute(restPath);
if (ResponseContentType != null)
httpReq.ResponseContentType = ResponseContentType;
var request = httpReq.Dto = CreateRequest(httpReq, restPath);
if (appHost.ApplyRequestFilters(httpReq, httpRes, request))
return;
var rawResponse = await ServiceStackHost.Instance.ServiceController.Execute(request, httpReq).ConfigureAwait(false);
if (httpRes.IsClosed)
return;
var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false);
if (appHost.ApplyResponseFilters(httpReq, httpRes, response))
return;
await httpRes.WriteToResponse(httpReq, response).ConfigureAwait(false);
}
public static object CreateRequest(IRequest httpReq, RestPath restPath)
{
var dtoFromBinder = GetCustomRequestFromBinder(httpReq, restPath.RequestType);
if (dtoFromBinder != null)
return dtoFromBinder;
var requestParams = httpReq.GetFlattenedRequestParams();
return CreateRequest(httpReq, restPath, requestParams);
}
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams)
{
var requestDto = CreateContentTypeRequest(httpReq, restPath.RequestType, httpReq.ContentType);
return CreateRequest(httpReq, restPath, requestParams, requestDto);
}
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams, object requestDto)
{
string contentType;
var pathInfo = !restPath.IsWildCardPath
? GetSanitizedPathInfo(httpReq.PathInfo, out contentType)
: httpReq.PathInfo;
return restPath.CreateRequest(pathInfo, requestParams, requestDto);
}
/// <summary>
/// Used in Unit tests
/// </summary>
/// <returns></returns>
public object CreateRequest(IRequest httpReq, string operationName)
{
if (this.RestPath == null)
throw new ArgumentNullException("No RestPath found");
return CreateRequest(httpReq, this.RestPath);
}
}
}

View File

@@ -0,0 +1,443 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using ServiceStack.Serialization;
namespace ServiceStack.Host
{
public class RestPath
{
private const string WildCard = "*";
private const char WildCardChar = '*';
private const string PathSeperator = "/";
private const char PathSeperatorChar = '/';
private const char ComponentSeperator = '.';
private const string VariablePrefix = "{";
readonly bool[] componentsWithSeparators;
private readonly string restPath;
private readonly string allowedVerbs;
private readonly bool allowsAllVerbs;
public bool IsWildCardPath { get; private set; }
private readonly string[] literalsToMatch;
private readonly string[] variablesNames;
private readonly bool[] isWildcard;
private readonly int wildcardCount = 0;
public int VariableArgsCount { get; set; }
/// <summary>
/// The number of segments separated by '/' determinable by path.Split('/').Length
/// e.g. /path/to/here.ext == 3
/// </summary>
public int PathComponentsCount { get; set; }
/// <summary>
/// The total number of segments after subparts have been exploded ('.')
/// e.g. /path/to/here.ext == 4
/// </summary>
public int TotalComponentsCount { get; set; }
public string[] Verbs
{
get
{
return allowsAllVerbs
? new[] { ActionContext.AnyAction }
: AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
}
public Type RequestType { get; private set; }
public string Path { get { return this.restPath; } }
public string Summary { get; private set; }
public string Notes { get; private set; }
public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } }
public string AllowedVerbs { get { return this.allowedVerbs; } }
public int Priority { get; set; } //passed back to RouteAttribute
public static string[] GetPathPartsForMatching(string pathInfo)
{
var parts = pathInfo.ToLower().Split(PathSeperatorChar)
.Where(x => !string.IsNullOrEmpty(x)).ToArray();
return parts;
}
public static IEnumerable<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
{
var hashPrefix = pathPartsForMatching.Length + PathSeperator;
return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
}
public static IEnumerable<string> GetFirstMatchWildCardHashKeys(string[] pathPartsForMatching)
{
const string hashPrefix = WildCard + PathSeperator;
return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
}
private static IEnumerable<string> GetPotentialMatchesWithPrefix(string hashPrefix, string[] pathPartsForMatching)
{
foreach (var part in pathPartsForMatching)
{
yield return hashPrefix + part;
var subParts = part.Split(ComponentSeperator);
if (subParts.Length == 1) continue;
foreach (var subPart in subParts)
{
yield return hashPrefix + subPart;
}
}
}
public RestPath(Type requestType, string path, string verbs, string summary = null, string notes = null)
{
this.RequestType = requestType;
this.Summary = summary;
this.Notes = notes;
this.restPath = path;
this.allowsAllVerbs = verbs == null || verbs == WildCard;
if (!this.allowsAllVerbs)
{
this.allowedVerbs = verbs.ToUpper();
}
var componentsList = new List<string>();
//We only split on '.' if the restPath has them. Allows for /{action}.{type}
var hasSeparators = new List<bool>();
foreach (var component in this.restPath.Split(PathSeperatorChar))
{
if (string.IsNullOrEmpty(component)) continue;
if (component.Contains(VariablePrefix)
&& component.IndexOf(ComponentSeperator) != -1)
{
hasSeparators.Add(true);
componentsList.AddRange(component.Split(ComponentSeperator));
}
else
{
hasSeparators.Add(false);
componentsList.Add(component);
}
}
var components = componentsList.ToArray();
this.TotalComponentsCount = components.Length;
this.literalsToMatch = new string[this.TotalComponentsCount];
this.variablesNames = new string[this.TotalComponentsCount];
this.isWildcard = new bool[this.TotalComponentsCount];
this.componentsWithSeparators = hasSeparators.ToArray();
this.PathComponentsCount = this.componentsWithSeparators.Length;
string firstLiteralMatch = null;
var sbHashKey = new StringBuilder();
for (var i = 0; i < components.Length; i++)
{
var component = components[i];
if (component.StartsWith(VariablePrefix))
{
var variableName = component.Substring(1, component.Length - 2);
if (variableName[variableName.Length - 1] == WildCardChar)
{
this.isWildcard[i] = true;
variableName = variableName.Substring(0, variableName.Length - 1);
}
this.variablesNames[i] = variableName;
this.VariableArgsCount++;
}
else
{
this.literalsToMatch[i] = component.ToLower();
sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
if (firstLiteralMatch == null)
{
firstLiteralMatch = this.literalsToMatch[i];
}
}
}
for (var i = 0; i < components.Length - 1; i++)
{
if (!this.isWildcard[i]) continue;
if (this.literalsToMatch[i + 1] == null)
{
throw new ArgumentException(
"A wildcard path component must be at the end of the path or followed by a literal path component.");
}
}
this.wildcardCount = this.isWildcard.Count(x => x);
this.IsWildCardPath = this.wildcardCount > 0;
this.FirstMatchHashKey = !this.IsWildCardPath
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
: WildCardChar + PathSeperator + firstLiteralMatch;
this.IsValid = sbHashKey.Length > 0;
this.UniqueMatchHashKey = sbHashKey.ToString();
this.typeDeserializer = new StringMapTypeDeserializer(this.RequestType);
RegisterCaseInsenstivePropertyNameMappings();
}
private void RegisterCaseInsenstivePropertyNameMappings()
{
foreach (var propertyInfo in RequestType.GetSerializableProperties())
{
var propertyName = propertyInfo.Name;
propertyNamesMap.Add(propertyName.ToLower(), propertyName);
}
}
public bool IsValid { get; set; }
/// <summary>
/// Provide for quick lookups based on hashes that can be determined from a request url
/// </summary>
public string FirstMatchHashKey { get; private set; }
public string UniqueMatchHashKey { get; private set; }
private readonly StringMapTypeDeserializer typeDeserializer;
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
public static Func<RestPath, string, string[], int> CalculateMatchScore { get; set; }
public int MatchScore(string httpMethod, string[] withPathInfoParts)
{
if (CalculateMatchScore != null)
return CalculateMatchScore(this, httpMethod, withPathInfoParts);
int wildcardMatchCount;
var isMatch = IsMatch(httpMethod, withPathInfoParts, out wildcardMatchCount);
if (!isMatch) return -1;
var score = 0;
//Routes with least wildcard matches get the highest score
score += Math.Max((100 - wildcardMatchCount), 1) * 1000;
//Routes with less variable (and more literal) matches
score += Math.Max((10 - VariableArgsCount), 1) * 100;
//Exact verb match is better than ANY
var exactVerb = httpMethod == AllowedVerbs;
score += exactVerb ? 10 : 1;
return score;
}
/// <summary>
/// For performance withPathInfoParts should already be a lower case string
/// to minimize redundant matching operations.
/// </summary>
/// <param name="httpMethod"></param>
/// <param name="withPathInfoParts"></param>
/// <param name="wildcardMatchCount"></param>
/// <returns></returns>
public bool IsMatch(string httpMethod, string[] withPathInfoParts, out int wildcardMatchCount)
{
wildcardMatchCount = 0;
if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false;
if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false;
if (!ExplodeComponents(ref withPathInfoParts)) return false;
if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false;
int pathIx = 0;
for (var i = 0; i < this.TotalComponentsCount; i++)
{
if (this.isWildcard[i])
{
if (i < this.TotalComponentsCount - 1)
{
// Continue to consume up until a match with the next literal
while (pathIx < withPathInfoParts.Length && withPathInfoParts[pathIx] != this.literalsToMatch[i + 1])
{
pathIx++;
wildcardMatchCount++;
}
// Ensure there are still enough parts left to match the remainder
if ((withPathInfoParts.Length - pathIx) < (this.TotalComponentsCount - i - 1))
{
return false;
}
}
else
{
// A wildcard at the end matches the remainder of path
wildcardMatchCount += withPathInfoParts.Length - pathIx;
pathIx = withPathInfoParts.Length;
}
}
else
{
var literalToMatch = this.literalsToMatch[i];
if (literalToMatch == null)
{
// Matching an ordinary (non-wildcard) variable consumes a single part
pathIx++;
continue;
}
if (withPathInfoParts.Length <= pathIx || withPathInfoParts[pathIx] != literalToMatch) return false;
pathIx++;
}
}
return pathIx == withPathInfoParts.Length;
}
private bool ExplodeComponents(ref string[] withPathInfoParts)
{
var totalComponents = new List<string>();
for (var i = 0; i < withPathInfoParts.Length; i++)
{
var component = withPathInfoParts[i];
if (string.IsNullOrEmpty(component)) continue;
if (this.PathComponentsCount != this.TotalComponentsCount
&& this.componentsWithSeparators[i])
{
var subComponents = component.Split(ComponentSeperator);
if (subComponents.Length < 2) return false;
totalComponents.AddRange(subComponents);
}
else
{
totalComponents.Add(component);
}
}
withPathInfoParts = totalComponents.ToArray();
return true;
}
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
{
var requestComponents = pathInfo.Split(PathSeperatorChar)
.Where(x => !string.IsNullOrEmpty(x)).ToArray();
ExplodeComponents(ref requestComponents);
if (requestComponents.Length != this.TotalComponentsCount)
{
var isValidWildCardPath = this.IsWildCardPath
&& requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount;
if (!isValidWildCardPath)
throw new ArgumentException(string.Format(
"Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'",
pathInfo, this.restPath));
}
var requestKeyValuesMap = new Dictionary<string, string>();
var pathIx = 0;
for (var i = 0; i < this.TotalComponentsCount; i++)
{
var variableName = this.variablesNames[i];
if (variableName == null)
{
pathIx++;
continue;
}
string propertyNameOnRequest;
if (!this.propertyNamesMap.TryGetValue(variableName.ToLower(), out propertyNameOnRequest))
{
if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase))
{
pathIx++;
continue;
}
throw new ArgumentException("Could not find property "
+ variableName + " on " + RequestType.GetOperationName());
}
var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch
if (value != null && this.isWildcard[i])
{
if (i == this.TotalComponentsCount - 1)
{
// Wildcard at end of path definition consumes all the rest
var sb = new StringBuilder();
sb.Append(value);
for (var j = pathIx + 1; j < requestComponents.Length; j++)
{
sb.Append(PathSeperatorChar + requestComponents[j]);
}
value = sb.ToString();
}
else
{
// Wildcard in middle of path definition consumes up until it
// hits a match for the next element in the definition (which must be a literal)
// It may consume 0 or more path parts
var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1];
if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
var sb = new StringBuilder();
sb.Append(value);
pathIx++;
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
}
value = sb.ToString();
}
else
{
value = null;
}
}
}
else
{
// Variable consumes single path item
pathIx++;
}
requestKeyValuesMap[propertyNameOnRequest] = value;
}
if (queryStringAndFormData != null)
{
//Query String and form data can override variable path matches
//path variables < query string < form data
foreach (var name in queryStringAndFormData)
{
requestKeyValuesMap[name.Key] = name.Value;
}
}
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
}
public override int GetHashCode()
{
return UniqueMatchHashKey.GetHashCode();
}
}
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
namespace ServiceStack.Host
{
public delegate Task<object> InstanceExecFn(IRequest requestContext, object intance, object request);
public delegate object ActionInvokerFn(object intance, object request);
public delegate void VoidActionInvokerFn(object intance, object request);
public class ServiceController
{
private readonly Func<IEnumerable<Type>> _resolveServicesFn;
public ServiceController(Func<IEnumerable<Type>> resolveServicesFn)
{
_resolveServicesFn = resolveServicesFn;
this.RequestTypeFactoryMap = new Dictionary<Type, Func<IRequest, object>>();
}
public Dictionary<Type, Func<IRequest, object>> RequestTypeFactoryMap { get; set; }
public void Init()
{
foreach (var serviceType in _resolveServicesFn())
{
RegisterService(serviceType);
}
}
private Type[] GetGenericArguments(Type type)
{
return type.GetTypeInfo().IsGenericTypeDefinition
? type.GetTypeInfo().GenericTypeParameters
: type.GetTypeInfo().GenericTypeArguments;
}
public void RegisterService(Type serviceType)
{
var processedReqs = new HashSet<Type>();
var actions = ServiceExecGeneral.Reset(serviceType);
var requiresRequestStreamTypeInfo = typeof(IRequiresRequestStream).GetTypeInfo();
var appHost = ServiceStackHost.Instance;
foreach (var mi in serviceType.GetActions())
{
var requestType = mi.GetParameters()[0].ParameterType;
if (processedReqs.Contains(requestType)) continue;
processedReqs.Add(requestType);
ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
var returnMarker = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IReturn<>));
var responseType = returnMarker != null ?
GetGenericArguments(returnMarker)[0]
: mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
mi.ReturnType
: Type.GetType(requestType.FullName + "Response");
RegisterRestPaths(requestType);
appHost.Metadata.Add(serviceType, requestType, responseType);
if (requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()))
{
this.RequestTypeFactoryMap[requestType] = req =>
{
var restPath = req.GetRoute();
var request = RestHandler.CreateRequest(req, restPath, req.GetRequestParams(), ServiceStackHost.Instance.CreateInstance(requestType));
var rawReq = (IRequiresRequestStream)request;
rawReq.RequestStream = req.InputStream;
return rawReq;
};
}
}
}
public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>();
public void RegisterRestPaths(Type requestType)
{
var appHost = ServiceStackHost.Instance;
var attrs = appHost.GetRouteAttributes(requestType);
foreach (MediaBrowser.Model.Services.RouteAttribute attr in attrs)
{
var restPath = new RestPath(requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
if (!restPath.IsValid)
throw new NotSupportedException(string.Format(
"RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetOperationName()));
RegisterRestPath(restPath);
}
}
private static readonly char[] InvalidRouteChars = new[] { '?', '&' };
public void RegisterRestPath(RestPath restPath)
{
if (!restPath.Path.StartsWith("/"))
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetOperationName()));
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
"See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetOperationName()));
List<RestPath> pathsAtFirstMatch;
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
{
pathsAtFirstMatch = new List<RestPath>();
RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
}
pathsAtFirstMatch.Add(restPath);
}
public void AfterInit()
{
var appHost = ServiceStackHost.Instance;
//Register any routes configured on Metadata.Routes
foreach (var restPath in appHost.RestPaths)
{
RegisterRestPath(restPath);
}
//Sync the RestPaths collections
appHost.RestPaths.Clear();
appHost.RestPaths.AddRange(RestPathMap.Values.SelectMany(x => x));
}
public RestPath GetRestPathForRequest(string httpMethod, string pathInfo)
{
var matchUsingPathParts = RestPath.GetPathPartsForMatching(pathInfo);
List<RestPath> firstMatches;
var yieldedHashMatches = RestPath.GetFirstMatchHashKeys(matchUsingPathParts);
foreach (var potentialHashMatch in yieldedHashMatches)
{
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
var bestScore = -1;
foreach (var restPath in firstMatches)
{
var score = restPath.MatchScore(httpMethod, matchUsingPathParts);
if (score > bestScore) bestScore = score;
}
if (bestScore > 0)
{
foreach (var restPath in firstMatches)
{
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts))
return restPath;
}
}
}
var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts);
foreach (var potentialHashMatch in yieldedWildcardMatches)
{
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
var bestScore = -1;
foreach (var restPath in firstMatches)
{
var score = restPath.MatchScore(httpMethod, matchUsingPathParts);
if (score > bestScore) bestScore = score;
}
if (bestScore > 0)
{
foreach (var restPath in firstMatches)
{
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts))
return restPath;
}
}
}
return null;
}
public async Task<object> Execute(object requestDto, IRequest req)
{
req.Dto = requestDto;
var requestType = requestDto.GetType();
req.OperationName = requestType.Name;
var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestType);
var service = ServiceStackHost.Instance.CreateInstance(serviceType);
//var service = typeFactory.CreateInstance(serviceType);
var serviceRequiresContext = service as IRequiresRequest;
if (serviceRequiresContext != null)
{
serviceRequiresContext.Request = req;
}
if (req.Dto == null) // Don't override existing batched DTO[]
req.Dto = requestDto;
//Executes the service and returns the result
var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false);
if (req.Response.Dto == null)
req.Response.Dto = response;
return response;
}
}
}

View File

@@ -0,0 +1,156 @@
//Copyright (c) Service Stack LLC. All Rights Reserved.
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
namespace ServiceStack.Host
{
public static class ServiceExecExtensions
{
public static IEnumerable<MethodInfo> GetActions(this Type serviceType)
{
foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic))
{
if (mi.GetParameters().Length != 1)
continue;
var actionName = mi.Name.ToUpper();
if (!HttpMethods.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
continue;
yield return mi;
}
}
}
internal static class ServiceExecGeneral
{
public static Dictionary<string, ActionContext> execMap = new Dictionary<string, ActionContext>();
public static void CreateServiceRunnersFor(Type requestType, List<ActionContext> actions)
{
foreach (var actionCtx in actions)
{
if (execMap.ContainsKey(actionCtx.Id)) continue;
execMap[actionCtx.Id] = actionCtx;
}
}
public static async Task<object> Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName)
{
var actionName = request.Verb
?? HttpMethods.Post; //MQ Services
ActionContext actionContext;
if (ServiceExecGeneral.execMap.TryGetValue(ActionContext.Key(serviceType, actionName, requestName), out actionContext)
|| ServiceExecGeneral.execMap.TryGetValue(ActionContext.AnyKey(serviceType, requestName), out actionContext))
{
if (actionContext.RequestFilters != null)
{
foreach (var requestFilter in actionContext.RequestFilters)
{
requestFilter.RequestFilter(request, request.Response, requestDto);
if (request.Response.IsClosed) return null;
}
}
var response = actionContext.ServiceAction(instance, requestDto);
var taskResponse = response as Task;
if (taskResponse != null)
{
await taskResponse.ConfigureAwait(false);
response = ServiceStackHost.Instance.GetTaskResult(taskResponse, requestName);
}
return response;
}
var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower();
throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetOperationName(), expectedMethodName, serviceType.GetOperationName()));
}
public static List<ActionContext> Reset(Type serviceType)
{
var actions = new List<ActionContext>();
foreach (var mi in serviceType.GetActions())
{
var actionName = mi.Name.ToUpper();
var args = mi.GetParameters();
var requestType = args[0].ParameterType;
var actionCtx = new ActionContext
{
Id = ActionContext.Key(serviceType, actionName, requestType.GetOperationName())
};
try
{
actionCtx.ServiceAction = CreateExecFn(serviceType, requestType, mi);
}
catch
{
//Potential problems with MONO, using reflection for fallback
actionCtx.ServiceAction = (service, request) =>
mi.Invoke(service, new[] { request });
}
var reqFilters = new List<IHasRequestFilter>();
foreach (var attr in mi.GetCustomAttributes(true))
{
var hasReqFilter = attr as IHasRequestFilter;
if (hasReqFilter != null)
reqFilters.Add(hasReqFilter);
}
if (reqFilters.Count > 0)
actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray();
actions.Add(actionCtx);
}
return actions;
}
private static ActionInvokerFn CreateExecFn(Type serviceType, Type requestType, MethodInfo mi)
{
var serviceParam = Expression.Parameter(typeof(object), "serviceObj");
var serviceStrong = Expression.Convert(serviceParam, serviceType);
var requestDtoParam = Expression.Parameter(typeof(object), "requestDto");
var requestDtoStrong = Expression.Convert(requestDtoParam, requestType);
Expression callExecute = Expression.Call(
serviceStrong, mi, requestDtoStrong);
if (mi.ReturnType != typeof(void))
{
var executeFunc = Expression.Lambda<ActionInvokerFn>
(callExecute, serviceParam, requestDtoParam).Compile();
return executeFunc;
}
else
{
var executeFunc = Expression.Lambda<VoidActionInvokerFn>
(callExecute, serviceParam, requestDtoParam).Compile();
return (service, request) =>
{
executeFunc(service, request);
return null;
};
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace ServiceStack.Host
{
public class ServiceMetadata
{
public ServiceMetadata()
{
this.OperationsMap = new Dictionary<Type, Type>();
}
public Dictionary<Type, Type> OperationsMap { get; protected set; }
public void Add(Type serviceType, Type requestType, Type responseType)
{
this.OperationsMap[requestType] = serviceType;
}
public Type GetServiceTypeByRequest(Type requestType)
{
Type serviceType;
OperationsMap.TryGetValue(requestType, out serviceType);
return serviceType;
}
}
}