Fix/issue 826 support cross thread minimal api (#827)
* remove repository global variable to suuport muliple thread (call by handle method) * add parallel call tests Co-authored-by: cedri <cedri@BAS>
This commit is contained in:
@@ -13,9 +13,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;
|
||||
/// <summary>
|
||||
/// List Catalog Brands
|
||||
/// </summary>
|
||||
public class CatalogBrandListEndpoint : IEndpoint<IResult>
|
||||
public class CatalogBrandListEndpoint : IEndpoint<IResult, IRepository<CatalogBrand>>
|
||||
{
|
||||
private IRepository<CatalogBrand> _catalogBrandRepository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CatalogBrandListEndpoint(IMapper mapper)
|
||||
@@ -28,18 +27,17 @@ public class CatalogBrandListEndpoint : IEndpoint<IResult>
|
||||
app.MapGet("api/catalog-brands",
|
||||
async (IRepository<CatalogBrand> catalogBrandRepository) =>
|
||||
{
|
||||
_catalogBrandRepository = catalogBrandRepository;
|
||||
return await HandleAsync();
|
||||
return await HandleAsync(catalogBrandRepository);
|
||||
})
|
||||
.Produces<ListCatalogBrandsResponse>()
|
||||
.WithTags("CatalogBrandEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync()
|
||||
public async Task<IResult> HandleAsync(IRepository<CatalogBrand> catalogBrandRepository)
|
||||
{
|
||||
var response = new ListCatalogBrandsResponse();
|
||||
|
||||
var items = await _catalogBrandRepository.ListAsync();
|
||||
var items = await catalogBrandRepository.ListAsync();
|
||||
|
||||
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>));
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
/// <summary>
|
||||
/// Get a Catalog Item by Id
|
||||
/// </summary>
|
||||
public class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemRequest>
|
||||
public class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemRequest, IRepository<CatalogItem>>
|
||||
{
|
||||
private IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public CatalogItemGetByIdEndpoint(IUriComposer uriComposer)
|
||||
@@ -26,18 +25,17 @@ public class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemR
|
||||
app.MapGet("api/catalog-items/{catalogItemId}",
|
||||
async (int catalogItemId, IRepository<CatalogItem> itemRepository) =>
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId));
|
||||
return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId), itemRepository);
|
||||
})
|
||||
.Produces<GetByIdCatalogItemResponse>()
|
||||
.WithTags("CatalogItemEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync(GetByIdCatalogItemRequest request)
|
||||
public async Task<IResult> HandleAsync(GetByIdCatalogItemRequest request, IRepository<CatalogItem> itemRepository)
|
||||
{
|
||||
var response = new GetByIdCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||
var item = await itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||
if (item is null)
|
||||
return Results.NotFound();
|
||||
|
||||
|
||||
@@ -15,9 +15,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
/// <summary>
|
||||
/// List Catalog Items (paged)
|
||||
/// </summary>
|
||||
public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogItemRequest>
|
||||
public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogItemRequest, IRepository<CatalogItem>>
|
||||
{
|
||||
private IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
@@ -32,19 +31,19 @@ public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogI
|
||||
app.MapGet("api/catalog-items",
|
||||
async (int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId, IRepository<CatalogItem> itemRepository) =>
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId));
|
||||
return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId), itemRepository);
|
||||
})
|
||||
.Produces<ListPagedCatalogItemResponse>()
|
||||
.WithTags("CatalogItemEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync(ListPagedCatalogItemRequest request)
|
||||
public async Task<IResult> HandleAsync(ListPagedCatalogItemRequest request, IRepository<CatalogItem> itemRepository)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
var response = new ListPagedCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId);
|
||||
int totalItems = await _itemRepository.CountAsync(filterSpec);
|
||||
int totalItems = await itemRepository.CountAsync(filterSpec);
|
||||
|
||||
var pagedSpec = new CatalogFilterPaginatedSpecification(
|
||||
skip: request.PageIndex.Value * request.PageSize.Value,
|
||||
@@ -52,7 +51,7 @@ public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogI
|
||||
brandId: request.CatalogBrandId,
|
||||
typeId: request.CatalogTypeId);
|
||||
|
||||
var items = await _itemRepository.ListAsync(pagedSpec);
|
||||
var items = await itemRepository.ListAsync(pagedSpec);
|
||||
|
||||
response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>));
|
||||
foreach (CatalogItemDto item in response.CatalogItems)
|
||||
|
||||
@@ -15,9 +15,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
/// <summary>
|
||||
/// Creates a new Catalog Item
|
||||
/// </summary>
|
||||
public class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemRequest>
|
||||
public class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemRequest, IRepository<CatalogItem>>
|
||||
{
|
||||
private IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public CreateCatalogItemEndpoint(IUriComposer uriComposer)
|
||||
@@ -31,26 +30,25 @@ public class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemReq
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async
|
||||
(CreateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
return await HandleAsync(request);
|
||||
return await HandleAsync(request, itemRepository);
|
||||
})
|
||||
.Produces<CreateCatalogItemResponse>()
|
||||
.WithTags("CatalogItemEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync(CreateCatalogItemRequest request)
|
||||
public async Task<IResult> HandleAsync(CreateCatalogItemRequest request, IRepository<CatalogItem> itemRepository)
|
||||
{
|
||||
var response = new CreateCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
var catalogItemNameSpecification = new CatalogItemNameSpecification(request.Name);
|
||||
var existingCataloogItem = await _itemRepository.CountAsync(catalogItemNameSpecification);
|
||||
var existingCataloogItem = await itemRepository.CountAsync(catalogItemNameSpecification);
|
||||
if (existingCataloogItem > 0)
|
||||
{
|
||||
throw new DuplicateException($"A catalogItem with name {request.Name} already exists");
|
||||
}
|
||||
|
||||
var newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
|
||||
newItem = await _itemRepository.AddAsync(newItem);
|
||||
newItem = await itemRepository.AddAsync(newItem);
|
||||
|
||||
if (newItem.Id != 0)
|
||||
{
|
||||
@@ -59,7 +57,7 @@ public class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemReq
|
||||
// In production, we recommend uploading to a blob storage and deliver the image via CDN after a verification process.
|
||||
|
||||
newItem.UpdatePictureUri("eCatalog-item-default.png");
|
||||
await _itemRepository.UpdateAsync(newItem);
|
||||
await itemRepository.UpdateAsync(newItem);
|
||||
}
|
||||
|
||||
var dto = new CatalogItemDto
|
||||
|
||||
@@ -13,32 +13,29 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
/// <summary>
|
||||
/// Deletes a Catalog Item
|
||||
/// </summary>
|
||||
public class DeleteCatalogItemEndpoint : IEndpoint<IResult, DeleteCatalogItemRequest>
|
||||
public class DeleteCatalogItemEndpoint : IEndpoint<IResult, DeleteCatalogItemRequest, IRepository<CatalogItem>>
|
||||
{
|
||||
private IRepository<CatalogItem> _itemRepository;
|
||||
|
||||
public void AddRoute(IEndpointRouteBuilder app)
|
||||
{
|
||||
app.MapDelete("api/catalog-items/{catalogItemId}",
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async
|
||||
(int catalogItemId, IRepository<CatalogItem> itemRepository) =>
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId));
|
||||
return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId), itemRepository);
|
||||
})
|
||||
.Produces<DeleteCatalogItemResponse>()
|
||||
.WithTags("CatalogItemEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync(DeleteCatalogItemRequest request)
|
||||
public async Task<IResult> HandleAsync(DeleteCatalogItemRequest request, IRepository<CatalogItem> itemRepository)
|
||||
{
|
||||
var response = new DeleteCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||
var itemToDelete = await itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||
if (itemToDelete is null)
|
||||
return Results.NotFound();
|
||||
|
||||
await _itemRepository.DeleteAsync(itemToDelete);
|
||||
await itemRepository.DeleteAsync(itemToDelete);
|
||||
|
||||
return Results.Ok(response);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
/// <summary>
|
||||
/// Updates a Catalog Item
|
||||
/// </summary>
|
||||
public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemRequest>
|
||||
public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemRequest, IRepository<CatalogItem>>
|
||||
{
|
||||
private IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public UpdateCatalogItemEndpoint(IUriComposer uriComposer)
|
||||
@@ -29,25 +28,24 @@ public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemReq
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async
|
||||
(UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
return await HandleAsync(request);
|
||||
return await HandleAsync(request, itemRepository);
|
||||
})
|
||||
.Produces<UpdateCatalogItemResponse>()
|
||||
.WithTags("CatalogItemEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync(UpdateCatalogItemRequest request)
|
||||
public async Task<IResult> HandleAsync(UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository)
|
||||
{
|
||||
var response = new UpdateCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
var existingItem = await _itemRepository.GetByIdAsync(request.Id);
|
||||
var existingItem = await itemRepository.GetByIdAsync(request.Id);
|
||||
|
||||
CatalogItem.CatalogItemDetails details = new(request.Name, request.Description, request.Price);
|
||||
existingItem.UpdateDetails(details);
|
||||
existingItem.UpdateBrand(request.CatalogBrandId);
|
||||
existingItem.UpdateType(request.CatalogTypeId);
|
||||
|
||||
await _itemRepository.UpdateAsync(existingItem);
|
||||
await itemRepository.UpdateAsync(existingItem);
|
||||
|
||||
var dto = new CatalogItemDto
|
||||
{
|
||||
|
||||
@@ -13,9 +13,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;
|
||||
/// <summary>
|
||||
/// List Catalog Types
|
||||
/// </summary>
|
||||
public class CatalogTypeListEndpoint : IEndpoint<IResult>
|
||||
public class CatalogTypeListEndpoint : IEndpoint<IResult, IRepository<CatalogType>>
|
||||
{
|
||||
private IRepository<CatalogType> _catalogTypeRepository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CatalogTypeListEndpoint(IMapper mapper)
|
||||
@@ -28,18 +27,17 @@ public class CatalogTypeListEndpoint : IEndpoint<IResult>
|
||||
app.MapGet("api/catalog-types",
|
||||
async (IRepository<CatalogType> catalogTypeRepository) =>
|
||||
{
|
||||
_catalogTypeRepository = catalogTypeRepository;
|
||||
return await HandleAsync();
|
||||
return await HandleAsync(catalogTypeRepository);
|
||||
})
|
||||
.Produces<ListCatalogTypesResponse>()
|
||||
.WithTags("CatalogTypeEndpoints");
|
||||
}
|
||||
|
||||
public async Task<IResult> HandleAsync()
|
||||
public async Task<IResult> HandleAsync(IRepository<CatalogType> catalogTypeRepository)
|
||||
{
|
||||
var response = new ListCatalogTypesResponse();
|
||||
|
||||
var items = await _catalogTypeRepository.ListAsync();
|
||||
var items = await catalogTypeRepository.ListAsync();
|
||||
|
||||
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>));
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PublicApiIntegrationTests.CatalogItemEndpoints
|
||||
@@ -45,5 +48,26 @@ namespace PublicApiIntegrationTests.CatalogItemEndpoints
|
||||
|
||||
Assert.AreEqual(totalExpected, model2.CatalogItems.Count());
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("catalog-items")]
|
||||
[DataRow("catalog-brands")]
|
||||
[DataRow("catalog-types")]
|
||||
[DataRow("catalog-items/1")]
|
||||
public async Task SuccessFullMutipleParallelCall(string endpointName)
|
||||
{
|
||||
var client = ProgramTest.NewClient;
|
||||
var tasks = new List<Task<HttpResponseMessage>>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var task = client.GetAsync($"/api/{endpointName}");
|
||||
tasks.Add(task);
|
||||
}
|
||||
await Task.WhenAll(tasks.ToList());
|
||||
var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK);
|
||||
|
||||
Assert.AreEqual(0, totalKO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user