├── src ├── Web │ ├── wwwroot │ │ ├── js │ │ │ ├── site.min.js │ │ │ └── site.js │ │ ├── css │ │ │ ├── _variables.css │ │ │ ├── _variables.min.css │ │ │ ├── shared │ │ │ │ └── components │ │ │ │ │ ├── header │ │ │ │ │ ├── header.min.css │ │ │ │ │ ├── header.css │ │ │ │ │ └── header.scss │ │ │ │ │ ├── pager │ │ │ │ │ ├── pager.min.css │ │ │ │ │ ├── pager.css │ │ │ │ │ └── pager.scss │ │ │ │ │ └── identity │ │ │ │ │ ├── identity.min.css │ │ │ │ │ ├── identity.css │ │ │ │ │ └── identity.scss │ │ │ ├── app.component.min.css │ │ │ ├── app.component.css │ │ │ ├── catalog │ │ │ │ └── pager.css │ │ │ ├── basket │ │ │ │ ├── basket-status │ │ │ │ │ ├── basket-status.component.min.css │ │ │ │ │ └── basket-status.component.css │ │ │ │ ├── basket.component.min.css │ │ │ │ └── basket.component.css │ │ │ ├── app.component.scss │ │ │ ├── orders │ │ │ │ └── orders.component.min.css │ │ │ └── app.min.css │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── brand.png │ │ │ ├── cart.png │ │ │ ├── logout.png │ │ │ ├── arrow-down.png │ │ │ ├── my_orders.png │ │ │ ├── products │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.jpg │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ ├── 7.png │ │ │ │ ├── 8.png │ │ │ │ ├── 9.png │ │ │ │ ├── 10.png │ │ │ │ ├── 11.png │ │ │ │ └── 12.png │ │ │ ├── main_banner.png │ │ │ ├── main_banner_text.png │ │ │ ├── arrow-right.svg │ │ │ └── refresh.svg │ │ └── fonts │ │ │ ├── Montserrat-Bold.eot │ │ │ ├── Montserrat-Bold.ttf │ │ │ ├── Montserrat-Bold.woff │ │ │ ├── Montserrat-Bold.woff2 │ │ │ ├── Montserrat-Regular.eot │ │ │ ├── Montserrat-Regular.ttf │ │ │ ├── Montserrat-Regular.woff │ │ │ └── Montserrat-Regular.woff2 │ ├── Pages │ │ ├── _ViewStart.cshtml │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── Components │ │ │ │ └── BasketComponent │ │ │ │ │ └── Default.cshtml │ │ │ ├── _editCatalog.cshtml │ │ │ └── _product.cshtml │ │ ├── _ViewImports.cshtml │ │ ├── Basket │ │ │ ├── Success.cshtml │ │ │ ├── Success.cshtml.cs │ │ │ ├── BasketViewModel.cs │ │ │ └── BasketItemViewModel.cs │ │ ├── Error.cshtml.cs │ │ ├── Admin │ │ │ ├── Index.cshtml.cs │ │ │ ├── EditCatalogItem.cshtml.cs │ │ │ └── EditCatalogItem.cshtml │ │ ├── Error.cshtml │ │ └── Index.cshtml.cs │ ├── Views │ │ ├── _ViewStart.cshtml │ │ ├── Manage │ │ │ ├── _ViewImports.cshtml │ │ │ ├── _StatusMessage.cshtml │ │ │ ├── _Layout.cshtml │ │ │ ├── _ManageNav.cshtml │ │ │ ├── GenerateRecoveryCodes.cshtml │ │ │ ├── Disable2fa.cshtml │ │ │ ├── ResetAuthenticator.cshtml │ │ │ ├── SetPassword.cshtml │ │ │ ├── ChangePassword.cshtml │ │ │ └── ManageNavPages.cs │ │ ├── Account │ │ │ └── Lockout.cshtml │ │ ├── Shared │ │ │ ├── Components │ │ │ │ └── Basket │ │ │ │ │ └── Default.cshtml │ │ │ ├── Error.cshtml │ │ │ ├── _CookieConsentPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ └── _ViewImports.cshtml │ ├── Areas │ │ └── Identity │ │ │ ├── Pages │ │ │ ├── _ViewStart.cshtml │ │ │ ├── Account │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ ├── Logout.cshtml │ │ │ │ ├── ConfirmEmail.cshtml │ │ │ │ ├── Logout.cshtml.cs │ │ │ │ └── ConfirmEmail.cshtml.cs │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ │ └── IdentityHostingStartup.cs │ ├── ViewModels │ │ ├── BasketComponentViewModel.cs │ │ ├── Manage │ │ │ ├── GenerateRecoveryCodesViewModel.cs │ │ │ ├── RemoveLoginViewModel.cs │ │ │ ├── TwoFactorAuthenticationViewModel.cs │ │ │ ├── ExternalLoginsViewModel.cs │ │ │ ├── IndexViewModel.cs │ │ │ ├── EnableAuthenticatorViewModel.cs │ │ │ ├── SetPasswordViewModel.cs │ │ │ └── ChangePasswordViewModel.cs │ │ ├── File │ │ │ └── FileViewModel.cs │ │ ├── CatalogItemViewModel.cs │ │ ├── OrderItemViewModel.cs │ │ ├── PaginationInfoViewModel.cs │ │ ├── Account │ │ │ ├── LoginViewModel.cs │ │ │ ├── LoginWith2faViewModel.cs │ │ │ ├── RegisterViewModel.cs │ │ │ └── ResetPasswordViewModel.cs │ │ ├── CatalogIndexViewModel.cs │ │ └── OrderViewModel.cs │ ├── .config │ │ └── dotnet-tools.json │ ├── Interfaces │ │ ├── IBasketViewModelService.cs │ │ ├── ICatalogItemViewModelService.cs │ │ └── ICatalogViewModelService.cs │ ├── appsettings.Development.json │ ├── Constants.cs │ ├── Controllers │ │ ├── Api │ │ │ └── BaseApiController.cs │ │ ├── FileController.cs │ │ └── OrderController.cs │ ├── Features │ │ ├── MyOrders │ │ │ └── GetMyOrders.cs │ │ └── OrderDetails │ │ │ └── GetOrderDetails.cs │ ├── Extensions │ │ ├── UrlHelperExtensions.cs │ │ ├── EmailSenderExtensions.cs │ │ └── CacheHelpers.cs │ ├── SlugifyParameterTransformer.cs │ ├── appsettings.Docker.json │ ├── appsettings.json │ ├── Dockerfile │ ├── bundleconfig.json │ ├── Configuration │ │ ├── ConfigureWebServices.cs │ │ ├── ConfigureCoreServices.cs │ │ └── ConfigureCookieSettings.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Services │ │ └── CatalogItemViewModelService.cs │ ├── compilerconfig.json │ ├── libman.json │ ├── HealthChecks │ │ └── HomePageHealthCheck.cs │ └── compilerconfig.json.defaults ├── BlazorShared │ ├── Models │ │ ├── CatalogBrand.cs │ │ ├── CatalogType.cs │ │ ├── DeleteCatalogItemResponse.cs │ │ ├── LookupData.cs │ │ ├── EditCatalogItemResponse.cs │ │ ├── CreateCatalogItemResponse.cs │ │ ├── CatalogTypeResponse.cs │ │ ├── CatalogBrandResponse.cs │ │ ├── PagedCatalogItemResponse.cs │ │ └── CreateCatalogItemRequest.cs │ ├── Authorization │ │ ├── Constants.cs │ │ ├── ClaimValue.cs │ │ └── UserInfo.cs │ ├── BaseUrlConfiguration.cs │ ├── Interfaces │ │ ├── ICatalogTypeService.cs │ │ ├── ICatalogBrandService.cs │ │ └── ICatalogItemService.cs │ └── BlazorShared.csproj ├── ApplicationCore │ ├── Interfaces │ │ ├── IAggregateRoot.cs │ │ ├── IUriComposer.cs │ │ ├── ITokenClaimsService.cs │ │ ├── IEmailSender.cs │ │ ├── IFileSystem.cs │ │ ├── IOrderService.cs │ │ ├── IOrderRepository.cs │ │ ├── IAppLogger.cs │ │ ├── IBasketService.cs │ │ └── IAsyncRepository.cs │ ├── CatalogSettings.cs │ ├── Entities │ │ ├── CatalogType.cs │ │ ├── BaseEntity.cs │ │ ├── CatalogBrand.cs │ │ ├── BuyerAggregate │ │ │ ├── PaymentMethod.cs │ │ │ └── Buyer.cs │ │ ├── OrderAggregate │ │ │ ├── OrderItem.cs │ │ │ ├── Address.cs │ │ │ └── CatalogItemOrdered.cs │ │ └── BasketAggregate │ │ │ ├── BasketItem.cs │ │ │ └── Basket.cs │ ├── Exceptions │ │ ├── DuplicateCatalogItemNameException.cs │ │ ├── BasketNotFoundException.cs │ │ ├── GuardExtensions.cs │ │ └── EmptyBasketOnCheckoutException.cs │ ├── Specifications │ │ ├── CatalogItemsSpecification.cs │ │ ├── CatalogFilterSpecification.cs │ │ ├── CustomerOrdersWithItemsSpecification.cs │ │ ├── CatalogFilterPaginatedSpecification.cs │ │ └── BasketWithItemsSpecification.cs │ ├── Constants │ │ └── AuthorizationConstants.cs │ ├── Services │ │ └── UriComposer.cs │ ├── Extensions │ │ └── JsonExtensions.cs │ └── ApplicationCore.csproj ├── BlazorAdmin │ ├── wwwroot │ │ ├── appsettings.Docker.json │ │ ├── css │ │ │ └── open-iconic │ │ │ │ ├── font │ │ │ │ └── fonts │ │ │ │ │ ├── open-iconic.eot │ │ │ │ │ ├── open-iconic.otf │ │ │ │ │ ├── open-iconic.ttf │ │ │ │ │ └── open-iconic.woff │ │ │ │ └── ICON-LICENSE │ │ ├── appsettings.json │ │ └── appsettings.Development.json │ ├── Shared │ │ ├── Spinner.razor │ │ ├── RedirectToLogin.razor │ │ ├── MainLayout.razor │ │ └── CustomInputSelect.cs │ ├── Pages │ │ └── Logout.razor │ ├── Services │ │ ├── CacheEntry.cs │ │ ├── CatalogTypeService.cs │ │ └── CatalogBrandService.cs │ ├── JavaScript │ │ ├── JSInteropConstants.cs │ │ ├── Route.cs │ │ ├── Css.cs │ │ └── Cookies.cs │ ├── Helpers │ │ ├── RefreshBroadcast.cs │ │ ├── BlazorComponent.cs │ │ └── BlazorLayoutComponent.cs │ ├── _Imports.razor │ ├── ServicesConfiguration.cs │ ├── Properties │ │ └── launchSettings.json │ └── App.razor ├── PublicApi │ ├── README.md │ ├── BaseRequest.cs │ ├── CatalogBrandEndpoints │ │ ├── CatalogBrandDto.cs │ │ └── List.ListCatalogBrandsResponse.cs │ ├── CatalogTypeEndpoints │ │ ├── CatalogTypeDto.cs │ │ └── List.ListCatalogTypesResponse.cs │ ├── CatalogItemEndpoints │ │ ├── GetById.GetByIdCatalogItemRequest.cs │ │ ├── Delete.DeleteCatalogItemRequest.cs │ │ ├── ListPaged.ListPagedCatalogItemRequest.cs │ │ ├── Create.CreateCatalogItemResponse.cs │ │ ├── Delete.DeleteCatalogItemResponse.cs │ │ ├── Update.UpdateCatalogItemResponse.cs │ │ ├── CatalogItemDto.cs │ │ ├── GetById.GetByIdCatalogItemResponse.cs │ │ ├── Create.CreateCatalogItemRequest.cs │ │ ├── ListPaged.ListPagedCatalogItemResponse.cs │ │ └── Update.UpdateCatalogItemRequest.cs │ ├── AuthEndpoints │ │ ├── Authenticate.AuthenticateRequest.cs │ │ ├── Authenticate.UserInfo.cs │ │ ├── Authenticate.ClaimValue.cs │ │ └── Authenticate.AuthenticateResponse.cs │ ├── appsettings.Development.json │ ├── BaseMessage.cs │ ├── BaseResponse.cs │ ├── CustomSchemaFilters.cs │ ├── appsettings.Docker.json │ ├── appsettings.json │ ├── MappingProfile.cs │ ├── Dockerfile │ ├── Properties │ │ └── launchSettings.json │ └── ImageValidators.cs └── Infrastructure │ ├── Identity │ ├── ApplicationUser.cs │ ├── AppIdentityDbContext.cs │ └── AppIdentityDbContextSeed.cs │ ├── Data │ ├── FileItem.cs │ ├── Config │ │ ├── BasketItemConfiguration.cs │ │ ├── BasketConfiguration.cs │ │ ├── CatalogTypeConfiguration.cs │ │ ├── CatalogBrandConfiguration.cs │ │ ├── OrderItemConfiguration.cs │ │ ├── CatalogItemConfiguration.cs │ │ └── OrderConfiguration.cs │ ├── OrderRepository.cs │ └── CatalogContext.cs │ ├── Services │ └── EmailSender.cs │ ├── Logging │ └── LoggerAdapter.cs │ └── Infrastructure.csproj ├── .dockerignore ├── .vscode ├── extensions.json ├── tasks.json └── launch.json ├── docker-compose.yml ├── tests ├── UnitTests │ ├── Web │ │ └── Extensions │ │ │ └── CacheHelpersTests │ │ │ ├── GenerateTypesCacheKey.cs │ │ │ ├── GenerateBrandsCacheKey.cs │ │ │ └── GenerateCatalogItemCacheKey.cs │ ├── ApplicationCore │ │ ├── Extensions │ │ │ ├── TestChild.cs │ │ │ ├── TestParent.cs │ │ │ └── JsonExtensions.cs │ │ ├── Entities │ │ │ ├── BasketTests │ │ │ │ └── BasketRemoveEmptyItems.cs │ │ │ └── OrderTests │ │ │ │ └── OrderTotal.cs │ │ └── Services │ │ │ └── BasketServiceTests │ │ │ ├── DeleteBasket.cs │ │ │ └── SetQuantities.cs │ ├── Builders │ │ ├── AddressBuilder.cs │ │ └── BasketBuilder.cs │ └── UnitTests.csproj ├── FunctionalTests │ ├── Web │ │ ├── Controllers │ │ │ ├── CatalogControllerIndex.cs │ │ │ └── OrderControllerIndex.cs │ │ └── Pages │ │ │ └── HomePageOnGet.cs │ └── FunctionalTests.csproj └── IntegrationTests │ └── IntegrationTests.csproj ├── .github └── workflows │ └── dotnetcore.yml ├── docker-compose.override.yml ├── docker-compose.dcproj └── LICENSE /src/Web/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/_variables.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/_variables.min.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /src/Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.eShopWeb.Web.Views.Manage -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account -------------------------------------------------------------------------------- /src/Web/wwwroot/images/brand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/brand.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/cart.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | */bin 8 | */obj 9 | **/.toolstarget -------------------------------------------------------------------------------- /src/Web/wwwroot/images/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/logout.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/arrow-down.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/my_orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/my_orders.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/1.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/2.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/3.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/4.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/5.jpg -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/5.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/6.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/7.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/8.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/9.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/main_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/main_banner.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/10.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/11.png -------------------------------------------------------------------------------- /src/Web/wwwroot/images/products/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/products/12.png -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Bold.eot -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Bold.woff -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Bold.woff2 -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Regular.eot -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /src/Web/wwwroot/images/main_banner_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/images/main_banner_text.png -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Regular.woff -------------------------------------------------------------------------------- /src/Web/wwwroot/fonts/Montserrat-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/Web/wwwroot/fonts/Montserrat-Regular.woff2 -------------------------------------------------------------------------------- /src/BlazorShared/Models/CatalogBrand.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public class CatalogBrand : LookupData 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/CatalogType.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public class CatalogType : LookupData 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 2 | { 3 | public interface IAggregateRoot 4 | { } 5 | } 6 | -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "http://localhost:5200/api/", 4 | "webBase": "http://host.docker.internal:5106/" 5 | } 6 | } -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/eShopOnWeb/HEAD/src/BlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /src/ApplicationCore/CatalogSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb 2 | { 3 | public class CatalogSettings 4 | { 5 | public string CatalogBaseUrl { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /src/Web/ViewModels/BasketComponentViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels 2 | { 3 | public class BasketComponentViewModel 4 | { 5 | public int ItemsCount { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/DeleteCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public class DeleteCatalogItemResponse 4 | { 5 | public string Status { get; set; } = "Deleted"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/LookupData.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public abstract class LookupData 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/PublicApi/README.md: -------------------------------------------------------------------------------- 1 | # API Endpoints 2 | 3 | This folder demonstrates how to configure API endpoints as individual classes. You can compare it to the traditional controller-based approach found in /Controllers/Api. 4 | 5 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IUriComposer.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 2 | { 3 | public interface IUriComposer 4 | { 5 | string ComposePicUri(string uriTemplate); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/Identity/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace Microsoft.eShopWeb.Infrastructure.Identity 4 | { 5 | public class ApplicationUser : IdentityUser 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/EditCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public class EditCatalogItemResult 4 | { 5 | public CatalogItem CatalogItem { get; set; } = new CatalogItem(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/CreateCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Models 2 | { 3 | public class CreateCatalogItemResponse 4 | { 5 | public CatalogItem CatalogItem { get; set; } = new CatalogItem(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Web/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "5.0.0", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/BlazorAdmin/Shared/Spinner.razor: -------------------------------------------------------------------------------- 1 | 2 | @inherits BlazorAdmin.Helpers.BlazorComponent 3 | 4 | @namespace BlazorAdmin.Shared 5 | 6 |
7 | Loading... 8 |
9 | 10 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/GenerateRecoveryCodesViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 2 | { 3 | public class GenerateRecoveryCodesViewModel 4 | { 5 | public string[] RecoveryCodes { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/PublicApi/BaseRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi 2 | { 3 | /// 4 | /// Base class used by API requests 5 | /// 6 | public abstract class BaseRequest : BaseMessage 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogBrandEndpoints/CatalogBrandDto.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints 2 | { 3 | public class CatalogBrandDto 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogTypeEndpoints/CatalogTypeDto.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints 2 | { 3 | public class CatalogTypeDto 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Web/Views/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Locked out"; 3 | } 4 | 5 |
6 |

@ViewData["Title"]

7 |

This account has been locked out, please try again later.

8 |
9 | -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LogoutModel 3 | @{ 4 | ViewData["Title"] = "Log out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You have successfully logged out of the application.

10 |
-------------------------------------------------------------------------------- /src/Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace Microsoft.eShopWeb.Web.Pages 4 | { 5 | public class PrivacyModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/GetById.GetByIdCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 2 | { 3 | public class GetByIdCatalogItemRequest : BaseRequest 4 | { 5 | public int CatalogItemId { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-dotnettools.csharp", 4 | "formulahendry.dotnet-test-explorer", 5 | "ms-vscode.vscode-node-azure-pack", 6 | "ms-kubernetes-tools.vscode-kubernetes-tools", 7 | "redhat.vscode-yaml" 8 | ] 9 | } -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/RemoveLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 2 | { 3 | public class RemoveLoginViewModel 4 | { 5 | public string LoginProvider { get; set; } 6 | public string ProviderKey { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/ITokenClaimsService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 4 | { 5 | public interface ITokenClaimsService 6 | { 7 | Task GetTokenAsync(string userName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/CatalogTypeResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorShared.Models 4 | { 5 | public class CatalogTypeResponse 6 | { 7 | public List CatalogTypes { get; set; } = new List(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ConfirmEmailModel 3 | @{ 4 | ViewData["Title"] = "Confirm email"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | Thank you for confirming your email. 11 |

12 |
13 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/header/header.min.css: -------------------------------------------------------------------------------- 1 | .esh-header{background-color:#00a69c;height:4rem;}.esh-header-back{color:rgba(255,255,255,.5);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s;}.esh-header-back:hover{color:#fff;transition:color .35s;} -------------------------------------------------------------------------------- /src/BlazorShared/Authorization/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Authorization 2 | { 3 | public static class Constants 4 | { 5 | public static class Roles 6 | { 7 | public const string ADMINISTRATORS = "Administrators"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/CatalogBrandResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorShared.Models 4 | { 5 | public class CatalogBrandResponse 6 | { 7 | public List CatalogBrands { get; set; } = new List(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/PublicApi/AuthEndpoints/Authenticate.AuthenticateRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints 2 | { 3 | public class AuthenticateRequest : BaseRequest 4 | { 5 | public string Username { get; set; } 6 | public string Password { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using Microsoft.eShopWeb.Web.Areas.Identity 3 | @using Microsoft.eShopWeb.Infrastructure.Identity 4 | @namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 4 | { 5 | 6 | public interface IEmailSender 7 | { 8 | Task SendEmailAsync(string email, string subject, string message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorShared/BaseUrlConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared 2 | { 3 | public class BaseUrlConfiguration 4 | { 5 | public const string CONFIG_NAME = "baseUrls"; 6 | 7 | public string ApiBase { get; set; } 8 | public string WebBase { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/ViewModels/File/FileViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels.File 2 | { 3 | public class FileViewModel 4 | { 5 | public string FileName { get; set; } 6 | public string Url { get; set; } 7 | public string DataBase64 { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Shared/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager Navigation 2 | 3 | @code { 4 | protected override void OnInitialized() 5 | { 6 | Navigation.NavigateTo($"Identity/Account/Login?returnUrl=" + 7 | $"/{Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri))}"); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Web/Interfaces/IBasketViewModelService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.Web.Pages.Basket; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.Web.Interfaces 5 | { 6 | public interface IBasketViewModelService 7 | { 8 | Task GetOrCreateBasketForUser(string userName); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/Interfaces/ICatalogItemViewModelService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.Web.ViewModels; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.Web.Interfaces 5 | { 6 | public interface ICatalogItemViewModelService 7 | { 8 | Task UpdateCatalogItem(CatalogItemViewModel viewModel); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "Logging": { 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Delete.DeleteCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class DeleteCatalogItemRequest : BaseRequest 6 | { 7 | //[FromRoute] 8 | public int CatalogItemId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/ViewModels/CatalogItemViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels 2 | { 3 | public class CatalogItemViewModel 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public string PictureUri { get; set; } 8 | public decimal Price { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/PagedCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorShared.Models 4 | { 5 | public class PagedCatalogItemResponse 6 | { 7 | public List CatalogItems { get; set; } = new List(); 8 | public int PageCount { get; set; } = 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/app.component.min.css: -------------------------------------------------------------------------------- 1 | .esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;bottom:0;}.esh-app-footer-brand{height:50px;width:230px;}.esh-app-header{margin:15px;}.esh-app-wrapper{display:flex;min-height:100vh;flex-direction:column;justify-content:space-between;} -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 5 | { 6 | public interface IFileSystem 7 | { 8 | Task SavePicture(string pictureName, string pictureBase64, CancellationToken cancellationToken); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web 2 | { 3 | public static class Constants 4 | { 5 | public const string BASKET_COOKIENAME = "eShop"; 6 | public const int ITEMS_PER_PAGE = 10; 7 | public const string DEFAULT_USERNAME = "Guest"; 8 | public const string BASKET_ID = "BasketId"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IOrderService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 5 | { 6 | public interface IOrderService 7 | { 8 | Task CreateOrderAsync(int basketId, Address shippingAddress); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/TwoFactorAuthenticationViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 2 | { 3 | public class TwoFactorAuthenticationViewModel 4 | { 5 | public bool HasAuthenticator { get; set; } 6 | public int RecoveryCodesLeft { get; set; } 7 | public bool Is2faEnabled { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Information", 10 | "Microsoft": "Warning", 11 | "System": "Warning" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/BlazorShared/Interfaces/ICatalogTypeService.cs: -------------------------------------------------------------------------------- 1 | using BlazorShared.Models; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace BlazorShared.Interfaces 6 | { 7 | public interface ICatalogTypeService 8 | { 9 | Task> List(); 10 | Task GetById(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/PublicApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "Logging": { 7 | "LogLevel": { 8 | "Default": "Information", 9 | "Microsoft": "Warning", 10 | "Microsoft.Hosting.Lifetime": "Information" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BlazorShared/Interfaces/ICatalogBrandService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using BlazorShared.Models; 4 | 5 | namespace BlazorShared.Interfaces 6 | { 7 | public interface ICatalogBrandService 8 | { 9 | Task> List(); 10 | Task GetById(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | eshopwebmvc: 5 | image: ${DOCKER_REGISTRY-}eshopwebmvc 6 | build: 7 | context: . 8 | dockerfile: src/Web/Dockerfile 9 | eshoppublicapi: 10 | image: ${DOCKER_REGISTRY-}eshoppublicapi 11 | build: 12 | context: . 13 | dockerfile: src/PublicApi/Dockerfile 14 | 15 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 5 | { 6 | 7 | public interface IOrderRepository : IAsyncRepository 8 | { 9 | Task GetByIdWithItemsAsync(int id); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Information", 10 | "Microsoft": "Warning", 11 | "System": "Warning" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Web/Controllers/Api/BaseApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Microsoft.eShopWeb.Web.Controllers.Api 4 | { 5 | // No longer used - shown for reference only if using full controllers instead of Endpoints for APIs 6 | [Route("api/[controller]/[action]")] 7 | [ApiController] 8 | public class BaseApiController : ControllerBase 9 | { } 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorShared/BlazorShared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | BlazorShared 6 | BlazorShared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/CatalogType.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Entities 4 | { 5 | public class CatalogType : BaseEntity, IAggregateRoot 6 | { 7 | public string Type { get; private set; } 8 | public CatalogType(string type) 9 | { 10 | Type = type; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Entities 2 | { 3 | // This can easily be modified to be BaseEntity and public T Id to support different key types. 4 | // Using non-generic integer types for simplicity and to ease caching logic 5 | public abstract class BaseEntity 6 | { 7 | public virtual int Id { get; protected set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/CatalogBrand.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Entities 4 | { 5 | public class CatalogBrand : BaseEntity, IAggregateRoot 6 | { 7 | public string Brand { get; private set; } 8 | public CatalogBrand(string brand) 9 | { 10 | Brand = brand; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/FileItem.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Infrastructure.Data 2 | { 3 | public class FileItem 4 | { 5 | public string FileName { get; set; } 6 | public string Url { get; set; } 7 | public long Size { get; set; } 8 | public string Ext { get; set; } 9 | public string Type { get; set; } 10 | public string DataBase64 { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/header/header.css: -------------------------------------------------------------------------------- 1 | .esh-header { 2 | background-color: #00A69C; 3 | height: 4rem; } 4 | .esh-header-back { 5 | color: rgba(255, 255, 255, 0.5); 6 | line-height: 4rem; 7 | text-decoration: none; 8 | text-transform: uppercase; 9 | transition: color 0.35s; } 10 | .esh-header-back:hover { 11 | color: #FFFFFF; 12 | transition: color 0.35s; } 13 | -------------------------------------------------------------------------------- /src/Web/Pages/Shared/Components/BasketComponent/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model BasketComponentViewModel 2 | @{ 3 | ViewData["Title"] = "My Basket"; 4 | } 5 | 7 |
8 | 9 |
10 |
11 | @Model.ItemsCount 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/ListPaged.ListPagedCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 2 | { 3 | public class ListPagedCatalogItemRequest : BaseRequest 4 | { 5 | public int PageSize { get; set; } 6 | public int PageIndex { get; set; } 7 | public int? CatalogBrandId { get; set; } 8 | public int? CatalogTypeId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Pages/Logout.razor: -------------------------------------------------------------------------------- 1 | @page "/logout" 2 | @inject IJSRuntime JSRuntime 3 | @inject HttpClient HttpClient 4 | @inherits BlazorAdmin.Helpers.BlazorComponent 5 | 6 | @code { 7 | 8 | protected override async Task OnInitializedAsync() 9 | { 10 | await HttpClient.PostAsync("Identity/Account/Logout", null); 11 | await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/Web/Views/Shared/Components/Basket/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model BasketComponentViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "My Basket"; 5 | } 6 | 7 | 8 |
9 | 10 |
11 |
12 | @Model.ItemsCount 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/pager/pager.min.css: -------------------------------------------------------------------------------- 1 | .esh-pager-wrapper{padding-top:1rem;text-align:center;}.esh-pager-item{margin:0 5vw;}.esh-pager-item.is-disabled{opacity:.5;pointer-events:none;}.esh-pager-item--navigable{cursor:pointer;display:inline-block;}.esh-pager-item--navigable:hover{color:#83d01b;}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem;}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 2.5vw;}} -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate 2 | { 3 | public class PaymentMethod : BaseEntity 4 | { 5 | public string Alias { get; private set; } 6 | public string CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe 7 | public string Last4 { get; private set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Services/CacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorAdmin.Services 4 | { 5 | public class CacheEntry 6 | { 7 | public CacheEntry(T item) 8 | { 9 | Value = item; 10 | } 11 | public CacheEntry() 12 | { 13 | 14 | } 15 | 16 | public T Value { get; set; } 17 | public DateTime DateCreated { get; set; } = DateTime.UtcNow; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.eShopWeb.Web 2 | @using Microsoft.eShopWeb.Web.ViewModels 3 | @using Microsoft.eShopWeb.Web.ViewModels.Account 4 | @using Microsoft.eShopWeb.Web.ViewModels.Manage 5 | @using Microsoft.eShopWeb.Web.Pages 6 | @using Microsoft.AspNetCore.Identity 7 | @using Microsoft.eShopWeb.Infrastructure.Identity 8 | @namespace Microsoft.eShopWeb.Web.Pages 9 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 10 | -------------------------------------------------------------------------------- /src/Web/ViewModels/OrderItemViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels 2 | { 3 | public class OrderItemViewModel 4 | { 5 | public int ProductId { get; set; } 6 | public string ProductName { get; set; } 7 | public decimal UnitPrice { get; set; } 8 | public decimal Discount => 0; 9 | public int Units { get; set; } 10 | public string PictureUrl { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.eShopWeb.Web 2 | @using Microsoft.eShopWeb.Web.ViewModels 3 | @using Microsoft.eShopWeb.Web.ViewModels.Account 4 | @using Microsoft.eShopWeb.Web.ViewModels.Manage 5 | @using Microsoft.eShopWeb.Web.Pages 6 | @using Microsoft.AspNetCore.Identity 7 | @using Microsoft.eShopWeb.Infrastructure.Identity 8 | @namespace Microsoft.eShopWeb.Web.Pages 9 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 10 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/ViewModels/PaginationInfoViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.Web.ViewModels 2 | { 3 | public class PaginationInfoViewModel 4 | { 5 | public int TotalItems { get; set; } 6 | public int ItemsPerPage { get; set; } 7 | public int ActualPage { get; set; } 8 | public int TotalPages { get; set; } 9 | public string Previous { get; set; } 10 | public string Next { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BlazorShared/Authorization/ClaimValue.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorShared.Authorization 2 | { 3 | public class ClaimValue 4 | { 5 | public ClaimValue() 6 | { 7 | } 8 | 9 | public ClaimValue(string type, string value) 10 | { 11 | Type = type; 12 | Value = value; 13 | } 14 | 15 | public string Type { get; set; } 16 | public string Value { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/Pages/Basket/Success.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SuccessModel 3 | @{ 4 | ViewData["Title"] = "Checkout Complete"; 5 | } 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |

Thanks for your Order!

15 | 16 | Continue Shopping... 17 |
-------------------------------------------------------------------------------- /src/Web/Features/MyOrders/GetMyOrders.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.eShopWeb.Web.ViewModels; 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.eShopWeb.Web.Features.MyOrders 6 | { 7 | public class GetMyOrders : IRequest> 8 | { 9 | public string UserName { get; set; } 10 | 11 | public GetMyOrders(string userName) 12 | { 13 | UserName = userName; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/PublicApi/BaseMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi 4 | { 5 | /// 6 | /// Base class used by API requests 7 | /// 8 | public abstract class BaseMessage 9 | { 10 | /// 11 | /// Unique Identifier used by logging 12 | /// 13 | protected Guid _correlationId = Guid.NewGuid(); 14 | public Guid CorrelationId() => _correlationId; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ApplicationCore/Exceptions/DuplicateCatalogItemNameException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Exceptions 4 | { 5 | public class DuplicateCatalogItemNameException : Exception 6 | { 7 | public DuplicateCatalogItemNameException(string message, int duplicateItemId) : base(message) 8 | { 9 | DuplicateItemId = duplicateItemId; 10 | } 11 | 12 | public int DuplicateItemId { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/PublicApi/BaseResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi 4 | { 5 | /// 6 | /// Base class used by API responses 7 | /// 8 | public abstract class BaseResponse : BaseMessage 9 | { 10 | public BaseResponse(Guid correlationId) : base() 11 | { 12 | base._correlationId = correlationId; 13 | } 14 | 15 | public BaseResponse() 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IAppLogger.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 2 | { 3 | /// 4 | /// This type eliminates the need to depend directly on the ASP.NET Core logging types. 5 | /// 6 | /// 7 | public interface IAppLogger 8 | { 9 | void LogInformation(string message, params object[] args); 10 | void LogWarning(string message, params object[] args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BlazorAdmin/JavaScript/JSInteropConstants.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorAdmin.JavaScript 2 | { 3 | public static class JSInteropConstants 4 | { 5 | public static string DeleteCookie => "deleteCookie"; 6 | public static string GetCookie => "getCookie"; 7 | public static string RouteOutside => "routeOutside"; 8 | public static string HideBodyOverflow => "hideBodyOverflow"; 9 | public static string ShowBodyOverflow => "showBodyOverflow"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Web/Pages/Basket/Success.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace Microsoft.eShopWeb.Web.Pages.Basket 10 | { 11 | [Authorize] 12 | public class SuccessModel : PageModel 13 | { 14 | public void OnGet() 15 | { 16 | 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Web/Areas/Identity/IdentityHostingStartup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | 3 | [assembly: HostingStartup(typeof(Microsoft.eShopWeb.Web.Areas.Identity.IdentityHostingStartup))] 4 | namespace Microsoft.eShopWeb.Web.Areas.Identity 5 | { 6 | public class IdentityHostingStartup : IHostingStartup 7 | { 8 | public void Configure(IWebHostBuilder builder) 9 | { 10 | builder.ConfigureServices((context, services) => { 11 | }); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateTypesCacheKey.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.Web.Extensions; 2 | using Xunit; 3 | 4 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests 5 | { 6 | public class GenerateTypesCacheKey 7 | { 8 | [Fact] 9 | public void ReturnsTypesCacheKey() 10 | { 11 | var result = CacheHelpers.GenerateTypesCacheKey(); 12 | 13 | Assert.Equal("types", result); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateBrandsCacheKey.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.Web.Extensions; 2 | using Xunit; 3 | 4 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests 5 | { 6 | public class GenerateBrandsCacheKey 7 | { 8 | [Fact] 9 | public void ReturnsBrandsCacheKey() 10 | { 11 | var result = CacheHelpers.GenerateBrandsCacheKey(); 12 | 13 | Assert.Equal("brands", result); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ApplicationCore/Specifications/CatalogItemsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace Microsoft.eShopWeb.ApplicationCore.Specifications 7 | { 8 | public class CatalogItemsSpecification : Specification 9 | { 10 | public CatalogItemsSpecification(params int[] ids) 11 | { 12 | Query.Where(c => ids.Contains(c.Id)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Create.CreateCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class CreateCatalogItemResponse : BaseResponse 6 | { 7 | public CreateCatalogItemResponse(Guid correlationId) : base(correlationId) 8 | { 9 | } 10 | 11 | public CreateCatalogItemResponse() 12 | { 13 | } 14 | 15 | public CatalogItemDto CatalogItem { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Delete.DeleteCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class DeleteCatalogItemResponse : BaseResponse 6 | { 7 | public DeleteCatalogItemResponse(Guid correlationId) : base(correlationId) 8 | { 9 | } 10 | 11 | public DeleteCatalogItemResponse() 12 | { 13 | } 14 | 15 | public string Status { get; set; } = "Deleted"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Update.UpdateCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class UpdateCatalogItemResponse : BaseResponse 6 | { 7 | public UpdateCatalogItemResponse(Guid correlationId) : base(correlationId) 8 | { 9 | } 10 | 11 | public UpdateCatalogItemResponse() 12 | { 13 | } 14 | 15 | public CatalogItemDto CatalogItem { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/CatalogItemDto.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 2 | { 3 | public class CatalogItemDto 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public string Description { get; set; } 8 | public decimal Price { get; set; } 9 | public string PictureUri { get; set; } 10 | public int CatalogTypeId { get; set; } 11 | public int CatalogBrandId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/GetById.GetByIdCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class GetByIdCatalogItemResponse : BaseResponse 6 | { 7 | public GetByIdCatalogItemResponse(Guid correlationId) : base(correlationId) 8 | { 9 | } 10 | 11 | public GetByIdCatalogItemResponse() 12 | { 13 | } 14 | 15 | public CatalogItemDto CatalogItem { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.AspNetCore.Mvc 2 | { 3 | public static class UrlHelperExtensions 4 | { 5 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 6 | { 7 | return urlHelper.Action( 8 | action: "GET", 9 | controller: "ConfirmEmail", 10 | values: new { userId, code }, 11 | protocol: scheme); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/app.component.css: -------------------------------------------------------------------------------- 1 | .esh-app-footer { 2 | background-color: #000000; 3 | border-top: 1px solid #EEEEEE; 4 | margin-top: 2.5rem; 5 | padding-bottom: 2.5rem; 6 | padding-top: 2.5rem; 7 | width: 100%; 8 | bottom: 0; } 9 | .esh-app-footer-brand { 10 | height: 50px; 11 | width: 230px; } 12 | 13 | .esh-app-header { 14 | margin: 15px; } 15 | 16 | .esh-app-wrapper { 17 | display: flex; 18 | min-height: 100vh; 19 | flex-direction: column; 20 | justify-content: space-between; } 21 | -------------------------------------------------------------------------------- /src/BlazorShared/Authorization/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorShared.Authorization 4 | { 5 | public class UserInfo 6 | { 7 | public static readonly UserInfo Anonymous = new UserInfo(); 8 | public bool IsAuthenticated { get; set; } 9 | public string NameClaimType { get; set; } 10 | public string RoleClaimType { get; set; } 11 | public string Token { get; set; } 12 | public IEnumerable Claims { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web/Features/OrderDetails/GetOrderDetails.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.eShopWeb.Web.ViewModels; 3 | 4 | namespace Microsoft.eShopWeb.Web.Features.OrderDetails 5 | { 6 | public class GetOrderDetails : IRequest 7 | { 8 | public string UserName { get; set; } 9 | public int OrderId { get; set; } 10 | 11 | public GetOrderDetails(string userName, int orderId) 12 | { 13 | UserName = userName; 14 | OrderId = orderId; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Account/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Account 4 | { 5 | public class LoginViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | 11 | [Required] 12 | [DataType(DataType.Password)] 13 | public string Password { get; set; } 14 | 15 | [Display(Name = "Remember me?")] 16 | public bool RememberMe { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/SlugifyParameterTransformer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Microsoft.eShopWeb.Web 5 | { 6 | 7 | public class SlugifyParameterTransformer : IOutboundParameterTransformer 8 | { 9 | public string TransformOutbound(object value) 10 | { 11 | if (value == null) { return null; } 12 | 13 | // Slugify value 14 | return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IBasketService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 5 | { 6 | public interface IBasketService 7 | { 8 | Task TransferBasketAsync(string anonymousId, string userName); 9 | Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1); 10 | Task SetQuantities(int basketId, Dictionary quantities); 11 | Task DeleteBasketAsync(int basketId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BlazorAdmin/JavaScript/Route.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Threading.Tasks; 3 | 4 | namespace BlazorAdmin.JavaScript 5 | { 6 | public class Route 7 | { 8 | private readonly IJSRuntime _jsRuntime; 9 | 10 | public Route(IJSRuntime jsRuntime) 11 | { 12 | _jsRuntime = jsRuntime; 13 | } 14 | 15 | public async Task RouteOutside(string path) 16 | { 17 | await _jsRuntime.InvokeAsync(JSInteropConstants.RouteOutside, path); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ApplicationCore/Constants/AuthorizationConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Constants 2 | { 3 | public class AuthorizationConstants 4 | { 5 | public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes"; 6 | 7 | // TODO: Don't use this in production 8 | public const string DEFAULT_PASSWORD = "Pass@word1"; 9 | 10 | // TODO: Change this to an environment variable 11 | public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/ExternalLoginsViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Identity; 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 6 | { 7 | public class ExternalLoginsViewModel 8 | { 9 | public IList CurrentLogins { get; set; } 10 | public IList OtherLogins { get; set; } 11 | public bool ShowRemoveButton { get; set; } 12 | public string StatusMessage { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web/Interfaces/ICatalogViewModelService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | using Microsoft.eShopWeb.Web.ViewModels; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.eShopWeb.Web.Services 7 | { 8 | public interface ICatalogViewModelService 9 | { 10 | Task GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId); 11 | Task> GetBrands(); 12 | Task> GetTypes(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web/Pages/Basket/BasketViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Microsoft.eShopWeb.Web.Pages.Basket 6 | { 7 | public class BasketViewModel 8 | { 9 | public int Id { get; set; } 10 | public List Items { get; set; } = new List(); 11 | public string BuyerId { get; set; } 12 | 13 | public decimal Total() 14 | { 15 | return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ApplicationCore/Specifications/CatalogFilterSpecification.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Specifications 5 | { 6 | public class CatalogFilterSpecification : Specification 7 | { 8 | public CatalogFilterSpecification(int? brandId, int? typeId) 9 | { 10 | Query.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) && 11 | (!typeId.HasValue || i.CatalogTypeId == typeId)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogTypeEndpoints/List.ListCatalogTypesResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints 5 | { 6 | public class ListCatalogTypesResponse : BaseResponse 7 | { 8 | public ListCatalogTypesResponse(Guid correlationId) : base(correlationId) 9 | { 10 | } 11 | 12 | public ListCatalogTypesResponse() 13 | { 14 | } 15 | 16 | public List CatalogTypes { get; set; } = new List(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PublicApi/AuthEndpoints/Authenticate.UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints 7 | { 8 | public class UserInfo 9 | { 10 | public static readonly UserInfo Anonymous = new UserInfo(); 11 | public bool IsAuthenticated { get; set; } 12 | public string NameClaimType { get; set; } 13 | public string RoleClaimType { get; set; } 14 | public IEnumerable Claims { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogBrandEndpoints/List.ListCatalogBrandsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints 5 | { 6 | public class ListCatalogBrandsResponse : BaseResponse 7 | { 8 | public ListCatalogBrandsResponse(Guid correlationId) : base(correlationId) 9 | { 10 | } 11 | 12 | public ListCatalogBrandsResponse() 13 | { 14 | } 15 | 16 | public List CatalogBrands { get; set; } = new List(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: eShopOnWeb Build and Test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup .NET 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: '5.0.x' 16 | 17 | - name: Build with dotnet 18 | run: dotnet build ./eShopOnWeb.sln --configuration Release 19 | 20 | - name: Test with dotnet 21 | run: dotnet test ./eShopOnWeb.sln --configuration Release 22 | -------------------------------------------------------------------------------- /src/BlazorShared/Interfaces/ICatalogItemService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using BlazorShared.Models; 4 | 5 | namespace BlazorShared.Interfaces 6 | { 7 | public interface ICatalogItemService 8 | { 9 | Task Create(CreateCatalogItemRequest catalogItem); 10 | Task Edit(CatalogItem catalogItem); 11 | Task Delete(int id); 12 | Task GetById(int id); 13 | Task> ListPaged(int pageSize); 14 | Task> List(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/PublicApi/AuthEndpoints/Authenticate.ClaimValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints 7 | { 8 | public class ClaimValue 9 | { 10 | public ClaimValue() 11 | { 12 | } 13 | 14 | public ClaimValue(string type, string value) 15 | { 16 | Type = type; 17 | Value = value; 18 | } 19 | 20 | public string Type { get; set; } 21 | public string Value { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/PublicApi/CustomSchemaFilters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace Microsoft.eShopWeb.PublicApi 5 | { 6 | public class CustomSchemaFilters : ISchemaFilter 7 | { 8 | public void Apply(OpenApiSchema schema, SchemaFilterContext context) 9 | { 10 | var excludeProperties = new[] { "CorrelationId" }; 11 | 12 | foreach (var prop in excludeProperties) 13 | if (schema.Properties.ContainsKey(prop)) 14 | schema.Properties.Remove(prop); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 4 | { 5 | public class IndexViewModel 6 | { 7 | public string Username { get; set; } 8 | 9 | public bool IsEmailConfirmed { get; set; } 10 | 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Phone] 16 | [Display(Name = "Phone number")] 17 | public string PhoneNumber { get; set; } 18 | 19 | public string StatusMessage { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Extensions/TestChild.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions 6 | { 7 | [DebuggerDisplay("Id={Id}, Date={Date}")] 8 | public class TestChild : IEquatable 9 | { 10 | public Guid Id { get; set; } = Guid.NewGuid(); 11 | 12 | public DateTime Date { get; set; } = DateTime.UtcNow; 13 | 14 | public bool Equals([AllowNull] TestChild other) => 15 | other?.Date == Date && other?.Id == Id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ApplicationCore/Services/UriComposer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Services 4 | { 5 | public class UriComposer : IUriComposer 6 | { 7 | private readonly CatalogSettings _catalogSettings; 8 | 9 | public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings; 10 | 11 | public string ComposePicUri(string uriTemplate) 12 | { 13 | return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Specifications 5 | { 6 | public class CustomerOrdersWithItemsSpecification : Specification 7 | { 8 | public CustomerOrdersWithItemsSpecification(string buyerId) 9 | { 10 | Query.Where(o => o.BuyerId == buyerId) 11 | .Include(o => o.OrderItems) 12 | .ThenInclude(i => i.ItemOrdered); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ApplicationCore/Extensions/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Microsoft.eShopWeb 4 | { 5 | public static class JsonExtensions 6 | { 7 | private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions 8 | { 9 | PropertyNameCaseInsensitive = true 10 | }; 11 | 12 | public static T FromJson(this string json) => 13 | JsonSerializer.Deserialize(json, _jsonOptions); 14 | 15 | public static string ToJson(this T obj) => 16 | JsonSerializer.Serialize(obj, _jsonOptions); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Create.CreateCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 2 | { 3 | public class CreateCatalogItemRequest : BaseRequest 4 | { 5 | public int CatalogBrandId { get; set; } 6 | public int CatalogTypeId { get; set; } 7 | public string Description { get; set; } 8 | public string Name { get; set; } 9 | public string PictureUri { get; set; } 10 | public string PictureBase64 { get; set; } 11 | public string PictureName { get; set; } 12 | public decimal Price { get; set; } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/ViewModels/CatalogIndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | using System.Collections.Generic; 3 | 4 | namespace Microsoft.eShopWeb.Web.ViewModels 5 | { 6 | public class CatalogIndexViewModel 7 | { 8 | public List CatalogItems { get; set; } 9 | public List Brands { get; set; } 10 | public List Types { get; set; } 11 | public int? BrandFilterApplied { get; set; } 12 | public int? TypesFilterApplied { get; set; } 13 | public PaginationInfoViewModel PaginationInfo { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using System.Diagnostics; 4 | 5 | namespace Microsoft.eShopWeb.Web.Pages 6 | { 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | public class ErrorModel : PageModel 9 | { 10 | public string RequestId { get; set; } 11 | 12 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 13 | 14 | public void OnGet() 15 | { 16 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/ListPaged.ListPagedCatalogItemResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 5 | { 6 | public class ListPagedCatalogItemResponse : BaseResponse 7 | { 8 | public ListPagedCatalogItemResponse(Guid correlationId) : base(correlationId) 9 | { 10 | } 11 | 12 | public ListPagedCatalogItemResponse() 13 | { 14 | } 15 | 16 | public List CatalogItems { get; set; } = new List(); 17 | public int PageCount { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Web/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", 4 | "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" 5 | }, 6 | "baseUrls": { 7 | "apiBase": "http://localhost:5200/api/", 8 | "webBase": "http://host.docker.internal:5106/" 9 | }, 10 | "Logging": { 11 | "LogLevel": { 12 | "Default": "Debug", 13 | "System": "Information", 14 | "Microsoft": "Information" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/header/header.scss: -------------------------------------------------------------------------------- 1 | @import '../../../variables.scss'; 2 | 3 | .esh-header { 4 | $header-height: 4rem; 5 | 6 | background-color: $color-brand; 7 | height: $header-height; 8 | 9 | &-back { 10 | color: rgba($color-foreground-brighter, .5); 11 | line-height: $header-height; 12 | text-decoration: none; 13 | text-transform: uppercase; 14 | transition: color $animation-speed-default; 15 | 16 | &:hover { 17 | color: $color-foreground-brighter; 18 | transition: color $animation-speed-default; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/pager/pager.css: -------------------------------------------------------------------------------- 1 | .esh-pager-wrapper { 2 | padding-top: 1rem; 3 | text-align: center; } 4 | 5 | .esh-pager-item { 6 | margin: 0 5vw; } 7 | .esh-pager-item.is-disabled { 8 | opacity: .5; 9 | pointer-events: none; } 10 | .esh-pager-item--navigable { 11 | cursor: pointer; 12 | display: inline-block; } 13 | .esh-pager-item--navigable:hover { 14 | color: #83D01B; } 15 | @media screen and (max-width: 1280px) { 16 | .esh-pager-item { 17 | font-size: 0.85rem; } } 18 | @media screen and (max-width: 1024px) { 19 | .esh-pager-item { 20 | margin: 0 2.5vw; } } 21 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | eshopwebmvc: 4 | environment: 5 | - ASPNETCORE_ENVIRONMENT=Docker 6 | - ASPNETCORE_URLS=http://+:80 7 | ports: 8 | - "5106:80" 9 | volumes: 10 | - ~/.aspnet/https:/root/.aspnet/https:ro 11 | - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro 12 | eshoppublicapi: 13 | environment: 14 | - ASPNETCORE_ENVIRONMENT=Docker 15 | - ASPNETCORE_URLS=http://+:80 16 | ports: 17 | - "5200:80" 18 | volumes: 19 | - ~/.aspnet/https:/root/.aspnet/https:ro 20 | - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/BasketItemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class BasketItemConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.Property(bi => bi.UnitPrice) 12 | .IsRequired(true) 13 | .HasColumnType("decimal(18,2)"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web/Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | using System.Text.Encodings.Web; 3 | using System.Threading.Tasks; 4 | 5 | namespace Microsoft.eShopWeb.Web.Services 6 | { 7 | public static class EmailSenderExtensions 8 | { 9 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 10 | { 11 | return emailSender.SendEmailAsync(email, "Confirm your email", 12 | $"Please confirm your account by clicking this link: link"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/Pages/Basket/BasketItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.Pages.Basket 4 | { 5 | public class BasketItemViewModel 6 | { 7 | public int Id { get; set; } 8 | public int CatalogItemId { get; set; } 9 | public string ProductName { get; set; } 10 | public decimal UnitPrice { get; set; } 11 | public decimal OldUnitPrice { get; set; } 12 | 13 | [Range(0, int.MaxValue, ErrorMessage = "Quantity must be bigger than 0")] 14 | public int Quantity { get; set; } 15 | 16 | public string PictureUrl { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/PublicApi/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", 4 | "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" 5 | }, 6 | "baseUrls": { 7 | "apiBase": "http://localhost:5200/api/", 8 | "webBase": "http://host.docker.internal:5106/" 9 | }, 10 | "Logging": { 11 | "LogLevel": { 12 | "Default": "Information", 13 | "Microsoft": "Warning", 14 | "Microsoft.Hosting.Lifetime": "Information" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Helpers/RefreshBroadcast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BlazorAdmin.Helpers 4 | { 5 | internal sealed class RefreshBroadcast 6 | { 7 | private static readonly Lazy 8 | Lazy = 9 | new Lazy 10 | (() => new RefreshBroadcast()); 11 | 12 | public static RefreshBroadcast Instance => Lazy.Value; 13 | 14 | private RefreshBroadcast() 15 | { 16 | } 17 | 18 | public event Action RefreshRequested; 19 | public void CallRequestRefresh() 20 | { 21 | RefreshRequested?.Invoke(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Web/Pages/Admin/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using Microsoft.eShopWeb.ApplicationCore.Constants; 4 | using Microsoft.eShopWeb.Web.Extensions; 5 | using Microsoft.eShopWeb.Web.Services; 6 | using Microsoft.eShopWeb.Web.ViewModels; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using System.Threading.Tasks; 9 | 10 | namespace Microsoft.eShopWeb.Web.Pages.Admin 11 | { 12 | [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)] 13 | public class IndexModel : PageModel 14 | { 15 | public IndexModel() 16 | { 17 | 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Account/LoginWith2faViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Account 4 | { 5 | public class LoginWith2faViewModel 6 | { 7 | [Required] 8 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 9 | [DataType(DataType.Text)] 10 | [Display(Name = "Authenticator code")] 11 | public string TwoFactorCode { get; set; } 12 | 13 | [Display(Name = "Remember this machine")] 14 | public bool RememberMachine { get; set; } 15 | 16 | public bool RememberMe { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/wwwroot/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/EnableAuthenticatorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 5 | { 6 | public class EnableAuthenticatorViewModel 7 | { 8 | [Required] 9 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 10 | [DataType(DataType.Text)] 11 | [Display(Name = "Verification Code")] 12 | public string Code { get; set; } 13 | 14 | [ReadOnly(true)] 15 | public string SharedKey { get; set; } 16 | 17 | public string AuthenticatorUri { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Helpers/BlazorComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace BlazorAdmin.Helpers 4 | { 5 | public class BlazorComponent : ComponentBase 6 | { 7 | private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance; 8 | 9 | protected override void OnInitialized() 10 | { 11 | _refresh.RefreshRequested += DoRefresh; 12 | base.OnInitialized(); 13 | } 14 | 15 | public void CallRequestRefresh() 16 | { 17 | _refresh.CallRequestRefresh(); 18 | } 19 | 20 | private void DoRefresh() 21 | { 22 | StateHasChanged(); 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Infrastructure/Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | using System.Threading.Tasks; 3 | 4 | namespace Microsoft.eShopWeb.Infrastructure.Services 5 | { 6 | // This class is used by the application to send email for account confirmation and password reset. 7 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 8 | public class EmailSender : IEmailSender 9 | { 10 | public Task SendEmailAsync(string email, string subject, string message) 11 | { 12 | // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. 13 | return Task.CompletedTask; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web/ViewModels/OrderViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.eShopWeb.Web.ViewModels 6 | { 7 | public class OrderViewModel 8 | { 9 | private const string DEFAULT_STATUS = "Pending"; 10 | 11 | public int OrderNumber { get; set; } 12 | public DateTimeOffset OrderDate { get; set; } 13 | public decimal Total { get; set; } 14 | public string Status => DEFAULT_STATUS; 15 | public Address ShippingAddress { get; set; } 16 | public List OrderItems { get; set; } = new List(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "ConnectionStrings": { 7 | "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", 8 | "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" 9 | }, 10 | "CatalogBaseUrl": "", 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Warning", 15 | "Microsoft": "Warning", 16 | "System": "Warning" 17 | }, 18 | "AllowedHosts": "*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Specifications 5 | { 6 | public class CatalogFilterPaginatedSpecification : Specification 7 | { 8 | public CatalogFilterPaginatedSpecification(int skip, int take, int? brandId, int? typeId) 9 | : base() 10 | { 11 | Query 12 | .Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) && 13 | (!typeId.HasValue || i.CatalogTypeId == typeId)) 14 | .Paginate(skip, take); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BlazorAdmin/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Authorization; 4 | @using Microsoft.AspNetCore.Components.Authorization 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 9 | @using Microsoft.JSInterop 10 | @using Microsoft.Extensions.Logging 11 | @using BlazorAdmin 12 | @using BlazorAdmin.Shared 13 | @using BlazorAdmin.Services 14 | @using BlazorAdmin.JavaScript 15 | @using BlazorShared.Authorization 16 | @using BlazorShared.Interfaces 17 | @using BlazorInputFile 18 | @using BlazorShared.Models 19 | -------------------------------------------------------------------------------- /src/PublicApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrls": { 3 | "apiBase": "https://localhost:5099/api/", 4 | "webBase": "https://localhost:44315/" 5 | }, 6 | "ConnectionStrings": { 7 | "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", 8 | "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" 9 | }, 10 | "CatalogBaseUrl": "", 11 | "Logging": { 12 | "IncludeScopes": false, 13 | "LogLevel": { 14 | "Default": "Warning", 15 | "Microsoft": "Warning", 16 | "System": "Warning" 17 | }, 18 | "AllowedHosts": "*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate 2 | { 3 | public class OrderItem : BaseEntity 4 | { 5 | public CatalogItemOrdered ItemOrdered { get; private set; } 6 | public decimal UnitPrice { get; private set; } 7 | public int Units { get; private set; } 8 | 9 | private OrderItem() 10 | { 11 | // required by EF 12 | } 13 | 14 | public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units) 15 | { 16 | ItemOrdered = itemOrdered; 17 | UnitPrice = unitPrice; 18 | Units = units; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Helpers/BlazorLayoutComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace BlazorAdmin.Helpers 4 | { 5 | public class BlazorLayoutComponent : LayoutComponentBase 6 | { 7 | private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance; 8 | 9 | protected override void OnInitialized() 10 | { 11 | _refresh.RefreshRequested += DoRefresh; 12 | base.OnInitialized(); 13 | } 14 | 15 | public void CallRequestRefresh() 16 | { 17 | _refresh.CallRequestRefresh(); 18 | } 19 | 20 | private void DoRefresh() 21 | { 22 | StateHasChanged(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web/Pages/Shared/_editCatalog.cshtml: -------------------------------------------------------------------------------- 1 | @model CatalogItemViewModel 2 | 3 |
4 |
5 | 6 |
7 | @Model.Name 8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /tests/UnitTests/Web/Extensions/CacheHelpersTests/GenerateCatalogItemCacheKey.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.Web; 2 | using Microsoft.eShopWeb.Web.Extensions; 3 | using Xunit; 4 | 5 | namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests 6 | { 7 | public class GenerateCatalogItemCacheKey 8 | { 9 | [Fact] 10 | public void ReturnsCatalogItemCacheKey() 11 | { 12 | var pageIndex = 0; 13 | int? brandId = null; 14 | int? typeId = null; 15 | 16 | var result = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId); 17 | 18 | Assert.Equal("items-0-10--", result); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BlazorAdmin/JavaScript/Css.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Threading.Tasks; 3 | 4 | namespace BlazorAdmin.JavaScript 5 | { 6 | public class Css 7 | { 8 | private readonly IJSRuntime _jsRuntime; 9 | 10 | public Css(IJSRuntime jsRuntime) 11 | { 12 | _jsRuntime = jsRuntime; 13 | } 14 | 15 | public async Task ShowBodyOverflow() 16 | { 17 | await _jsRuntime.InvokeAsync(JSInteropConstants.ShowBodyOverflow); 18 | } 19 | 20 | public async Task HideBodyOverflow() 21 | { 22 | return await _jsRuntime.InvokeAsync(JSInteropConstants.HideBodyOverflow); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/identity/identity.min.css: -------------------------------------------------------------------------------- 1 | .esh-identity{line-height:3rem;position:relative;text-align:right;}.esh-identity-section{display:inline-block;width:100%;}.esh-identity-name{display:inline-block;}.esh-identity-name--upper{text-transform:uppercase;}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem;}}.esh-identity-image{display:inline-block;}.esh-identity-drop{background:#fff;height:10px;width:10rem;overflow:hidden;padding:.5rem;position:absolute;right:0;top:2.5rem;transition:height .35s;}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:10rem;transition:height .35s;}.esh-identity-item{cursor:pointer;transition:color .35s;}.esh-identity-item:hover{color:#75b918;transition:color .35s;} -------------------------------------------------------------------------------- /src/BlazorAdmin/JavaScript/Cookies.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Threading.Tasks; 3 | 4 | namespace BlazorAdmin.JavaScript 5 | { 6 | public class Cookies 7 | { 8 | private readonly IJSRuntime _jsRuntime; 9 | 10 | public Cookies(IJSRuntime jsRuntime) 11 | { 12 | _jsRuntime = jsRuntime; 13 | } 14 | 15 | public async Task DeleteCookie(string name) 16 | { 17 | await _jsRuntime.InvokeAsync(JSInteropConstants.DeleteCookie, name); 18 | } 19 | 20 | public async Task GetCookie(string name) 21 | { 22 | return await _jsRuntime.InvokeAsync(JSInteropConstants.GetCookie, name); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web/Pages/Shared/_product.cshtml: -------------------------------------------------------------------------------- 1 | @model CatalogItemViewModel 2 | 3 |
4 | 5 | 6 |
7 | @Model.Name 8 |
9 |
10 | @Model.Price.ToString("N2") 11 |
12 | 13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/ApplicationCore/ApplicationCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Microsoft.eShopWeb.ApplicationCore 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ApplicationCore/Specifications/BasketWithItemsSpecification.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 3 | 4 | namespace Microsoft.eShopWeb.ApplicationCore.Specifications 5 | { 6 | public sealed class BasketWithItemsSpecification : Specification 7 | { 8 | public BasketWithItemsSpecification(int basketId) 9 | { 10 | Query 11 | .Where(b => b.Id == basketId) 12 | .Include(b => b.Items); 13 | } 14 | 15 | public BasketWithItemsSpecification(string buyerId) 16 | { 17 | Query 18 | .Where(b => b.BuyerId == buyerId) 19 | .Include(b => b.Items); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/BasketConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class BasketConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); 12 | navigation.SetPropertyAccessMode(PropertyAccessMode.Field); 13 | 14 | builder.Property(b => b.BuyerId) 15 | .IsRequired() 16 | .HasMaxLength(40); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/PublicApi/AuthEndpoints/Authenticate.AuthenticateResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints 4 | { 5 | public class AuthenticateResponse : BaseResponse 6 | { 7 | public AuthenticateResponse(Guid correlationId) : base(correlationId) 8 | { 9 | } 10 | 11 | public AuthenticateResponse() 12 | { 13 | } 14 | public bool Result { get; set; } = false; 15 | public string Token { get; set; } = string.Empty; 16 | public string Username { get; set; } = string.Empty; 17 | public bool IsLockedOut { get; set; } = false; 18 | public bool IsNotAllowed { get; set; } = false; 19 | public bool RequiresTwoFactor { get; set; } = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Extensions/TestParent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | 6 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions 7 | { 8 | public class TestParent : IEquatable 9 | { 10 | public int Id { get; set; } 11 | 12 | public string Name { get; set; } 13 | 14 | public IEnumerable Children { get; set; } 15 | 16 | public bool Equals([AllowNull] TestParent other) => 17 | other?.Id == Id && other?.Name == Name && 18 | (other?.Children is null && Children is null || 19 | (other?.Children?.Zip(Children)?.All(t => t.First?.Equals(t.Second) ?? false) ?? false)); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class CatalogTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(ci => ci.Id); 12 | 13 | builder.Property(ci => ci.Id) 14 | .UseHiLo("catalog_type_hilo") 15 | .IsRequired(); 16 | 17 | builder.Property(cb => cb.Type) 18 | .IsRequired() 19 | .HasMaxLength(100); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class CatalogBrandConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(ci => ci.Id); 12 | 13 | builder.Property(ci => ci.Id) 14 | .UseHiLo("catalog_brand_hilo") 15 | .IsRequired(); 16 | 17 | builder.Property(cb => cb.Brand) 18 | .IsRequired() 19 | .HasMaxLength(100); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Infrastructure/Logging/LoggerAdapter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Microsoft.eShopWeb.Infrastructure.Logging 5 | { 6 | public class LoggerAdapter : IAppLogger 7 | { 8 | private readonly ILogger _logger; 9 | public LoggerAdapter(ILoggerFactory loggerFactory) 10 | { 11 | _logger = loggerFactory.CreateLogger(); 12 | } 13 | 14 | public void LogWarning(string message, params object[] args) 15 | { 16 | _logger.LogWarning(message, args); 17 | } 18 | 19 | public void LogInformation(string message, params object[] args) 20 | { 21 | _logger.LogInformation(message, args); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/catalog/pager.css: -------------------------------------------------------------------------------- 1 | .esh-pager-wrapper { 2 | padding-top: 1rem; 3 | text-align: center; 4 | } 5 | 6 | .esh-pager-item-left { 7 | float: left; 8 | } 9 | 10 | .esh-pager-item-right { 11 | float: right; 12 | } 13 | 14 | .esh-pager-item--navigable { 15 | display: inline-block; 16 | cursor: pointer; 17 | } 18 | 19 | .esh-pager-item--navigable.is-disabled { 20 | opacity: 0; 21 | pointer-events: none; 22 | } 23 | 24 | .esh-pager-item--navigable:hover { 25 | color: #83D01B; 26 | } 27 | 28 | @media screen and (max-width: 1280px) { 29 | .esh-pager-item { 30 | font-size: 0.85rem; 31 | } 32 | } 33 | 34 | @media screen and (max-width: 1024px) { 35 | .esh-pager-item { 36 | margin: 0 4vw; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ApplicationCore/Exceptions/BasketNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Exceptions 4 | { 5 | public class BasketNotFoundException : Exception 6 | { 7 | public BasketNotFoundException(int basketId) : base($"No basket found with id {basketId}") 8 | { 9 | } 10 | 11 | protected BasketNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) 12 | { 13 | } 14 | 15 | public BasketNotFoundException(string message) : base(message) 16 | { 17 | } 18 | 19 | public BasketNotFoundException(string message, Exception innerException) : base(message, innerException) 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/Dockerfile: -------------------------------------------------------------------------------- 1 | # RUN ALL CONTAINERS FROM ROOT (folder with .sln file): 2 | # docker-compose build 3 | # docker-compose up 4 | # 5 | # RUN JUST THIS CONTAINER FROM ROOT (folder with .sln file): 6 | # docker build --pull -t web -f src/Web/Dockerfile . 7 | # 8 | # RUN COMMAND 9 | # docker run --name eshopweb --rm -it -p 5106:5106 web 10 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 11 | WORKDIR /app 12 | 13 | COPY *.sln . 14 | COPY . . 15 | WORKDIR /app/src/Web 16 | RUN dotnet restore 17 | 18 | RUN dotnet publish -c Release -o out 19 | 20 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime 21 | WORKDIR /app 22 | COPY --from=build /app/src/Web/out ./ 23 | 24 | # Optional: Set this here if not setting it from docker-compose.yml 25 | # ENV ASPNETCORE_ENVIRONMENT Development 26 | 27 | ENTRYPOINT ["dotnet", "Web.dll"] 28 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css: -------------------------------------------------------------------------------- 1 | .esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s;}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none;}.esh-basketstatus-image{height:36px;margin-top:.5rem;}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem;}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem;}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s;} -------------------------------------------------------------------------------- /src/Web/Views/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @inject SignInManager SignInManager 2 | @{ 3 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 4 | } 5 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Entities/BasketTests/BasketRemoveEmptyItems.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 2 | using Xunit; 3 | 4 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.BasketTests 5 | { 6 | public class BasketRemoveEmptyItems 7 | { 8 | private readonly int _testCatalogItemId = 123; 9 | private readonly decimal _testUnitPrice = 1.23m; 10 | private readonly string _buyerId = "Test buyerId"; 11 | 12 | [Fact] 13 | public void RemovesEmptyBasketItems() 14 | { 15 | var basket = new Basket(_buyerId); 16 | basket.AddItem(_testCatalogItemId, _testUnitPrice, 0); 17 | basket.RemoveEmptyItems(); 18 | 19 | Assert.Equal(0, basket.Items.Count); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/OrderAggregate/Address.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate 2 | { 3 | public class Address // ValueObject 4 | { 5 | public string Street { get; private set; } 6 | 7 | public string City { get; private set; } 8 | 9 | public string State { get; private set; } 10 | 11 | public string Country { get; private set; } 12 | 13 | public string ZipCode { get; private set; } 14 | 15 | private Address() { } 16 | 17 | public Address(string street, string city, string state, string country, string zipcode) 18 | { 19 | Street = street; 20 | City = city; 21 | State = state; 22 | Country = country; 23 | ZipCode = zipcode; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/app.component.scss: -------------------------------------------------------------------------------- 1 | @import './_variables.scss'; 2 | 3 | .esh-app { 4 | &-footer { 5 | $margin: 2.5rem; 6 | $padding: 2.5rem; 7 | 8 | background-color: $color-background-darker; 9 | border-top: $border-light solid $color-foreground-bright; 10 | margin-top: $margin; 11 | padding-bottom: $padding; 12 | padding-top: $padding; 13 | width: 100%; 14 | bottom: 0; 15 | $height: 50px; 16 | 17 | &-brand { 18 | height: $height; 19 | width: 230px; 20 | } 21 | } 22 | 23 | &-header { 24 | margin: 15px; 25 | } 26 | 27 | &-wrapper { 28 | display: flex; 29 | min-height: 100vh; 30 | flex-direction: column; 31 | justify-content: space-between 32 | } 33 | } -------------------------------------------------------------------------------- /src/ApplicationCore/Exceptions/GuardExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 2 | using Microsoft.eShopWeb.ApplicationCore.Exceptions; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Ardalis.GuardClauses 7 | { 8 | public static class BasketGuards 9 | { 10 | public static void NullBasket(this IGuardClause guardClause, int basketId, Basket basket) 11 | { 12 | if (basket == null) 13 | throw new BasketNotFoundException(basketId); 14 | } 15 | 16 | public static void EmptyBasketOnCheckout(this IGuardClause guardClause, IReadOnlyCollection basketItems) 17 | { 18 | if (!basketItems.Any()) 19 | throw new EmptyBasketOnCheckoutException(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/PublicApi/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | using Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints; 4 | using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; 5 | using Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints; 6 | 7 | namespace Microsoft.eShopWeb.PublicApi 8 | { 9 | public class MappingProfile : Profile 10 | { 11 | public MappingProfile() 12 | { 13 | CreateMap(); 14 | CreateMap() 15 | .ForMember(dto => dto.Name, options => options.MapFrom(src => src.Type)); 16 | CreateMap() 17 | .ForMember(dto => dto.Name, options => options.MapFrom(src => src.Brand)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/SetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 4 | { 5 | public class SetPasswordViewModel 6 | { 7 | [Required] 8 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 9 | [DataType(DataType.Password)] 10 | [Display(Name = "New password")] 11 | public string NewPassword { get; set; } 12 | 13 | [DataType(DataType.Password)] 14 | [Display(Name = "Confirm new password")] 15 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 16 | public string ConfirmPassword { get; set; } 17 | 18 | public string StatusMessage { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | {1FCBE191-34FE-4B2E-8915-CA81553958AD} 7 | True 8 | {Scheme}://localhost:{ServicePort} 9 | eshopwebmvc 10 | 11 | 12 | 13 | docker-compose.yml 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/OrderRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 3 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.eShopWeb.Infrastructure.Data 7 | { 8 | public class OrderRepository : EfRepository, IOrderRepository 9 | { 10 | public OrderRepository(CatalogContext dbContext) : base(dbContext) 11 | { 12 | } 13 | 14 | public Task GetByIdWithItemsAsync(int id) 15 | { 16 | return _dbContext.Orders 17 | .Include(o => o.OrderItems) 18 | .Include($"{nameof(Order.OrderItems)}.{nameof(OrderItem.ItemOrdered)}") 19 | .FirstOrDefaultAsync(x => x.Id == id); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Web/Extensions/CacheHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.Web.Extensions 4 | { 5 | public static class CacheHelpers 6 | { 7 | public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30); 8 | private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}"; 9 | 10 | public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId) 11 | { 12 | return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId); 13 | } 14 | 15 | public static string GenerateBrandsCacheKey() 16 | { 17 | return "brands"; 18 | } 19 | 20 | public static string GenerateTypesCacheKey() 21 | { 22 | return "types"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/PublicApi/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build 9 | WORKDIR /app 10 | COPY . . 11 | #COPY ["src/PublicApi/PublicApi.csproj", "./PublicApi/"] 12 | #RUN dotnet restore "./PublicApi/PublicApi.csproj" 13 | #COPY . . 14 | WORKDIR "/app/src/PublicApi" 15 | RUN dotnet restore 16 | 17 | RUN dotnet build "./PublicApi.csproj" -c Release -o /app/build 18 | 19 | FROM build AS publish 20 | RUN dotnet publish "./PublicApi.csproj" -c Release -o /app/publish 21 | 22 | FROM base AS final 23 | WORKDIR /app 24 | COPY --from=publish /app/publish . 25 | ENTRYPOINT ["dotnet", "PublicApi.dll"] -------------------------------------------------------------------------------- /src/ApplicationCore/Exceptions/EmptyBasketOnCheckoutException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Exceptions 4 | { 5 | public class EmptyBasketOnCheckoutException : Exception 6 | { 7 | public EmptyBasketOnCheckoutException() 8 | : base($"Basket cannot have 0 items on checkout") 9 | { 10 | } 11 | 12 | protected EmptyBasketOnCheckoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) 13 | { 14 | } 15 | 16 | public EmptyBasketOnCheckoutException(string message) : base(message) 17 | { 18 | } 19 | 20 | public EmptyBasketOnCheckoutException(string message, Exception innerException) : base(message, innerException) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/pager/pager.scss: -------------------------------------------------------------------------------- 1 | @import '../../../variables.scss'; 2 | 3 | .esh-pager { 4 | 5 | &-wrapper { 6 | padding-top: 1rem; 7 | text-align: center; 8 | } 9 | 10 | &-item { 11 | $margin: 5vw; 12 | margin: 0 $margin; 13 | 14 | &.is-disabled { 15 | opacity: .5; 16 | pointer-events: none; 17 | } 18 | 19 | &--navigable { 20 | cursor: pointer; 21 | display: inline-block; 22 | 23 | &:hover { 24 | color: $color-secondary; 25 | } 26 | } 27 | 28 | @media screen and (max-width: $media-screen-l) { 29 | font-size: $font-size-s; 30 | } 31 | 32 | @media screen and (max-width: $media-screen-m) { 33 | margin: 0 $margin / 2; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.GuardClauses; 2 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate 6 | { 7 | public class Buyer : BaseEntity, IAggregateRoot 8 | { 9 | public string IdentityGuid { get; private set; } 10 | 11 | private List _paymentMethods = new List(); 12 | 13 | public IEnumerable PaymentMethods => _paymentMethods.AsReadOnly(); 14 | 15 | private Buyer() 16 | { 17 | // required by EF 18 | } 19 | 20 | public Buyer(string identity) : this() 21 | { 22 | Guard.Against.NullOrEmpty(identity, nameof(identity)); 23 | IdentityGuid = identity; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/css/site.min.css", 4 | "inputFiles": [ 5 | "wwwroot/css/app.css", 6 | "wwwroot/css/app.component.css", 7 | "wwwroot/css/shared/components/header/header.css", 8 | "wwwroot/css/shared/components/identity/identity.css", 9 | "wwwroot/css/shared/components/pager/pager.css", 10 | "wwwroot/css/basket/basket.component.css", 11 | "wwwroot/css/basket/basket-status/basket-status.component.css", 12 | "wwwroot/css/catalog/catalog.component.css", 13 | "wwwroot/css/orders/orders.component.css" 14 | ] 15 | }, 16 | { 17 | "outputFileName": "wwwroot/js/site.min.js", 18 | "inputFiles": [ 19 | "wwwroot/js/site.js" 20 | ], 21 | "minify": { 22 | "enabled": true, 23 | "renameLocals": true 24 | }, 25 | "sourceMap": false 26 | } 27 | ] -------------------------------------------------------------------------------- /src/BlazorAdmin/ServicesConfiguration.cs: -------------------------------------------------------------------------------- 1 | using BlazorAdmin.Services; 2 | using BlazorShared.Interfaces; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace BlazorAdmin 6 | { 7 | public static class ServicesConfiguration 8 | { 9 | public static IServiceCollection AddBlazorServices(this IServiceCollection services) 10 | { 11 | services.AddScoped(); 12 | services.AddScoped(); 13 | services.AddScoped(); 14 | services.AddScoped(); 15 | services.AddScoped(); 16 | services.AddScoped(); 17 | 18 | return services; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/PublicApi/CatalogItemEndpoints/Update.UpdateCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints 4 | { 5 | public class UpdateCatalogItemRequest : BaseRequest 6 | { 7 | [Range(1, 10000)] 8 | public int Id { get; set; } 9 | [Range(1, 10000)] 10 | public int CatalogBrandId { get; set; } 11 | [Range(1, 10000)] 12 | public int CatalogTypeId { get; set; } 13 | [Required] 14 | public string Description { get; set; } 15 | [Required] 16 | public string Name { get; set; } 17 | public string PictureBase64 { get; set; } 18 | public string PictureUri { get; set; } 19 | public string PictureName { get; set; } 20 | [Range(0.01, 10000)] 21 | public decimal Price { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Account/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Account 4 | { 5 | public class RegisterViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | [Display(Name = "Email")] 10 | public string Email { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "Password")] 16 | public string Password { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm password")] 20 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 21 | public string ConfirmPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Account/ResetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Account 4 | { 5 | public class ResetPasswordViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | 11 | [Required] 12 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Password)] 14 | public string Password { get; set; } 15 | 16 | [DataType(DataType.Password)] 17 | [Display(Name = "Confirm password")] 18 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 19 | public string ConfirmPassword { get; set; } 20 | 21 | public string Code { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Infrastructure/Identity/AppIdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Identity 6 | { 7 | public class AppIdentityDbContext : IdentityDbContext 8 | { 9 | public AppIdentityDbContext(DbContextOptions options) 10 | : base(options) 11 | { 12 | } 13 | 14 | protected override void OnModelCreating(ModelBuilder builder) 15 | { 16 | base.OnModelCreating(builder); 17 | // Customize the ASP.NET Identity model and override the defaults if needed. 18 | // For example, you can rename the ASP.NET Identity table names and more. 19 | // Add your customizations after calling base.OnModelCreating(builder); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @model GenerateRecoveryCodesViewModel 2 | @{ 3 | ViewData["Title"] = "Recovery codes"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 | 17 |
18 |
19 | @for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2) 20 | { 21 | @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
22 | } 23 |
24 |
-------------------------------------------------------------------------------- /src/Web/Views/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 4 | } 5 | 6 |

@ViewData["Title"]

7 | 8 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/OrderItemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class OrderItemConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.OwnsOne(i => i.ItemOrdered, io => 12 | { 13 | io.WithOwner(); 14 | 15 | io.Property(cio => cio.ProductName) 16 | .HasMaxLength(50) 17 | .IsRequired(); 18 | }); 19 | 20 | builder.Property(oi => oi.UnitPrice) 21 | .IsRequired(true) 22 | .HasColumnType("decimal(18,2)"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/PublicApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52023", 7 | "sslPort": 44339 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "PublicApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "https://localhost:5099;http://localhost:5098" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/UnitTests/Builders/AddressBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 2 | 3 | namespace Microsoft.eShopWeb.UnitTests.Builders 4 | { 5 | public class AddressBuilder 6 | { 7 | private Address _address; 8 | public string TestStreet => "123 Main St."; 9 | public string TestCity => "Kent"; 10 | public string TestState => "OH"; 11 | public string TestCountry => "USA"; 12 | public string TestZipCode => "44240"; 13 | 14 | public AddressBuilder() 15 | { 16 | _address = WithDefaultValues(); 17 | } 18 | public Address Build() 19 | { 20 | return _address; 21 | } 22 | public Address WithDefaultValues() 23 | { 24 | _address = new Address(TestStreet, TestCity, TestState, TestCountry, TestZipCode); 25 | return _address; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Reset authenticator key"; 3 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 4 | } 5 | 6 |

@ViewData["Title"]

7 | 17 |
18 |
19 | 20 |
21 |
-------------------------------------------------------------------------------- /src/Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers 6 | { 7 | [Collection("Sequential")] 8 | public class CatalogControllerIndex : IClassFixture 9 | { 10 | public CatalogControllerIndex(WebTestFixture factory) 11 | { 12 | Client = factory.CreateClient(); 13 | } 14 | 15 | public HttpClient Client { get; } 16 | 17 | [Fact] 18 | public async Task ReturnsHomePageWithProductListing() 19 | { 20 | // Arrange & Act 21 | var response = await Client.GetAsync("/"); 22 | response.EnsureSuccessStatusCode(); 23 | var stringResponse = await response.Content.ReadAsStringAsync(); 24 | 25 | // Assert 26 | Assert.Contains(".NET Bot Black Sweatshirt", stringResponse); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | using Microsoft.eShopWeb.Web.Services; 3 | using Microsoft.eShopWeb.Web.ViewModels; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.eShopWeb.Web.Pages 7 | { 8 | public class IndexModel : PageModel 9 | { 10 | private readonly ICatalogViewModelService _catalogViewModelService; 11 | 12 | public IndexModel(ICatalogViewModelService catalogViewModelService) 13 | { 14 | _catalogViewModelService = catalogViewModelService; 15 | } 16 | 17 | public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel(); 18 | 19 | public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId) 20 | { 21 | CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/FunctionalTests/Web/Pages/HomePageOnGet.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.FunctionalTests.Web; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages 7 | { 8 | [Collection("Sequential")] 9 | public class HomePageOnGet : IClassFixture 10 | { 11 | public HomePageOnGet(WebTestFixture factory) 12 | { 13 | Client = factory.CreateClient(); 14 | } 15 | 16 | public HttpClient Client { get; } 17 | 18 | [Fact] 19 | public async Task ReturnsHomePageWithProductListing() 20 | { 21 | // Arrange & Act 22 | var response = await Client.GetAsync("/"); 23 | response.EnsureSuccessStatusCode(); 24 | var stringResponse = await response.Content.ReadAsStringAsync(); 25 | 26 | // Assert 27 | Assert.Contains(".NET Bot Black Sweatshirt", stringResponse); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Web/ViewModels/Manage/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.eShopWeb.Web.ViewModels.Manage 4 | { 5 | public class ChangePasswordViewModel 6 | { 7 | [Required] 8 | [DataType(DataType.Password)] 9 | [Display(Name = "Current password")] 10 | public string OldPassword { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "New password")] 16 | public string NewPassword { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm new password")] 20 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 21 | public string ConfirmPassword { get; set; } 22 | 23 | public string StatusMessage { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/basket/basket.component.min.css: -------------------------------------------------------------------------------- 1 | .esh-basket{min-height:80vh;}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem;}.esh-basket-titles--clean{padding-bottom:0;padding-top:0;}.esh-basket-title{text-transform:uppercase;}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0;}.esh-basket-items--border:last-of-type{border-color:transparent;}.esh-basket-items-margin-left1{margin-left:1px;}.esh-basket-item{font-size:1rem;font-weight:300;}.esh-basket-item--middle{line-height:8rem;}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem;}}.esh-basket-item--mark{color:#00a69c;}.esh-basket-image{height:8rem;}.esh-basket-input{line-height:1rem;width:100%;}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s;}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s;}.esh-basket-checkout:visited{color:#fff;} -------------------------------------------------------------------------------- /src/Web/wwwroot/css/orders/orders.component.min.css: -------------------------------------------------------------------------------- 1 | .esh-orders{min-height:80vh;overflow-x:hidden;}.esh-orders-header{background-color:#00a69c;height:4rem;}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s;}.esh-orders-back:hover{color:#fff;transition:color .35s;}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem;}.esh-orders-title{text-transform:uppercase;}.esh-orders-items{height:2rem;line-height:2rem;position:relative;}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1;}.esh-orders-item{font-weight:300;}.esh-orders-item--hover{opacity:0;pointer-events:none;}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all;}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s;}.esh-orders-link:hover{color:#75b918;transition:color .35s;}.esh-orders-detail-section{padding-bottom:30px;}.esh-orders-detail-title{font-size:25px;} -------------------------------------------------------------------------------- /src/PublicApi/ImageValidators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Microsoft.eShopWeb.PublicApi 5 | { 6 | public static class ImageValidators 7 | { 8 | private const int ImageMaximumBytes = 512000; 9 | 10 | public static bool IsValidImage(this byte[] postedFile, string fileName) 11 | { 12 | return postedFile != null && postedFile.Length > 0 && postedFile.Length <= ImageMaximumBytes && IsExtensionValid(fileName); 13 | } 14 | 15 | private static bool IsExtensionValid(string fileName) 16 | { 17 | var extension = Path.GetExtension(fileName); 18 | 19 | return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) || 20 | string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) || 21 | string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) || 22 | string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web/Configuration/ConfigureWebServices.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.eShopWeb.Web.Interfaces; 3 | using Microsoft.eShopWeb.Web.Services; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Microsoft.eShopWeb.Web.Configuration 8 | { 9 | public static class ConfigureWebServices 10 | { 11 | public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) 12 | { 13 | services.AddMediatR(typeof(BasketViewModelService).Assembly); 14 | services.AddScoped(); 15 | services.AddScoped(); 16 | services.AddScoped(); 17 | services.Configure(configuration); 18 | services.AddScoped(); 19 | 20 | return services; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:17469", 7 | "sslPort": 44315 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | }, 18 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 19 | }, 20 | "Web - PROD": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Production" 26 | }, 27 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/BlazorAdmin/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:58126", 7 | "sslPort": 44315 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "BlazorAdmin": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/BlazorShared/Models/CreateCatalogItemRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BlazorShared.Models 4 | { 5 | public class CreateCatalogItemRequest 6 | { 7 | public int CatalogTypeId { get; set; } 8 | 9 | public int CatalogBrandId { get; set; } 10 | 11 | [Required(ErrorMessage = "The Name field is required")] 12 | public string Name { get; set; } = string.Empty; 13 | 14 | [Required(ErrorMessage = "The Description field is required")] 15 | public string Description { get; set; } = string.Empty; 16 | 17 | // decimal(18,2) 18 | [RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")] 19 | [Range(0.01, 1000)] 20 | [DataType(DataType.Currency)] 21 | public decimal Price { get; set; } = 0; 22 | 23 | public string PictureUri { get; set; } = string.Empty; 24 | public string PictureBase64 { get; set; } = string.Empty; 25 | public string PictureName { get; set; } = string.Empty; 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Web/Services/CatalogItemViewModelService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities; 2 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 3 | using Microsoft.eShopWeb.Web.Interfaces; 4 | using Microsoft.eShopWeb.Web.ViewModels; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.eShopWeb.Web.Services 8 | { 9 | public class CatalogItemViewModelService : ICatalogItemViewModelService 10 | { 11 | private readonly IAsyncRepository _catalogItemRepository; 12 | 13 | public CatalogItemViewModelService(IAsyncRepository catalogItemRepository) 14 | { 15 | _catalogItemRepository = catalogItemRepository; 16 | } 17 | 18 | public async Task UpdateCatalogItem(CatalogItemViewModel viewModel) 19 | { 20 | var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id); 21 | existingCatalogItem.UpdateDetails(viewModel.Name, existingCatalogItem.Description, viewModel.Price); 22 | await _catalogItemRepository.UpdateAsync(existingCatalogItem); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/BasketAggregate/BasketItem.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.GuardClauses; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate 4 | { 5 | public class BasketItem : BaseEntity 6 | { 7 | 8 | public decimal UnitPrice { get; private set; } 9 | public int Quantity { get; private set; } 10 | public int CatalogItemId { get; private set; } 11 | public int BasketId { get; private set; } 12 | 13 | public BasketItem(int catalogItemId, int quantity, decimal unitPrice) 14 | { 15 | CatalogItemId = catalogItemId; 16 | UnitPrice = unitPrice; 17 | SetQuantity(quantity); 18 | } 19 | 20 | public void AddQuantity(int quantity) 21 | { 22 | Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue); 23 | 24 | Quantity += quantity; 25 | } 26 | 27 | public void SetQuantity(int quantity) 28 | { 29 | Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue); 30 | 31 | Quantity = quantity; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Testing; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers 8 | { 9 | [Collection("Sequential")] 10 | public class OrderIndexOnGet : IClassFixture 11 | { 12 | public OrderIndexOnGet(WebTestFixture factory) 13 | { 14 | Client = factory.CreateClient(new WebApplicationFactoryClientOptions 15 | { 16 | AllowAutoRedirect = false 17 | }); 18 | } 19 | 20 | public HttpClient Client { get; } 21 | 22 | [Fact] 23 | public async Task ReturnsRedirectGivenAnonymousUser() 24 | { 25 | var response = await Client.GetAsync("/order/my-orders"); 26 | var redirectLocation = response.Headers.Location.OriginalString; 27 | 28 | Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); 29 | Assert.Contains("/Account/Login", redirectLocation); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Web/Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 17 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/UnitTests/Builders/BasketBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 2 | using Moq; 3 | 4 | namespace Microsoft.eShopWeb.UnitTests.Builders 5 | { 6 | public class BasketBuilder 7 | { 8 | private Basket _basket; 9 | public string BasketBuyerId => "testbuyerId@test.com"; 10 | 11 | public int BasketId => 1; 12 | 13 | public BasketBuilder() 14 | { 15 | _basket = WithNoItems(); 16 | } 17 | 18 | public Basket Build() 19 | { 20 | return _basket; 21 | } 22 | 23 | public Basket WithNoItems() 24 | { 25 | var basketMock = new Mock(BasketBuyerId); 26 | basketMock.SetupGet(s => s.Id).Returns(BasketId); 27 | 28 | _basket = basketMock.Object; 29 | return _basket; 30 | } 31 | 32 | public Basket WithOneBasketItem() 33 | { 34 | var basketMock = new Mock(BasketBuyerId); 35 | _basket = basketMock.Object; 36 | _basket.AddItem(2, 3.40m, 4); 37 | return _basket; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/identity/identity.css: -------------------------------------------------------------------------------- 1 | .esh-identity { 2 | line-height: 3rem; 3 | position: relative; 4 | text-align: right; 5 | } 6 | 7 | .esh-identity-section { 8 | display: inline-block; 9 | width: 100%; 10 | } 11 | 12 | .esh-identity-name { 13 | display: inline-block; 14 | } 15 | 16 | .esh-identity-name--upper { 17 | text-transform: uppercase; 18 | } 19 | 20 | @media screen and (max-width: 768px) { 21 | .esh-identity-name { 22 | font-size: 0.85rem; 23 | } 24 | } 25 | 26 | .esh-identity-image { 27 | display: inline-block; 28 | } 29 | 30 | .esh-identity-drop { 31 | background: #FFFFFF; 32 | height: 10px; 33 | width: 10rem; 34 | overflow: hidden; 35 | padding: .5rem; 36 | position: absolute; 37 | right: 0; 38 | top: 2.5rem; 39 | transition: height 0.35s; 40 | } 41 | 42 | .esh-identity:hover .esh-identity-drop { 43 | border: 1px solid #EEEEEE; 44 | height: 14rem; 45 | transition: height 0.35s; 46 | z-index: 10; 47 | } 48 | 49 | .esh-identity-item { 50 | cursor: pointer; 51 | transition: color 0.35s; 52 | } 53 | 54 | .esh-identity-item:hover { 55 | color: #75b918; 56 | transition: color 0.35s; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /tests/IntegrationTests/IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Microsoft.eShopWeb.IntegrationTests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Infrastructure/Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Microsoft.eShopWeb.Infrastructure 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/BlazorAdmin/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | @if (!context.User.Identity.IsAuthenticated) 8 | { 9 | 10 | } 11 | else 12 | { 13 |

Not Authorized

14 |

15 | You are not authorized to access 16 | this resource. 17 | 18 | Return to eShop 19 |

20 | } 21 |
22 |
23 |
24 | 25 | 26 |

Sorry, there's nothing at this address.

27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /src/Web/compilerconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFile": "wwwroot/css/shared/components/header/header.css", 4 | "inputFile": "wwwroot/css/shared/components/header/header.scss" 5 | }, 6 | { 7 | "outputFile": "wwwroot/css/orders/orders.component.css", 8 | "inputFile": "wwwroot/css/orders/orders.component.scss" 9 | }, 10 | { 11 | "outputFile": "wwwroot/css/catalog/catalog.component.css", 12 | "inputFile": "wwwroot/css/catalog/catalog.component.scss" 13 | }, 14 | { 15 | "outputFile": "wwwroot/css/basket/basket.component.css", 16 | "inputFile": "wwwroot/css/basket/basket.component.scss" 17 | }, 18 | { 19 | "outputFile": "wwwroot/css/basket/basket-status/basket-status.component.css", 20 | "inputFile": "wwwroot/css/basket/basket-status/basket-status.component.scss" 21 | }, 22 | { 23 | "outputFile": "wwwroot/css/app.component.css", 24 | "inputFile": "wwwroot/css/app.component.scss" 25 | }, 26 | { 27 | "outputFile": "wwwroot/css/_variables.css", 28 | "inputFile": "wwwroot/css/_variables.scss" 29 | }, 30 | { 31 | "outputFile": "wwwroot/css/shared/components/pager/pager.css", 32 | "inputFile": "wwwroot/css/shared/components/pager/pager.scss" 33 | } 34 | ] -------------------------------------------------------------------------------- /src/Infrastructure/Data/CatalogContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 4 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 5 | using System.Reflection; 6 | 7 | namespace Microsoft.eShopWeb.Infrastructure.Data 8 | { 9 | 10 | public class CatalogContext : DbContext 11 | { 12 | public CatalogContext(DbContextOptions options) : base(options) 13 | { 14 | } 15 | 16 | public DbSet Baskets { get; set; } 17 | public DbSet CatalogItems { get; set; } 18 | public DbSet CatalogBrands { get; set; } 19 | public DbSet CatalogTypes { get; set; } 20 | public DbSet Orders { get; set; } 21 | public DbSet OrderItems { get; set; } 22 | public DbSet BasketItems { get; set; } 23 | 24 | protected override void OnModelCreating(ModelBuilder builder) 25 | { 26 | base.OnModelCreating(builder); 27 | builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inject AuthenticationStateProvider AuthStateProvider 2 | @inject IJSRuntime JSRuntime 3 | 4 | @inherits BlazorAdmin.Helpers.BlazorLayoutComponent 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | About eShopOnWeb 15 |
16 | 17 |
18 | @Body 19 |
20 |
21 | @code 22 | { 23 | protected override async Task OnAfterRenderAsync(bool firstRender) 24 | { 25 | if (firstRender) 26 | { 27 | var authState = await AuthStateProvider.GetAuthenticationStateAsync(); 28 | 29 | if (authState.User == null) 30 | { 31 | await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); 32 | } 33 | CallRequestRefresh(); 34 | } 35 | 36 | await base.OnAfterRenderAsync(firstRender); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Web/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/basket/basket-status/basket-status.component.css: -------------------------------------------------------------------------------- 1 | .esh-basketstatus { 2 | cursor: pointer; 3 | display: inline-block; 4 | float: right; 5 | position: relative; 6 | transition: all 0.35s; } 7 | .esh-basketstatus.is-disabled { 8 | opacity: .5; 9 | pointer-events: none; } 10 | .esh-basketstatus-image { 11 | height: 36px; 12 | margin-top: .5rem; } 13 | .esh-basketstatus-badge { 14 | background-color: #83D01B; 15 | border-radius: 50%; 16 | color: #FFFFFF; 17 | display: block; 18 | height: 1.5rem; 19 | left: 50%; 20 | position: absolute; 21 | text-align: center; 22 | top: 0; 23 | transform: translateX(-38%); 24 | transition: all 0.35s; 25 | width: 1.5rem; } 26 | .esh-basketstatus-badge-inoperative { 27 | background-color: #ff0000; 28 | border-radius: 50%; 29 | color: #FFFFFF; 30 | display: block; 31 | height: 1.5rem; 32 | left: 50%; 33 | position: absolute; 34 | text-align: center; 35 | top: 0; 36 | transform: translateX(-38%); 37 | transition: all 0.35s; 38 | width: 1.5rem; } 39 | .esh-basketstatus:hover .esh-basketstatus-badge { 40 | background-color: transparent; 41 | color: #75b918; 42 | transition: all 0.35s; } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | 4 | 5 | Copyright (c) .NET Foundation and Contributors 6 | 7 | 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | 11 | of this software and associated documentation files (the "Software"), to deal 12 | 13 | in the Software without restriction, including without limitation the rights 14 | 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | 17 | copies of the Software, and to permit persons to whom the Software is 18 | 19 | furnished to do so, subject to the following conditions: 20 | 21 | 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | 25 | copies or substantial portions of the Software. 26 | 27 | 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | 37 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | 41 | SOFTWARE. 42 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.GuardClauses; 2 | 3 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate 4 | { 5 | /// 6 | /// Represents a snapshot of the item that was ordered. If catalog item details change, details of 7 | /// the item that was part of a completed order should not change. 8 | /// 9 | public class CatalogItemOrdered // ValueObject 10 | { 11 | public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri) 12 | { 13 | Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue); 14 | Guard.Against.NullOrEmpty(productName, nameof(productName)); 15 | Guard.Against.NullOrEmpty(pictureUri, nameof(pictureUri)); 16 | 17 | CatalogItemId = catalogItemId; 18 | ProductName = productName; 19 | PictureUri = pictureUri; 20 | } 21 | 22 | private CatalogItemOrdered() 23 | { 24 | // required by EF 25 | } 26 | 27 | public int CatalogItemId { get; private set; } 28 | public string ProductName { get; private set; } 29 | public string PictureUri { get; private set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ApplicationCore/Interfaces/IAsyncRepository.cs: -------------------------------------------------------------------------------- 1 | using Ardalis.Specification; 2 | using Microsoft.eShopWeb.ApplicationCore.Entities; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.eShopWeb.ApplicationCore.Interfaces 8 | { 9 | public interface IAsyncRepository where T : BaseEntity, IAggregateRoot 10 | { 11 | Task GetByIdAsync(int id, CancellationToken cancellationToken = default); 12 | Task> ListAllAsync(CancellationToken cancellationToken = default); 13 | Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default); 14 | Task AddAsync(T entity, CancellationToken cancellationToken = default); 15 | Task UpdateAsync(T entity, CancellationToken cancellationToken = default); 16 | Task DeleteAsync(T entity, CancellationToken cancellationToken = default); 17 | Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default); 18 | Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default); 19 | Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Web/Configuration/ConfigureCoreServices.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | using Microsoft.eShopWeb.ApplicationCore.Services; 3 | using Microsoft.eShopWeb.Infrastructure.Data; 4 | using Microsoft.eShopWeb.Infrastructure.Logging; 5 | using Microsoft.eShopWeb.Infrastructure.Services; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Microsoft.eShopWeb.Web.Configuration 10 | { 11 | public static class ConfigureCoreServices 12 | { 13 | public static IServiceCollection AddCoreServices(this IServiceCollection services, IConfiguration configuration) 14 | { 15 | services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>)); 16 | services.AddScoped(); 17 | services.AddScoped(); 18 | services.AddScoped(); 19 | services.AddSingleton(new UriComposer(configuration.Get())); 20 | services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); 21 | services.AddTransient(); 22 | 23 | return services; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/app.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s} -------------------------------------------------------------------------------- /src/Infrastructure/Identity/AppIdentityDbContextSeed.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.eShopWeb.ApplicationCore.Constants; 3 | using System.Threading.Tasks; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Identity 6 | { 7 | public class AppIdentityDbContextSeed 8 | { 9 | public static async Task SeedAsync(UserManager userManager, RoleManager roleManager) 10 | { 11 | await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); 12 | 13 | var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; 14 | await userManager.CreateAsync(defaultUser, AuthorizationConstants.DEFAULT_PASSWORD); 15 | 16 | string adminUserName = "admin@microsoft.com"; 17 | var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName }; 18 | await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD); 19 | adminUser = await userManager.FindByNameAsync(adminUserName); 20 | await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [ 5 | { 6 | "library": "jquery@3.3.1", 7 | "destination": "wwwroot/lib/jquery/" 8 | }, 9 | { 10 | "library": "twitter-bootstrap@3.3.7", 11 | "files": [ 12 | "css/bootstrap.css", 13 | "css/bootstrap.css.map", 14 | "css/bootstrap.min.css", 15 | "css/bootstrap.min.css.map", 16 | "js/bootstrap.js", 17 | "js/bootstrap.min.js" 18 | ], 19 | "destination": "wwwroot/lib/bootstrap/dist/" 20 | }, 21 | { 22 | "library": "jquery-validation-unobtrusive@3.2.10", 23 | "destination": "wwwroot/lib/jquery-validation-unobtrusive/" 24 | }, 25 | { 26 | "library": "jquery-validate@1.17.0", 27 | "destination": "wwwroot/lib/jquery-validate/", 28 | "files": [ 29 | "jquery.validate.min.js", 30 | "jquery.validate.js" 31 | ] 32 | }, 33 | { 34 | "library": "toastr.js@2.1.4", 35 | "destination": "wwwroot/lib/toastr/" 36 | }, 37 | { 38 | "library": "aspnet-signalr@1.0.3", 39 | "files": [ 40 | "signalr.js", 41 | "signalr.min.js" 42 | ], 43 | "destination": "wwwroot/lib/@aspnet/signalr/dist/browser/" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Extensions/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions 4 | { 5 | public class JsonExtensions 6 | { 7 | [Fact] 8 | public void CorrectlySerializesAndDeserializesObject() 9 | { 10 | var testParent = new TestParent 11 | { 12 | Id = 7, 13 | Name = "Test name", 14 | Children = new[] 15 | { 16 | new TestChild(), 17 | new TestChild(), 18 | new TestChild() 19 | } 20 | }; 21 | 22 | var json = testParent.ToJson(); 23 | var result = json.FromJson(); 24 | Assert.Equal(testParent, result); 25 | } 26 | 27 | [ 28 | Theory, 29 | InlineData("{ \"id\": 9, \"name\": \"Another test\" }", 9, "Another test"), 30 | InlineData("{ \"id\": 3124, \"name\": \"Test Value 1\" }", 3124, "Test Value 1"), 31 | ] 32 | public void CorrectlyDeserializesJson(string json, int expectedId, string expectedName) => 33 | Assert.Equal(new TestParent { Id = expectedId, Name = expectedName }, json.FromJson()); 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/CatalogItemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class CatalogItemConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.ToTable("Catalog"); 12 | 13 | builder.Property(ci => ci.Id) 14 | .UseHiLo("catalog_hilo") 15 | .IsRequired(); 16 | 17 | builder.Property(ci => ci.Name) 18 | .IsRequired(true) 19 | .HasMaxLength(50); 20 | 21 | builder.Property(ci => ci.Price) 22 | .IsRequired(true) 23 | .HasColumnType("decimal(18,2)"); 24 | 25 | builder.Property(ci => ci.PictureUri) 26 | .IsRequired(false); 27 | 28 | builder.HasOne(ci => ci.CatalogBrand) 29 | .WithMany() 30 | .HasForeignKey(ci => ci.CatalogBrandId); 31 | 32 | builder.HasOne(ci => ci.CatalogType) 33 | .WithMany() 34 | .HasForeignKey(ci => ci.CatalogTypeId); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Entities/OrderTests/OrderTotal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 2 | using Microsoft.eShopWeb.UnitTests.Builders; 3 | using System.Collections.Generic; 4 | using Xunit; 5 | 6 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.OrderTests 7 | { 8 | public class OrderTotal 9 | { 10 | private decimal _testUnitPrice = 42m; 11 | 12 | [Fact] 13 | public void IsZeroForNewOrder() 14 | { 15 | var order = new OrderBuilder().WithNoItems(); 16 | 17 | Assert.Equal(0, order.Total()); 18 | } 19 | 20 | [Fact] 21 | public void IsCorrectGiven1Item() 22 | { 23 | var builder = new OrderBuilder(); 24 | var items = new List 25 | { 26 | new OrderItem(builder.TestCatalogItemOrdered, _testUnitPrice, 1) 27 | }; 28 | var order = new OrderBuilder().WithItems(items); 29 | Assert.Equal(_testUnitPrice, order.Total()); 30 | } 31 | 32 | [Fact] 33 | public void IsCorrectGiven3Items() 34 | { 35 | var builder = new OrderBuilder(); 36 | var order = builder.WithDefaultValues(); 37 | 38 | Assert.Equal(builder.TestUnitPrice * builder.TestUnits, order.Total()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/Web/Web.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/src/Web/Web.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/src/Web/Web.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/Web/Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model SetPasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Set password"; 4 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 5 | } 6 | 7 |

Set your password

8 | 9 |

10 | You do not have a local username/password for this site. Add a local 11 | account so you can log in without an external login. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | 32 | @section Scripts { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/ApplicationCore/Entities/BasketAggregate/Basket.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate 6 | { 7 | public class Basket : BaseEntity, IAggregateRoot 8 | { 9 | public string BuyerId { get; private set; } 10 | private readonly List _items = new List(); 11 | public IReadOnlyCollection Items => _items.AsReadOnly(); 12 | 13 | public Basket(string buyerId) 14 | { 15 | BuyerId = buyerId; 16 | } 17 | 18 | public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1) 19 | { 20 | if (!Items.Any(i => i.CatalogItemId == catalogItemId)) 21 | { 22 | _items.Add(new BasketItem(catalogItemId, quantity, unitPrice)); 23 | return; 24 | } 25 | var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId); 26 | existingItem.AddQuantity(quantity); 27 | } 28 | 29 | public void RemoveEmptyItems() 30 | { 31 | _items.RemoveAll(i => i.Quantity == 0); 32 | } 33 | 34 | public void SetNewBuyerId(string buyerId) 35 | { 36 | BuyerId = buyerId; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Web/wwwroot/images/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Infrastructure/Data/Config/OrderConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 4 | 5 | namespace Microsoft.eShopWeb.Infrastructure.Data.Config 6 | { 7 | public class OrderConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); 12 | 13 | navigation.SetPropertyAccessMode(PropertyAccessMode.Field); 14 | 15 | builder.OwnsOne(o => o.ShipToAddress, a => 16 | { 17 | a.WithOwner(); 18 | 19 | a.Property(a => a.ZipCode) 20 | .HasMaxLength(18) 21 | .IsRequired(); 22 | 23 | a.Property(a => a.Street) 24 | .HasMaxLength(180) 25 | .IsRequired(); 26 | 27 | a.Property(a => a.State) 28 | .HasMaxLength(60); 29 | 30 | a.Property(a => a.Country) 31 | .HasMaxLength(90) 32 | .IsRequired(); 33 | 34 | a.Property(a => a.City) 35 | .HasMaxLength(100) 36 | .IsRequired(); 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Web/Controllers/FileController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.eShopWeb.Web.ViewModels.File; 4 | using System; 5 | using System.IO; 6 | 7 | namespace Microsoft.eShopWeb.Web.Controllers 8 | { 9 | [Route("[controller]")] 10 | [ApiController] 11 | public class FileController : ControllerBase 12 | { 13 | [HttpPost] 14 | [AllowAnonymous] 15 | public IActionResult Upload(FileViewModel fileViewModel) 16 | { 17 | if (!Request.Headers.ContainsKey("auth-key") || Request.Headers["auth-key"].ToString() != ApplicationCore.Constants.AuthorizationConstants.AUTH_KEY) 18 | { 19 | return Unauthorized(); 20 | } 21 | 22 | if(fileViewModel == null || string.IsNullOrEmpty(fileViewModel.DataBase64)) return BadRequest(); 23 | 24 | var fileData = Convert.FromBase64String(fileViewModel.DataBase64); 25 | if (fileData.Length <= 0) return BadRequest(); 26 | 27 | var fullPath = Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot/images/products", fileViewModel.FileName); 28 | if (System.IO.File.Exists(fullPath)) 29 | { 30 | System.IO.File.Delete(fullPath); 31 | } 32 | System.IO.File.WriteAllBytes(fullPath, fileData); 33 | 34 | return Ok(); 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /src/Web/wwwroot/css/shared/components/identity/identity.scss: -------------------------------------------------------------------------------- 1 | @import '../../../variables.scss'; 2 | 3 | .esh-identity { 4 | line-height: 3rem; 5 | position: relative; 6 | text-align: right; 7 | 8 | &-section { 9 | display: inline-block; 10 | width: 100%; 11 | } 12 | 13 | &-name { 14 | display: inline-block; 15 | 16 | &--upper { 17 | text-transform: uppercase; 18 | } 19 | 20 | @media screen and (max-width: $media-screen-s) { 21 | font-size: $font-size-s; 22 | } 23 | } 24 | 25 | &-image { 26 | display: inline-block; 27 | } 28 | 29 | &-drop { 30 | background: $color-background-brighter; 31 | height: 10px; 32 | width: 10rem; 33 | overflow: hidden; 34 | padding: .5rem; 35 | position: absolute; 36 | right: 0; 37 | top: 2.5rem; 38 | transition: height $animation-speed-default; 39 | } 40 | 41 | &:hover &-drop { 42 | border: $border-light solid $color-foreground-bright; 43 | height: 10rem; 44 | transition: height $animation-speed-default; 45 | } 46 | 47 | &-item { 48 | cursor: pointer; 49 | transition: color $animation-speed-default; 50 | 51 | &:hover { 52 | color: $color-secondary-dark; 53 | transition: color $animation-speed-default; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Services/CatalogTypeService.cs: -------------------------------------------------------------------------------- 1 | using BlazorShared; 2 | using BlazorShared.Interfaces; 3 | using BlazorShared.Models; 4 | using Microsoft.Extensions.Logging; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Net.Http.Json; 9 | using System.Threading.Tasks; 10 | 11 | namespace BlazorAdmin.Services 12 | { 13 | public class CatalogTypeService : ICatalogTypeService 14 | { 15 | // TODO: Make a generic service for any LookupData type 16 | private readonly HttpClient _httpClient; 17 | private readonly ILogger _logger; 18 | private string _apiUrl; 19 | 20 | public CatalogTypeService(HttpClient httpClient, 21 | BaseUrlConfiguration baseUrlConfiguration, 22 | ILogger logger) 23 | { 24 | _httpClient = httpClient; 25 | _logger = logger; 26 | _apiUrl = baseUrlConfiguration.ApiBase; 27 | } 28 | 29 | public async Task GetById(int id) 30 | { 31 | return (await List()).FirstOrDefault(x => x.Id == id); 32 | } 33 | 34 | public async Task> List() 35 | { 36 | _logger.LogInformation("Fetching types from API."); 37 | return (await _httpClient.GetFromJsonAsync($"{_apiUrl}catalog-types"))?.CatalogTypes; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Shared/CustomInputSelect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Forms; 2 | 3 | namespace BlazorAdmin.Shared 4 | { 5 | /// 6 | /// This is needed until 5.0 ships with native support 7 | /// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/ 8 | /// 9 | /// 10 | public class CustomInputSelect : InputSelect 11 | { 12 | protected override bool TryParseValueFromString(string value, out TValue result, 13 | out string validationErrorMessage) 14 | { 15 | if (typeof(TValue) == typeof(int)) 16 | { 17 | if (int.TryParse(value, out var resultInt)) 18 | { 19 | result = (TValue)(object)resultInt; 20 | validationErrorMessage = null; 21 | return true; 22 | } 23 | else 24 | { 25 | result = default; 26 | validationErrorMessage = 27 | $"The selected value {value} is not a valid number."; 28 | return false; 29 | } 30 | } 31 | else 32 | { 33 | return base.TryParseValueFromString(value, out result, 34 | out validationErrorMessage); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 2 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 3 | using Microsoft.eShopWeb.ApplicationCore.Services; 4 | using Moq; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests 9 | { 10 | public class DeleteBasket 11 | { 12 | private readonly string _buyerId = "Test buyerId"; 13 | private readonly Mock> _mockBasketRepo; 14 | 15 | public DeleteBasket() 16 | { 17 | _mockBasketRepo = new Mock>(); 18 | } 19 | 20 | [Fact] 21 | public async Task ShouldInvokeBasketRepositoryDeleteAsyncOnce() 22 | { 23 | var basket = new Basket(_buyerId); 24 | basket.AddItem(1, It.IsAny(), It.IsAny()); 25 | basket.AddItem(2, It.IsAny(), It.IsAny()); 26 | _mockBasketRepo.Setup(x => x.GetByIdAsync(It.IsAny(),default)) 27 | .ReturnsAsync(basket); 28 | var basketService = new BasketService(_mockBasketRepo.Object, null); 29 | 30 | await basketService.DeleteBasketAsync(It.IsAny()); 31 | 32 | _mockBasketRepo.Verify(x => x.DeleteAsync(It.IsAny(),default), Times.Once); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BlazorAdmin/Services/CatalogBrandService.cs: -------------------------------------------------------------------------------- 1 | using BlazorShared; 2 | using BlazorShared.Interfaces; 3 | using BlazorShared.Models; 4 | using Microsoft.Extensions.Logging; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Net.Http.Json; 9 | using System.Threading.Tasks; 10 | 11 | 12 | namespace BlazorAdmin.Services 13 | { 14 | public class CatalogBrandService : ICatalogBrandService 15 | { 16 | // TODO: Make a generic service for any LookupData type 17 | private readonly HttpClient _httpClient; 18 | private readonly ILogger _logger; 19 | private string _apiUrl; 20 | 21 | public CatalogBrandService(HttpClient httpClient, 22 | BaseUrlConfiguration baseUrlConfiguration, 23 | ILogger logger) 24 | { 25 | _httpClient = httpClient; 26 | _logger = logger; 27 | _apiUrl = baseUrlConfiguration.ApiBase; 28 | } 29 | 30 | public async Task GetById(int id) 31 | { 32 | return (await List()).FirstOrDefault(x => x.Id == id); 33 | } 34 | 35 | public async Task> List() 36 | { 37 | _logger.LogInformation("Fetching brands from API."); 38 | return (await _httpClient.GetFromJsonAsync($"{_apiUrl}catalog-brands"))?.CatalogBrands; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AutoMapper.Configuration.Annotations; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.eShopWeb.Infrastructure.Identity; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account 11 | { 12 | [AllowAnonymous] 13 | [IgnoreAntiforgeryToken] 14 | public class LogoutModel : PageModel 15 | { 16 | private readonly SignInManager _signInManager; 17 | private readonly ILogger _logger; 18 | 19 | public LogoutModel(SignInManager signInManager, ILogger logger) 20 | { 21 | _signInManager = signInManager; 22 | _logger = logger; 23 | } 24 | 25 | public void OnGet() 26 | { 27 | } 28 | 29 | public async Task OnPost(string returnUrl = null) 30 | { 31 | await _signInManager.SignOutAsync(); 32 | _logger.LogInformation("User logged out."); 33 | if (returnUrl != null) 34 | { 35 | return LocalRedirect(returnUrl); 36 | } 37 | else 38 | { 39 | return RedirectToPage("/Index"); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Web/Configuration/ConfigureCookieSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace Microsoft.eShopWeb.Web.Configuration 7 | { 8 | public static class ConfigureCookieSettings 9 | { 10 | public static IServiceCollection AddCookieSettings(this IServiceCollection services) 11 | { 12 | services.Configure(options => 13 | { 14 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 15 | //TODO need to check that. 16 | //options.CheckConsentNeeded = context => true; 17 | options.MinimumSameSitePolicy = SameSiteMode.Strict; 18 | }); 19 | services.ConfigureApplicationCookie(options => 20 | { 21 | options.Cookie.HttpOnly = true; 22 | options.ExpireTimeSpan = TimeSpan.FromHours(1); 23 | options.LoginPath = "/Account/Login"; 24 | options.LogoutPath = "/Account/Logout"; 25 | options.Cookie = new CookieBuilder 26 | { 27 | IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy 28 | }; 29 | }); 30 | 31 | return services; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Web/HealthChecks/HomePageHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Diagnostics.HealthChecks; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.eShopWeb.Web.HealthChecks 8 | { 9 | public class HomePageHealthCheck : IHealthCheck 10 | { 11 | private readonly IHttpContextAccessor _httpContextAccessor; 12 | 13 | public HomePageHealthCheck(IHttpContextAccessor httpContextAccessor) 14 | { 15 | _httpContextAccessor = httpContextAccessor; 16 | } 17 | 18 | public async Task CheckHealthAsync( 19 | HealthCheckContext context, 20 | CancellationToken cancellationToken = default(CancellationToken)) 21 | { 22 | var request = _httpContextAccessor.HttpContext.Request; 23 | string myUrl = request.Scheme + "://" + request.Host.ToString(); 24 | 25 | var client = new HttpClient(); 26 | var response = await client.GetAsync(myUrl); 27 | var pageContents = await response.Content.ReadAsStringAsync(); 28 | if (pageContents.Contains(".NET Bot Black Sweatshirt")) 29 | { 30 | return HealthCheckResult.Healthy("The check indicates a healthy result."); 31 | } 32 | 33 | return HealthCheckResult.Unhealthy("The check indicates an unhealthy result."); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Web/Controllers/OrderController.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.eShopWeb.Web.Features.MyOrders; 5 | using Microsoft.eShopWeb.Web.Features.OrderDetails; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.eShopWeb.Web.Controllers 9 | { 10 | [ApiExplorerSettings(IgnoreApi = true)] 11 | [Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages 12 | [Route("[controller]/[action]")] 13 | public class OrderController : Controller 14 | { 15 | private readonly IMediator _mediator; 16 | 17 | public OrderController(IMediator mediator) 18 | { 19 | _mediator = mediator; 20 | } 21 | 22 | [HttpGet] 23 | public async Task MyOrders() 24 | { 25 | var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); 26 | 27 | return View(viewModel); 28 | } 29 | 30 | [HttpGet("{orderId}")] 31 | public async Task Detail(int orderId) 32 | { 33 | var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId)); 34 | 35 | if (viewModel == null) 36 | { 37 | return BadRequest("No such order found for this user."); 38 | } 39 | 40 | return View(viewModel); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/UnitTests/UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Microsoft.eShopWeb.UnitTests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Web/Pages/Admin/EditCatalogItem.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | using Microsoft.eShopWeb.ApplicationCore.Constants; 5 | using Microsoft.eShopWeb.Web.Interfaces; 6 | using Microsoft.eShopWeb.Web.ViewModels; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.eShopWeb.Web.Pages.Admin 10 | { 11 | [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)] 12 | public class EditCatalogItemModel : PageModel 13 | { 14 | private readonly ICatalogItemViewModelService _catalogItemViewModelService; 15 | 16 | public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService) 17 | { 18 | _catalogItemViewModelService = catalogItemViewModelService; 19 | } 20 | 21 | [BindProperty] 22 | public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel(); 23 | 24 | public void OnGet(CatalogItemViewModel catalogModel) 25 | { 26 | CatalogModel = catalogModel; 27 | } 28 | 29 | public async Task OnPostAsync() 30 | { 31 | if (ModelState.IsValid) 32 | { 33 | await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel); 34 | } 35 | 36 | return RedirectToPage("/Admin/Index"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ChangePasswordViewModel 2 | @{ 3 | ViewData["Title"] = "Change password"; 4 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 5 | } 6 | 7 |

@ViewData["Title"]

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/FunctionalTests/FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Microsoft.eShopWeb.FunctionalTests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/UnitTests/ApplicationCore/Services/BasketServiceTests/SetQuantities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; 2 | using Microsoft.eShopWeb.ApplicationCore.Exceptions; 3 | using Microsoft.eShopWeb.ApplicationCore.Interfaces; 4 | using Microsoft.eShopWeb.ApplicationCore.Services; 5 | using Moq; 6 | using System; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests 11 | { 12 | public class SetQuantities 13 | { 14 | private readonly int _invalidId = -1; 15 | private readonly Mock> _mockBasketRepo; 16 | 17 | public SetQuantities() 18 | { 19 | _mockBasketRepo = new Mock>(); 20 | } 21 | 22 | [Fact] 23 | public async Task ThrowsGivenInvalidBasketId() 24 | { 25 | var basketService = new BasketService(_mockBasketRepo.Object, null); 26 | 27 | await Assert.ThrowsAsync(async () => 28 | await basketService.SetQuantities(_invalidId, new System.Collections.Generic.Dictionary())); 29 | } 30 | 31 | [Fact] 32 | public async Task ThrowsGivenNullQuantities() 33 | { 34 | var basketService = new BasketService(null, null); 35 | 36 | await Assert.ThrowsAsync(async () => 37 | await basketService.SetQuantities(123, null)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Web/compilerconfig.json.defaults: -------------------------------------------------------------------------------- 1 | { 2 | "compilers": { 3 | "less": { 4 | "autoPrefix": "", 5 | "cssComb": "none", 6 | "ieCompat": true, 7 | "strictMath": false, 8 | "strictUnits": false, 9 | "relativeUrls": true, 10 | "rootPath": "", 11 | "sourceMapRoot": "", 12 | "sourceMapBasePath": "", 13 | "sourceMap": false 14 | }, 15 | "sass": { 16 | "autoPrefix": "", 17 | "includePath": "", 18 | "indentType": "space", 19 | "indentWidth": 2, 20 | "outputStyle": "nested", 21 | "Precision": 5, 22 | "relativeUrls": true, 23 | "sourceMapRoot": "", 24 | "lineFeed": "", 25 | "sourceMap": false 26 | }, 27 | "stylus": { 28 | "sourceMap": false 29 | }, 30 | "babel": { 31 | "sourceMap": false 32 | }, 33 | "coffeescript": { 34 | "bare": false, 35 | "runtimeMode": "node", 36 | "sourceMap": false 37 | }, 38 | "handlebars": { 39 | "root": "", 40 | "noBOM": false, 41 | "name": "", 42 | "namespace": "", 43 | "knownHelpersOnly": false, 44 | "forcePartial": false, 45 | "knownHelpers": [], 46 | "commonjs": "", 47 | "amd": false, 48 | "sourceMap": false 49 | } 50 | }, 51 | "minifiers": { 52 | "css": { 53 | "enabled": true, 54 | "termSemicolons": true, 55 | "gzip": false 56 | }, 57 | "javascript": { 58 | "enabled": true, 59 | "termSemicolons": true, 60 | "gzip": false 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/Web/bin/Debug/net5.0/Web.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src/Web", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.eShopWeb.Infrastructure.Identity; 10 | 11 | namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account 12 | { 13 | [AllowAnonymous] 14 | public class ConfirmEmailModel : PageModel 15 | { 16 | private readonly UserManager _userManager; 17 | 18 | public ConfirmEmailModel(UserManager userManager) 19 | { 20 | _userManager = userManager; 21 | } 22 | 23 | public async Task OnGetAsync(string userId, string code) 24 | { 25 | if (userId == null || code == null) 26 | { 27 | return RedirectToPage("/Index"); 28 | } 29 | 30 | var user = await _userManager.FindByIdAsync(userId); 31 | if (user == null) 32 | { 33 | return NotFound($"Unable to load user with ID '{userId}'."); 34 | } 35 | 36 | var result = await _userManager.ConfirmEmailAsync(user, code); 37 | if (!result.Succeeded) 38 | { 39 | throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':"); 40 | } 41 | 42 | return Page(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Web/wwwroot/css/basket/basket.component.css: -------------------------------------------------------------------------------- 1 | .esh-basket { 2 | min-height: 80vh; } 3 | .esh-basket-titles { 4 | padding-bottom: 1rem; 5 | padding-top: 2rem; } 6 | .esh-basket-titles--clean { 7 | padding-bottom: 0; 8 | padding-top: 0; } 9 | .esh-basket-title { 10 | text-transform: uppercase; } 11 | .esh-basket-items--border { 12 | border-bottom: 1px solid #EEEEEE; 13 | padding: .5rem 0; } 14 | .esh-basket-items--border:last-of-type { 15 | border-color: transparent; } 16 | .esh-basket-items-margin-left1 { 17 | margin-left: 1px; } 18 | .esh-basket-item { 19 | font-size: 1rem; 20 | font-weight: 300; } 21 | .esh-basket-item--middle { 22 | line-height: 8rem; } 23 | @media screen and (max-width: 1024px) { 24 | .esh-basket-item--middle { 25 | line-height: 1rem; } } 26 | .esh-basket-item--mark { 27 | color: #00A69C; } 28 | .esh-basket-image { 29 | height: 8rem; } 30 | .esh-basket-input { 31 | line-height: 1rem; 32 | width: 100%; } 33 | .esh-basket-checkout { 34 | background-color: #83D01B; 35 | border: 0; 36 | border-radius: 0; 37 | color: #FFFFFF; 38 | display: inline-block; 39 | font-size: 1rem; 40 | font-weight: 400; 41 | margin-top: 1rem; 42 | padding: 1rem 1.5rem; 43 | text-align: center; 44 | text-transform: uppercase; 45 | transition: all 0.35s; } 46 | .esh-basket-checkout:hover { 47 | background-color: #4a760f; 48 | transition: all 0.35s; } 49 | .esh-basket-checkout:visited { 50 | color: #FFFFFF; } 51 | -------------------------------------------------------------------------------- /src/Web/Views/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 3 | using System; 4 | 5 | namespace Microsoft.eShopWeb.Web.Views.Manage 6 | { 7 | public static class ManageNavPages 8 | { 9 | public static string ActivePageKey => "ActivePage"; 10 | 11 | public static string Index => "Index"; 12 | 13 | public static string ChangePassword => "ChangePassword"; 14 | 15 | public static string ExternalLogins => "ExternalLogins"; 16 | 17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 18 | 19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 20 | 21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 22 | 23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 24 | 25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 26 | 27 | public static string PageNavClass(ViewContext viewContext, string page) 28 | { 29 | var activePage = viewContext.ViewData["ActivePage"] as string; 30 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 31 | } 32 | 33 | public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Web/Pages/Admin/EditCatalogItem.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @{ 3 | ViewData["Title"] = "Admin - Edit Catalog"; 4 | @model EditCatalogItemModel 5 | } 6 | 7 |
8 |
9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | 35 | 36 | @section Scripts { 37 | 38 | } --------------------------------------------------------------------------------