Add Blazor WebAssembly Admin Page (#426)
* Added Blazor Client Configured PublicAPI CORS to allow traffic from client * Make admin page home page; remove extra pages Add CatalogType list endpoint * Wired up Types and Brands in the API and the admin list page * Adding a custom HttpClient to talk securely to API * Ardalis/blazor (#419) * Login added * AuthService will handel http request secure and not secure. * Logout added * CatalogBrandService in it is own service * Get token from localstorage when refresh. * used GetAsync * Fixed Login and Logout switch. * CatalogItemService added * CatalogTypeService added & Auth for CatalogType. using not used removed. * Made BlazorComponent and BlazorLayoutComponent for refresh. Index now small enough to be in one file. * Removed the service from program main and use lazy singleton. * used OnInitialized * Refactoring and detecting login status in login.razor * Refactoring login to redirect if user is already logged in * Blazor login with MVC (#420) * Blazor login with MVC * return back the PasswordSignInAsync in Login page * CRUD added (#422) * CRUD added * Unit Test changed to meet new redirect /admin * CreateCatalogItemRequest added. * Action caption added. * Validation added for name and price. * Updated port of api Redirect to returnUrl from login * Add username to /admin; link to my profile * Working on authorization of /admin * Working on custom auth locking down /admin page * Microsoft authorize working.Login.razor removed.Login from SignInMana… (#425) * Microsoft authorize working.Login.razor removed.Login from SignInManager and create token from it.unit test fixed. * GetTokenFromController function used in CustomAuthStateProvider * Cleaned up button styles Refactored to use codebehind for List component Updated Not Authorized view Co-authored-by: Shady Nagy <shadynagi@gmail.com>
This commit is contained in:
@@ -11,8 +11,11 @@ namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
|
||||
public AuthenticateResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Result { get; set; }
|
||||
public string Token { get; set; }
|
||||
public bool Result { get; set; } = false;
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public bool IsLockedOut { get; set; } = false;
|
||||
public bool IsNotAllowed { get; set; } = false;
|
||||
public bool RequiresTwoFactor { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
23
src/PublicApi/AuthEndpoints/Authenticate.ClaimValue.cs
Normal file
23
src/PublicApi/AuthEndpoints/Authenticate.ClaimValue.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
|
||||
{
|
||||
public class ClaimValue
|
||||
{
|
||||
public ClaimValue()
|
||||
{
|
||||
}
|
||||
|
||||
public ClaimValue(string type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
16
src/PublicApi/AuthEndpoints/Authenticate.UserInfo.cs
Normal file
16
src/PublicApi/AuthEndpoints/Authenticate.UserInfo.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
|
||||
{
|
||||
public class UserInfo
|
||||
{
|
||||
public static readonly UserInfo Anonymous = new UserInfo();
|
||||
public bool IsAuthenticated { get; set; }
|
||||
public string NameClaimType { get; set; }
|
||||
public string RoleClaimType { get; set; }
|
||||
public IEnumerable<ClaimValue> Claims { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,16 @@ namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
|
||||
{
|
||||
var response = new AuthenticateResponse(request.CorrelationId());
|
||||
|
||||
// This doesn't count login failures towards account lockout
|
||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||
//var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
|
||||
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, true);
|
||||
|
||||
response.Result = result.Succeeded;
|
||||
|
||||
response.IsLockedOut = result.IsLockedOut;
|
||||
response.IsNotAllowed = result.IsNotAllowed;
|
||||
response.RequiresTwoFactor = result.RequiresTwoFactor;
|
||||
response.Username = request.Username;
|
||||
response.Token = await _tokenClaimsService.GetTokenAsync(request.Username);
|
||||
|
||||
return response;
|
||||
|
||||
8
src/PublicApi/CatalogBrandEndpoints/CatalogBrandDto.cs
Normal file
8
src/PublicApi/CatalogBrandEndpoints/CatalogBrandDto.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints
|
||||
{
|
||||
public class CatalogBrandDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints
|
||||
{
|
||||
public class ListCatalogBrandsResponse : BaseResponse
|
||||
{
|
||||
public ListCatalogBrandsResponse(Guid correlationId) : base(correlationId)
|
||||
{
|
||||
}
|
||||
|
||||
public ListCatalogBrandsResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public List<CatalogBrandDto> CatalogBrands { get; set; } = new List<CatalogBrandDto>();
|
||||
}
|
||||
}
|
||||
42
src/PublicApi/CatalogBrandEndpoints/List.cs
Normal file
42
src/PublicApi/CatalogBrandEndpoints/List.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Ardalis.ApiEndpoints;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints
|
||||
{
|
||||
public class List : BaseAsyncEndpoint<ListCatalogBrandsResponse>
|
||||
{
|
||||
private readonly IAsyncRepository<CatalogBrand> _catalogBrandRepository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public List(IAsyncRepository<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()
|
||||
{
|
||||
var response = new ListCatalogBrandsResponse();
|
||||
|
||||
var items = await _catalogBrandRepository.ListAllAsync();
|
||||
|
||||
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>));
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,12 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
public class Create : BaseAsyncEndpoint<CreateCatalogItemRequest, CreateCatalogItemResponse>
|
||||
{
|
||||
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public Create(IAsyncRepository<CatalogItem> itemRepository)
|
||||
public Create(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
_uriComposer = uriComposer;
|
||||
}
|
||||
|
||||
[HttpPost("api/catalog-items")]
|
||||
@@ -43,7 +45,7 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
CatalogTypeId = newItem.CatalogTypeId,
|
||||
Description = newItem.Description,
|
||||
Name = newItem.Name,
|
||||
PictureUri = newItem.PictureUri,
|
||||
PictureUri = _uriComposer.ComposePicUri(newItem.PictureUri),
|
||||
Price = newItem.Price
|
||||
};
|
||||
response.CatalogItem = dto;
|
||||
|
||||
@@ -10,10 +10,12 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
public class GetById : BaseAsyncEndpoint<GetByIdCatalogItemRequest, GetByIdCatalogItemResponse>
|
||||
{
|
||||
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public GetById(IAsyncRepository<CatalogItem> itemRepository)
|
||||
public GetById(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
_uriComposer = uriComposer;
|
||||
}
|
||||
|
||||
[HttpGet("api/catalog-items/{CatalogItemId}")]
|
||||
@@ -23,7 +25,7 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
OperationId = "catalog-items.GetById",
|
||||
Tags = new[] { "CatalogItemEndpoints" })
|
||||
]
|
||||
public override async Task<ActionResult<GetByIdCatalogItemResponse>> HandleAsync([FromRoute]GetByIdCatalogItemRequest request)
|
||||
public override async Task<ActionResult<GetByIdCatalogItemResponse>> HandleAsync([FromRoute] GetByIdCatalogItemRequest request)
|
||||
{
|
||||
var response = new GetByIdCatalogItemResponse(request.CorrelationId());
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
CatalogTypeId = item.CatalogTypeId,
|
||||
Description = item.Description,
|
||||
Name = item.Name,
|
||||
PictureUri = item.PictureUri,
|
||||
PictureUri = _uriComposer.ComposePicUri(item.PictureUri),
|
||||
Price = item.Price
|
||||
};
|
||||
return Ok(response);
|
||||
|
||||
@@ -16,10 +16,13 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
public class Update : BaseAsyncEndpoint<UpdateCatalogItemRequest, UpdateCatalogItemResponse>
|
||||
{
|
||||
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public Update(IAsyncRepository<CatalogItem> itemRepository)
|
||||
public Update(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
||||
{
|
||||
_itemRepository = itemRepository;
|
||||
_uriComposer = uriComposer;
|
||||
|
||||
}
|
||||
|
||||
[HttpPut("api/catalog-items")]
|
||||
@@ -48,7 +51,7 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||
CatalogTypeId = existingItem.CatalogTypeId,
|
||||
Description = existingItem.Description,
|
||||
Name = existingItem.Name,
|
||||
PictureUri = existingItem.PictureUri,
|
||||
PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri),
|
||||
Price = existingItem.Price
|
||||
};
|
||||
response.CatalogItem = dto;
|
||||
|
||||
8
src/PublicApi/CatalogTypeEndpoints/CatalogTypeDto.cs
Normal file
8
src/PublicApi/CatalogTypeEndpoints/CatalogTypeDto.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints
|
||||
{
|
||||
public class CatalogTypeDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints
|
||||
{
|
||||
public class ListCatalogTypesResponse : BaseResponse
|
||||
{
|
||||
public ListCatalogTypesResponse(Guid correlationId) : base(correlationId)
|
||||
{
|
||||
}
|
||||
|
||||
public ListCatalogTypesResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public List<CatalogTypeDto> CatalogTypes { get; set; } = new List<CatalogTypeDto>();
|
||||
}
|
||||
}
|
||||
42
src/PublicApi/CatalogTypeEndpoints/List.cs
Normal file
42
src/PublicApi/CatalogTypeEndpoints/List.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Ardalis.ApiEndpoints;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints
|
||||
{
|
||||
public class List : BaseAsyncEndpoint<ListCatalogTypesResponse>
|
||||
{
|
||||
private readonly IAsyncRepository<CatalogType> _catalogTypeRepository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public List(IAsyncRepository<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()
|
||||
{
|
||||
var response = new ListCatalogTypesResponse();
|
||||
|
||||
var items = await _catalogTypeRepository.ListAllAsync();
|
||||
|
||||
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>));
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints;
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints;
|
||||
|
||||
namespace Microsoft.eShopWeb.PublicApi
|
||||
{
|
||||
@@ -9,6 +11,10 @@ namespace Microsoft.eShopWeb.PublicApi
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<CatalogItem, CatalogItemDto>();
|
||||
CreateMap<CatalogType, CatalogTypeDto>()
|
||||
.ForMember(dto => dto.Name, options => options.MapFrom(src => src.Type));
|
||||
CreateMap<CatalogBrand, CatalogBrandDto>()
|
||||
.ForMember(dto => dto.Name, options => options.MapFrom(src => src.Brand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Microsoft.eShopWeb.PublicApi
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private const string CORS_POLICY = "CorsPolicy";
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
@@ -120,7 +121,19 @@ namespace Microsoft.eShopWeb.PublicApi
|
||||
});
|
||||
|
||||
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(name: CORS_POLICY,
|
||||
builder =>
|
||||
{
|
||||
builder.WithOrigins("http://localhost:44319",
|
||||
"https://localhost:44319",
|
||||
"http://localhost:44315",
|
||||
"https://localhost:44315");
|
||||
builder.AllowAnyMethod();
|
||||
builder.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
services.AddControllers();
|
||||
services.AddMediatR(typeof(CatalogItem).Assembly);
|
||||
@@ -175,6 +188,8 @@ namespace Microsoft.eShopWeb.PublicApi
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseCors(CORS_POLICY);
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
// Enable middleware to serve generated Swagger as a JSON endpoint.
|
||||
|
||||
Reference in New Issue
Block a user