diff --git a/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 308c3358b0..53e8ea5196 100644 --- a/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using System; -using AutoMapper; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Routing; @@ -7,14 +6,16 @@ namespace JsonApiDotNetCore.Extensions { - public static class IServiceCollectionExtensions - { - public static void AddJsonApi(this IServiceCollection services, Action configurationAction) + public static class IServiceCollectionExtensions { - var configBuilder = new JsonApiConfigurationBuilder(configurationAction); - var config = configBuilder.Build(); - IRouter router = new Router(config, new RouteBuilder(config), new ControllerBuilder()); - services.AddSingleton(_ => router); + public static void AddJsonApi(this IServiceCollection services, Action configurationAction) + { + var configBuilder = new JsonApiConfigurationBuilder(configurationAction); + var config = configBuilder.Build(); + services.AddTransient(_ => + { + return (IRouter)new Router(config, new RouteBuilder(config), new ControllerBuilder()); + }); + } } - } } diff --git a/JsonApiDotNetCore/Extensions/StringExtensions.cs b/JsonApiDotNetCore/Extensions/StringExtensions.cs index e003512fbb..f0c079c44f 100644 --- a/JsonApiDotNetCore/Extensions/StringExtensions.cs +++ b/JsonApiDotNetCore/Extensions/StringExtensions.cs @@ -21,8 +21,19 @@ public static string ToProperCase(this string str) var chars = str.ToCharArray(); if (chars.Length > 0) { - chars[0] = new string(chars[0], 1).ToUpper().ToCharArray()[0]; - return new String(chars); + chars[0] = char.ToUpper(chars[0]); + var builder = new StringBuilder(); + for(var i = 0; i < chars.Length; i++) + { + if((chars[i]) == '-') { + i = i + 1; + builder.Append(char.ToUpper(chars[i])); + } + else { + builder.Append(chars[i]); + } + } + return builder.ToString(); } return str; } diff --git a/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index c7b9425942..bb69a2304b 100644 --- a/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -30,7 +30,7 @@ public async Task Invoke(HttpContext context) _logger.LogInformation("Passing request to JsonApiService: " + context.Request.Path); if(IsJsonApiRequest(context)) { - var routeWasHandled = _router.HandleJsonApiRoute(context, _serviceProvider); + var routeWasHandled = await _router.HandleJsonApiRouteAsync(context, _serviceProvider); if(!routeWasHandled) RespondNotFound(context); } diff --git a/JsonApiDotNetCore/Routing/IRouter.cs b/JsonApiDotNetCore/Routing/IRouter.cs index 75fc0c9167..28f7e1584f 100644 --- a/JsonApiDotNetCore/Routing/IRouter.cs +++ b/JsonApiDotNetCore/Routing/IRouter.cs @@ -1,10 +1,11 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Routing { public interface IRouter { - bool HandleJsonApiRoute(HttpContext context, IServiceProvider serviceProvider); + Task HandleJsonApiRouteAsync(HttpContext context, IServiceProvider serviceProvider); } } diff --git a/JsonApiDotNetCore/Routing/Router.cs b/JsonApiDotNetCore/Routing/Router.cs index de4b9d34c0..02cd23751b 100644 --- a/JsonApiDotNetCore/Routing/Router.cs +++ b/JsonApiDotNetCore/Routing/Router.cs @@ -1,90 +1,88 @@ using System; using System.Text; +using System.Threading.Tasks; using JsonApiDotNetCore.Abstractions; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; namespace JsonApiDotNetCore.Routing { - public class Router : IRouter - { - private readonly JsonApiModelConfiguration _jsonApiModelConfiguration; - private IServiceProvider _serviceProvider; - private JsonApiContext _jsonApiContext; - private IRouteBuilder _routeBuilder; - private IControllerBuilder _controllerBuilder; - - public Router(JsonApiModelConfiguration configuration, IRouteBuilder routeBuilder, IControllerBuilder controllerBuilder) + public class Router : IRouter { - _jsonApiModelConfiguration = configuration; - _routeBuilder = routeBuilder; - _controllerBuilder = controllerBuilder; - } + private readonly JsonApiModelConfiguration _jsonApiModelConfiguration; + private IServiceProvider _serviceProvider; + private IRouteBuilder _routeBuilder; + private IControllerBuilder _controllerBuilder; - public bool HandleJsonApiRoute(HttpContext context, IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; + public Router(JsonApiModelConfiguration configuration, IRouteBuilder routeBuilder, IControllerBuilder controllerBuilder) + { + _jsonApiModelConfiguration = configuration; + _routeBuilder = routeBuilder; + _controllerBuilder = controllerBuilder; + } - var route = _routeBuilder.BuildFromRequest(context.Request); - if (route == null) return false; + public async Task HandleJsonApiRouteAsync(HttpContext context, IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; - InitializeContext(context, route); - CallController(); + var route = _routeBuilder.BuildFromRequest(context.Request); + if (route == null) return false; - return true; - } + var jsonApiContext = InitializeContext(context, route); + await CallController(jsonApiContext); - private void InitializeContext(HttpContext context, Route route) - { - var dbContext = _serviceProvider.GetService(_jsonApiModelConfiguration.ContextType); - _jsonApiContext = new JsonApiContext(context, route, dbContext, _jsonApiModelConfiguration); - } + return true; + } - private void CallController() - { - var controller = _controllerBuilder.BuildController(_jsonApiContext); + private JsonApiContext InitializeContext(HttpContext context, Route route) + { + var dbContext = _serviceProvider.GetService(_jsonApiModelConfiguration.ContextType); + Console.WriteLine("InitializingContext"); + return new JsonApiContext(context, route, dbContext, _jsonApiModelConfiguration); + } - var result = ActivateControllerMethod(controller); + private async Task CallController(JsonApiContext jsonApiContext) + { + var controller = _controllerBuilder.BuildController(jsonApiContext); - result.Value = SerializeResult(result.Value); + var result = ActivateControllerMethod(controller, jsonApiContext); - SendResponse(result); - } + result.Value = SerializeResult(result.Value, jsonApiContext); - private ObjectResult ActivateControllerMethod(IJsonApiController controller) - { - var route = _jsonApiContext.Route; - switch (route.RequestMethod) - { - case "GET": - return string.IsNullOrEmpty(route.ResourceId) ? controller.Get() : controller.Get(route.ResourceId); - case "POST": - return controller.Post(new JsonApiDeserializer(_jsonApiContext).GetEntityFromRequest()); - case "PATCH": - return controller.Patch(route.ResourceId, new JsonApiDeserializer(_jsonApiContext).GetEntityPatch()); - case "DELETE": - return controller.Delete(route.ResourceId); - default: - throw new ArgumentException("Request method not supported", nameof(route)); - } - } + await SendResponse(jsonApiContext.HttpContext, result); + } - private object SerializeResult(object result) - { - return result == null ? null : new JsonApiSerializer(_jsonApiContext).ToJsonApiDocument(result); - } + private ObjectResult ActivateControllerMethod(IJsonApiController controller, JsonApiContext jsonApiContext) + { + var route = jsonApiContext.Route; + switch (route.RequestMethod) + { + case "GET": + return string.IsNullOrEmpty(route.ResourceId) ? controller.Get() : controller.Get(route.ResourceId); + case "POST": + return controller.Post(new JsonApiDeserializer(jsonApiContext).GetEntityFromRequest()); + case "PATCH": + return controller.Patch(route.ResourceId, new JsonApiDeserializer(jsonApiContext).GetEntityPatch()); + case "DELETE": + return controller.Delete(route.ResourceId); + default: + throw new ArgumentException("Request method not supported", nameof(route)); + } + } - private void SendResponse(ObjectResult result) - { - var context = _jsonApiContext.HttpContext; - context.Response.StatusCode = result.StatusCode ?? 500; - context.Response.ContentType = "application/vnd.api+json"; - context.Response.WriteAsync(result.Value == null ? "" : result.Value.ToString(), Encoding.UTF8); - context.Response.Body.Flush(); + private object SerializeResult(object result, JsonApiContext jsonApiContext) + { + return result == null ? null : new JsonApiSerializer(jsonApiContext).ToJsonApiDocument(result); + } + + private async Task SendResponse(HttpContext context, ObjectResult result) + { + context.Response.StatusCode = result.StatusCode ?? 500; + context.Response.ContentType = "application/vnd.api+json"; + await context.Response.WriteAsync(result.Value == null ? "" : result.Value.ToString(), Encoding.UTF8); + } } - } } diff --git a/JsonApiDotNetCore/project.json b/JsonApiDotNetCore/project.json index f5f287ffb1..a810687e16 100644 --- a/JsonApiDotNetCore/project.json +++ b/JsonApiDotNetCore/project.json @@ -1,8 +1,21 @@ { - "version": "0.1.3", + "version": "0.1.4-beta-*", "packOptions": { + "summary": "Complete JSONAPI Middleware, Routing, and Controller package for .Net Core", + "tags": [ + "dotnetcore", + "jsonapi", + "emberjs" + ], + "owners": [ + "Jared Nance", + "Children's Research Institue" + ], "licenseUrl": "https://github.com/Research-Institute/json-api-dotnet-core/tree/master/JsonApiDotNetCore/LICENSE", - "projectUrl": "https://github.com/Research-Institute/json-api-dotnet-core/" + "projectUrl": "https://github.com/Research-Institute/json-api-dotnet-core", + "repository": { + "url": "https://github.com/Research-Institute/json-api-dotnet-core" + } }, "dependencies": { "NETStandard.Library": "1.6.0", diff --git a/JsonApiDotNetCoreExample/Startup.cs b/JsonApiDotNetCoreExample/Startup.cs index 5593be0f12..da54d30c21 100644 --- a/JsonApiDotNetCoreExample/Startup.cs +++ b/JsonApiDotNetCoreExample/Startup.cs @@ -32,7 +32,8 @@ public Startup(IHostingEnvironment env) public void ConfigureServices(IServiceCollection services) { // Add framework services. - services.AddMvc(); + services.AddCors(); + services.AddDbContext(options => options.UseNpgsql(Configuration["Data:ConnectionString"]), ServiceLifetime.Transient); @@ -54,6 +55,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); + app.UseCors(builder => + builder.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowAnyMethod().AllowCredentials()); + app.UseJsonApi(); } } diff --git a/JsonApiDotNetCoreExample/project.json b/JsonApiDotNetCoreExample/project.json index 6c77a0b5e3..078e5c1743 100644 --- a/JsonApiDotNetCoreExample/project.json +++ b/JsonApiDotNetCoreExample/project.json @@ -11,7 +11,7 @@ "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "JsonApiDotNetCore": "0.1.2", + "JsonApiDotNetCore": "0.1.4-beta-*", "Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.0", "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final" }, diff --git a/JsonApiDotNetCoreTests/Extensions/UnitTests/StringExtensionsTests.cs b/JsonApiDotNetCoreTests/Extensions/UnitTests/StringExtensionsTests.cs index d3b4f3cd71..5aad0151cd 100644 --- a/JsonApiDotNetCoreTests/Extensions/UnitTests/StringExtensionsTests.cs +++ b/JsonApiDotNetCoreTests/Extensions/UnitTests/StringExtensionsTests.cs @@ -19,6 +19,7 @@ public void ToCamelCase_ConvertsString_ToCamelCase(string input, string expected [Theory] [InlineData("todoItem", "TodoItem")] + [InlineData("todo-items", "TodoItems")] public void ToProperCase_ConvertsString_ToProperCase(string input, string expectedOutput) { // arrange diff --git a/JsonApiDotNetCoreTests/Helpers/TestRouter.cs b/JsonApiDotNetCoreTests/Helpers/TestRouter.cs index 1e3d6dd964..04e1c9ab8d 100644 --- a/JsonApiDotNetCoreTests/Helpers/TestRouter.cs +++ b/JsonApiDotNetCoreTests/Helpers/TestRouter.cs @@ -1,16 +1,18 @@ using System; +using System.Threading.Tasks; using JsonApiDotNetCore.Routing; using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCoreTests.Helpers { - public class TestRouter : IRouter - { - public bool DidHandleRoute { get; set; } - public bool HandleJsonApiRoute(HttpContext context, IServiceProvider serviceProvider) + public class TestRouter : IRouter { - DidHandleRoute = true; - return true; + public bool DidHandleRoute { get; set; } + + Task IRouter.HandleJsonApiRouteAsync(HttpContext context, IServiceProvider serviceProvider) + { + DidHandleRoute = true; + return Task.Run(() => true); + } } - } } diff --git a/JsonApiDotNetCoreTests/Routing/UnitTests/RouterTests.cs b/JsonApiDotNetCoreTests/Routing/UnitTests/RouterTests.cs index 064027fbf7..b1354f5a4f 100644 --- a/JsonApiDotNetCoreTests/Routing/UnitTests/RouterTests.cs +++ b/JsonApiDotNetCoreTests/Routing/UnitTests/RouterTests.cs @@ -7,10 +7,6 @@ using JsonApiDotNetCore.Routing; using JsonApiDotNetCore.Controllers; using Microsoft.AspNetCore.Mvc; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http.Internal; using System.IO; namespace JsonApiDotNetCoreTests.Routing.UnitTests @@ -18,87 +14,87 @@ namespace JsonApiDotNetCoreTests.Routing.UnitTests public class RouterTests { [Fact] - public void HandleJsonApiRoute_ReturnsFalse_IfTheRouteCannotBeBuilt() + public async void HandleJsonApiRoute_ReturnsFalse_IfTheRouteCannotBeBuilt() { - //--> arrange - var httpContextMock = new Mock(); - httpContextMock.SetupAllProperties(); + //--> arrange + var httpContextMock = new Mock(); + httpContextMock.SetupAllProperties(); - // since this is an empty stub, IRouteBuilder.BuildFromRequest() will always return null - var routeBuilderMock = new Mock(); + // since this is an empty stub, IRouteBuilder.BuildFromRequest() will always return null + var routeBuilderMock = new Mock(); - var router = new Router(null, routeBuilderMock.Object, null); + var router = new Router(null, routeBuilderMock.Object, null); - //--> act - var result = router.HandleJsonApiRoute(httpContextMock.Object, null); + //--> act + var result = await router.HandleJsonApiRouteAsync(httpContextMock.Object, null); - //--> assert - Assert.False(result); + //--> assert + Assert.False(result); } [Fact] - public void HandleJsonApiRoute_CallsGetMethod_ForGetRequest() + public async void HandleJsonApiRoute_CallsGetMethod_ForGetRequest() { - //--> arrange - var httpContextMock = new Mock(); - httpContextMock.SetupAllProperties(); - var httpResponseMock = new Mock(); - httpResponseMock.Setup(r => r.Body).Returns(new MemoryStream()); - httpContextMock.Setup(c => c.Response).Returns(httpResponseMock.Object); + //--> arrange + var httpContextMock = new Mock(); + httpContextMock.SetupAllProperties(); + var httpResponseMock = new Mock(); + httpResponseMock.Setup(r => r.Body).Returns(new MemoryStream()); + httpContextMock.Setup(c => c.Response).Returns(httpResponseMock.Object); - var route = new Route(null, "GET", null, null, null); + var route = new Route(null, "GET", null, null, null); - var routeBuilderMock = new Mock(); - routeBuilderMock.Setup(rb => rb.BuildFromRequest(null)).Returns(route); + var routeBuilderMock = new Mock(); + routeBuilderMock.Setup(rb => rb.BuildFromRequest(null)).Returns(route); - var serviceProviderMock = new Mock(); + var serviceProviderMock = new Mock(); - var controllerMock = new Mock(); - controllerMock.Setup(c => c.Get()).Returns(new OkObjectResult(null)); - var controllerBuilder = new Mock(); - controllerBuilder.Setup(cb => cb.BuildController(It.IsAny())).Returns(controllerMock.Object); + var controllerMock = new Mock(); + controllerMock.Setup(c => c.Get()).Returns(new OkObjectResult(null)); + var controllerBuilder = new Mock(); + controllerBuilder.Setup(cb => cb.BuildController(It.IsAny())).Returns(controllerMock.Object); - var router = new Router(new JsonApiModelConfiguration(), routeBuilderMock.Object, controllerBuilder.Object); + var router = new Router(new JsonApiModelConfiguration(), routeBuilderMock.Object, controllerBuilder.Object); - //--> act - var result = router.HandleJsonApiRoute(httpContextMock.Object, serviceProviderMock.Object); + //--> act + var result = await router.HandleJsonApiRouteAsync(httpContextMock.Object, serviceProviderMock.Object); - //--> assert - Assert.True(result); - controllerMock.Verify(c => c.Get()); + //--> assert + Assert.True(result); + controllerMock.Verify(c => c.Get()); } [Fact] - public void HandleJsonApiRoute_CallsGetIdMethod_ForGetIdRequest() + public async void HandleJsonApiRoute_CallsGetIdMethod_ForGetIdRequest() { - //--> arrange - const string resourceId = "1"; - var httpContextMock = new Mock(); - httpContextMock.SetupAllProperties(); - var httpResponseMock = new Mock(); - httpResponseMock.Setup(r => r.Body).Returns(new MemoryStream()); - httpContextMock.Setup(c => c.Response).Returns(httpResponseMock.Object); + //--> arrange + const string resourceId = "1"; + var httpContextMock = new Mock(); + httpContextMock.SetupAllProperties(); + var httpResponseMock = new Mock(); + httpResponseMock.Setup(r => r.Body).Returns(new MemoryStream()); + httpContextMock.Setup(c => c.Response).Returns(httpResponseMock.Object); - var route = new Route(null, "GET", resourceId, null, null); + var route = new Route(null, "GET", resourceId, null, null); - var routeBuilderMock = new Mock(); - routeBuilderMock.Setup(rb => rb.BuildFromRequest(null)).Returns(route); + var routeBuilderMock = new Mock(); + routeBuilderMock.Setup(rb => rb.BuildFromRequest(null)).Returns(route); - var serviceProviderMock = new Mock(); + var serviceProviderMock = new Mock(); - var controllerMock = new Mock(); - controllerMock.Setup(c => c.Get(resourceId)).Returns(new OkObjectResult(null)); - var controllerBuilder = new Mock(); - controllerBuilder.Setup(cb => cb.BuildController(It.IsAny())).Returns(controllerMock.Object); + var controllerMock = new Mock(); + controllerMock.Setup(c => c.Get(resourceId)).Returns(new OkObjectResult(null)); + var controllerBuilder = new Mock(); + controllerBuilder.Setup(cb => cb.BuildController(It.IsAny())).Returns(controllerMock.Object); - var router = new Router(new JsonApiModelConfiguration(), routeBuilderMock.Object, controllerBuilder.Object); + var router = new Router(new JsonApiModelConfiguration(), routeBuilderMock.Object, controllerBuilder.Object); - //--> act - var result = router.HandleJsonApiRoute(httpContextMock.Object, serviceProviderMock.Object); + //--> act + var result = await router.HandleJsonApiRouteAsync(httpContextMock.Object, serviceProviderMock.Object); - //--> assert - Assert.True(result); - controllerMock.Verify(c => c.Get(resourceId)); + //--> assert + Assert.True(result); + controllerMock.Verify(c => c.Get(resourceId)); } } } diff --git a/JsonApiDotNetCoreTests/project.json b/JsonApiDotNetCoreTests/project.json index a229b7367d..e418dff238 100644 --- a/JsonApiDotNetCoreTests/project.json +++ b/JsonApiDotNetCoreTests/project.json @@ -1,8 +1,7 @@ { - "version": "1.0.0-alpha", "testRunner": "xunit", "dependencies": { - "JsonApiDotNetCore": "0.1.2", + "JsonApiDotNetCore": "0.1.4-beta-*", "dotnet-test-xunit": "2.2.0-preview2-build1029", "xunit": "2.2.0-beta2-build3300", "moq": "4.6.38-alpha" diff --git a/README.md b/README.md index 36b4d505a4..2d945db2f5 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,16 @@ [![NuGet](https://img.shields.io/nuget/v/JsonApiDotNetCore.svg)](https://www.nuget.org/packages/JsonApiDotNetCore/) [![MyGet CI](https://img.shields.io/myget/research-institute/v/JsonApiDotNetCore.svg)](https://www.myget.org/feed/research-institute/package/nuget/JsonApiDotNetCore) -JSON API Spec Conformance: **Non Conforming** - ## Installation +`Install-Package JsonApiDotNetCore` + +Click [here](https://www.nuget.org/packages/JsonApiDotNetCore/) for the latest NuGet version. + For pre-releases, add the [MyGet](https://www.myget.org/feed/Details/research-institute) package feed (https://www.myget.org/F/research-institute/api/v3/index.json) to your nuget configuration. -`Install-Package JsonApiDotNetCore` - -``` -"JsonApiDotNetCore": "0.1.0" -``` - ## Usage Go [here](https://github.com/Research-Institute/json-api-dotnet-core/wiki/Request-Examples) to see examples of HTTP requests and responses @@ -42,7 +38,9 @@ services.AddJsonApi(config => { app.UseJsonApi(); ``` -## Specifying The Presenter / ViewModel +## Specifying The JsonApiResources + +You can think of these as the presenter / view model definitions for your models. - When you define a model, you **MUST** specify the associated resource class using the `JsonApiResource` attribute. - The specified resource class **MUST** implement `IJsonApiResource`.