Feature/migrate to minimal api (#662)
* migrate from classic controller to minimal api * fix all PublicApi integration test * update all nuget package add forget project * fix pay now * Adapt readme use in memory database * undo AuthenticateEndpoint to use EndpointBaseAsync * Update README.md Co-authored-by: Steve Smith <steve@kentsmiths.com> Co-authored-by: Steve Smith <steve@kentsmiths.com>
This commit is contained in:
15
README.md
15
README.md
@@ -55,18 +55,13 @@ You can also run the samples in Docker (see below).
|
|||||||
|
|
||||||
### Configuring the sample to use SQL Server
|
### Configuring the sample to use SQL Server
|
||||||
|
|
||||||
1. Update `Startup.cs`'s `ConfigureDevelopmentServices` method as follows:
|
1. By default, the project uses a real database. If you want an in memory database, you can add in `appsettings.json`
|
||||||
|
|
||||||
```csharp
|
```json
|
||||||
public void ConfigureDevelopmentServices(IServiceCollection services)
|
{
|
||||||
{
|
"UseOnlyInMemoryDatabase": true
|
||||||
// use in-memory database
|
}
|
||||||
//ConfigureTestingServices(services);
|
|
||||||
|
|
||||||
// use real database
|
|
||||||
ConfigureProductionServices(services);
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Ensure your connection strings in `appsettings.json` point to a local SQL Server instance.
|
1. Ensure your connection strings in `appsettings.json` point to a local SQL Server instance.
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorAdmin", "src\BlazorAd
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorShared", "src\BlazorShared\BlazorShared.csproj", "{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorShared", "src\BlazorShared\BlazorShared.csproj", "{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApiIntegrationTests", "tests\PublicApiIntegrationTests\PublicApiIntegrationTests.csproj", "{D53EF010-8F8C-4337-A059-456E19D8AE63}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -84,6 +86,10 @@ Global
|
|||||||
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D53EF010-8F8C-4337-A059-456E19D8AE63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D53EF010-8F8C-4337-A059-456E19D8AE63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D53EF010-8F8C-4337-A059-456E19D8AE63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D53EF010-8F8C-4337-A059-456E19D8AE63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -98,6 +104,7 @@ Global
|
|||||||
{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
{B5E4F33C-4667-4A55-AF6A-740F84C4CF3A} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
||||||
{71368733-80A4-4869-B215-3A7001878577} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
{71368733-80A4-4869-B215-3A7001878577} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
||||||
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
{715CF7AF-A1EE-40A6-94A0-8DA3F3B2CAE9} = {419A6ACE-0419-4315-A6FB-B0E63D39432E}
|
||||||
|
{D53EF010-8F8C-4337-A059-456E19D8AE63} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {49813262-5DA3-4D61-ABD3-493C74CE8C2B}
|
SolutionGuid = {49813262-5DA3-4D61-ABD3-493C74CE8C2B}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<PackageReference Include="Ardalis.Specification" Version="5.2.0" />
|
<PackageReference Include="Ardalis.Specification" Version="5.2.0" />
|
||||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.0" />
|
<PackageReference Include="System.Text.Json" Version="6.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.1.5" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.1.5" />
|
||||||
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
|
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
|
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
|
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
|
||||||
<PackageReference Include="FluentValidation" Version="10.3.5" />
|
<PackageReference Include="FluentValidation" Version="10.3.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
40
src/Infrastructure/Dependencies.cs
Normal file
40
src/Infrastructure/Dependencies.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.eShopWeb.Infrastructure.Data;
|
||||||
|
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Infrastructure;
|
||||||
|
|
||||||
|
public static class Dependencies
|
||||||
|
{
|
||||||
|
public static void ConfigureServices(IConfiguration configuration, IServiceCollection services)
|
||||||
|
{
|
||||||
|
var useOnlyInMemoryDatabase = false;
|
||||||
|
if (configuration["UseOnlyInMemoryDatabase"] != null)
|
||||||
|
{
|
||||||
|
useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useOnlyInMemoryDatabase)
|
||||||
|
{
|
||||||
|
services.AddDbContext<CatalogContext>(c =>
|
||||||
|
c.UseInMemoryDatabase("Catalog"));
|
||||||
|
|
||||||
|
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||||
|
options.UseInMemoryDatabase("Identity"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use real database
|
||||||
|
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||||
|
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
||||||
|
services.AddDbContext<CatalogContext>(c =>
|
||||||
|
c.UseSqlServer(configuration.GetConnectionString("CatalogConnection")));
|
||||||
|
|
||||||
|
// Add Identity DbContext
|
||||||
|
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||||
|
options.UseSqlServer(configuration.GetConnectionString("IdentityConnection")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="5.2.0" />
|
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="5.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,14 +9,17 @@ using Swashbuckle.AspNetCore.Annotations;
|
|||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
||||||
|
|
||||||
public class Authenticate : EndpointBaseAsync
|
/// <summary>
|
||||||
|
/// Authenticates a user
|
||||||
|
/// </summary>
|
||||||
|
public class AuthenticateEndpoint : EndpointBaseAsync
|
||||||
.WithRequest<AuthenticateRequest>
|
.WithRequest<AuthenticateRequest>
|
||||||
.WithActionResult<AuthenticateResponse>
|
.WithActionResult<AuthenticateResponse>
|
||||||
{
|
{
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
private readonly ITokenClaimsService _tokenClaimsService;
|
private readonly ITokenClaimsService _tokenClaimsService;
|
||||||
|
|
||||||
public Authenticate(SignInManager<ApplicationUser> signInManager,
|
public AuthenticateEndpoint(SignInManager<ApplicationUser> signInManager,
|
||||||
ITokenClaimsService tokenClaimsService)
|
ITokenClaimsService tokenClaimsService)
|
||||||
{
|
{
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
@@ -30,7 +33,7 @@ public class Authenticate : EndpointBaseAsync
|
|||||||
OperationId = "auth.authenticate",
|
OperationId = "auth.authenticate",
|
||||||
Tags = new[] { "AuthEndpoints" })
|
Tags = new[] { "AuthEndpoints" })
|
||||||
]
|
]
|
||||||
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request, CancellationToken cancellationToken)
|
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var response = new AuthenticateResponse(request.CorrelationId());
|
var response = new AuthenticateResponse(request.CorrelationId());
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Catalog Brands
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogBrandListEndpoint : IEndpoint<IResult>
|
||||||
|
{
|
||||||
|
private IRepository<CatalogBrand> _catalogBrandRepository;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public CatalogBrandListEndpoint(IMapper mapper)
|
||||||
|
{
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
app.MapGet("api/catalog-brands",
|
||||||
|
async (IRepository<CatalogBrand> catalogBrandRepository) =>
|
||||||
|
{
|
||||||
|
_catalogBrandRepository = catalogBrandRepository;
|
||||||
|
return await HandleAsync();
|
||||||
|
})
|
||||||
|
.Produces<ListCatalogBrandsResponse>()
|
||||||
|
.WithTags("CatalogBrandEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync()
|
||||||
|
{
|
||||||
|
var response = new ListCatalogBrandsResponse();
|
||||||
|
|
||||||
|
var items = await _catalogBrandRepository.ListAsync();
|
||||||
|
|
||||||
|
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>));
|
||||||
|
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using AutoMapper;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;
|
|
||||||
|
|
||||||
public class List : EndpointBaseAsync
|
|
||||||
.WithoutRequest
|
|
||||||
.WithActionResult<ListCatalogBrandsResponse>
|
|
||||||
{
|
|
||||||
private readonly IRepository<CatalogBrand> _catalogBrandRepository;
|
|
||||||
private readonly IMapper _mapper;
|
|
||||||
|
|
||||||
public List(IRepository<CatalogBrand> catalogBrandRepository,
|
|
||||||
IMapper mapper)
|
|
||||||
{
|
|
||||||
_catalogBrandRepository = catalogBrandRepository;
|
|
||||||
_mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("api/catalog-brands")]
|
|
||||||
[SwaggerOperation(
|
|
||||||
Summary = "List Catalog Brands",
|
|
||||||
Description = "List Catalog Brands",
|
|
||||||
OperationId = "catalog-brands.List",
|
|
||||||
Tags = new[] { "CatalogBrandEndpoints" })
|
|
||||||
]
|
|
||||||
public override async Task<ActionResult<ListCatalogBrandsResponse>> HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var response = new ListCatalogBrandsResponse();
|
|
||||||
|
|
||||||
var items = await _catalogBrandRepository.ListAsync(cancellationToken);
|
|
||||||
|
|
||||||
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>));
|
|
||||||
|
|
||||||
return Ok(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
public class GetByIdCatalogItemRequest : BaseRequest
|
||||||
|
{
|
||||||
|
public int CatalogItemId { get; init; }
|
||||||
|
|
||||||
|
public GetByIdCatalogItemRequest(int catalogItemId)
|
||||||
|
{
|
||||||
|
CatalogItemId = catalogItemId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a Catalog Item by Id
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemRequest>
|
||||||
|
{
|
||||||
|
private IRepository<CatalogItem> _itemRepository;
|
||||||
|
private readonly IUriComposer _uriComposer;
|
||||||
|
|
||||||
|
public CatalogItemGetByIdEndpoint(IUriComposer uriComposer)
|
||||||
|
{
|
||||||
|
_uriComposer = uriComposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
app.MapGet("api/catalog-items/{catalogItemId}",
|
||||||
|
async (int catalogItemId, IRepository<CatalogItem> itemRepository) =>
|
||||||
|
{
|
||||||
|
_itemRepository = itemRepository;
|
||||||
|
return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId));
|
||||||
|
})
|
||||||
|
.Produces<GetByIdCatalogItemResponse>()
|
||||||
|
.WithTags("CatalogItemEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync(GetByIdCatalogItemRequest request)
|
||||||
|
{
|
||||||
|
var response = new GetByIdCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
|
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||||
|
if (item is null)
|
||||||
|
return Results.NotFound();
|
||||||
|
|
||||||
|
response.CatalogItem = new CatalogItemDto
|
||||||
|
{
|
||||||
|
Id = item.Id,
|
||||||
|
CatalogBrandId = item.CatalogBrandId,
|
||||||
|
CatalogTypeId = item.CatalogTypeId,
|
||||||
|
Description = item.Description,
|
||||||
|
Name = item.Name,
|
||||||
|
PictureUri = _uriComposer.ComposePicUri(item.PictureUri),
|
||||||
|
Price = item.Price
|
||||||
|
};
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
public class ListPagedCatalogItemRequest : BaseRequest
|
||||||
|
{
|
||||||
|
public int? PageSize { get; init; }
|
||||||
|
public int? PageIndex { get; init; }
|
||||||
|
public int? CatalogBrandId { get; init; }
|
||||||
|
public int? CatalogTypeId { get; init; }
|
||||||
|
|
||||||
|
public ListPagedCatalogItemRequest(int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId)
|
||||||
|
{
|
||||||
|
PageSize = pageSize ?? 0;
|
||||||
|
PageIndex = pageIndex ?? 0;
|
||||||
|
CatalogBrandId = catalogBrandId;
|
||||||
|
CatalogTypeId = catalogTypeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +1,58 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
public class ListPaged : EndpointBaseAsync
|
/// <summary>
|
||||||
.WithRequest<ListPagedCatalogItemRequest>
|
/// List Catalog Items (paged)
|
||||||
.WithActionResult<ListPagedCatalogItemResponse>
|
/// </summary>
|
||||||
|
public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogItemRequest>
|
||||||
{
|
{
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
private IRepository<CatalogItem> _itemRepository;
|
||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
public ListPaged(IRepository<CatalogItem> itemRepository,
|
public CatalogItemListPagedEndpoint(IUriComposer uriComposer, IMapper mapper)
|
||||||
IUriComposer uriComposer,
|
|
||||||
IMapper mapper)
|
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("api/catalog-items")]
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
[SwaggerOperation(
|
{
|
||||||
Summary = "List Catalog Items (paged)",
|
app.MapGet("api/catalog-items",
|
||||||
Description = "List Catalog Items (paged)",
|
async (int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId, IRepository<CatalogItem> itemRepository) =>
|
||||||
OperationId = "catalog-items.ListPaged",
|
{
|
||||||
Tags = new[] { "CatalogItemEndpoints" })
|
_itemRepository = itemRepository;
|
||||||
]
|
return await HandleAsync(new ListPagedCatalogItemRequest(pageSize, pageIndex, catalogBrandId, catalogTypeId));
|
||||||
public override async Task<ActionResult<ListPagedCatalogItemResponse>> HandleAsync([FromQuery] ListPagedCatalogItemRequest request, CancellationToken cancellationToken)
|
})
|
||||||
|
.Produces<ListPagedCatalogItemResponse>()
|
||||||
|
.WithTags("CatalogItemEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync(ListPagedCatalogItemRequest request)
|
||||||
{
|
{
|
||||||
var response = new ListPagedCatalogItemResponse(request.CorrelationId());
|
var response = new ListPagedCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId);
|
var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId);
|
||||||
int totalItems = await _itemRepository.CountAsync(filterSpec, cancellationToken);
|
int totalItems = await _itemRepository.CountAsync(filterSpec);
|
||||||
|
|
||||||
var pagedSpec = new CatalogFilterPaginatedSpecification(
|
var pagedSpec = new CatalogFilterPaginatedSpecification(
|
||||||
skip: request.PageIndex * request.PageSize,
|
skip: request.PageIndex.Value * request.PageSize.Value,
|
||||||
take: request.PageSize,
|
take: request.PageSize.Value,
|
||||||
brandId: request.CatalogBrandId,
|
brandId: request.CatalogBrandId,
|
||||||
typeId: request.CatalogTypeId);
|
typeId: request.CatalogTypeId);
|
||||||
|
|
||||||
var items = await _itemRepository.ListAsync(pagedSpec, cancellationToken);
|
var items = await _itemRepository.ListAsync(pagedSpec);
|
||||||
|
|
||||||
response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>));
|
response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>));
|
||||||
foreach (CatalogItemDto item in response.CatalogItems)
|
foreach (CatalogItemDto item in response.CatalogItems)
|
||||||
@@ -59,13 +62,13 @@ public class ListPaged : EndpointBaseAsync
|
|||||||
|
|
||||||
if (request.PageSize > 0)
|
if (request.PageSize > 0)
|
||||||
{
|
{
|
||||||
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString());
|
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize.Value).ToString());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
response.PageCount = totalItems > 0 ? 1 : 0;
|
response.PageCount = totalItems > 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(response);
|
return Results.Ok(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,56 @@
|
|||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
|
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
/// <summary>
|
||||||
public class Create : EndpointBaseAsync
|
/// Creates a new Catalog Item
|
||||||
.WithRequest<CreateCatalogItemRequest>
|
/// </summary>
|
||||||
.WithActionResult<CreateCatalogItemResponse>
|
public class CreateCatalogItemEndpoint : IEndpoint<IResult, CreateCatalogItemRequest>
|
||||||
{
|
{
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
private IRepository<CatalogItem> _itemRepository;
|
||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
|
|
||||||
public Create(IRepository<CatalogItem> itemRepository,
|
public CreateCatalogItemEndpoint(IUriComposer uriComposer)
|
||||||
IUriComposer uriComposer)
|
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("api/catalog-items")]
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
[SwaggerOperation(
|
{
|
||||||
Summary = "Creates a new Catalog Item",
|
app.MapPost("api/catalog-items",
|
||||||
Description = "Creates a new Catalog Item",
|
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async
|
||||||
OperationId = "catalog-items.create",
|
(CreateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>
|
||||||
Tags = new[] { "CatalogItemEndpoints" })
|
{
|
||||||
]
|
_itemRepository = itemRepository;
|
||||||
public override async Task<ActionResult<CreateCatalogItemResponse>> HandleAsync(CreateCatalogItemRequest request, CancellationToken cancellationToken)
|
return await HandleAsync(request);
|
||||||
|
})
|
||||||
|
.Produces<CreateCatalogItemResponse>()
|
||||||
|
.WithTags("CatalogItemEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync(CreateCatalogItemRequest request)
|
||||||
{
|
{
|
||||||
var response = new CreateCatalogItemResponse(request.CorrelationId());
|
var response = new CreateCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
var catalogItemNameSpecification = new CatalogItemNameSpecification(request.Name);
|
var catalogItemNameSpecification = new CatalogItemNameSpecification(request.Name);
|
||||||
var existingCataloogItem = await _itemRepository.CountAsync(catalogItemNameSpecification, cancellationToken);
|
var existingCataloogItem = await _itemRepository.CountAsync(catalogItemNameSpecification);
|
||||||
if (existingCataloogItem > 0)
|
if (existingCataloogItem > 0)
|
||||||
{
|
{
|
||||||
throw new DuplicateException($"A catalogItem with name {request.Name} already exists");
|
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);
|
var newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
|
||||||
newItem = await _itemRepository.AddAsync(newItem, cancellationToken);
|
newItem = await _itemRepository.AddAsync(newItem);
|
||||||
|
|
||||||
if (newItem.Id != 0)
|
if (newItem.Id != 0)
|
||||||
{
|
{
|
||||||
@@ -55,7 +59,7 @@ public class Create : EndpointBaseAsync
|
|||||||
// In production, we recommend uploading to a blob storage and deliver the image via CDN after a verification process.
|
// 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");
|
newItem.UpdatePictureUri("eCatalog-item-default.png");
|
||||||
await _itemRepository.UpdateAsync(newItem, cancellationToken);
|
await _itemRepository.UpdateAsync(newItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
var dto = new CatalogItemDto
|
||||||
@@ -69,8 +73,6 @@ public class Create : EndpointBaseAsync
|
|||||||
Price = newItem.Price
|
Price = newItem.Price
|
||||||
};
|
};
|
||||||
response.CatalogItem = dto;
|
response.CatalogItem = dto;
|
||||||
return response;
|
return Results.Created($"api/catalog-items/{dto.Id}", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
public class DeleteCatalogItemRequest : BaseRequest
|
|
||||||
{
|
|
||||||
//[FromRoute]
|
|
||||||
public int CatalogItemId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
|
||||||
public class Delete : EndpointBaseAsync
|
|
||||||
.WithRequest<DeleteCatalogItemRequest>
|
|
||||||
.WithActionResult<DeleteCatalogItemResponse>
|
|
||||||
{
|
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
|
||||||
|
|
||||||
public Delete(IRepository<CatalogItem> itemRepository)
|
|
||||||
{
|
|
||||||
_itemRepository = itemRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("api/catalog-items/{CatalogItemId}")]
|
|
||||||
[SwaggerOperation(
|
|
||||||
Summary = "Deletes a Catalog Item",
|
|
||||||
Description = "Deletes a Catalog Item",
|
|
||||||
OperationId = "catalog-items.Delete",
|
|
||||||
Tags = new[] { "CatalogItemEndpoints" })
|
|
||||||
]
|
|
||||||
public override async Task<ActionResult<DeleteCatalogItemResponse>> HandleAsync([FromRoute] DeleteCatalogItemRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var response = new DeleteCatalogItemResponse(request.CorrelationId());
|
|
||||||
|
|
||||||
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId, cancellationToken);
|
|
||||||
if (itemToDelete is null) return NotFound();
|
|
||||||
|
|
||||||
await _itemRepository.DeleteAsync(itemToDelete, cancellationToken);
|
|
||||||
|
|
||||||
return Ok(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
public class DeleteCatalogItemRequest : BaseRequest
|
||||||
|
{
|
||||||
|
public int CatalogItemId { get; init; }
|
||||||
|
|
||||||
|
public DeleteCatalogItemRequest(int catalogItemId)
|
||||||
|
{
|
||||||
|
CatalogItemId = catalogItemId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a Catalog Item
|
||||||
|
/// </summary>
|
||||||
|
public class DeleteCatalogItemEndpoint : IEndpoint<IResult, DeleteCatalogItemRequest>
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
})
|
||||||
|
.Produces<DeleteCatalogItemResponse>()
|
||||||
|
.WithTags("CatalogItemEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync(DeleteCatalogItemRequest request)
|
||||||
|
{
|
||||||
|
var response = new DeleteCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
|
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId);
|
||||||
|
if (itemToDelete is null)
|
||||||
|
return Results.NotFound();
|
||||||
|
|
||||||
|
await _itemRepository.DeleteAsync(itemToDelete);
|
||||||
|
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
public class GetByIdCatalogItemRequest : BaseRequest
|
|
||||||
{
|
|
||||||
public int CatalogItemId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
public class GetById : EndpointBaseAsync
|
|
||||||
.WithRequest<GetByIdCatalogItemRequest>
|
|
||||||
.WithActionResult<GetByIdCatalogItemResponse>
|
|
||||||
{
|
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
|
||||||
private readonly IUriComposer _uriComposer;
|
|
||||||
|
|
||||||
public GetById(IRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
|
||||||
{
|
|
||||||
_itemRepository = itemRepository;
|
|
||||||
_uriComposer = uriComposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("api/catalog-items/{CatalogItemId}")]
|
|
||||||
[SwaggerOperation(
|
|
||||||
Summary = "Get a Catalog Item by Id",
|
|
||||||
Description = "Gets a Catalog Item by Id",
|
|
||||||
OperationId = "catalog-items.GetById",
|
|
||||||
Tags = new[] { "CatalogItemEndpoints" })
|
|
||||||
]
|
|
||||||
public override async Task<ActionResult<GetByIdCatalogItemResponse>> HandleAsync([FromRoute] GetByIdCatalogItemRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var response = new GetByIdCatalogItemResponse(request.CorrelationId());
|
|
||||||
|
|
||||||
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId, cancellationToken);
|
|
||||||
if (item is null) return NotFound();
|
|
||||||
|
|
||||||
response.CatalogItem = new CatalogItemDto
|
|
||||||
{
|
|
||||||
Id = item.Id,
|
|
||||||
CatalogBrandId = item.CatalogBrandId,
|
|
||||||
CatalogTypeId = item.CatalogTypeId,
|
|
||||||
Description = item.Description,
|
|
||||||
Name = item.Name,
|
|
||||||
PictureUri = _uriComposer.ComposePicUri(item.PictureUri),
|
|
||||||
Price = item.Price
|
|
||||||
};
|
|
||||||
return Ok(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
public class ListPagedCatalogItemRequest : BaseRequest
|
|
||||||
{
|
|
||||||
public int PageSize { get; set; }
|
|
||||||
public int PageIndex { get; set; }
|
|
||||||
public int? CatalogBrandId { get; set; }
|
|
||||||
public int? CatalogTypeId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
|
|
||||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
|
||||||
public class Update : EndpointBaseAsync
|
|
||||||
.WithRequest<UpdateCatalogItemRequest>
|
|
||||||
.WithActionResult<UpdateCatalogItemResponse>
|
|
||||||
{
|
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
|
||||||
private readonly IUriComposer _uriComposer;
|
|
||||||
|
|
||||||
public Update(IRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
|
||||||
{
|
|
||||||
_itemRepository = itemRepository;
|
|
||||||
_uriComposer = uriComposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("api/catalog-items")]
|
|
||||||
[SwaggerOperation(
|
|
||||||
Summary = "Updates a Catalog Item",
|
|
||||||
Description = "Updates a Catalog Item",
|
|
||||||
OperationId = "catalog-items.update",
|
|
||||||
Tags = new[] { "CatalogItemEndpoints" })
|
|
||||||
]
|
|
||||||
public override async Task<ActionResult<UpdateCatalogItemResponse>> HandleAsync(UpdateCatalogItemRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var response = new UpdateCatalogItemResponse(request.CorrelationId());
|
|
||||||
|
|
||||||
var existingItem = await _itemRepository.GetByIdAsync(request.Id, cancellationToken);
|
|
||||||
|
|
||||||
existingItem.UpdateDetails(request.Name, request.Description, request.Price);
|
|
||||||
existingItem.UpdateBrand(request.CatalogBrandId);
|
|
||||||
existingItem.UpdateType(request.CatalogTypeId);
|
|
||||||
|
|
||||||
await _itemRepository.UpdateAsync(existingItem, cancellationToken);
|
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
|
||||||
{
|
|
||||||
Id = existingItem.Id,
|
|
||||||
CatalogBrandId = existingItem.CatalogBrandId,
|
|
||||||
CatalogTypeId = existingItem.CatalogTypeId,
|
|
||||||
Description = existingItem.Description,
|
|
||||||
Name = existingItem.Name,
|
|
||||||
PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri),
|
|
||||||
Price = existingItem.Price
|
|
||||||
};
|
|
||||||
response.CatalogItem = dto;
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a Catalog Item
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemRequest>
|
||||||
|
{
|
||||||
|
private IRepository<CatalogItem> _itemRepository;
|
||||||
|
private readonly IUriComposer _uriComposer;
|
||||||
|
|
||||||
|
public UpdateCatalogItemEndpoint(IUriComposer uriComposer)
|
||||||
|
{
|
||||||
|
_uriComposer = uriComposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
app.MapPut("api/catalog-items",
|
||||||
|
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async
|
||||||
|
(UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) =>
|
||||||
|
{
|
||||||
|
_itemRepository = itemRepository;
|
||||||
|
return await HandleAsync(request);
|
||||||
|
})
|
||||||
|
.Produces<UpdateCatalogItemResponse>()
|
||||||
|
.WithTags("CatalogItemEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync(UpdateCatalogItemRequest request)
|
||||||
|
{
|
||||||
|
var response = new UpdateCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
|
var existingItem = await _itemRepository.GetByIdAsync(request.Id);
|
||||||
|
|
||||||
|
existingItem.UpdateDetails(request.Name, request.Description, request.Price);
|
||||||
|
existingItem.UpdateBrand(request.CatalogBrandId);
|
||||||
|
existingItem.UpdateType(request.CatalogTypeId);
|
||||||
|
|
||||||
|
await _itemRepository.UpdateAsync(existingItem);
|
||||||
|
|
||||||
|
var dto = new CatalogItemDto
|
||||||
|
{
|
||||||
|
Id = existingItem.Id,
|
||||||
|
CatalogBrandId = existingItem.CatalogBrandId,
|
||||||
|
CatalogTypeId = existingItem.CatalogTypeId,
|
||||||
|
Description = existingItem.Description,
|
||||||
|
Name = existingItem.Name,
|
||||||
|
PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri),
|
||||||
|
Price = existingItem.Price
|
||||||
|
};
|
||||||
|
response.CatalogItem = dto;
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using MinimalApi.Endpoint;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Catalog Types
|
||||||
|
/// </summary>
|
||||||
|
public class CatalogTypeListEndpoint : IEndpoint<IResult>
|
||||||
|
{
|
||||||
|
private IRepository<CatalogType> _catalogTypeRepository;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public CatalogTypeListEndpoint(IMapper mapper)
|
||||||
|
{
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRoute(IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
app.MapGet("api/catalog-types",
|
||||||
|
async (IRepository<CatalogType> catalogTypeRepository) =>
|
||||||
|
{
|
||||||
|
_catalogTypeRepository = catalogTypeRepository;
|
||||||
|
return await HandleAsync();
|
||||||
|
})
|
||||||
|
.Produces<ListCatalogTypesResponse>()
|
||||||
|
.WithTags("CatalogTypeEndpoints");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> HandleAsync()
|
||||||
|
{
|
||||||
|
var response = new ListCatalogTypesResponse();
|
||||||
|
|
||||||
|
var items = await _catalogTypeRepository.ListAsync();
|
||||||
|
|
||||||
|
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>));
|
||||||
|
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.ApiEndpoints;
|
|
||||||
using AutoMapper;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;
|
|
||||||
|
|
||||||
public class List : EndpointBaseAsync
|
|
||||||
.WithoutRequest
|
|
||||||
.WithActionResult<ListCatalogTypesResponse>
|
|
||||||
{
|
|
||||||
private readonly IRepository<CatalogType> _catalogTypeRepository;
|
|
||||||
private readonly IMapper _mapper;
|
|
||||||
|
|
||||||
public List(IRepository<CatalogType> catalogTypeRepository,
|
|
||||||
IMapper mapper)
|
|
||||||
{
|
|
||||||
_catalogTypeRepository = catalogTypeRepository;
|
|
||||||
_mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("api/catalog-types")]
|
|
||||||
[SwaggerOperation(
|
|
||||||
Summary = "List Catalog Types",
|
|
||||||
Description = "List Catalog Types",
|
|
||||||
OperationId = "catalog-types.List",
|
|
||||||
Tags = new[] { "CatalogTypeEndpoints" })
|
|
||||||
]
|
|
||||||
public override async Task<ActionResult<ListCatalogTypesResponse>> HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var response = new ListCatalogTypesResponse();
|
|
||||||
|
|
||||||
var items = await _catalogTypeRepository.ListAsync(cancellationToken);
|
|
||||||
|
|
||||||
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>));
|
|
||||||
|
|
||||||
return Ok(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BlazorShared;
|
using BlazorShared;
|
||||||
using BlazorShared.Models;
|
using BlazorShared.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -9,7 +8,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.eShopWeb;
|
using Microsoft.eShopWeb;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
@@ -25,20 +23,18 @@ using Microsoft.Extensions.Hosting;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
using MinimalApi.Endpoint.Configurations.Extensions;
|
||||||
|
using MinimalApi.Endpoint.Extensions;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddEndpoints();
|
||||||
|
|
||||||
|
//Use to force loading of appsettings.json of test project
|
||||||
|
builder.Configuration.AddConfigurationFile();
|
||||||
builder.Logging.AddConsole();
|
builder.Logging.AddConsole();
|
||||||
|
|
||||||
// use real database
|
Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);
|
||||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
|
||||||
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
|
||||||
builder.Services.AddDbContext<CatalogContext>(c =>
|
|
||||||
c.UseSqlServer(builder.Configuration.GetConnectionString("CatalogConnection")));
|
|
||||||
|
|
||||||
// Add Identity DbContext
|
|
||||||
builder.Services.AddDbContext<AppIdentityDbContext>(options =>
|
|
||||||
options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection")));
|
|
||||||
|
|
||||||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
|
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||||
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
||||||
@@ -92,6 +88,7 @@ builder.Services.AddControllers();
|
|||||||
builder.Services.AddMediatR(typeof(CatalogItem).Assembly);
|
builder.Services.AddMediatR(typeof(CatalogItem).Assembly);
|
||||||
builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly);
|
builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly);
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen(c =>
|
builder.Services.AddSwaggerGen(c =>
|
||||||
{
|
{
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
|
||||||
@@ -182,5 +179,8 @@ using (var scope = app.Services.CreateScope())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.MapEndpoints();
|
||||||
app.Logger.LogInformation("LAUNCHING PublicApi");
|
app.Logger.LogInformation("LAUNCHING PublicApi");
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
public partial class Program { }
|
||||||
|
|||||||
@@ -13,21 +13,22 @@
|
|||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
|
||||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
|
||||||
|
<PackageReference Include="MinimalApi.Endpoint" Version="1.0.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.1" />
|
||||||
|
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ public class BasketItemViewModel
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int CatalogItemId { get; set; }
|
public int CatalogItemId { get; set; }
|
||||||
public string ProductName { get; set; }
|
public string? ProductName { get; set; }
|
||||||
public decimal UnitPrice { get; set; }
|
public decimal UnitPrice { get; set; }
|
||||||
public decimal OldUnitPrice { get; set; }
|
public decimal OldUnitPrice { get; set; }
|
||||||
|
|
||||||
[Range(0, int.MaxValue, ErrorMessage = "Quantity must be bigger than 0")]
|
[Range(0, int.MaxValue, ErrorMessage = "Quantity must be bigger than 0")]
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
public string PictureUrl { get; set; }
|
public string? PictureUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
|||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.eShopWeb;
|
using Microsoft.eShopWeb;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Data;
|
using Microsoft.eShopWeb.Infrastructure.Data;
|
||||||
@@ -22,15 +21,7 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
|
|
||||||
builder.Logging.AddConsole();
|
builder.Logging.AddConsole();
|
||||||
|
|
||||||
// use real database
|
Microsoft.eShopWeb.Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);
|
||||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
|
||||||
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
|
||||||
builder.Services.AddDbContext<CatalogContext>(c =>
|
|
||||||
c.UseSqlServer(builder.Configuration.GetConnectionString("CatalogConnection")));
|
|
||||||
|
|
||||||
// Add Identity DbContext
|
|
||||||
builder.Services.AddDbContext<AppIdentityDbContext>(options =>
|
|
||||||
options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection")));
|
|
||||||
|
|
||||||
builder.Services.AddCookieSettings();
|
builder.Services.AddCookieSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||||
|
|||||||
@@ -20,16 +20,16 @@
|
|||||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
|
||||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" Condition="'$(Configuration)'=='Release'" PrivateAssets="All" />
|
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" Condition="'$(Configuration)'=='Release'" PrivateAssets="All" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.113" />
|
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.113" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RootNamespace>Microsoft.eShopWeb.FunctionalTests</RootNamespace>
|
<RootNamespace>Microsoft.eShopWeb.FunctionalTests</RootNamespace>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -13,14 +15,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Hosting;
|
|||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
namespace Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
||||||
|
|
||||||
public class TestApiApplication : WebApplicationFactory<Authenticate>
|
public class TestApiApplication : WebApplicationFactory<AuthenticateEndpoint>
|
||||||
{
|
{
|
||||||
private readonly string _environment = "Testing";
|
private readonly string _environment = "Testing";
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
using System.Net.Http;
|
//using System.Net.Http;
|
||||||
using System.Text;
|
//using System.Text;
|
||||||
using System.Text.Json;
|
//using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
//using System.Threading.Tasks;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
//using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
//using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
||||||
using Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
//using Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
||||||
using Xunit;
|
//using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
//namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
||||||
|
|
||||||
[Collection("Sequential")]
|
//[Collection("Sequential")]
|
||||||
public class AuthenticateEndpoint : IClassFixture<TestApiApplication>
|
//public class AuthenticateEndpoint : IClassFixture<TestApiApplication>
|
||||||
{
|
//{
|
||||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
// JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
public AuthenticateEndpoint(TestApiApplication factory)
|
// public AuthenticateEndpoint(TestApiApplication factory)
|
||||||
{
|
// {
|
||||||
Client = factory.CreateClient();
|
// Client = factory.CreateClient();
|
||||||
}
|
// }
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
// public HttpClient Client { get; }
|
||||||
|
|
||||||
[Theory]
|
// [Theory]
|
||||||
[InlineData("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
|
// [InlineData("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
|
||||||
[InlineData("demouser@microsoft.com", "badpassword", false)]
|
// [InlineData("demouser@microsoft.com", "badpassword", false)]
|
||||||
[InlineData("baduser@microsoft.com", "badpassword", false)]
|
// [InlineData("baduser@microsoft.com", "badpassword", false)]
|
||||||
public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
|
// public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
|
||||||
{
|
// {
|
||||||
var request = new AuthenticateRequest()
|
// var request = new AuthenticateRequest()
|
||||||
{
|
// {
|
||||||
Username = testUsername,
|
// Username = testUsername,
|
||||||
Password = testPassword
|
// Password = testPassword
|
||||||
};
|
// };
|
||||||
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
// var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||||
var response = await Client.PostAsync("api/authenticate", jsonContent);
|
// var response = await Client.PostAsync("api/authenticate", jsonContent);
|
||||||
response.EnsureSuccessStatusCode();
|
// response.EnsureSuccessStatusCode();
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
// var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
var model = stringResponse.FromJson<AuthenticateResponse>();
|
// var model = stringResponse.FromJson<AuthenticateResponse>();
|
||||||
|
|
||||||
Assert.Equal(expectedResult, model.Result);
|
// Assert.Equal(expectedResult, model.Result);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
|
||||||
using Microsoft.eShopWeb.Web.ViewModels;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
|
||||||
|
|
||||||
[Collection("Sequential")]
|
|
||||||
public class ApiCatalogControllerList : IClassFixture<TestApiApplication>
|
|
||||||
{
|
|
||||||
public ApiCatalogControllerList(TestApiApplication factory)
|
|
||||||
{
|
|
||||||
Client = factory.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsFirst10CatalogItems()
|
|
||||||
{
|
|
||||||
var response = await Client.GetAsync("/api/catalog-items?pageSize=10");
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
var model = stringResponse.FromJson<CatalogIndexViewModel>();
|
|
||||||
|
|
||||||
Assert.Equal(10, model.CatalogItems.Count());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsLast2CatalogItemsGivenPageIndex1()
|
|
||||||
{
|
|
||||||
var response = await Client.GetAsync("/api/catalog-items?pageSize=10&pageIndex=1");
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
var model = stringResponse.FromJson<CatalogIndexViewModel>();
|
|
||||||
|
|
||||||
Assert.Equal(2, model.CatalogItems.Count());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.Web.Api;
|
|
||||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
|
||||||
|
|
||||||
[Collection("Sequential")]
|
|
||||||
public class CreateEndpoint : IClassFixture<TestApiApplication>
|
|
||||||
{
|
|
||||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
|
||||||
private int _testBrandId = 1;
|
|
||||||
private int _testTypeId = 2;
|
|
||||||
private string _testDescription = "test description";
|
|
||||||
private string _testName = "test name";
|
|
||||||
private decimal _testPrice = 1.23m;
|
|
||||||
|
|
||||||
public CreateEndpoint(TestApiApplication factory)
|
|
||||||
{
|
|
||||||
Client = factory.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsNotAuthorizedGivenNormalUserToken()
|
|
||||||
{
|
|
||||||
var jsonContent = GetValidNewItemJson();
|
|
||||||
var token = ApiTokenHelper.GetNormalUserToken();
|
|
||||||
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
||||||
var response = await Client.PostAsync("api/catalog-items", jsonContent);
|
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()
|
|
||||||
{
|
|
||||||
var jsonContent = GetValidNewItemJson();
|
|
||||||
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
|
||||||
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
|
||||||
var response = await Client.PostAsync("api/catalog-items", jsonContent);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
var model = stringResponse.FromJson<CreateCatalogItemResponse>();
|
|
||||||
|
|
||||||
Assert.Equal(_testBrandId, model.CatalogItem.CatalogBrandId);
|
|
||||||
Assert.Equal(_testTypeId, model.CatalogItem.CatalogTypeId);
|
|
||||||
Assert.Equal(_testDescription, model.CatalogItem.Description);
|
|
||||||
Assert.Equal(_testName, model.CatalogItem.Name);
|
|
||||||
Assert.Equal(_testPrice, model.CatalogItem.Price);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringContent GetValidNewItemJson()
|
|
||||||
{
|
|
||||||
var request = new CreateCatalogItemRequest()
|
|
||||||
{
|
|
||||||
CatalogBrandId = _testBrandId,
|
|
||||||
CatalogTypeId = _testTypeId,
|
|
||||||
Description = _testDescription,
|
|
||||||
Name = _testName,
|
|
||||||
Price = _testPrice
|
|
||||||
};
|
|
||||||
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
return jsonContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.Web.Api;
|
|
||||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
|
||||||
|
|
||||||
[Collection("Sequential")]
|
|
||||||
public class DeleteEndpoint : IClassFixture<TestApiApplication>
|
|
||||||
{
|
|
||||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
|
||||||
|
|
||||||
public DeleteEndpoint(TestApiApplication factory)
|
|
||||||
{
|
|
||||||
Client = factory.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsSuccessGivenValidIdAndAdminUserToken()
|
|
||||||
{
|
|
||||||
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
|
||||||
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
|
||||||
var response = await Client.DeleteAsync("api/catalog-items/12");
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
var model = stringResponse.FromJson<DeleteCatalogItemResponse>();
|
|
||||||
|
|
||||||
Assert.Equal("Deleted", model.Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()
|
|
||||||
{
|
|
||||||
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
|
||||||
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
|
||||||
var response = await Client.DeleteAsync("api/catalog-items/0");
|
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
|
||||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
|
||||||
|
|
||||||
[Collection("Sequential")]
|
|
||||||
public class GetByIdEndpoint : IClassFixture<TestApiApplication>
|
|
||||||
{
|
|
||||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
|
||||||
|
|
||||||
public GetByIdEndpoint(TestApiApplication factory)
|
|
||||||
{
|
|
||||||
Client = factory.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsItemGivenValidId()
|
|
||||||
{
|
|
||||||
var response = await Client.GetAsync("api/catalog-items/5");
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
var model = stringResponse.FromJson<GetByIdCatalogItemResponse>();
|
|
||||||
|
|
||||||
Assert.Equal(5, model.CatalogItem.Id);
|
|
||||||
Assert.Equal("Roslyn Red Sheet", model.CatalogItem.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReturnsNotFoundGivenInvalidId()
|
|
||||||
{
|
|
||||||
var response = await Client.GetAsync("api/catalog-items/0");
|
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|||||||
50
tests/PublicApiIntegrationTests/ApiTokenHelper.cs
Normal file
50
tests/PublicApiIntegrationTests/ApiTokenHelper.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests
|
||||||
|
{
|
||||||
|
public class ApiTokenHelper
|
||||||
|
{
|
||||||
|
public static string GetAdminUserToken()
|
||||||
|
{
|
||||||
|
string userName = "admin@microsoft.com";
|
||||||
|
string[] roles = { "Administrators" };
|
||||||
|
|
||||||
|
return CreateToken(userName, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetNormalUserToken()
|
||||||
|
{
|
||||||
|
string userName = "demouser@microsoft.com";
|
||||||
|
string[] roles = { };
|
||||||
|
|
||||||
|
return CreateToken(userName, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateToken(string userName, string[] roles)
|
||||||
|
{
|
||||||
|
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };
|
||||||
|
|
||||||
|
foreach (var role in roles)
|
||||||
|
{
|
||||||
|
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(claims.ToArray()),
|
||||||
|
Expires = DateTime.UtcNow.AddHours(1),
|
||||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||||
|
};
|
||||||
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
|
return tokenHandler.WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.eShopWeb;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||||
|
using Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests.AuthEndpoints
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class AuthenticateEndpoint
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
|
||||||
|
[DataRow("demouser@microsoft.com", "badpassword", false)]
|
||||||
|
[DataRow("baduser@microsoft.com", "badpassword", false)]
|
||||||
|
public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
|
||||||
|
{
|
||||||
|
var request = new AuthenticateRequest()
|
||||||
|
{
|
||||||
|
Username = testUsername,
|
||||||
|
Password = testPassword
|
||||||
|
};
|
||||||
|
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||||
|
var response = await ProgramTest.NewClient.PostAsync("api/authenticate", jsonContent);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<AuthenticateResponse>();
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedResult, model.Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.eShopWeb;
|
||||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CatalogItemGetByIdEndpointTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsItemGivenValidId()
|
||||||
|
{
|
||||||
|
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<GetByIdCatalogItemResponse>();
|
||||||
|
|
||||||
|
Assert.AreEqual(5, model.CatalogItem.Id);
|
||||||
|
Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsNotFoundGivenInvalidId()
|
||||||
|
{
|
||||||
|
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0");
|
||||||
|
|
||||||
|
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.eShopWeb;
|
||||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||||
|
using Microsoft.eShopWeb.Web.ViewModels;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CatalogItemListPagedEndpoint
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsFirst10CatalogItems()
|
||||||
|
{
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
var response = await client.GetAsync("/api/catalog-items?pageSize=10");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<CatalogIndexViewModel>();
|
||||||
|
|
||||||
|
Assert.AreEqual(10, model.CatalogItems.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsCorrectCatalogItemsGivenPageIndex1()
|
||||||
|
{
|
||||||
|
|
||||||
|
var pageSize = 10;
|
||||||
|
var pageIndex = 1;
|
||||||
|
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
var response = await client.GetAsync($"/api/catalog-items");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<ListPagedCatalogItemResponse>();
|
||||||
|
var totalItem = model.CatalogItems.Count();
|
||||||
|
|
||||||
|
var response2 = await client.GetAsync($"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse2 = await response2.Content.ReadAsStringAsync();
|
||||||
|
var model2 = stringResponse2.FromJson<ListPagedCatalogItemResponse>();
|
||||||
|
|
||||||
|
var totalExpected = totalItem - (pageSize * pageIndex);
|
||||||
|
|
||||||
|
Assert.AreEqual(totalExpected, model2.CatalogItems.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using BlazorShared.Models;
|
||||||
|
using Microsoft.eShopWeb;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests.AuthEndpoints
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CreateCatalogItemEndpointTest
|
||||||
|
{
|
||||||
|
private int _testBrandId = 1;
|
||||||
|
private int _testTypeId = 2;
|
||||||
|
private string _testDescription = "test description";
|
||||||
|
private string _testName = "test name";
|
||||||
|
private decimal _testPrice = 1.23m;
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsNotAuthorizedGivenNormalUserToken()
|
||||||
|
{
|
||||||
|
var jsonContent = GetValidNewItemJson();
|
||||||
|
var token = ApiTokenHelper.GetNormalUserToken();
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
var response = await client.PostAsync("api/catalog-items", jsonContent);
|
||||||
|
|
||||||
|
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()
|
||||||
|
{
|
||||||
|
var jsonContent = GetValidNewItemJson();
|
||||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
||||||
|
var response = await client.PostAsync("api/catalog-items", jsonContent);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<CreateCatalogItemResponse>();
|
||||||
|
|
||||||
|
Assert.AreEqual(_testBrandId, model.CatalogItem.CatalogBrandId);
|
||||||
|
Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId);
|
||||||
|
Assert.AreEqual(_testDescription, model.CatalogItem.Description);
|
||||||
|
Assert.AreEqual(_testName, model.CatalogItem.Name);
|
||||||
|
Assert.AreEqual(_testPrice, model.CatalogItem.Price);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringContent GetValidNewItemJson()
|
||||||
|
{
|
||||||
|
var request = new CreateCatalogItemRequest()
|
||||||
|
{
|
||||||
|
CatalogBrandId = _testBrandId,
|
||||||
|
CatalogTypeId = _testTypeId,
|
||||||
|
Description = _testDescription,
|
||||||
|
Name = _testName,
|
||||||
|
Price = _testPrice
|
||||||
|
};
|
||||||
|
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
return jsonContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using BlazorShared.Models;
|
||||||
|
using Microsoft.eShopWeb;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class DeleteCatalogItemEndpointTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsSuccessGivenValidIdAndAdminUserToken()
|
||||||
|
{
|
||||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
||||||
|
var response = await client.DeleteAsync("api/catalog-items/12");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
var model = stringResponse.FromJson<DeleteCatalogItemResponse>();
|
||||||
|
|
||||||
|
Assert.AreEqual("Deleted", model.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()
|
||||||
|
{
|
||||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken();
|
||||||
|
var client = ProgramTest.NewClient;
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
|
||||||
|
var response = await client.DeleteAsync("api/catalog-items/0");
|
||||||
|
|
||||||
|
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tests/PublicApiIntegrationTests/ProgramTest.cs
Normal file
27
tests/PublicApiIntegrationTests/ProgramTest.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace PublicApiIntegrationTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class ProgramTest
|
||||||
|
{
|
||||||
|
private static WebApplicationFactory<Program> _application;
|
||||||
|
|
||||||
|
public static HttpClient NewClient
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _application.CreateClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AssemblyInitialize]
|
||||||
|
public static void AssemblyInitialize(TestContext _)
|
||||||
|
{
|
||||||
|
_application = new WebApplicationFactory<Program>();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="appsettings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\PublicApi\PublicApi.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Web\Web.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
3
tests/PublicApiIntegrationTests/appsettings.json
Normal file
3
tests/PublicApiIntegrationTests/appsettings.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"UseOnlyInMemoryDatabase": true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user