mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-06 18:26:33 +00:00
update portable projects
This commit is contained in:
27
ServiceStack/FilterAttributeCache.cs
Normal file
27
ServiceStack/FilterAttributeCache.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using ServiceStack;
|
||||
|
||||
namespace ServiceStack.Support.WebHost
|
||||
{
|
||||
public static class FilterAttributeCache
|
||||
{
|
||||
public static MediaBrowser.Model.Services.IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType)
|
||||
{
|
||||
var attributes = requestDtoType.AllAttributes().OfType<MediaBrowser.Model.Services.IHasRequestFilter>().ToList();
|
||||
|
||||
var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestDtoType);
|
||||
if (serviceType != null)
|
||||
{
|
||||
attributes.AddRange(serviceType.AllAttributes().OfType<MediaBrowser.Model.Services.IHasRequestFilter>());
|
||||
}
|
||||
|
||||
attributes.Sort((x,y) => x.Priority - y.Priority);
|
||||
|
||||
return attributes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
ServiceStack/Host/ActionContext.cs
Normal file
27
ServiceStack/Host/ActionContext.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
ServiceStack/Host/ContentTypes.cs
Normal file
77
ServiceStack/Host/ContentTypes.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
95
ServiceStack/Host/HttpResponseStreamWrapper.cs
Normal file
95
ServiceStack/Host/HttpResponseStreamWrapper.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
200
ServiceStack/Host/RestHandler.cs
Normal file
200
ServiceStack/Host/RestHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
443
ServiceStack/Host/RestPath.cs
Normal file
443
ServiceStack/Host/RestPath.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
220
ServiceStack/Host/ServiceController.cs
Normal file
220
ServiceStack/Host/ServiceController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
156
ServiceStack/Host/ServiceExec.cs
Normal file
156
ServiceStack/Host/ServiceExec.cs
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
ServiceStack/Host/ServiceMetadata.cs
Normal file
27
ServiceStack/Host/ServiceMetadata.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
ServiceStack/HttpHandlerFactory.cs
Normal file
27
ServiceStack/HttpHandlerFactory.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Host;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public class HttpHandlerFactory
|
||||
{
|
||||
// Entry point for HttpListener
|
||||
public static RestHandler GetHandler(IHttpRequest httpReq)
|
||||
{
|
||||
var pathInfo = httpReq.PathInfo;
|
||||
|
||||
var pathParts = pathInfo.TrimStart('/').Split('/');
|
||||
if (pathParts.Length == 0) return null;
|
||||
|
||||
string contentType;
|
||||
var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType);
|
||||
if (restPath != null)
|
||||
return new RestHandler { RestPath = restPath, RequestName = restPath.RequestType.GetOperationName(), ResponseContentType = contentType };
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
ServiceStack/HttpRequestExtensions.cs
Normal file
127
ServiceStack/HttpRequestExtensions.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Host;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
/**
|
||||
*
|
||||
Input: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?q=item#fragment
|
||||
|
||||
Some HttpRequest path and URL properties:
|
||||
Request.ApplicationPath: /Cambia3
|
||||
Request.CurrentExecutionFilePath: /Cambia3/Temp/Test.aspx
|
||||
Request.FilePath: /Cambia3/Temp/Test.aspx
|
||||
Request.Path: /Cambia3/Temp/Test.aspx/path/info
|
||||
Request.PathInfo: /path/info
|
||||
Request.PhysicalApplicationPath: D:\Inetpub\wwwroot\CambiaWeb\Cambia3\
|
||||
Request.QueryString: /Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||
Request.Url.AbsolutePath: /Cambia3/Temp/Test.aspx/path/info
|
||||
Request.Url.AbsoluteUri: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||
Request.Url.Fragment:
|
||||
Request.Url.Host: localhost
|
||||
Request.Url.LocalPath: /Cambia3/Temp/Test.aspx/path/info
|
||||
Request.Url.PathAndQuery: /Cambia3/Temp/Test.aspx/path/info?query=arg
|
||||
Request.Url.Port: 96
|
||||
Request.Url.Query: ?query=arg
|
||||
Request.Url.Scheme: http
|
||||
Request.Url.Segments: /
|
||||
Cambia3/
|
||||
Temp/
|
||||
Test.aspx/
|
||||
path/
|
||||
info
|
||||
* */
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate Params are given a unique key by appending a #1 suffix
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> GetRequestParams(this IRequest request)
|
||||
{
|
||||
var map = new Dictionary<string, string>();
|
||||
|
||||
foreach (var name in request.QueryString.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
|
||||
var values = request.QueryString.GetValues(name);
|
||||
if (values.Length == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put)
|
||||
&& request.FormData != null)
|
||||
{
|
||||
foreach (var name in request.FormData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
|
||||
var values = request.FormData.GetValues(name);
|
||||
if (values.Length == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate params have their values joined together in a comma-delimited string
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> GetFlattenedRequestParams(this IRequest request)
|
||||
{
|
||||
var map = new Dictionary<string, string>();
|
||||
|
||||
foreach (var name in request.QueryString.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
map[name] = request.QueryString[name];
|
||||
}
|
||||
|
||||
if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put)
|
||||
&& request.FormData != null)
|
||||
{
|
||||
foreach (var name in request.FormData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
map[name] = request.FormData[name];
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void SetRoute(this IRequest req, RestPath route)
|
||||
{
|
||||
req.Items["__route"] = route;
|
||||
}
|
||||
|
||||
public static RestPath GetRoute(this IRequest req)
|
||||
{
|
||||
object route;
|
||||
req.Items.TryGetValue("__route", out route);
|
||||
return route as RestPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
237
ServiceStack/HttpResponseExtensionsInternal.cs
Normal file
237
ServiceStack/HttpResponseExtensionsInternal.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
//Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Host;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public static class HttpResponseExtensionsInternal
|
||||
{
|
||||
public static async Task<bool> WriteToOutputStream(IResponse response, object result)
|
||||
{
|
||||
var asyncStreamWriter = result as IAsyncStreamWriter;
|
||||
if (asyncStreamWriter != null)
|
||||
{
|
||||
await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var streamWriter = result as IStreamWriter;
|
||||
if (streamWriter != null)
|
||||
{
|
||||
streamWriter.WriteTo(response.OutputStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
var stream = result as Stream;
|
||||
if (stream != null)
|
||||
{
|
||||
WriteTo(stream, response.OutputStream);
|
||||
return true;
|
||||
}
|
||||
|
||||
var bytes = result as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
response.ContentType = "application/octet-stream";
|
||||
response.SetContentLength(bytes.Length);
|
||||
|
||||
response.OutputStream.Write(bytes, 0, bytes.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static long WriteTo(Stream inStream, Stream outStream)
|
||||
{
|
||||
var memoryStream = inStream as MemoryStream;
|
||||
if (memoryStream != null)
|
||||
{
|
||||
memoryStream.WriteTo(outStream);
|
||||
return memoryStream.Position;
|
||||
}
|
||||
|
||||
var data = new byte[4096];
|
||||
long total = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0)
|
||||
{
|
||||
outStream.Write(data, 0, bytesRead);
|
||||
total += bytesRead;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End a ServiceStack Request with no content
|
||||
/// </summary>
|
||||
public static void EndRequestWithNoContent(this IResponse httpRes)
|
||||
{
|
||||
if (httpRes.StatusCode == (int)HttpStatusCode.OK)
|
||||
{
|
||||
httpRes.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
}
|
||||
|
||||
httpRes.SetContentLength(0);
|
||||
}
|
||||
|
||||
public static Task WriteToResponse(this IResponse httpRes, MediaBrowser.Model.Services.IRequest httpReq, object result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
httpRes.EndRequestWithNoContent();
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var httpResult = result as IHttpResult;
|
||||
if (httpResult != null)
|
||||
{
|
||||
httpResult.RequestContext = httpReq;
|
||||
httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType;
|
||||
var httpResSerializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType);
|
||||
return httpRes.WriteToResponse(httpResult, httpResSerializer, httpReq);
|
||||
}
|
||||
|
||||
var serializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType);
|
||||
return httpRes.WriteToResponse(result, serializer, httpReq);
|
||||
}
|
||||
|
||||
private static object GetDto(object response)
|
||||
{
|
||||
if (response == null) return null;
|
||||
var httpResult = response as IHttpResult;
|
||||
return httpResult != null ? httpResult.Response : response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to response.
|
||||
/// Response headers are customizable by implementing IHasHeaders an returning Dictionary of Http headers.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
/// <param name="result">Whether or not it was implicity handled by ServiceStack's built-in handlers.</param>
|
||||
/// <param name="defaultAction">The default action.</param>
|
||||
/// <param name="request">The serialization context.</param>
|
||||
/// <returns></returns>
|
||||
public static async Task WriteToResponse(this IResponse response, object result, Action<IRequest, object, IResponse> defaultAction, MediaBrowser.Model.Services.IRequest request)
|
||||
{
|
||||
var defaultContentType = request.ResponseContentType;
|
||||
if (result == null)
|
||||
{
|
||||
response.EndRequestWithNoContent();
|
||||
return;
|
||||
}
|
||||
|
||||
var httpResult = result as IHttpResult;
|
||||
if (httpResult != null)
|
||||
{
|
||||
if (httpResult.RequestContext == null)
|
||||
httpResult.RequestContext = request;
|
||||
|
||||
response.Dto = response.Dto ?? GetDto(httpResult);
|
||||
|
||||
response.StatusCode = httpResult.Status;
|
||||
response.StatusDescription = httpResult.StatusDescription ?? httpResult.StatusCode.ToString();
|
||||
if (string.IsNullOrEmpty(httpResult.ContentType))
|
||||
{
|
||||
httpResult.ContentType = defaultContentType;
|
||||
}
|
||||
response.ContentType = httpResult.ContentType;
|
||||
|
||||
if (httpResult.Cookies != null)
|
||||
{
|
||||
var httpRes = response as IHttpResponse;
|
||||
if (httpRes != null)
|
||||
{
|
||||
foreach (var cookie in httpResult.Cookies)
|
||||
{
|
||||
httpRes.SetCookie(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Dto = result;
|
||||
}
|
||||
|
||||
/* Mono Error: Exception: Method not found: 'System.Web.HttpResponse.get_Headers' */
|
||||
var responseOptions = result as IHasHeaders;
|
||||
if (responseOptions != null)
|
||||
{
|
||||
//Reserving options with keys in the format 'xx.xxx' (No Http headers contain a '.' so its a safe restriction)
|
||||
const string reservedOptions = ".";
|
||||
|
||||
foreach (var responseHeaders in responseOptions.Headers)
|
||||
{
|
||||
if (responseHeaders.Key.Contains(reservedOptions)) continue;
|
||||
if (responseHeaders.Key == "Content-Length")
|
||||
{
|
||||
response.SetContentLength(long.Parse(responseHeaders.Value));
|
||||
continue;
|
||||
}
|
||||
|
||||
response.AddHeader(responseHeaders.Key, responseHeaders.Value);
|
||||
}
|
||||
}
|
||||
|
||||
//ContentType='text/html' is the default for a HttpResponse
|
||||
//Do not override if another has been set
|
||||
if (response.ContentType == null || response.ContentType == "text/html")
|
||||
{
|
||||
response.ContentType = defaultContentType;
|
||||
}
|
||||
|
||||
if (new HashSet<string> { "application/json", }.Contains(response.ContentType))
|
||||
{
|
||||
response.ContentType += "; charset=utf-8";
|
||||
}
|
||||
|
||||
var disposableResult = result as IDisposable;
|
||||
var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false);
|
||||
if (writeToOutputStreamResult)
|
||||
{
|
||||
response.Flush(); //required for Compression
|
||||
if (disposableResult != null) disposableResult.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpResult != null)
|
||||
result = httpResult.Response;
|
||||
|
||||
var responseText = result as string;
|
||||
if (responseText != null)
|
||||
{
|
||||
if (response.ContentType == null || response.ContentType == "text/html")
|
||||
response.ContentType = defaultContentType;
|
||||
response.Write(responseText);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (defaultAction == null)
|
||||
{
|
||||
throw new ArgumentNullException("defaultAction", String.Format(
|
||||
"As result '{0}' is not a supported responseType, a defaultAction must be supplied",
|
||||
(result != null ? result.GetType().GetOperationName() : "")));
|
||||
}
|
||||
|
||||
|
||||
if (result != null)
|
||||
defaultAction(request, result, response);
|
||||
|
||||
if (disposableResult != null)
|
||||
disposableResult.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
250
ServiceStack/HttpResult.cs
Normal file
250
ServiceStack/HttpResult.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Host;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public class HttpResult
|
||||
: IHttpResult, IAsyncStreamWriter
|
||||
{
|
||||
public HttpResult()
|
||||
: this((object)null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpResult(object response)
|
||||
: this(response, null)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpResult(object response, string contentType)
|
||||
: this(response, contentType, HttpStatusCode.OK)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpResult(HttpStatusCode statusCode, string statusDescription)
|
||||
: this()
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
StatusDescription = statusDescription;
|
||||
}
|
||||
|
||||
public HttpResult(object response, HttpStatusCode statusCode)
|
||||
: this(response, null, statusCode)
|
||||
{ }
|
||||
|
||||
public HttpResult(object response, string contentType, HttpStatusCode statusCode)
|
||||
{
|
||||
this.Headers = new Dictionary<string, string>();
|
||||
this.Cookies = new List<Cookie>();
|
||||
|
||||
this.Response = response;
|
||||
this.ContentType = contentType;
|
||||
this.StatusCode = statusCode;
|
||||
}
|
||||
|
||||
public HttpResult(Stream responseStream, string contentType)
|
||||
: this(null, contentType, HttpStatusCode.OK)
|
||||
{
|
||||
this.ResponseStream = responseStream;
|
||||
}
|
||||
|
||||
public HttpResult(string responseText, string contentType)
|
||||
: this(null, contentType, HttpStatusCode.OK)
|
||||
{
|
||||
this.ResponseText = responseText;
|
||||
}
|
||||
|
||||
public HttpResult(byte[] responseBytes, string contentType)
|
||||
: this(null, contentType, HttpStatusCode.OK)
|
||||
{
|
||||
this.ResponseStream = new MemoryStream(responseBytes);
|
||||
}
|
||||
|
||||
public string ResponseText { get; private set; }
|
||||
|
||||
public Stream ResponseStream { get; private set; }
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IDictionary<string, string> Headers { get; private set; }
|
||||
|
||||
public List<Cookie> Cookies { get; private set; }
|
||||
|
||||
public string ETag { get; set; }
|
||||
|
||||
public TimeSpan? Age { get; set; }
|
||||
|
||||
public TimeSpan? MaxAge { get; set; }
|
||||
|
||||
public DateTime? Expires { get; set; }
|
||||
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
public Func<IDisposable> ResultScope { get; set; }
|
||||
|
||||
public string Location
|
||||
{
|
||||
set
|
||||
{
|
||||
if (StatusCode == HttpStatusCode.OK)
|
||||
StatusCode = HttpStatusCode.Redirect;
|
||||
|
||||
this.Headers["Location"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPermanentCookie(string name, string value)
|
||||
{
|
||||
SetCookie(name, value, DateTime.UtcNow.AddYears(20), null);
|
||||
}
|
||||
|
||||
public void SetPermanentCookie(string name, string value, string path)
|
||||
{
|
||||
SetCookie(name, value, DateTime.UtcNow.AddYears(20), path);
|
||||
}
|
||||
|
||||
public void SetSessionCookie(string name, string value)
|
||||
{
|
||||
SetSessionCookie(name, value, null);
|
||||
}
|
||||
|
||||
public void SetSessionCookie(string name, string value, string path)
|
||||
{
|
||||
path = path ?? "/";
|
||||
this.Headers["Set-Cookie"] = string.Format("{0}={1};path=" + path, name, value);
|
||||
}
|
||||
|
||||
public void SetCookie(string name, string value, TimeSpan expiresIn, string path)
|
||||
{
|
||||
var expiresAt = DateTime.UtcNow.Add(expiresIn);
|
||||
SetCookie(name, value, expiresAt, path);
|
||||
}
|
||||
|
||||
public void SetCookie(string name, string value, DateTime expiresAt, string path, bool secure = false, bool httpOnly = false)
|
||||
{
|
||||
path = path ?? "/";
|
||||
var cookie = string.Format("{0}={1};expires={2};path={3}", name, value, expiresAt.ToString("R"), path);
|
||||
if (secure)
|
||||
cookie += ";Secure";
|
||||
if (httpOnly)
|
||||
cookie += ";HttpOnly";
|
||||
|
||||
this.Headers["Set-Cookie"] = cookie;
|
||||
}
|
||||
|
||||
public void DeleteCookie(string name)
|
||||
{
|
||||
var cookie = string.Format("{0}=;expires={1};path=/", name, DateTime.UtcNow.AddDays(-1).ToString("R"));
|
||||
this.Headers["Set-Cookie"] = cookie;
|
||||
}
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get { return (HttpStatusCode)Status; }
|
||||
set { Status = (int)value; }
|
||||
}
|
||||
|
||||
public string StatusDescription { get; set; }
|
||||
|
||||
public object Response { get; set; }
|
||||
|
||||
public MediaBrowser.Model.Services.IRequest RequestContext { get; set; }
|
||||
|
||||
public string View { get; set; }
|
||||
|
||||
public string Template { get; set; }
|
||||
|
||||
public int PaddingLength { get; set; }
|
||||
|
||||
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WriteToInternalAsync(responseStream, cancellationToken).ConfigureAwait(false);
|
||||
responseStream.Flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeStream();
|
||||
}
|
||||
}
|
||||
|
||||
public static Task WriteTo(Stream inStream, Stream outStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var memoryStream = inStream as MemoryStream;
|
||||
if (memoryStream != null)
|
||||
{
|
||||
memoryStream.WriteTo(outStream);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return inStream.CopyToAsync(outStream, 81920, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task WriteToInternalAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = RequestContext != null ? RequestContext.Response : null;
|
||||
|
||||
if (this.ResponseStream != null)
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
var ms = ResponseStream as MemoryStream;
|
||||
if (ms != null)
|
||||
{
|
||||
response.SetContentLength(ms.Length);
|
||||
|
||||
await ms.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteTo(this.ResponseStream, responseStream, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ResponseText != null)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(this.ResponseText);
|
||||
if (response != null)
|
||||
response.SetContentLength(bytes.Length);
|
||||
|
||||
await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var bytesResponse = this.Response as byte[];
|
||||
if (bytesResponse != null)
|
||||
{
|
||||
if (response != null)
|
||||
response.SetContentLength(bytesResponse.Length);
|
||||
|
||||
await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream);
|
||||
}
|
||||
|
||||
private void DisposeStream()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ResponseStream != null)
|
||||
{
|
||||
this.ResponseStream.Dispose();
|
||||
}
|
||||
}
|
||||
catch { /*ignore*/ }
|
||||
}
|
||||
}
|
||||
}
|
||||
34
ServiceStack/HttpUtils.cs
Normal file
34
ServiceStack/HttpUtils.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
//Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||
//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
internal static class HttpMethods
|
||||
{
|
||||
static readonly string[] allVerbs = new[] {
|
||||
"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616
|
||||
"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518
|
||||
"VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
|
||||
"MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253
|
||||
"ORDERPATCH", // RFC 3648
|
||||
"ACL", // RFC 3744
|
||||
"PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/
|
||||
"SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/
|
||||
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY",
|
||||
"POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx
|
||||
};
|
||||
|
||||
public static HashSet<string> AllVerbs = new HashSet<string>(allVerbs);
|
||||
|
||||
public const string Get = "GET";
|
||||
public const string Put = "PUT";
|
||||
public const string Post = "POST";
|
||||
public const string Delete = "DELETE";
|
||||
public const string Options = "OPTIONS";
|
||||
public const string Head = "HEAD";
|
||||
public const string Patch = "PATCH";
|
||||
}
|
||||
}
|
||||
25
ServiceStack/Properties/AssemblyInfo.cs
Normal file
25
ServiceStack/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ServiceStack")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Service Stack LLC")]
|
||||
[assembly: AssemblyProduct("ServiceStack")]
|
||||
[assembly: AssemblyCopyright("Copyright (c) ServiceStack 2016")]
|
||||
[assembly: AssemblyTrademark("Service Stack")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("06704d66-af8e-411f-8260-8d05de5ce6ad")]
|
||||
|
||||
[assembly: AssemblyVersion("4.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("4.0.0.0")]
|
||||
270
ServiceStack/ReflectionExtensions.cs
Normal file
270
ServiceStack/ReflectionExtensions.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public static bool IsInstanceOf(this Type type, Type thisOrBaseType)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
if (type == thisOrBaseType)
|
||||
return true;
|
||||
|
||||
type = type.BaseType();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Type FirstGenericType(this Type type)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
if (type.IsGeneric())
|
||||
return type;
|
||||
|
||||
type = type.BaseType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetTypeWithGenericTypeDefinitionOf(this Type type, Type genericTypeDefinition)
|
||||
{
|
||||
foreach (var t in type.GetTypeInterfaces())
|
||||
{
|
||||
if (t.IsGeneric() && t.GetGenericTypeDefinition() == genericTypeDefinition)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
var genericType = type.FirstGenericType();
|
||||
if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition)
|
||||
{
|
||||
return genericType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PropertyInfo[] GetAllProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface())
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var subType = queue.Dequeue();
|
||||
foreach (var subInterface in subType.GetTypeInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface)) continue;
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
var typeProperties = subType.GetTypesProperties();
|
||||
|
||||
var newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetTypesProperties()
|
||||
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static PropertyInfo[] GetPublicProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface())
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var subType = queue.Dequeue();
|
||||
foreach (var subInterface in subType.GetTypeInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface)) continue;
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
var typeProperties = subType.GetTypesPublicProperties();
|
||||
|
||||
var newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetTypesPublicProperties()
|
||||
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public const string DataMember = "DataMemberAttribute";
|
||||
|
||||
internal static string[] IgnoreAttributesNamed = new[] {
|
||||
"IgnoreDataMemberAttribute",
|
||||
"JsonIgnoreAttribute"
|
||||
};
|
||||
|
||||
public static PropertyInfo[] GetSerializableProperties(this Type type)
|
||||
{
|
||||
var properties = type.IsDto()
|
||||
? type.GetAllProperties()
|
||||
: type.GetPublicProperties();
|
||||
return properties.OnlySerializableProperties(type);
|
||||
}
|
||||
|
||||
|
||||
private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) };
|
||||
|
||||
public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] properties, Type type = null)
|
||||
{
|
||||
var isDto = type.IsDto();
|
||||
var readableProperties = properties.Where(x => x.PropertyGetMethod(nonPublic: isDto) != null);
|
||||
|
||||
if (isDto)
|
||||
{
|
||||
return readableProperties.Where(attr =>
|
||||
attr.HasAttribute<DataMemberAttribute>()).ToArray();
|
||||
}
|
||||
|
||||
// else return those properties that are not decorated with IgnoreDataMember
|
||||
return readableProperties
|
||||
.Where(prop => prop.AllAttributes()
|
||||
.All(attr =>
|
||||
{
|
||||
var name = attr.GetType().Name;
|
||||
return !IgnoreAttributesNamed.Contains(name);
|
||||
}))
|
||||
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlatformExtensions //Because WinRT is a POS
|
||||
{
|
||||
public static bool IsInterface(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().IsInterface;
|
||||
}
|
||||
|
||||
public static bool IsGeneric(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().IsGenericType;
|
||||
}
|
||||
|
||||
public static Type BaseType(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().BaseType;
|
||||
}
|
||||
|
||||
public static Type[] GetTypeInterfaces(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().ImplementedInterfaces.ToArray();
|
||||
}
|
||||
|
||||
internal static PropertyInfo[] GetTypesPublicProperties(this Type subType)
|
||||
{
|
||||
var pis = new List<PropertyInfo>();
|
||||
foreach (var pi in subType.GetRuntimeProperties())
|
||||
{
|
||||
var mi = pi.GetMethod ?? pi.SetMethod;
|
||||
if (mi != null && mi.IsStatic) continue;
|
||||
pis.Add(pi);
|
||||
}
|
||||
return pis.ToArray();
|
||||
}
|
||||
|
||||
internal static PropertyInfo[] GetTypesProperties(this Type subType)
|
||||
{
|
||||
var pis = new List<PropertyInfo>();
|
||||
foreach (var pi in subType.GetRuntimeProperties())
|
||||
{
|
||||
var mi = pi.GetMethod ?? pi.SetMethod;
|
||||
if (mi != null && mi.IsStatic) continue;
|
||||
pis.Add(pi);
|
||||
}
|
||||
return pis.ToArray();
|
||||
}
|
||||
|
||||
public static bool HasAttribute<T>(this Type type)
|
||||
{
|
||||
return type.AllAttributes().Any(x => x.GetType() == typeof(T));
|
||||
}
|
||||
|
||||
public static bool HasAttribute<T>(this PropertyInfo pi)
|
||||
{
|
||||
return pi.AllAttributes().Any(x => x.GetType() == typeof(T));
|
||||
}
|
||||
|
||||
public static bool IsDto(this Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.HasAttribute<DataContractAttribute>();
|
||||
}
|
||||
|
||||
public static MethodInfo PropertyGetMethod(this PropertyInfo pi, bool nonPublic = false)
|
||||
{
|
||||
return pi.GetMethod;
|
||||
}
|
||||
|
||||
public static object[] AllAttributes(this PropertyInfo propertyInfo)
|
||||
{
|
||||
return propertyInfo.GetCustomAttributes(true).ToArray();
|
||||
}
|
||||
|
||||
public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrType)
|
||||
{
|
||||
return propertyInfo.GetCustomAttributes(true).Where(x => attrType.IsInstanceOf(x.GetType())).ToArray();
|
||||
}
|
||||
|
||||
public static object[] AllAttributes(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GetCustomAttributes(true).ToArray();
|
||||
}
|
||||
|
||||
public static TAttr[] AllAttributes<TAttr>(this PropertyInfo pi)
|
||||
{
|
||||
return pi.AllAttributes(typeof(TAttr)).Cast<TAttr>().ToArray();
|
||||
}
|
||||
|
||||
public static TAttr[] AllAttributes<TAttr>(this Type type)
|
||||
where TAttr : Attribute
|
||||
{
|
||||
return type.GetTypeInfo().GetCustomAttributes<TAttr>(true).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
131
ServiceStack/ServiceStack.csproj
Normal file
131
ServiceStack/ServiceStack.csproj
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{680A1709-25EB-4D52-A87F-EE03FFD94BAA}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ServiceStack</RootNamespace>
|
||||
<AssemblyName>ServiceStack</AssemblyName>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<UpgradeBackupLocation />
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>False</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;MONO</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Signed|AnyCPU'">
|
||||
<OutputPath>bin\Signed\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<DocumentationFile>bin\Release\ServiceStack.XML</DocumentationFile>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="HttpUtils.cs" />
|
||||
<Compile Include="Host\ContentTypes.cs" />
|
||||
<Compile Include="ReflectionExtensions.cs" />
|
||||
<Compile Include="StringMapTypeDeserializer.cs" />
|
||||
<Compile Include="Host\HttpResponseStreamWrapper.cs" />
|
||||
<Compile Include="HttpResult.cs" />
|
||||
<Compile Include="ServiceStackHost.cs" />
|
||||
<Compile Include="ServiceStackHost.Runtime.cs" />
|
||||
<Compile Include="Host\ServiceExec.cs" />
|
||||
<Compile Include="UrlExtensions.cs" />
|
||||
<Compile Include="Host\ActionContext.cs" />
|
||||
<Compile Include="HttpRequestExtensions.cs" />
|
||||
<Compile Include="Host\RestPath.cs" />
|
||||
<Compile Include="Host\ServiceController.cs" />
|
||||
<Compile Include="Host\ServiceMetadata.cs" />
|
||||
<Compile Include="Host\RestHandler.cs" />
|
||||
<Compile Include="HttpResponseExtensionsInternal.cs" />
|
||||
<Compile Include="HttpHandlerFactory.cs" />
|
||||
<Compile Include="FilterAttributeCache.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
|
||||
<Name>MediaBrowser.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
|
||||
<Name>MediaBrowser.Model</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
6
ServiceStack/ServiceStack.nuget.targets
Normal file
6
ServiceStack/ServiceStack.nuget.targets
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Target Name="EmitMSBuildWarning" BeforeTargets="Build">
|
||||
<Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
|
||||
</Target>
|
||||
</Project>
|
||||
19
ServiceStack/ServiceStack.xproj
Normal file
19
ServiceStack/ServiceStack.xproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>b2d733ab-620e-4c53-88a4-4b6638ab6a7a</ProjectGuid>
|
||||
<RootNamespace>ServiceStack</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
74
ServiceStack/ServiceStackHost.Runtime.cs
Normal file
74
ServiceStack/ServiceStackHost.Runtime.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Service Stack LLC. All Rights Reserved.
|
||||
// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt
|
||||
|
||||
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Support.WebHost;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public abstract partial class ServiceStackHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the request filters. Returns whether or not the request has been handled
|
||||
/// and no more processing should be done.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool ApplyRequestFilters(IRequest req, IResponse res, object requestDto)
|
||||
{
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
|
||||
//Exec all RequestFilter attributes with Priority < 0
|
||||
var attributes = FilterAttributeCache.GetRequestFilterAttributes(requestDto.GetType());
|
||||
var i = 0;
|
||||
for (; i < attributes.Length && attributes[i].Priority < 0; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
attribute.RequestFilter(req, res, requestDto);
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
}
|
||||
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
|
||||
//Exec global filters
|
||||
foreach (var requestFilter in GlobalRequestFilters)
|
||||
{
|
||||
requestFilter(req, res, requestDto);
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
}
|
||||
|
||||
//Exec remaining RequestFilter attributes with Priority >= 0
|
||||
for (; i < attributes.Length && attributes[i].Priority >= 0; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
attribute.RequestFilter(req, res, requestDto);
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
}
|
||||
|
||||
return res.IsClosed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the response filters. Returns whether or not the request has been handled
|
||||
/// and no more processing should be done.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool ApplyResponseFilters(IRequest req, IResponse res, object response)
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
}
|
||||
|
||||
//Exec global filters
|
||||
foreach (var responseFilter in GlobalResponseFilters)
|
||||
{
|
||||
responseFilter(req, res, response);
|
||||
if (res.IsClosed) return res.IsClosed;
|
||||
}
|
||||
|
||||
return res.IsClosed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
104
ServiceStack/ServiceStackHost.cs
Normal file
104
ServiceStack/ServiceStackHost.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Services;
|
||||
using ServiceStack.Host;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
public abstract partial class ServiceStackHost : IDisposable
|
||||
{
|
||||
public static ServiceStackHost Instance { get; protected set; }
|
||||
|
||||
protected ServiceStackHost(string serviceName)
|
||||
{
|
||||
ServiceName = serviceName;
|
||||
ServiceController = CreateServiceController();
|
||||
|
||||
RestPaths = new List<RestPath>();
|
||||
Metadata = new ServiceMetadata();
|
||||
GlobalRequestFilters = new List<Action<IRequest, IResponse, object>>();
|
||||
GlobalResponseFilters = new List<Action<IRequest, IResponse, object>>();
|
||||
}
|
||||
|
||||
public abstract void Configure();
|
||||
|
||||
public abstract object CreateInstance(Type type);
|
||||
|
||||
protected abstract ServiceController CreateServiceController();
|
||||
|
||||
public virtual ServiceStackHost Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
ServiceController.Init();
|
||||
Configure();
|
||||
|
||||
ServiceController.AfterInit();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual ServiceStackHost Start(string urlBase)
|
||||
{
|
||||
throw new NotImplementedException("Start(listeningAtUrlBase) is not supported by this AppHost");
|
||||
}
|
||||
|
||||
public string ServiceName { get; set; }
|
||||
|
||||
public ServiceMetadata Metadata { get; set; }
|
||||
|
||||
public ServiceController ServiceController { get; set; }
|
||||
|
||||
public List<RestPath> RestPaths = new List<RestPath>();
|
||||
|
||||
public List<Action<IRequest, IResponse, object>> GlobalRequestFilters { get; set; }
|
||||
|
||||
public List<Action<IRequest, IResponse, object>> GlobalResponseFilters { get; set; }
|
||||
|
||||
public abstract T TryResolve<T>();
|
||||
public abstract T Resolve<T>();
|
||||
|
||||
public virtual MediaBrowser.Model.Services.RouteAttribute[] GetRouteAttributes(Type requestType)
|
||||
{
|
||||
return requestType.AllAttributes<MediaBrowser.Model.Services.RouteAttribute>();
|
||||
}
|
||||
|
||||
public abstract object GetTaskResult(Task task, string requestName);
|
||||
|
||||
public abstract Func<string, object> GetParseFn(Type propertyType);
|
||||
|
||||
public abstract void SerializeToJson(object o, Stream stream);
|
||||
public abstract void SerializeToXml(object o, Stream stream);
|
||||
public abstract object DeserializeXml(Type type, Stream stream);
|
||||
public abstract object DeserializeJson(Type type, Stream stream);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
//JsConfig.Reset(); //Clears Runtime Attributes
|
||||
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
protected abstract ILogger Logger
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public void OnLogError(Type type, string message)
|
||||
{
|
||||
Logger.Error(message);
|
||||
}
|
||||
|
||||
public void OnLogError(Type type, string message, Exception ex)
|
||||
{
|
||||
Logger.ErrorException(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
ServiceStack/StringMapTypeDeserializer.cs
Normal file
126
ServiceStack/StringMapTypeDeserializer.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ServiceStack.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls)
|
||||
/// </summary>
|
||||
public class StringMapTypeDeserializer
|
||||
{
|
||||
internal class PropertySerializerEntry
|
||||
{
|
||||
public PropertySerializerEntry(Action<object,object> propertySetFn, Func<string, object> propertyParseStringFn)
|
||||
{
|
||||
PropertySetFn = propertySetFn;
|
||||
PropertyParseStringFn = propertyParseStringFn;
|
||||
}
|
||||
|
||||
public Action<object, object> PropertySetFn;
|
||||
public Func<string,object> PropertyParseStringFn;
|
||||
public Type PropertyType;
|
||||
}
|
||||
|
||||
private readonly Type type;
|
||||
private readonly Dictionary<string, PropertySerializerEntry> propertySetterMap
|
||||
= new Dictionary<string, PropertySerializerEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Func<string, object> GetParseFn(Type propertyType)
|
||||
{
|
||||
//Don't JSV-decode string values for string properties
|
||||
if (propertyType == typeof(string))
|
||||
return s => s;
|
||||
|
||||
return ServiceStackHost.Instance.GetParseFn(propertyType);
|
||||
}
|
||||
|
||||
public StringMapTypeDeserializer(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
|
||||
foreach (var propertyInfo in type.GetSerializableProperties())
|
||||
{
|
||||
var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
|
||||
var propertyType = propertyInfo.PropertyType;
|
||||
var propertyParseStringFn = GetParseFn(propertyType);
|
||||
var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };
|
||||
|
||||
var attr = propertyInfo.AllAttributes<DataMemberAttribute>().FirstOrDefault();
|
||||
if (attr != null && attr.Name != null)
|
||||
{
|
||||
propertySetterMap[attr.Name] = propertySerializer;
|
||||
}
|
||||
propertySetterMap[propertyInfo.Name] = propertySerializer;
|
||||
}
|
||||
}
|
||||
|
||||
public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
|
||||
{
|
||||
string propertyName = null;
|
||||
string propertyTextValue = null;
|
||||
PropertySerializerEntry propertySerializerEntry = null;
|
||||
|
||||
if (instance == null)
|
||||
instance = ServiceStackHost.Instance.CreateInstance(type);
|
||||
|
||||
foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value)))
|
||||
{
|
||||
propertyName = pair.Key;
|
||||
propertyTextValue = pair.Value;
|
||||
|
||||
if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
|
||||
{
|
||||
if (propertyName == "v")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (propertySerializerEntry.PropertySetFn == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (propertySerializerEntry.PropertyType == typeof(bool))
|
||||
{
|
||||
//InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
|
||||
propertyTextValue = LeftPart(propertyTextValue, ',');
|
||||
}
|
||||
|
||||
var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
propertySerializerEntry.PropertySetFn(instance, value);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static string LeftPart(string strVal, char needle)
|
||||
{
|
||||
if (strVal == null) return null;
|
||||
var pos = strVal.IndexOf(needle);
|
||||
return pos == -1
|
||||
? strVal
|
||||
: strVal.Substring(0, pos);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TypeAccessor
|
||||
{
|
||||
public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
|
||||
{
|
||||
if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null;
|
||||
|
||||
var setMethodInfo = propertyInfo.SetMethod;
|
||||
return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
|
||||
}
|
||||
}
|
||||
}
|
||||
33
ServiceStack/UrlExtensions.cs
Normal file
33
ServiceStack/UrlExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace ServiceStack
|
||||
{
|
||||
/// <summary>
|
||||
/// Donated by Ivan Korneliuk from his post:
|
||||
/// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
|
||||
///
|
||||
/// Modified to only allow using routes matching the supplied HTTP Verb
|
||||
/// </summary>
|
||||
public static class UrlExtensions
|
||||
{
|
||||
public static string GetOperationName(this Type type)
|
||||
{
|
||||
var typeName = type.FullName != null //can be null, e.g. generic types
|
||||
? LeftPart(type.FullName, "[[") //Generic Fullname
|
||||
.Replace(type.Namespace + ".", "") //Trim Namespaces
|
||||
.Replace("+", ".") //Convert nested into normal type
|
||||
: type.Name;
|
||||
|
||||
return type.IsGenericParameter ? "'" + typeName : typeName;
|
||||
}
|
||||
|
||||
public static string LeftPart(string strVal, string needle)
|
||||
{
|
||||
if (strVal == null) return null;
|
||||
var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase);
|
||||
return pos == -1
|
||||
? strVal
|
||||
: strVal.Substring(0, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
ServiceStack/packages.config
Normal file
3
ServiceStack/packages.config
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
</packages>
|
||||
17
ServiceStack/project.json
Normal file
17
ServiceStack/project.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"frameworks":{
|
||||
"netstandard1.6":{
|
||||
"dependencies":{
|
||||
"NETStandard.Library":"1.6.0",
|
||||
}
|
||||
},
|
||||
".NETPortable,Version=v4.5,Profile=Profile7":{
|
||||
"buildOptions": {
|
||||
"define": [ ]
|
||||
},
|
||||
"frameworkAssemblies":{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user