diff --git a/GeekLearning.RestKit.sln b/GeekLearning.RestKit.sln index ef0d638..bc7aebd 100644 --- a/GeekLearning.RestKit.sln +++ b/GeekLearning.RestKit.sln @@ -7,6 +7,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.RestKit.Core", EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.RestKit.Json", "src\GeekLearning.RestKit.Json\GeekLearning.RestKit.Json.xproj", "{9C387D89-0D16-4279-8B94-37D60DAA7FF5}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Http.Logging", "src\GeekLearning.Http.Logging\GeekLearning.Http.Logging.xproj", "{380EE612-647B-4CDD-B491-B9B4AE7E1479}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.RestKit.FormData", "src\GeekLearning.RestKit.FormData\GeekLearning.RestKit.FormData.xproj", "{0A64C2AC-3813-46DA-8039-1C79E1950C2A}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.RestKit.Tests", "tests\GeekLearning.RestKit.Tests\GeekLearning.RestKit.Tests.xproj", "{2CFD4C43-C052-42E0-835C-FEA669C4EE91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +27,18 @@ Global {9C387D89-0D16-4279-8B94-37D60DAA7FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C387D89-0D16-4279-8B94-37D60DAA7FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C387D89-0D16-4279-8B94-37D60DAA7FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {380EE612-647B-4CDD-B491-B9B4AE7E1479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {380EE612-647B-4CDD-B491-B9B4AE7E1479}.Debug|Any CPU.Build.0 = Debug|Any CPU + {380EE612-647B-4CDD-B491-B9B4AE7E1479}.Release|Any CPU.ActiveCfg = Release|Any CPU + {380EE612-647B-4CDD-B491-B9B4AE7E1479}.Release|Any CPU.Build.0 = Release|Any CPU + {0A64C2AC-3813-46DA-8039-1C79E1950C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A64C2AC-3813-46DA-8039-1C79E1950C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A64C2AC-3813-46DA-8039-1C79E1950C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A64C2AC-3813-46DA-8039-1C79E1950C2A}.Release|Any CPU.Build.0 = Release|Any CPU + {2CFD4C43-C052-42E0-835C-FEA669C4EE91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CFD4C43-C052-42E0-835C-FEA669C4EE91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CFD4C43-C052-42E0-835C-FEA669C4EE91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CFD4C43-C052-42E0-835C-FEA669C4EE91}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..cbb5d94 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,9 @@ +branches: + dev(elop)?(ment)?$: + tag: alpha + features?[/-]: + tag: alpha.{BranchName} + releases?[/-]: + mode: ContinuousDeployment + hotfix(es)?[/-]: + mode: ContinuousDeployment \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..1aea5e4 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "tests" ], + "sdk": { + "version": "1.0.0-preview2-1-003177" + } +} diff --git a/src/GeekLearning.Http.Logging/GeekLearning.Http.Logging.xproj b/src/GeekLearning.Http.Logging/GeekLearning.Http.Logging.xproj new file mode 100644 index 0000000..5df76bd --- /dev/null +++ b/src/GeekLearning.Http.Logging/GeekLearning.Http.Logging.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 380ee612-647b-4cdd-b491-b9b4ae7e1479 + GeekLearning.Http.Logging + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/GeekLearning.Http.Logging/HttpRequestLogger.cs b/src/GeekLearning.Http.Logging/HttpRequestLogger.cs new file mode 100644 index 0000000..eb97e95 --- /dev/null +++ b/src/GeekLearning.Http.Logging/HttpRequestLogger.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Net.Http; +using System.Threading; +using Microsoft.Extensions.Logging; +using GeekLearning.D64; +using System.Diagnostics; + +namespace GeekLearning.Http.Logging +{ + public class HttpRequestLogger : DelegatingHandler + { + private ILogger logger; + private HttpRequestLoggerOptions options; + TimebasedId timebaseId; + + public HttpRequestLogger(HttpMessageHandler innerHandler, ILogger logger) : this(innerHandler, logger, new HttpRequestLoggerOptions()) + { + } + + public HttpRequestLogger(HttpMessageHandler innerHandler, ILogger logger, HttpRequestLoggerOptions options) : base(innerHandler) + { + this.timebaseId = new TimebasedId(false); + this.logger = logger; + this.options = options; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var correlationId = timebaseId.NewId(); + + if (request.Content != null) + { + await request.Content.LoadIntoBufferAsync(); + var requestBody = await request.Content.ReadAsStringAsync(); + this.logger.LogInformation( + "`{0}` to `{1}` with correlationId `{2}`:\n{3}\n\n{4}", + request.Method, + request.RequestUri, + correlationId, + string.Join("\n", request.Headers.Select(h => $"{h.Key}: {string.Join(" ", h.Value)}")), + TruncateMessageIfTooBig(requestBody)); + } + else + { + this.logger.LogInformation( + "`{0}` to `{1}` with correlationId `{2}`:\n{3}", + request.Method, + request.RequestUri, + correlationId, + string.Join("\n", request.Headers.Select(h => $"{h.Key}: {string.Join(" ", h.Value)}"))); + } + Stopwatch stopwatch = null; + if (this.options.MeasureRequestTime) + { + stopwatch = new Stopwatch(); + stopwatch.Start(); + } + var response = await base.SendAsync(request, cancellationToken); + await response.Content.LoadIntoBufferAsync(); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (stopwatch != null) + { + stopwatch.Stop(); + this.logger.LogInformation( + "`REQUEST` `{0}` ran for `{1}` ms", + correlationId, + stopwatch.Elapsed.TotalMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture) + ); + } + + this.logger.LogInformation( + "`RECEIVE` `{0}` `{1}` with correlationId `{2}`:\n{3}\n\n{4}", + (int)response.StatusCode, + response.ReasonPhrase, + correlationId, + string.Join("\n", response.Headers.Select(h => $"{h.Key}: {string.Join(" ", h.Value)}")), + TruncateMessageIfTooBig(responseBody)); + + return response; + } + + private string TruncateMessageIfTooBig(string body) + { + if (body.Length > options.MaxSize) + { + return body.Substring(0, options.MaxSize / 2) + "<< TRUNCATED IN LOGS >>" + body.Substring(body.Length - options.MaxSize / 2); + } + else + { + return body; + } + } + + } + + public class HttpRequestLogger : HttpRequestLogger + where TInnerHandler : HttpMessageHandler, new() + { + public HttpRequestLogger(ILogger logger) : base(new TInnerHandler(), logger) + { + } + } +} diff --git a/src/GeekLearning.Http.Logging/HttpRequestLoggerOptions.cs b/src/GeekLearning.Http.Logging/HttpRequestLoggerOptions.cs new file mode 100644 index 0000000..f4ec432 --- /dev/null +++ b/src/GeekLearning.Http.Logging/HttpRequestLoggerOptions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.Http.Logging +{ + public class HttpRequestLoggerOptions + { + public HttpRequestLoggerOptions() : this(measureRequestTime: true) + { + + } + + public HttpRequestLoggerOptions(bool measureRequestTime) + { + this.MeasureRequestTime = measureRequestTime; + } + + public bool MeasureRequestTime { get; set; } + + public int MaxSize { get; set; } = 512000; + } +} diff --git a/src/GeekLearning.Http.Logging/Properties/AssemblyInfo.cs b/src/GeekLearning.Http.Logging/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..11edf9e --- /dev/null +++ b/src/GeekLearning.Http.Logging/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GeekLearning.Http.Logging")] +[assembly: AssemblyTrademark("")] + +// 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("380ee612-647b-4cdd-b491-b9b4ae7e1479")] diff --git a/src/GeekLearning.Http.Logging/project.json b/src/GeekLearning.Http.Logging/project.json new file mode 100644 index 0000000..f874746 --- /dev/null +++ b/src/GeekLearning.Http.Logging/project.json @@ -0,0 +1,16 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "1.1.0", + "NETStandard.Library": "1.6.1", + "GeekLearning.D64": "1.0.0" + }, + + "frameworks": { + "net452": {}, + "netstandard1.3": { + "imports": "dnxcore50" + } + } +} diff --git a/src/GeekLearning.RestKit.Core/ApiException.cs b/src/GeekLearning.RestKit.Core/ApiException.cs index ee6db6e..73497fe 100644 --- a/src/GeekLearning.RestKit.Core/ApiException.cs +++ b/src/GeekLearning.RestKit.Core/ApiException.cs @@ -11,5 +11,12 @@ public abstract class ApiException: Exception public ApiException() { } + + public ApiException(HttpResponseMessage response) + { + this.ResponseMessage = response; + } + + public HttpResponseMessage ResponseMessage { get; protected set; } } } diff --git a/src/GeekLearning.RestKit.Core/ApiException{TResponse}.cs b/src/GeekLearning.RestKit.Core/ApiException{TResponse}.cs index 97febe6..a159da1 100644 --- a/src/GeekLearning.RestKit.Core/ApiException{TResponse}.cs +++ b/src/GeekLearning.RestKit.Core/ApiException{TResponse}.cs @@ -1,15 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { - public abstract class ApiException: ApiException + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class ApiException: ApiException, IApiException { - public ApiException() + public ApiException(HttpResponseMessage response, TResponse data) : base(response) { + this.Response = data; } - + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/BadRequestApiException.cs b/src/GeekLearning.RestKit.Core/BadRequestApiException.cs index 575bdf7..bee8ad7 100644 --- a/src/GeekLearning.RestKit.Core/BadRequestApiException.cs +++ b/src/GeekLearning.RestKit.Core/BadRequestApiException.cs @@ -1,15 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + public class BadRequestApiException: ApiException { + public BadRequestApiException() + { + } + + public BadRequestApiException(HttpResponseMessage message): base(message) + { + } } - public class BadRequestApiException : ApiException + public class BadRequestApiException : BadRequestApiException, IApiException { + public BadRequestApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/ClientBase.cs b/src/GeekLearning.RestKit.Core/ClientBase.cs index 6073014..9088bc8 100644 --- a/src/GeekLearning.RestKit.Core/ClientBase.cs +++ b/src/GeekLearning.RestKit.Core/ClientBase.cs @@ -11,34 +11,49 @@ using Microsoft.Extensions.DependencyInjection; public abstract class ClientBase - where TOptions : class, IProvideRequestFilters, new() + where TOptions : class, IProvideRequestFilters, IProvideErrorHandlingPolicy, new() { private IMediaFormatterProvider mediaFormatterProvider; private IServiceProvider serviceProvider; private Lazy requestFilters; + private IHttpClientFactory httpClientFactory; public ClientBase(IOptions options, + IHttpClientFactory httpClientFactory, IMediaFormatterProvider mediaFormatterProvider, IServiceProvider serviceProvider) { this.Options = options.Value; this.mediaFormatterProvider = mediaFormatterProvider; this.serviceProvider = serviceProvider; + this.httpClientFactory = httpClientFactory; this.requestFilters = new Lazy(() => - this.Options.RequestFilters.Select(x=> ActivatorUtilities.CreateInstance(this.serviceProvider, x.Type, x.Arguments)).Cast().ToArray() + this.Options.RequestFilters.Select(x => ActivatorUtilities.CreateInstance(this.serviceProvider, x.Type, x.Arguments)).Cast().ToArray() ); } + protected HttpClient GetClient() + { + return this.httpClientFactory.CreateClient(); + } + protected TOptions Options { get; private set; } protected Task TransformResponseAsync(HttpResponseMessage message) { - IMediaFormatter mediaFormatter = this.mediaFormatterProvider.GetMediaFormatter(message.Content.Headers.ContentType); - if (mediaFormatter == null) + if (message.Content != null && message.Content.Headers.ContentLength > 0) { - throw new UnsupportedMediaTypeApiException(message.Content.Headers.ContentType); + IMediaFormatter mediaFormatter = this.mediaFormatterProvider.GetMediaFormatter(message.Content.Headers.ContentType); + if (mediaFormatter == null) + { + throw new UnsupportedMediaTypeApiException(message); + } + return mediaFormatter.TransformAsync(message.Content); + } + else + { + return Task.FromResult(default(TTarget)); } - return mediaFormatter.TransformAsync(message.Content); } protected HttpRequestMessage ApplyFilters(HttpRequestMessage requestMessage, params string[] securityDefinitions) @@ -52,14 +67,99 @@ protected HttpRequestMessage ApplyFilters(HttpRequestMessage requestMessage, par return finalMessage; } - protected HttpContent TransformRequestBody(object data, string mediaType) + protected HttpContent TransformRequestBody(object body, IDictionary formData, string mediaType) { IMediaFormatter mediaFormatter = this.mediaFormatterProvider.GetMediaFormatter(mediaType); if (mediaFormatter == null) { throw new UnsupportedMediaTypeApiException(mediaType); } - return mediaFormatter.Format(data); + return mediaFormatter.Format(body, formData); + } + + protected ApiException MapToException(HttpResponseMessage message) + { + switch (message.StatusCode) + { + case HttpStatusCode.BadRequest: + return new BadRequestApiException(message); + case HttpStatusCode.Unauthorized: + return new UnauthorizedApiException(message); + case HttpStatusCode.Forbidden: + return new ForbiddenApiException(message); + case HttpStatusCode.NotFound: + return new NotFoundApiException(message); + case HttpStatusCode.Conflict: + return new ConflictApiException(message); + case HttpStatusCode.InternalServerError: + return new InternalServerErrorApiException(message); + case HttpStatusCode.ServiceUnavailable: + return new ServiceUnavailableApiException(message); + default: + return new UnhandledApiException(message); + } + } + + protected async Task MapToException(HttpResponseMessage message) + { + if (message.Content == null) + { + return this.MapToException(message); + } + else + { + var result = await this.TransformResponseAsync(message); + + switch (message.StatusCode) + { + case HttpStatusCode.BadRequest: + return new BadRequestApiException(message, result); + case HttpStatusCode.Unauthorized: + return new UnauthorizedApiException(message, result); + case HttpStatusCode.Forbidden: + return new ForbiddenApiException(message, result); + case HttpStatusCode.NotFound: + return new NotFoundApiException(message, result); + case HttpStatusCode.Conflict: + return new ConflictApiException(message, result); + case HttpStatusCode.InternalServerError: + return new ConflictApiException(message, result); + case HttpStatusCode.ServiceUnavailable: + return new ServiceUnavailableApiException(message, result); + default: + return new UnhandledApiException(message, result); + } + } + } + + protected bool MatchStatus(HttpResponseMessage message, int status) + { + return status == (int)message.StatusCode; + } + + protected bool MatchStatus(HttpResponseMessage message, string status) + { + return message.StatusCode.ToString().Equals(status, StringComparison.OrdinalIgnoreCase) + || message.ReasonPhrase == status; + } + + protected IFormData GetFormData(object data) + { + var formData = data as IFormData; + if (formData != null) + { + return formData; + } + return new FormData(data); + } + + protected string SafeToString(object value) + { + if (value == null) + { + return null; + } + return value.ToString(); } /// diff --git a/src/GeekLearning.RestKit.Core/ClientOptionsBase.cs b/src/GeekLearning.RestKit.Core/ClientOptionsBase.cs index 882faf1..2e2b13a 100644 --- a/src/GeekLearning.RestKit.Core/ClientOptionsBase.cs +++ b/src/GeekLearning.RestKit.Core/ClientOptionsBase.cs @@ -4,8 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Polly; - public abstract class ClientOptionsBase : IProvideRequestFilters + public abstract class ClientOptionsBase : IProvideRequestFilters, IProvideErrorHandlingPolicy { private List requestfilters = new List(); @@ -25,5 +26,7 @@ public IEnumerable RequestFilters return requestfilters; } } + + public Policy Policy { get; set; } } } diff --git a/src/GeekLearning.RestKit.Core/ConflictApiException.cs b/src/GeekLearning.RestKit.Core/ConflictApiException.cs index d020401..10bd97e 100644 --- a/src/GeekLearning.RestKit.Core/ConflictApiException.cs +++ b/src/GeekLearning.RestKit.Core/ConflictApiException.cs @@ -1,15 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { - public class ConflictApiException: ApiException + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class ConflictApiException : ApiException { + public ConflictApiException() + { + } + + public ConflictApiException(HttpResponseMessage message) : base(message) + { + } } - public class ConflictApiException : ApiException + public class ConflictApiException : ConflictApiException, IApiException { + public ConflictApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/CustomHandlerHttpClientFactory.cs b/src/GeekLearning.RestKit.Core/CustomHandlerHttpClientFactory.cs new file mode 100644 index 0000000..2577714 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/CustomHandlerHttpClientFactory.cs @@ -0,0 +1,12 @@ +using System.Net.Http; + +namespace GeekLearning.RestKit.Core +{ + public class CustomHandlerHttpClientFactory : IHttpClientFactory + where THandler: HttpMessageHandler, new() + { + public HttpClient CreateClient(){ + return new HttpClient(new THandler()); + } + } +} diff --git a/src/GeekLearning.RestKit.Core/DefaultHttpClientFactory.cs b/src/GeekLearning.RestKit.Core/DefaultHttpClientFactory.cs new file mode 100644 index 0000000..c411320 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/DefaultHttpClientFactory.cs @@ -0,0 +1,11 @@ +using System.Net.Http; + +namespace GeekLearning.RestKit.Core +{ + public class DefaultHttpClientFactory: IHttpClientFactory + { + public HttpClient CreateClient(){ + return new HttpClient(); + } + } +} diff --git a/src/GeekLearning.RestKit.Core/FileFactory.cs b/src/GeekLearning.RestKit.Core/FileFactory.cs new file mode 100644 index 0000000..b7ad808 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/FileFactory.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public class FileFactory + { + public IFile Get(byte[] data, string fileName) + { + return Get(new MemoryStream(data), fileName); + } + + public IFile Get(Stream stream, string fileName) + { + return new Internal.StreamFormFile(stream, fileName); + } + + public IFile Get(FileStream stream) + { + return Get(stream, stream.Name); + } + } +} diff --git a/src/GeekLearning.RestKit.Core/ForbiddenApiException.cs b/src/GeekLearning.RestKit.Core/ForbiddenApiException.cs index 919c96b..ff88864 100644 --- a/src/GeekLearning.RestKit.Core/ForbiddenApiException.cs +++ b/src/GeekLearning.RestKit.Core/ForbiddenApiException.cs @@ -1,16 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { - public class ForbiddenApiException: ApiException + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class ForbiddenApiException : ApiException { + public ForbiddenApiException() + { + } + public ForbiddenApiException(HttpResponseMessage message) : base(message) + { + } } - public class ForbiddenApiException: ApiException + public class ForbiddenApiException : ForbiddenApiException, IApiException { + public ForbiddenApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/FormData.cs b/src/GeekLearning.RestKit.Core/FormData.cs new file mode 100644 index 0000000..a2fbbdc --- /dev/null +++ b/src/GeekLearning.RestKit.Core/FormData.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public class FormData: IFormData + { + public FormData(object data) + { + Data = data; + } + + public object Data { get; set; } + } +} diff --git a/src/GeekLearning.RestKit.Core/FormattedData.cs b/src/GeekLearning.RestKit.Core/FormattedData.cs new file mode 100644 index 0000000..2da8c85 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/FormattedData.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public class FormattedData + { + public object Data { get; set; } + + public string CollectionFormat { get; set; } + + public string Type { get; set; } + } +} diff --git a/src/GeekLearning.RestKit.Core/IApiException{TResponse}.cs b/src/GeekLearning.RestKit.Core/IApiException{TResponse}.cs new file mode 100644 index 0000000..f945f9c --- /dev/null +++ b/src/GeekLearning.RestKit.Core/IApiException{TResponse}.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public interface IApiException + { + TResponse Response { get; } + } +} diff --git a/src/GeekLearning.RestKit.Core/IFile.cs b/src/GeekLearning.RestKit.Core/IFile.cs new file mode 100644 index 0000000..5350ed3 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/IFile.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public interface IFile: IFormData + { + string FileName { get; } + HttpContent CreateContent(); + } +} diff --git a/src/GeekLearning.RestKit.Core/IFormData.cs b/src/GeekLearning.RestKit.Core/IFormData.cs new file mode 100644 index 0000000..45a8f1d --- /dev/null +++ b/src/GeekLearning.RestKit.Core/IFormData.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public interface IFormData + { + object Data { get;} + } +} diff --git a/src/GeekLearning.RestKit.Core/IHttpClientFactory.cs b/src/GeekLearning.RestKit.Core/IHttpClientFactory.cs new file mode 100644 index 0000000..9094b70 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/IHttpClientFactory.cs @@ -0,0 +1,9 @@ +using System.Net.Http; + +namespace GeekLearning.RestKit.Core +{ + public interface IHttpClientFactory + { + HttpClient CreateClient(); + } +} diff --git a/src/GeekLearning.RestKit.Core/IMediaFormatter.cs b/src/GeekLearning.RestKit.Core/IMediaFormatter.cs index 87cec2f..535e23d 100644 --- a/src/GeekLearning.RestKit.Core/IMediaFormatter.cs +++ b/src/GeekLearning.RestKit.Core/IMediaFormatter.cs @@ -9,7 +9,7 @@ namespace GeekLearning.RestKit.Core public interface IMediaFormatter { Task TransformAsync(HttpContent content); - HttpContent Format(object data); - bool Supports(string contentType); + HttpContent Format(object body, IDictionary formData); + bool Supports(ParsedMediaType mediaType); } } diff --git a/src/GeekLearning.RestKit.Core/IProvideErrorHandlingPolicy.cs b/src/GeekLearning.RestKit.Core/IProvideErrorHandlingPolicy.cs new file mode 100644 index 0000000..e061aeb --- /dev/null +++ b/src/GeekLearning.RestKit.Core/IProvideErrorHandlingPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.RestKit.Core +{ + using Polly; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public interface IProvideErrorHandlingPolicy + { + Policy Policy { get; } + } +} diff --git a/src/GeekLearning.RestKit.Core/Internal/MediaFormatterProvider.cs b/src/GeekLearning.RestKit.Core/Internal/MediaFormatterProvider.cs index c81098e..9f104f5 100644 --- a/src/GeekLearning.RestKit.Core/Internal/MediaFormatterProvider.cs +++ b/src/GeekLearning.RestKit.Core/Internal/MediaFormatterProvider.cs @@ -6,7 +6,7 @@ namespace GeekLearning.RestKit.Core.Internal { - public class MediaFormatterProvider: IMediaFormatterProvider + public class MediaFormatterProvider : IMediaFormatterProvider { private IEnumerable mediaFormatters; @@ -17,12 +17,17 @@ public MediaFormatterProvider(IEnumerable mediaFormatters) public IMediaFormatter GetMediaFormatter(MediaTypeHeaderValue contentType) { - return GetMediaFormatter(contentType.MediaType); + return GetMediaFormatter(new ParsedMediaType(contentType.MediaType)); } - public IMediaFormatter GetMediaFormatter(string contentType) + public IMediaFormatter GetMediaFormatter(string mediaType) { - return mediaFormatters.First(f=> f.Supports(contentType)); + return GetMediaFormatter(new ParsedMediaType(mediaType)); + } + + public IMediaFormatter GetMediaFormatter(ParsedMediaType mediaType) + { + return mediaFormatters.FirstOrDefault(f => f.Supports(mediaType)); } } } diff --git a/src/GeekLearning.RestKit.Core/Internal/StreamFormFile.cs b/src/GeekLearning.RestKit.Core/Internal/StreamFormFile.cs new file mode 100644 index 0000000..8ab2b7f --- /dev/null +++ b/src/GeekLearning.RestKit.Core/Internal/StreamFormFile.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core.Internal +{ + public class StreamFormFile : IFile + { + public StreamFormFile(Stream stream, string fileName) + { + this.FileName = fileName; + this.Stream = stream; + } + + public object Data => this.Stream; + + public string FileName { get; } + public Stream Stream { get; } + + public HttpContent CreateContent() + { + return new StreamContent(this.Stream); + } + } +} diff --git a/src/GeekLearning.RestKit.Core/InternalServerErrorApiException.cs b/src/GeekLearning.RestKit.Core/InternalServerErrorApiException.cs new file mode 100644 index 0000000..22d50dc --- /dev/null +++ b/src/GeekLearning.RestKit.Core/InternalServerErrorApiException.cs @@ -0,0 +1,29 @@ +namespace GeekLearning.RestKit.Core +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class InternalServerErrorApiException: ApiException + { + public InternalServerErrorApiException() + { + } + + public InternalServerErrorApiException(HttpResponseMessage message): base(message) + { + } + } + + public class InternalServerErrorApiException : InternalServerErrorApiException, IApiException + { + public InternalServerErrorApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } + } +} diff --git a/src/GeekLearning.RestKit.Core/NotFoundApiException.cs b/src/GeekLearning.RestKit.Core/NotFoundApiException.cs index 1c0124c..5e2368b 100644 --- a/src/GeekLearning.RestKit.Core/NotFoundApiException.cs +++ b/src/GeekLearning.RestKit.Core/NotFoundApiException.cs @@ -1,15 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { - public class NotFoundApiException: ApiException + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + + public class NotFoundApiException : ApiException { + public NotFoundApiException() + { + } + + public NotFoundApiException(HttpResponseMessage message) : base(message) + { + } } - public class NotFoundApiException : ApiException + public class NotFoundApiException : NotFoundApiException, IApiException { + public NotFoundApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/ParsedMediaType.cs b/src/GeekLearning.RestKit.Core/ParsedMediaType.cs new file mode 100644 index 0000000..1e8bd84 --- /dev/null +++ b/src/GeekLearning.RestKit.Core/ParsedMediaType.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.RestKit.Core +{ + public class ParsedMediaType + { + public ParsedMediaType(string mediaType) + { + var lastPlus = mediaType.LastIndexOf('+'); + var firstSlash = mediaType.IndexOf('/'); + + Type = mediaType.Substring(0, firstSlash); + if (lastPlus >= 0) + { + SubType = mediaType.Substring(firstSlash + 1, lastPlus - firstSlash - 1); + Suffix = mediaType.Substring(lastPlus + 1); + } + else + { + SubType = mediaType.Substring(firstSlash + 1); + } + } + + public string Type { get; } + + public string SubType { get; } + + public string Suffix { get; } + + public string StructuredType => this.Suffix ?? this.SubType; + } +} diff --git a/src/GeekLearning.RestKit.Core/RestKitExtensions.cs b/src/GeekLearning.RestKit.Core/RestKitExtensions.cs index 10d85fa..a442351 100644 --- a/src/GeekLearning.RestKit.Core/RestKitExtensions.cs +++ b/src/GeekLearning.RestKit.Core/RestKitExtensions.cs @@ -11,6 +11,16 @@ public static class RestKitExtensions { public static IRestKitServicesBuilder AddRestKit(this IServiceCollection services) { + services.TryAddSingleton(); + services.TryAddSingleton(); + + return new Internal.RestKitServicesBuilder(services); + } + + public static IRestKitServicesBuilder AddRestKit(this IServiceCollection services) + where THttpClientFactory : class, IHttpClientFactory + { + services.TryAddSingleton(); services.TryAddSingleton(); return new Internal.RestKitServicesBuilder(services); diff --git a/src/GeekLearning.RestKit.Core/ServiceUnavailableApiException.cs b/src/GeekLearning.RestKit.Core/ServiceUnavailableApiException.cs new file mode 100644 index 0000000..ad0f3fc --- /dev/null +++ b/src/GeekLearning.RestKit.Core/ServiceUnavailableApiException.cs @@ -0,0 +1,29 @@ +namespace GeekLearning.RestKit.Core +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class ServiceUnavailableApiException : ApiException + { + public ServiceUnavailableApiException() + { + } + + public ServiceUnavailableApiException(HttpResponseMessage message): base(message) + { + } + } + + public class ServiceUnavailableApiException : ServiceUnavailableApiException, IApiException + { + public ServiceUnavailableApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } + } +} diff --git a/src/GeekLearning.RestKit.Core/UnauthorizedApiException.cs b/src/GeekLearning.RestKit.Core/UnauthorizedApiException.cs index 209bb78..a0ab5b2 100644 --- a/src/GeekLearning.RestKit.Core/UnauthorizedApiException.cs +++ b/src/GeekLearning.RestKit.Core/UnauthorizedApiException.cs @@ -1,15 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { - public class UnauthorizedApiException: ApiException + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + + public class UnauthorizedApiException : ApiException { + public UnauthorizedApiException() + { + } + + public UnauthorizedApiException(HttpResponseMessage message) : base(message) + { + } } - public class UnauthorizedApiException : ApiException + public class UnauthorizedApiException : UnauthorizedApiException, IApiException { + public UnauthorizedApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } } } diff --git a/src/GeekLearning.RestKit.Core/UnhandledApiException.cs b/src/GeekLearning.RestKit.Core/UnhandledApiException.cs index b6a7035..a81ca3d 100644 --- a/src/GeekLearning.RestKit.Core/UnhandledApiException.cs +++ b/src/GeekLearning.RestKit.Core/UnhandledApiException.cs @@ -1,16 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace GeekLearning.RestKit.Core +namespace GeekLearning.RestKit.Core { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + + public class UnhandledApiException : ApiException { - public UnhandledApiException(HttpResponseMessage message) + public UnhandledApiException() { + } + public UnhandledApiException(HttpResponseMessage message) : base(message) + { } } + + public class UnhandledApiException : UnhandledApiException, IApiException + { + public UnhandledApiException(HttpResponseMessage message, TResponse response) : base(message) + { + this.Response = response; + } + + public TResponse Response { get; } + } } diff --git a/src/GeekLearning.RestKit.Core/UnsupportedMediaTypeApiException.cs b/src/GeekLearning.RestKit.Core/UnsupportedMediaTypeApiException.cs index 6439296..d5d2613 100644 --- a/src/GeekLearning.RestKit.Core/UnsupportedMediaTypeApiException.cs +++ b/src/GeekLearning.RestKit.Core/UnsupportedMediaTypeApiException.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -8,14 +9,10 @@ namespace GeekLearning.RestKit.Core { public class UnsupportedMediaTypeApiException: ApiException { - public UnsupportedMediaTypeApiException() + + public UnsupportedMediaTypeApiException(HttpResponseMessage response): base(response) { - - } - - public UnsupportedMediaTypeApiException(MediaTypeHeaderValue header) - { - this.MediaType = header.MediaType; + this.MediaType = response.Content.Headers.ContentType.MediaType; } public UnsupportedMediaTypeApiException(string mediaType) diff --git a/src/GeekLearning.RestKit.Core/project.json b/src/GeekLearning.RestKit.Core/project.json index 49e0783..8d9961f 100644 --- a/src/GeekLearning.RestKit.Core/project.json +++ b/src/GeekLearning.RestKit.Core/project.json @@ -3,13 +3,16 @@ "dependencies": { "Microsoft.Extensions.Options": "1.0.0", - "NETStandard.Library": "1.6.0" + "NETStandard.Library": "1.6.0", + "Polly": "5.0.2-*" }, "frameworks": { - "net452": {}, + "net452": { + "dependencies": { + } + }, "netstandard1.3": { - "imports": "dnxcore50" } } } diff --git a/src/GeekLearning.RestKit.FormData/GeekLearning.RestKit.FormData.xproj b/src/GeekLearning.RestKit.FormData/GeekLearning.RestKit.FormData.xproj new file mode 100644 index 0000000..40bf593 --- /dev/null +++ b/src/GeekLearning.RestKit.FormData/GeekLearning.RestKit.FormData.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 0a64c2ac-3813-46da-8039-1c79e1950c2a + GeekLearning.RestKit.FormData + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/GeekLearning.RestKit.FormData/MultipartFormDataFormatter.cs b/src/GeekLearning.RestKit.FormData/MultipartFormDataFormatter.cs new file mode 100644 index 0000000..cbd4145 --- /dev/null +++ b/src/GeekLearning.RestKit.FormData/MultipartFormDataFormatter.cs @@ -0,0 +1,57 @@ +namespace GeekLearning.RestKit.FormData +{ + using GeekLearning.RestKit.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using System.Net.Http; + using System.Collections; + + public class MultipartFormDataFormatter : IMediaFormatter + { + public MultipartFormDataFormatter() + { + } + + public HttpContent Format(object body, IDictionary formData) + { + var containerContent = new MultipartFormDataContent(); + foreach (var item in formData) + { + var file = item.Value as IFile; + + if (file != null) + { + var content = file.CreateContent(); + content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data"); + content.Headers.ContentDisposition.Name = item.Key; + content.Headers.ContentDisposition.FileName = file.FileName; + content.Headers.ContentDisposition.FileNameStar = file.FileName; + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + containerContent.Add(content); + } + else + { + var content = new StringContent(Convert.ToString(item.Value.Data, System.Globalization.CultureInfo.InvariantCulture)); + content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data"); + content.Headers.ContentDisposition.Name = item.Key; + containerContent.Add(content); + } + } + + return containerContent; + } + + public bool Supports(ParsedMediaType mediaType) + { + return string.Equals(mediaType.Type, "multipart", StringComparison.OrdinalIgnoreCase) + && string.Equals(mediaType.SubType, "form-data", StringComparison.OrdinalIgnoreCase); + } + + public Task TransformAsync(HttpContent content) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/GeekLearning.RestKit.FormData/Properties/AssemblyInfo.cs b/src/GeekLearning.RestKit.FormData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7f14920 --- /dev/null +++ b/src/GeekLearning.RestKit.FormData/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GeekLearning.RestKit.FormData")] +[assembly: AssemblyTrademark("")] + +// 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("0a64c2ac-3813-46da-8039-1c79e1950c2a")] diff --git a/src/GeekLearning.RestKit.FormData/RestKitFormDataExtensions.cs b/src/GeekLearning.RestKit.FormData/RestKitFormDataExtensions.cs new file mode 100644 index 0000000..83a6294 --- /dev/null +++ b/src/GeekLearning.RestKit.FormData/RestKitFormDataExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GeekLearning.RestKit.Core; + +namespace GeekLearning.RestKit.FormData +{ + public static class RestKitFormDataExtensions + { + public static IRestKitServicesBuilder AddFormData(this IRestKitServicesBuilder restKitServicesBuilder) + { + return restKitServicesBuilder.AddMediaFormater(); + } + } +} diff --git a/src/GeekLearning.RestKit.FormData/project.json b/src/GeekLearning.RestKit.FormData/project.json new file mode 100644 index 0000000..c079c4a --- /dev/null +++ b/src/GeekLearning.RestKit.FormData/project.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.6.0", + "GeekLearning.RestKit.Core": "*" + }, + + "frameworks": { + "net452": {}, + "netstandard1.3": { + "imports": "dnxcore50" + } + } +} diff --git a/src/GeekLearning.RestKit.Json/JsonMediaFormatter.cs b/src/GeekLearning.RestKit.Json/JsonMediaFormatter.cs index 223b547..97261a0 100644 --- a/src/GeekLearning.RestKit.Json/JsonMediaFormatter.cs +++ b/src/GeekLearning.RestKit.Json/JsonMediaFormatter.cs @@ -17,16 +17,22 @@ public JsonMediaFormatter() { } - public HttpContent Format(object data) + public HttpContent Format(object body, IDictionary formData) { - return new StringContent(JsonConvert.SerializeObject(data), System.Text.Encoding.UTF8, JsonMediaType); + return new StringContent(JsonConvert.SerializeObject(body), System.Text.Encoding.UTF8, JsonMediaType); } - public bool Supports(string contentType) + public bool Supports(ParsedMediaType mediaType) { - return contentType.Equals(JsonMediaType, StringComparison.OrdinalIgnoreCase); + return string.Equals(mediaType.Type, "application", StringComparison.OrdinalIgnoreCase) + && string.Equals(mediaType.StructuredType, "json", StringComparison.OrdinalIgnoreCase); } + //public bool Supports(string contentType) + //{ + // return contentType.Equals(JsonMediaType, StringComparison.OrdinalIgnoreCase); + //} + public async Task TransformAsync(HttpContent content) { var stringContent = await content.ReadAsStringAsync(); diff --git a/tests/GeekLearning.RestKit.Tests/GeekLearning.RestKit.Tests.xproj b/tests/GeekLearning.RestKit.Tests/GeekLearning.RestKit.Tests.xproj new file mode 100644 index 0000000..c8d465a --- /dev/null +++ b/tests/GeekLearning.RestKit.Tests/GeekLearning.RestKit.Tests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 2cfd4c43-c052-42e0-835c-fea669c4ee91 + GeekLearning.RestKit.Tests + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/tests/GeekLearning.RestKit.Tests/MediaTypeParserTest.cs b/tests/GeekLearning.RestKit.Tests/MediaTypeParserTest.cs new file mode 100644 index 0000000..4f61460 --- /dev/null +++ b/tests/GeekLearning.RestKit.Tests/MediaTypeParserTest.cs @@ -0,0 +1,28 @@ +using GeekLearning.RestKit.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace GeekLearning.RestKit.Tests +{ + public class MediaTypeParserTest + { + + [Theory(DisplayName = nameof(TestMediaTypes))] + [InlineData("application/json", "application", "json", null, "json")] + [InlineData("application/vnd.apptype+json", "application", "vnd.apptype", "json", "json")] + [InlineData("application/vnd.apptype+xml", "application", "vnd.apptype", "xml", "xml")] + [InlineData("application/xml+json", "application", "xml", "json", "json")] + public void TestMediaTypes(string mediaType, string type, string subType, string suffix, string structured) + { + var parsedMediaType = new ParsedMediaType(mediaType); + + Assert.Equal(type, parsedMediaType.Type); + Assert.Equal(subType, parsedMediaType.SubType); + Assert.Equal(suffix, parsedMediaType.Suffix); + Assert.Equal(structured, parsedMediaType.StructuredType); + } + } +} diff --git a/tests/GeekLearning.RestKit.Tests/Properties/AssemblyInfo.cs b/tests/GeekLearning.RestKit.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..eefe2c1 --- /dev/null +++ b/tests/GeekLearning.RestKit.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GeekLearning.RestKit.Tests")] +[assembly: AssemblyTrademark("")] + +// 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("2cfd4c43-c052-42e0-835c-fea669c4ee91")] diff --git a/tests/GeekLearning.RestKit.Tests/project.json b/tests/GeekLearning.RestKit.Tests/project.json new file mode 100644 index 0000000..50e8116 --- /dev/null +++ b/tests/GeekLearning.RestKit.Tests/project.json @@ -0,0 +1,32 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + }, + + "testRunner": "xunit", + + "dependencies": { + "GeekLearning.RestKit.Core": "*", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "xunit": "2.2.0-beta2-build3300", + "xunit.runner.visualstudio": "2.2.0-beta2-build1149" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50", + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + } + } + }, + "net452": { + "frameworkAssemblies": { + }, + "dependencies": { + } + } + } +}