Skip to content

Commit

Permalink
Merge pull request #58 from Research-Institute/feature/client-generat…
Browse files Browse the repository at this point in the history
…ed-ids

Feature/client generated ids
  • Loading branch information
jaredcnance authored Mar 16, 2017
2 parents 6cbc1cd + 96bcc18 commit 3d51f1c
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 10 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
- [Filtering](#filtering)
- [Sorting](#sorting)
- [Meta](#meta)
- [Client Generated Ids](#client-generated-ids)
- [Tests](#tests)

## Comprehensive Demo
Expand Down Expand Up @@ -342,6 +343,20 @@ public class Person : Identifiable<int>, IHasMeta
}
```

### Client Generated Ids

By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
received with a client generated id. However, this can be allowed by setting the `AllowClientGeneratedIds`
flag in the options:

```csharp
services.AddJsonApi<AppDbContext>(opt =>
{
opt.AllowClientGeneratedIds = true;
// ..
});
```

## Tests

I am using DotNetCoreDocs to generate sample requests and documentation.
Expand Down
1 change: 1 addition & 0 deletions src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public class JsonApiOptions
public string Namespace { get; set; }
public int DefaultPageSize { get; set; }
public bool IncludeTotalRecordCount { get; set; }
public bool AllowClientGeneratedIds { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
return UnprocessableEntity();
}

if (!string.IsNullOrEmpty(entity.StringId))
if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId))
return Forbidden();

await _entities.CreateAsync(entity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCoreExampleTests.Startups;
using System;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
{
Expand All @@ -37,7 +39,7 @@ public CreatingDataTests(DocsFixture<Startup, JsonDocWriter> fixture)
}

[Fact]
public async Task Can_Create_Guid_Identifiable_Entities()
public async Task Can_Create_Guid_Identifiable_Entity()
{
// arrange
var builder = new WebHostBuilder()
Expand Down Expand Up @@ -74,7 +76,7 @@ public async Task Can_Create_Guid_Identifiable_Entities()
};
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);

Expand All @@ -83,9 +85,10 @@ public async Task Can_Create_Guid_Identifiable_Entities()
}

[Fact]
public async Task Request_With_ClientGeneratedId_Returns_403()
public async Task Cannot_Create_Entity_With_Client_Generate_Id()
{
// arrange
var context = _fixture.GetService<AppDbContext>();
var builder = new WebHostBuilder()
.UseStartup<Startup>();
var httpMethod = new HttpMethod("POST");
Expand All @@ -94,29 +97,124 @@ public async Task Request_With_ClientGeneratedId_Returns_403()
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
var todoItem = _todoItemFaker.Generate();
const int clientDefinedId = 9999;
var content = new
{
data = new
{
type = "todo-items",
id = "9999",
id = $"{clientDefinedId}",
attributes = new
{
description = todoItem.Description,
ordinal = todoItem.Ordinal
}
}
};

request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}

[Fact]
public async Task Can_Create_Entity_With_Client_Defined_Id_If_Configured()
{
// arrange
var context = _fixture.GetService<AppDbContext>();
var builder = new WebHostBuilder()
.UseStartup<ClientGeneratedIdsStartup>();
var httpMethod = new HttpMethod("POST");
var route = "/api/v1/todo-items";
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
var todoItem = _todoItemFaker.Generate();
const int clientDefinedId = 9999;
var content = new
{
data = new
{
type = "todo-items",
id = $"{clientDefinedId}",
attributes = new
{
description = todoItem.Description,
ordinal = todoItem.Ordinal
}
}
};

request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context);

// assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.Equal(clientDefinedId, deserializedBody.Id);
}


[Fact]
public async Task Can_Create_Guid_Identifiable_Entity_With_Client_Defined_Id_If_Configured()
{
// arrange
var builder = new WebHostBuilder()
.UseStartup<ClientGeneratedIdsStartup>();
var httpMethod = new HttpMethod("POST");
var server = new TestServer(builder);
var client = server.CreateClient();

var context = _fixture.GetService<AppDbContext>();

var owner = new JsonApiDotNetCoreExample.Models.Person();
context.People.Add(owner);
await context.SaveChangesAsync();

var route = "/api/v1/todo-item-collections";
var request = new HttpRequestMessage(httpMethod, route);
var clientDefinedId = Guid.NewGuid();
var content = new
{
data = new
{
type = "todo-item-collections",
id = $"{clientDefinedId}",
relationships = new
{
owner = new
{
data = new
{
type = "people",
id = owner.Id.ToString()
}
}
}
}
};
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var deserializedBody = (TodoItemCollection)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context);

// assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.Equal(clientDefinedId, deserializedBody.Id);
}

[Fact]
public async Task Can_Create_And_Set_HasMany_Relationships()
{
Expand Down Expand Up @@ -167,14 +265,14 @@ public async Task Can_Create_And_Set_HasMany_Relationships()

request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var deserializedBody = (TodoItemCollection)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context);
var newId = deserializedBody.Id;
var contextCollection = context.TodoItemCollections
.Include(c=> c.Owner)
.Include(c => c.Owner)
.Include(c => c.TodoItems)
.SingleOrDefault(c => c.Id == newId);

Expand Down Expand Up @@ -210,7 +308,7 @@ public async Task ShouldReceiveLocationHeader_InResponse()
};
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Expand Down Expand Up @@ -247,7 +345,7 @@ public async Task Respond_409_ToIncorrectEntityType()
};
request.Content = new StringContent(JsonConvert.SerializeObject(content));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");

// act
var response = await client.SendAsync(request);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCore.Extensions;
using DotNetCoreDocs.Configuration;
using System;
using JsonApiDotNetCoreExample;

namespace JsonApiDotNetCoreExampleTests.Startups
{
public class ClientGeneratedIdsStartup : Startup
{
public ClientGeneratedIdsStartup(IHostingEnvironment env)
: base (env)
{ }

public override IServiceProvider ConfigureServices(IServiceCollection services)
{
var loggerFactory = new LoggerFactory();

loggerFactory
.AddConsole(LogLevel.Trace);

services.AddSingleton<ILoggerFactory>(loggerFactory);

services.AddDbContext<AppDbContext>(options =>
{
options.UseNpgsql(GetDbConnectionString());
}, ServiceLifetime.Transient);

services.AddJsonApi<AppDbContext>(opt =>
{
opt.Namespace = "api/v1";
opt.DefaultPageSize = 5;
opt.IncludeTotalRecordCount = true;
opt.AllowClientGeneratedIds = true;
});

services.AddDocumentationConfiguration(Config);

return services.BuildServiceProvider();
}
}
}

0 comments on commit 3d51f1c

Please sign in to comment.