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 |
8 |
9 |
10 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
11 |
12 |
13 | This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
14 | If you do not complete your authenticator app configuration you may lose access to your account.
15 |
16 |
17 |
18 |
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 |
12 | Use this space to summarize your privacy and cookie use policy.
Learn More.
13 |
16 |
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 |
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 |
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 |
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 |
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 |
31 |
32 |
33 |
34 |
35 |
36 | @section Scripts {
37 |
38 | }
--------------------------------------------------------------------------------