Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/ardalis/apiendpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalis committed Aug 7, 2020
2 parents c8fbe8e + e4695d7 commit 319391e
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 54 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ services.AddSwaggerGen(c => {
c.EnableAnnotations();
});
```
3. Add the following attribtue to endpoint methods
3. Add the following attribute to endpoint methods
``` csharp
[HttpPost("/authors")]
[SwaggerOperation(
Expand Down Expand Up @@ -126,9 +126,11 @@ One thing that Controllers do have is built-in support in the framework to use t
- [SimpleEndpoints](https://github.com/dasiths/SimpleEndpoints)
- [FunctionMonkey](https://github.com/JamesRandall/FunctionMonkey) A similar approach for Azure Functions.
- [https://github.com/Kahbazi/MediatR.AspNetCore.Endpoints](https://github.com/Kahbazi/MediatR.AspNetCore.Endpoints) A similar approach using MediatR and middleware.
- [Voyager](https://github.com/smithgeek/voyager) A similar approach using MediatR that works for ASP.NET core and Azure Functions.

## Projects Using ApiEndpoints

If you're using them or find one not in this list, feel free to add it here via a pull request!

- [CleanArchitecture](https://github.com/ardalis/CleanArchitecture): A solution template for ASP.NET 3.x solutions using Clean Architecture.
- [PayrollProcessor](https://github.com/KyleMcMaster/payroll-processor): A smorgasbord of modern .NET tech written with functional and asynchronous patterns.
22 changes: 22 additions & 0 deletions sample/Sample.FunctionalTests/AuthorEndpoints/CreateEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
using SampleEndpointApp.Authors;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -44,5 +46,25 @@ public async Task CreatesANewAuthor()
Assert.Equal(result.PluralsightUrl, newAuthor.PluralsightUrl);
Assert.Equal(result.TwitterAlias, newAuthor.TwitterAlias);
}

[Fact]
public async Task GivenLongRunningCreateRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));
var lastAuthor = SeedData.Authors().Last();
var newAuthor = new CreateAuthorCommand()
{
Name = "James Eastham",
PluralsightUrl = "https://app.pluralsight.com",
TwitterAlias = "jeasthamdev",
};

// Act
var request = _client.PostAsync("/authors", new StringContent(JsonConvert.SerializeObject(newAuthor), Encoding.UTF8, "application/json"), tokenSource.Token);

// Assert
await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
15 changes: 15 additions & 0 deletions sample/Sample.FunctionalTests/AuthorEndpoints/DeleteEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
using SampleEndpointApp.Authors;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -40,5 +42,18 @@ public async Task DeleteAnExistingAuthor()
Assert.Equal(2, result.DeletedAuthorId);
Assert.True(listResult.Count() <= 2);
}

[Fact]
public async Task GivenLongRunningDeleteRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));

// Act
var request = _client.DeleteAsync("/authors/2", tokenSource.Token);

// Assert
await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
16 changes: 16 additions & 0 deletions sample/Sample.FunctionalTests/AuthorEndpoints/GetEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
using SampleEndpointApp;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -34,5 +36,19 @@ public async Task ReturnsAuthorById()
Assert.Equal(firstAuthor.PluralsightUrl, result.PluralsightUrl);
Assert.Equal(firstAuthor.TwitterAlias, result.TwitterAlias);
}

[Fact]
public async Task GivenLongRunningGetRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));
var firstAuthor = SeedData.Authors().First();

// Act
var request = _client.GetAsync($"/authors/{firstAuthor}", tokenSource.Token);

// Assert
await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
15 changes: 15 additions & 0 deletions sample/Sample.FunctionalTests/AuthorEndpoints/ListEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using SampleEndpointApp;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -30,5 +32,18 @@ public async Task ReturnsTwoGivenTwoAuthors()
Assert.NotNull(result);
Assert.Equal(SeedData.Authors().Count(), result.Count());
}

[Fact]
public async Task GivenLongRunningListRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));

// Act
var request = _client.GetAsync("/authors", tokenSource.Token);

// Assert
var response = await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using SampleEndpointApp;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -28,7 +30,20 @@ public async Task Page1PerPage1_ShouldReturnFirstAuthor()
var result = JsonConvert.DeserializeObject<IEnumerable<Author>>(stringResponse);

Assert.NotNull(result);
Assert.Equal(1, result.Count());
Assert.Single(result);
}

[Fact]
public async Task GivenLongRunningPaginatedListRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));

// Act
var request = _client.GetAsync("/authors?perPage=1&page=1", tokenSource.Token);

// Assert
var response = await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
21 changes: 21 additions & 0 deletions sample/Sample.FunctionalTests/AuthorEndpoints/UpdateEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
using SampleEndpointApp.Authors;
using SampleEndpointApp.DataAccess;
using SampleEndpointApp.DomainModel;
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -44,5 +46,24 @@ public async Task UpdateAnExistingAuthor()
Assert.Equal(result.PluralsightUrl, authorPreUpdate.PluralsightUrl);
Assert.Equal(result.TwitterAlias, authorPreUpdate.TwitterAlias);
}

[Fact]
public async Task GivenLongRunningUpdateRequest_WhenTokenSourceCallsForCancellation_RequestIsTerminated()
{
// Arrange, generate a token source that times out instantly
var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(0));
var authorPreUpdate = SeedData.Authors().FirstOrDefault(p => p.Id == 2);
var updatedAuthor = new UpdateAuthorCommand()
{
Id = 2,
Name = "James Eastham",
};

// Act
var request = _client.PutAsync("/authors", new StringContent(JsonConvert.SerializeObject(updatedAuthor), Encoding.UTF8, "application/json"), tokenSource.Token);

// Assert
await Assert.ThrowsAsync<OperationCanceledException>(async () => await request);
}
}
}
5 changes: 3 additions & 2 deletions sample/SampleEndpointApp/AuthorEndpoints/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Mvc;
using SampleEndpointApp.DomainModel;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading;
using System.Threading.Tasks;

namespace SampleEndpointApp.Authors
Expand All @@ -26,11 +27,11 @@ public Create(IAsyncRepository<Author> repository,
OperationId = "Author.Create",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<CreateAuthorResult>> HandleAsync([FromBody]CreateAuthorCommand request)
public override async Task<ActionResult<CreateAuthorResult>> HandleAsync([FromBody]CreateAuthorCommand request, CancellationToken cancellationToken)
{
var author = new Author();
_mapper.Map(request, author);
await _repository.AddAsync(author);
await _repository.AddAsync(author, cancellationToken);

var result = _mapper.Map<CreateAuthorResult>(author);
return Ok(result);
Expand Down
14 changes: 10 additions & 4 deletions sample/SampleEndpointApp/AuthorEndpoints/Delete.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;
using System.Threading;

namespace SampleEndpointApp.Authors
{
Expand All @@ -23,11 +24,16 @@ public Delete(IAsyncRepository<Author> repository)
OperationId = "Author.Delete",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<DeletedAuthorResult>> HandleAsync(int id)
public override async Task<ActionResult<DeletedAuthorResult>> HandleAsync(int id, CancellationToken cancellationToken)
{
var author = await _repository.GetByIdAsync(id);
if (author == null) return NotFound(id);
await _repository.DeleteAsync(author);
var author = await _repository.GetByIdAsync(id, cancellationToken);

if (author is null)
{
return NotFound(id);
}

await _repository.DeleteAsync(author, cancellationToken);

// return NoContent(); another option; see https://restfulapi.net/http-methods/#delete
return Ok(new DeletedAuthorResult { DeletedAuthorId = id });
Expand Down
7 changes: 3 additions & 4 deletions sample/SampleEndpointApp/AuthorEndpoints/Get.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using Microsoft.AspNetCore.Mvc;
using SampleEndpointApp.DomainModel;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;
using System.Threading;

namespace SampleEndpointApp.Authors
{
Expand All @@ -21,16 +21,15 @@ public Get(IAsyncRepository<Author> repository,
}

[HttpGet("/authors/{id}")]

[SwaggerOperation(
Summary = "Get a specific Author",
Description = "Get a specific Author",
OperationId = "Author.Get",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<AuthorResult>> HandleAsync(int id)
public override async Task<ActionResult<AuthorResult>> HandleAsync(int id, CancellationToken cancellationToken)
{
var author = await _repository.GetByIdAsync(id);
var author = await _repository.GetByIdAsync(id, cancellationToken);

var result = _mapper.Map<AuthorResult>(author);

Expand Down
6 changes: 3 additions & 3 deletions sample/SampleEndpointApp/AuthorEndpoints/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using SampleEndpointApp.DomainModel;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;
using System.Threading;

namespace SampleEndpointApp.Authors
{
Expand All @@ -29,9 +29,9 @@ public List(IAsyncRepository<Author> repository,
OperationId = "Author.List",
Tags = new[] { "AuthorEndpoint" })
]
public async Task<ActionResult> HandleAsync([FromQuery] int page = 1, int perPage = 10)
public async Task<ActionResult> HandleAsync([FromQuery] int page = 1, int perPage = 10, CancellationToken cancellationToken = default)
{
var result = (await _repository.ListAllAsync(perPage, page))
var result = (await _repository.ListAllAsync(perPage, page, cancellationToken))
.Select(i => _mapper.Map<AuthorListResult>(i));

return Ok(result);
Expand Down
7 changes: 4 additions & 3 deletions sample/SampleEndpointApp/AuthorEndpoints/Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;

using Swashbuckle.AspNetCore.Annotations;
using System.Threading;

namespace SampleEndpointApp.Authors
{
Expand All @@ -28,11 +29,11 @@ public Update(IAsyncRepository<Author> repository,
OperationId = "Author.Update",
Tags = new[] { "AuthorEndpoint" })
]
public override async Task<ActionResult<UpdatedAuthorResult>> HandleAsync([FromBody]UpdateAuthorCommand request)
public override async Task<ActionResult<UpdatedAuthorResult>> HandleAsync([FromBody]UpdateAuthorCommand request, CancellationToken cancellationToken)
{
var author = await _repository.GetByIdAsync(request.Id);
var author = await _repository.GetByIdAsync(request.Id, cancellationToken);
_mapper.Map(request, author);
await _repository.UpdateAsync(author);
await _repository.UpdateAsync(author, cancellationToken);

var result = _mapper.Map<UpdatedAuthorResult>(author);
return Ok(result);
Expand Down
Loading

0 comments on commit 319391e

Please sign in to comment.