fix conflict

This commit is contained in:
zedy
2023-04-20 09:23:28 +08:00
49 changed files with 533 additions and 475 deletions

View File

@@ -1,83 +0,0 @@
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
FROM mcr.microsoft.com/dotnet/sdk:7.0
# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# [Optional] Version of Node.js to install.
ARG INSTALL_NODE="false"
ARG NODE_VERSION="lts/*"
ENV NVM_DIR=/usr/local/share/nvm
# [Optional] Install the Azure CLI
ARG INSTALL_AZURE_CLI="false"
# Configure apt and install packages
RUN curl -fsSL https://aka.ms/install-azd.sh | bash \
&& apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
#
# Verify git, process tools, lsb-release (common in install instructions for CLIs) installed
&& apt-get -y install git openssh-client less iproute2 procps apt-transport-https gnupg2 curl lsb-release \
#
# Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user.
&& groupadd --gid $USER_GID $USERNAME \
&& useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
# [Optional] Add sudo support for the non-root user
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
&& chmod 0440 /etc/sudoers.d/$USERNAME \
#
# [Optional] Install Node.js for ASP.NET Core Web Applicationss
&& if [ "$INSTALL_NODE" = "true" ]; then \
#
# Install nvm and Node
mkdir -p ${NVM_DIR} \
&& curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash 2>&1 \
&& chown -R ${USER_UID}:${USER_GID} ${NVM_DIR} \
&& /bin/bash -c "source $NVM_DIR/nvm.sh \
&& nvm alias default ${NODE_VERSION}" 2>&1 \
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh" && [ -s "$NVM_DIR/bash_completion" ] && \\. "$NVM_DIR/bash_completion"' \
| tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc >> /root/.zshrc \
&& echo "if [ \"\$(stat -c '%U' ${NVM_DIR})\" != \"${USERNAME}\" ]; then sudo chown -R ${USER_UID}:root ${NVM_DIR}; fi" \
| tee -a /root/.bashrc /root/.zshrc /home/${USERNAME}/.bashrc >> /home/${USERNAME}/.zshrc \
&& chown ${USER_UID}:${USER_GID} /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc \
&& chown -R ${USER_UID}:root ${NVM_DIR} \
#
# Install yarn
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends yarn; \
fi \
#
# [Optional] Install the Azure CLI
&& if [ "$INSTALL_AZURE_CLI" = "true" ]; then \
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/azure-cli.list \
&& curl -sL https://packages.microsoft.com/keys/microsoft.asc | apt-key add - 2>/dev/null \
&& apt-get update \
&& apt-get install -y azure-cli; \
fi \
#
# Install EF Core dotnet tool
&& dotnet tool install dotnet-ef --tool-path /home/$USERNAME/.dotnet/tools \
&& chown -R $USERNAME /home/$USERNAME/.dotnet \
#
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Set PATH for dotnet tools
ENV PATH "$PATH:/home/$USERNAME/.dotnet/tools"

View File

@@ -1,74 +1,43 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/dotnetcore-3.1
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "eShopOnWeb",
"build": {
"dockerfile": "Dockerfile",
"args": {
"USERNAME": "vscode",
"INSTALL_NODE": "false",
"NODE_VERSION": "lts/*",
"INSTALL_AZURE_CLI": "false"
}
},
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "2.38"
},
"ghcr.io/devcontainers/features/docker-from-docker:1": {
"version": "20.10"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "2"
}
},
// Comment out to connect as root user. See https://aka.ms/vscode-remote/containers/non-root.
// make sure this is the same as USERNAME above
"remoteUser": "vscode",
"runArgs": [
"-v",
"/var/run/docker.sock:/var/run/docker.sock"
],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.profiles.linux": {
"bash": {
"path": "bash",
"icon": "terminal-bash"
}
}
"name": "eShopOnWeb",
"image": "mcr.microsoft.com/devcontainers/dotnet:0-7.0",
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csharp",
"formulahendry.dotnet-test-explorer",
"ms-vscode.vscode-node-azure-pack",
"ms-kubernetes-tools.vscode-kubernetes-tools",
"redhat.vscode-yaml"
]
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csharp",
"formulahendry.dotnet-test-explorer",
"ms-vscode.vscode-node-azure-pack",
"ms-kubernetes-tools.vscode-kubernetes-tools",
"redhat.vscode-yaml"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5000, 5001],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "dotnet dev-certs https --trust"
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5000, 5001],
// [Optional] To reuse of your local HTTPS dev cert, first export it locally using this command:
// * Windows PowerShell:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
// * macOS/Linux terminal:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
//
// Next, after running the command above, uncomment lines in the 'mounts' and 'remoteEnv' lines below,
// and open / rebuild the container so the settings take effect.
//
"mounts": [
// "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind"
],
"remoteEnv": {
// "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere",
// "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx",
}
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "dotnet restore"
// [Optional] To reuse of your local HTTPS dev cert, first export it locally using this command:
// * Windows PowerShell:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
// * macOS/Linux terminal:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
//
// Next, after running the command above, uncomment lines in the 'mounts' and 'remoteEnv' lines below,
// and open / rebuild the container so the settings take effect.
//
// "mounts": [
// // "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind"
// ],
// "remoteEnv": {
// // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere",
// // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx",
// },
}

View File

@@ -0,0 +1,36 @@
# Dev container
This project includes a [dev container](https://containers.dev/), which lets you use a container as a full-featured dev environment.
You can use the dev container configuration in this folder to build and run the app without needing to install any of its tools locally! You can use it in [GitHub Codespaces](https://github.com/features/codespaces) or the [VS Code Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
## GitHub Codespaces
Follow these steps to open this sample in a Codespace:
1. Click the **Code** drop-down menu at the top of https://github.com/dotnet-architecture/eShopOnWeb.
1. Click on the **Codespaces** tab.
1. Click **Create codespace on main** .
For more info, check out the [GitHub documentation](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace#creating-a-codespace).
## VS Code Dev Containers
If you already have VS Code and Docker installed, you can click [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/dotnet-architecture/eShopOnWeb) to get started. This will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
You can also follow these steps to open this sample in a container using the VS Code Dev Containers extension:
1. If this is your first time using a development container, please ensure your system meets the pre-reqs (i.e. have Docker installed) in the [getting started steps](https://aka.ms/vscode-remote/containers/getting-started).
2. Open a locally cloned copy of the code:
- Clone this repository to your local filesystem.
- Press <kbd>F1</kbd> and select the **Dev Containers: Open Folder in Container...** command.
- Select the cloned copy of this folder, wait for the container to start, and try things out!
You can learn more in the [Dev Containers documentation](https://code.visualstudio.com/docs/devcontainers/containers).
## Tips and tricks
* Since the dev container is Linux-based, you won't be able to use LocalDB. Add ` "UseOnlyInMemoryDatabase": true,` to the [appsettings.json](../src/Web/appsettings.json) file (there's additional context on this [in the app's readme](../README.md#configuring-the-sample-to-use-sql-server)).
* If you get a `502 bad gateway` error, you may need to set your port to the https protocol. You can do this by opening the Ports view in VS Code (**Ports: Focus on Ports View**), right-clicking on the port you're using, select **Change Port Protocol**, and set **https**.
* If you are working with the same repository folder in a container and Windows, you'll want consistent line endings (otherwise you may see hundreds of changes in the SCM view). The `.gitattributes` file in the root of this repo disables line ending conversion and should prevent this. See [tips and tricks](https://code.visualstudio.com/docs/devcontainers/tips-and-tricks#_resolving-git-line-ending-issues-in-containers-resulting-in-many-modified-files) for more info.
* If you'd like to review the contents of the image used in this dev container, you can check it out in the [devcontainers/images](https://github.com/devcontainers/images/tree/main/src/dotnet) repo.

View File

@@ -141,4 +141,10 @@ csharp_preserve_single_line_blocks = true
###############################
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion###############################
######################################
# Configure Nullable Reference Types #
######################################
[{**/*Dto.cs,**/*Request.cs,**/*Response.cs}]
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = none

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

View File

@@ -1,56 +1,56 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.ApiEndpoints" Version="4.0.1" />
<PackageVersion Include="Ardalis.GuardClauses" Version="4.0.1" />
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="6.1.0" />
<PackageVersion Include="Ardalis.Result" Version="4.1.0" />
<PackageVersion Include="Ardalis.Result" Version="7.0.0" />
<PackageVersion Include="Ardalis.Specification" Version="6.1.0" />
<PackageVersion Include="Ardalis.ListStartupServices" Version="1.1.4" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.1" />
<PackageVersion Include="Azure.Identity" Version="1.9.0-beta.2" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageVersion Include="BlazorInputFile" Version="0.2.0" />
<PackageVersion Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageVersion Include="BuildBundlerMinifier" Version="3.2.449" PrivateAssets="All" />
<PackageVersion Include="FluentValidation" Version="11.4.0" />
<PackageVersion Include="MediatR" Version="11.1.0" />
<PackageVersion Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.2" PrivateAssets="all" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.2" />
<PackageVersion Include="FluentValidation" Version="11.5.1" />
<PackageVersion Include="MediatR" Version="12.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.4" PrivateAssets="all" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="7.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="7.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="7.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.2" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.5" />
<PackageVersion Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageVersion Include="MinimalApi.Endpoint" Version="1.3.0" />
<PackageVersion Include="System.Net.Http.Json" Version="7.0.0" />
<PackageVersion Include="System.Net.Http.Json" Version="7.0.1" />
<PackageVersion Include="System.Security.Claims" Version="4.3.0" />
<PackageVersion Include="System.Text.Json" Version="7.0.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
<PackageVersion Include="System.Text.Json" Version="7.0.2" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<!-- Test -->
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>

92
Everything.sln Normal file
View File

@@ -0,0 +1,92 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{1A5759FF-9990-4CF5-AD78-528452C5EFCC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorAdmin", "src\BlazorAdmin\BlazorAdmin.csproj", "{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorShared", "src\BlazorShared\BlazorShared.csproj", "{6FD75683-D186-4BE3-ABD0-2324650B46B5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{35457566-83CE-44FC-A650-265CC9C544DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApi", "src\PublicApi\PublicApi.csproj", "{7F226129-E8B0-4274-87A7-347AA4F7D374}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{7559FA9E-7CFC-4615-8D09-3CDEFC765455}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BAA5312D-B54C-42D6-A3B9-504DD12F8250}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalTests", "tests\FunctionalTests\FunctionalTests.csproj", "{020545FF-D985-4274-9FDB-FD8B9B32D2ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "tests\IntegrationTests\IntegrationTests.csproj", "{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApiIntegrationTests", "tests\PublicApiIntegrationTests\PublicApiIntegrationTests.csproj", "{698594AE-78D3-429F-B5CC-3A6F6BCE397A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTests\UnitTests.csproj", "{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Release|Any CPU.Build.0 = Release|Any CPU
{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Release|Any CPU.Build.0 = Release|Any CPU
{6FD75683-D186-4BE3-ABD0-2324650B46B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FD75683-D186-4BE3-ABD0-2324650B46B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FD75683-D186-4BE3-ABD0-2324650B46B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FD75683-D186-4BE3-ABD0-2324650B46B5}.Release|Any CPU.Build.0 = Release|Any CPU
{35457566-83CE-44FC-A650-265CC9C544DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35457566-83CE-44FC-A650-265CC9C544DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35457566-83CE-44FC-A650-265CC9C544DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35457566-83CE-44FC-A650-265CC9C544DC}.Release|Any CPU.Build.0 = Release|Any CPU
{7F226129-E8B0-4274-87A7-347AA4F7D374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F226129-E8B0-4274-87A7-347AA4F7D374}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F226129-E8B0-4274-87A7-347AA4F7D374}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F226129-E8B0-4274-87A7-347AA4F7D374}.Release|Any CPU.Build.0 = Release|Any CPU
{7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Release|Any CPU.Build.0 = Release|Any CPU
{020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Release|Any CPU.Build.0 = Release|Any CPU
{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Release|Any CPU.Build.0 = Release|Any CPU
{698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Release|Any CPU.Build.0 = Release|Any CPU
{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1A5759FF-9990-4CF5-AD78-528452C5EFCC} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{6FD75683-D186-4BE3-ABD0-2324650B46B5} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{35457566-83CE-44FC-A650-265CC9C544DC} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{7F226129-E8B0-4274-87A7-347AA4F7D374} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{7559FA9E-7CFC-4615-8D09-3CDEFC765455} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}
{020545FF-D985-4274-9FDB-FD8B9B32D2ED} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250}
{D6829485-DD9C-42CE-BEDE-4EB0E81021AC} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250}
{698594AE-78D3-429F-B5CC-3A6F6BCE397A} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250}
{EAD6CF0B-2979-462C-BBB9-AF723B1EB570} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250}
EndGlobalSection
EndGlobal

View File

@@ -12,7 +12,7 @@ A list of Frequently Asked Questions about this repository can be found [here](h
## eBook
This reference application is meant to support the free .PDF download ebook: [Architecting Modern Web Applications with ASP.NET Core and Azure](https://aka.ms/webappebook), updated to **ASP.NET Core 6.0**. [Also available in ePub/mobi formats](https://dotnet.microsoft.com/learn/web/aspnet-architecture).
This reference application is meant to support the free .PDF download ebook: [Architecting Modern Web Applications with ASP.NET Core and Azure](https://aka.ms/webappebook), updated to **ASP.NET Core 7.0**. [Also available in ePub/mobi formats](https://dotnet.microsoft.com/learn/web/aspnet-architecture).
You can also read the book in online pages at the .NET docs here:
https://docs.microsoft.com/dotnet/architecture/modern-web-apps-azure/
@@ -100,13 +100,12 @@ You can also run the samples in Docker (see below).
### Configuring the sample to use SQL Server
1. By default, the project uses a real database. If you want an in memory database, you can add in `appsettings.json`
1. By default, the project uses a real database. If you want an in memory database, you can add in the `appsettings.json` file in the Web folder
```json
{
"UseOnlyInMemoryDatabase": true
}
```
1. Ensure your connection strings in `appsettings.json` point to a local SQL Server instance.
@@ -140,6 +139,14 @@ You can also run the samples in Docker (see below).
dotnet ef migrations add InitialIdentityModel --context appidentitydbcontext -p ../Infrastructure/Infrastructure.csproj -s Web.csproj -o Identity/Migrations
```
## Running the sample in the dev container
This project includes a `.devcontainer` folder with a [dev container configuration](https://containers.dev/), which lets you use a container as a full-featured dev environment.
You can use the dev container to build and run the app without needing to install any of its tools locally! You can work in GitHub Codespaces or the VS Code Dev Containers extension.
Learn more about using the dev container in its [readme](/.devcontainer/devcontainerreadme.md).
## Running the sample using Docker
You can run the Web sample by running these commands from the root folder (where the .sln file is located):

View File

@@ -10,10 +10,10 @@ public static class Dependencies
{
public static void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
var useOnlyInMemoryDatabase = false;
bool useOnlyInMemoryDatabase = false;
if (configuration["UseOnlyInMemoryDatabase"] != null)
{
useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]);
useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]!);
}
if (useOnlyInMemoryDatabase)

View File

@@ -24,6 +24,9 @@ public class AppIdentityDbContextSeed
var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName };
await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD);
adminUser = await userManager.FindByNameAsync(adminUserName);
await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
if (adminUser != null)
{
await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
}
}
}

View File

@@ -25,6 +25,7 @@ public class IdentityTokenClaimService : ITokenClaimsService
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);
var user = await _userManager.FindByNameAsync(userName);
if (user == null) throw new UserNotFoundException(userName);
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };

View File

@@ -0,0 +1,10 @@
using System;
namespace Microsoft.eShopWeb.Infrastructure.Identity;
public class UserNotFoundException : Exception
{
public UserNotFoundException(string userName) : base($"No user found with username: {userName}")
{
}
}

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;
public class ClaimValue
{
@@ -17,6 +12,6 @@ public class ClaimValue
Value = value;
}
public string Type { get; set; }
public string Value { get; set; }
public string Type { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints;
@@ -9,7 +6,7 @@ public class UserInfo
{
public static readonly UserInfo Anonymous = new UserInfo();
public bool IsAuthenticated { get; set; }
public string NameClaimType { get; set; }
public string RoleClaimType { get; set; }
public IEnumerable<ClaimValue> Claims { get; set; }
public string NameClaimType { get; set; } = string.Empty;
public string RoleClaimType { get; set; } = string.Empty;
public IEnumerable<ClaimValue> Claims { get; set; } = new List<ClaimValue>();
}

View File

@@ -33,7 +33,8 @@ public class AuthenticateEndpoint : EndpointBaseAsync
OperationId = "auth.authenticate",
Tags = new[] { "AuthEndpoints" })
]
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request, CancellationToken cancellationToken = default)
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request,
CancellationToken cancellationToken = default)
{
var response = new AuthenticateResponse(request.CorrelationId());

View File

@@ -2,8 +2,8 @@
public class ListPagedCatalogItemRequest : BaseRequest
{
public int? PageSize { get; init; }
public int? PageIndex { get; init; }
public int PageSize { get; init; }
public int PageIndex { get; init; }
public int? CatalogBrandId { get; init; }
public int? CatalogTypeId { get; init; }

View File

@@ -46,8 +46,8 @@ public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogI
int totalItems = await itemRepository.CountAsync(filterSpec);
var pagedSpec = new CatalogFilterPaginatedSpecification(
skip: request.PageIndex.Value * request.PageSize.Value,
take: request.PageSize.Value,
skip: request.PageIndex * request.PageSize,
take: request.PageSize,
brandId: request.CatalogBrandId,
typeId: request.CatalogTypeId);
@@ -61,7 +61,7 @@ public class CatalogItemListPagedEndpoint : IEndpoint<IResult, ListPagedCatalogI
if (request.PageSize > 0)
{
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize.Value).ToString());
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString());
}
else
{

View File

@@ -39,6 +39,10 @@ public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemReq
var response = new UpdateCatalogItemResponse(request.CorrelationId());
var existingItem = await itemRepository.GetByIdAsync(request.Id);
if (existingItem == null)
{
return Results.NotFound();
}
CatalogItem.CatalogItemDetails details = new(request.Name, request.Description, request.Price);
existingItem.UpdateDetails(details);

View File

@@ -41,12 +41,14 @@ public class ExceptionMiddleware
Message = duplicationException.Message
}.ToString());
}
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync(new ErrorDetails()
else
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Text;
using BlazorShared;
using BlazorShared.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
@@ -41,7 +40,8 @@ builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
builder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
builder.Services.Configure<CatalogSettings>(builder.Configuration);
builder.Services.AddSingleton<IUriComposer>(new UriComposer(builder.Configuration.Get<CatalogSettings>()));
var catalogSettings = builder.Configuration.Get<CatalogSettings>() ?? new CatalogSettings();
builder.Services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));
builder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
builder.Services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
@@ -73,12 +73,12 @@ const string CORS_POLICY = "CorsPolicy";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: CORS_POLICY,
corsPolicyBuilder =>
{
corsPolicyBuilder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/'));
corsPolicyBuilder.AllowAnyMethod();
corsPolicyBuilder.AllowAnyHeader();
});
corsPolicyBuilder =>
{
corsPolicyBuilder.WithOrigins(baseUrlConfig!.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/'));
corsPolicyBuilder.AllowAnyMethod();
corsPolicyBuilder.AllowAnyHeader();
});
});
builder.Services.AddControllers();
@@ -172,12 +172,9 @@ app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.MapControllers();
app.MapEndpoints();
app.Logger.LogInformation("LAUNCHING PublicApi");
app.Run();

View File

@@ -25,7 +25,7 @@ public class LoginModel : PageModel
}
[BindProperty]
public InputModel? Input { get; set; }
public required InputModel Input { get; set; }
public IList<AuthenticationScheme>? ExternalLogins { get; set; }
@@ -74,7 +74,8 @@ public class LoginModel : PageModel
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
//var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
var result = await _signInManager.PasswordSignInAsync(Input?.Email, Input?.Password, false, true);
var result = await _signInManager.PasswordSignInAsync(Input!.Email!, Input!.Password!,
false, true);
if (result.Succeeded)
{

View File

@@ -35,7 +35,7 @@ public class RegisterModel : PageModel
}
[BindProperty]
public InputModel? Input { get; set; }
public required InputModel Input { get; set; }
public string? ReturnUrl { get; set; }
@@ -69,7 +69,7 @@ public class RegisterModel : PageModel
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input?.Email, Email = Input?.Email };
var result = await _userManager.CreateAsync(user, Input?.Password);
var result = await _userManager.CreateAsync(user, Input?.Password!);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
@@ -82,7 +82,7 @@ public class RegisterModel : PageModel
protocol: Request.Scheme);
Guard.Against.Null(callbackUrl, nameof(callbackUrl));
await _emailSender.SendEmailAsync(Input?.Email, "Confirm your email",
await _emailSender.SendEmailAsync(Input!.Email!, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);

View File

@@ -4,8 +4,6 @@ using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Data.Queries;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.eShopWeb.Infrastructure.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.eShopWeb.Web.Configuration;
@@ -20,7 +18,10 @@ public static class ConfigureCoreServices
services.AddScoped<IBasketService, BasketService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IBasketQueryService, BasketQueryService>();
services.AddSingleton<IUriComposer>(new UriComposer(configuration.Get<CatalogSettings>()));
var catalogSettings = configuration.Get<CatalogSettings>() ?? new CatalogSettings();
services.AddSingleton<IUriComposer>(new UriComposer(catalogSettings));
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddTransient<IEmailSender, EmailSender>();

View File

@@ -8,7 +8,8 @@ public static class ConfigureWebServices
{
public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddMediatR(typeof(BasketViewModelService).Assembly);
services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(BasketViewModelService).Assembly));
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
services.AddScoped<CatalogViewModelService>();
services.AddScoped<ICatalogItemViewModelService, CatalogItemViewModelService>();

View File

@@ -122,6 +122,11 @@ public class ManageController : Controller
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
Guard.Against.Null(callbackUrl, nameof(callbackUrl));
var email = user.Email;
if (email == null)
{
throw new ApplicationException($"No email associated with user {user.UserName}'.");
}
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
StatusMessage = "Verification email sent. Please check your email.";
@@ -162,7 +167,8 @@ public class ManageController : Controller
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
var changePasswordResult = await _userManager
.ChangePasswordAsync(user, model.OldPassword!, model.NewPassword!);
if (!changePasswordResult.Succeeded)
{
AddErrors(changePasswordResult);
@@ -211,7 +217,7 @@ public class ManageController : Controller
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword);
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword!);
if (!addPasswordResult.Succeeded)
{
AddErrors(addPasswordResult);
@@ -293,6 +299,10 @@ public class ManageController : Controller
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
return View(model);
}
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
if (!result.Succeeded)
@@ -407,7 +417,7 @@ public class ManageController : Controller
}
// Strip spaces and hypens
var verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty);
string verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty) ?? "";
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
@@ -421,7 +431,7 @@ public class ManageController : Controller
await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List<string>();
TempData[RecoveryCodesKey] = recoveryCodes.ToArray();
return RedirectToAction(nameof(ShowRecoveryCodes));
@@ -465,7 +475,7 @@ public class ManageController : Controller
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
}
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List<string>();
_logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
@@ -533,8 +543,8 @@ public class ManageController : Controller
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
model.SharedKey = FormatKey(unformattedKey);
model.AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey);
model.SharedKey = FormatKey(unformattedKey!);
model.AuthenticatorUri = GenerateQrCodeUri(user.Email!, unformattedKey!);
}
}

View File

@@ -13,7 +13,7 @@ public class IndexModel : PageModel
_catalogViewModelService = catalogViewModelService;
}
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();
public required CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
{

View File

@@ -106,7 +106,7 @@ var baseUrlConfig = configSection.Get<BaseUrlConfiguration>();
// Blazor Admin Required Services for Prerendering
builder.Services.AddScoped<HttpClient>(s => new HttpClient
{
BaseAddress = new Uri(baseUrlConfig.WebBase)
BaseAddress = new Uri(baseUrlConfig!.WebBase)
});
// add blazor services
@@ -195,15 +195,13 @@ app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") });
endpoints.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") });
//endpoints.MapBlazorHub("/admin");
endpoints.MapFallbackToFile("index.html");
});
app.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
app.MapRazorPages();
app.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") });
app.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") });
//endpoints.MapBlazorHub("/admin");
app.MapFallbackToFile("index.html");
app.Logger.LogInformation("LAUNCHING");
app.Run();

View File

@@ -21,30 +21,30 @@ public class CachedCatalogViewModelService : ICatalogViewModelService
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>
return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>
{
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetBrands();
});
})) ?? new List<SelectListItem>();
}
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
{
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
return (await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId);
});
})) ?? new CatalogIndexViewModel();
}
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>
return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>
{
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetTypes();
});
})) ?? new List<SelectListItem>();
}
}

View File

@@ -1,13 +1,12 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.eShopWeb.Web.ViewModels;
public class CatalogIndexViewModel
{
public List<CatalogItemViewModel>? CatalogItems { get; set; }
public List<SelectListItem>? Brands { get; set; }
public List<SelectListItem>? Types { get; set; }
public List<CatalogItemViewModel> CatalogItems { get; set; } = new List<CatalogItemViewModel>();
public List<SelectListItem>? Brands { get; set; } = new List<SelectListItem>();
public List<SelectListItem>? Types { get; set; } = new List<SelectListItem>();
public int? BrandFilterApplied { get; set; }
public int? TypesFilterApplied { get; set; }
public PaginationInfoViewModel? PaginationInfo { get; set; }

View File

@@ -1,7 +1,11 @@
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
public class RemoveLoginViewModel
{
public string? LoginProvider { get; set; }
public string? ProviderKey { get; set; }
[Required]
public string LoginProvider { get; set; } = string.Empty;
[Required]
public string ProviderKey { get; set; } = string.Empty;
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.Web.ViewModels;
@@ -13,5 +11,5 @@ public class OrderViewModel
public decimal Total { get; set; }
public string Status => DEFAULT_STATUS;
public Address? ShippingAddress { get; set; }
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
public List<OrderItemViewModel> OrderItems { get; set; } = new();
}

View File

@@ -16,10 +16,13 @@
</div>
<div class="row">
<div class="col-md-12">
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
@if (Model.RecoveryCodes != null)
{
<code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
{
<code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
}
</div>
</div>
© 2021 GitHub, Inc.
© 2023 GitHub, Inc.

View File

@@ -30,15 +30,15 @@
</article>
<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.Street</section>
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress?.Street</section>
</article>
<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.City</section>
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress?.City</section>
</article>
<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.Country</section>
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress?.Country</section>
</article>
</section>

View File

@@ -1,4 +1,4 @@
@if (Context.User.Identity.IsAuthenticated)
@if (Context!.User!.Identity!.IsAuthenticated)
{
<section class="col-lg-4 col-md-5 col-xs-12">
<div class="esh-identity">

View File

@@ -19,7 +19,6 @@
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="MediatR" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" />
<PackageReference Include="BuildBundlerMinifier" Condition="'$(Configuration)'=='Release'" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />

View File

@@ -1,7 +1,7 @@
{
"ConnectionStrings": {
"CatalogConnection": "Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;",
"IdentityConnection": "Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;"
"CatalogConnection": "Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;",
"IdentityConnection": "Server=sqlserver,1433;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;User Id=sa;Password=@someThingComplicated1234;Trusted_Connection=false;TrustServerCertificate=true;"
},
"baseUrls": {
"apiBase": "http://localhost:5200/api/",

View File

@@ -17,4 +17,4 @@
},
"AllowedHosts": "*"
}
}
}

View File

@@ -23,7 +23,7 @@ public class OrderIndexOnGet : IClassFixture<TestApplication>
public async Task ReturnsRedirectGivenAnonymousUser()
{
var response = await Client.GetAsync("/order/my-orders");
var redirectLocation = response.Headers.Location.OriginalString;
var redirectLocation = response!.Headers.Location!.OriginalString;
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Contains("/Account/Login", redirectLocation);

View File

@@ -45,6 +45,6 @@ public class BasketPageCheckout : IClassFixture<TestApplication>
formContent = new FormUrlEncodedContent(keyValues);
var postResponse2 = await Client.PostAsync("/Basket/Checkout", formContent);
Assert.Contains("/Identity/Account/Login", postResponse2.RequestMessage.RequestUri.ToString());
Assert.Contains("/Identity/Account/Login", postResponse2!.RequestMessage!.RequestUri!.ToString()!);
}
}

View File

@@ -62,7 +62,7 @@ public class CheckoutTest : IClassFixture<TestApplication>
var checkOutResponse = await Client.PostAsync("/basket/checkout", checkOutContent);
var stringCheckOutResponse = await checkOutResponse.Content.ReadAsStringAsync();
Assert.Contains("/Basket/Success", checkOutResponse.RequestMessage.RequestUri.ToString());
Assert.Contains("/Basket/Success", checkOutResponse.RequestMessage!.RequestUri!.ToString());
Assert.Contains("Thanks for your Order!", stringCheckOutResponse);
}
}

View File

@@ -52,7 +52,7 @@ public class IndexTest : IClassFixture<TestApplication>
var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync();
Assert.Contains("/basket/update", updateResponse.RequestMessage.RequestUri.ToString());
Assert.Contains("/basket/update", updateResponse!.RequestMessage!.RequestUri!.ToString()!);
decimal expectedTotalAmount = 416.50M;
Assert.Contains(expectedTotalAmount.ToString("N2"), stringUpdateResponse);
}
@@ -92,7 +92,7 @@ public class IndexTest : IClassFixture<TestApplication>
var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync();
Assert.Contains("/basket/update", updateResponse.RequestMessage.RequestUri.ToString());
Assert.Contains("/basket/update", updateResponse!.RequestMessage!.RequestUri!.ToString()!);
Assert.Contains("Basket is empty", stringUpdateResponse);
}
}

View File

@@ -22,6 +22,6 @@ public static class WebPageHelpers
{
var regex = new Regex(regexpression);
var match = regex.Match(input);
return match.Groups.Values.LastOrDefault().Value;
return match!.Groups!.Values!.LastOrDefault()!.Value;
}
}

View File

@@ -23,6 +23,16 @@ public class TestApplication : WebApplicationFactory<IBasketViewModelService>
// Add mock/test services to the builder here
builder.ConfigureServices(services =>
{
var descriptors = services.Where(d =>
d.ServiceType == typeof(DbContextOptions<CatalogContext>) ||
d.ServiceType == typeof(DbContextOptions<AppIdentityDbContext>))
.ToList();
foreach (var descriptor in descriptors)
{
services.Remove(descriptor);
}
services.AddScoped(sp =>
{
// Replace SQLite with in-memory database for tests

View File

@@ -7,29 +7,28 @@ using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.PublicApi.AuthEndpoints;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace PublicApiIntegrationTests.AuthEndpoints
{
[TestClass]
public class AuthenticateEndpoint
{
[TestMethod]
[DataRow("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
[DataRow("demouser@microsoft.com", "badpassword", false)]
[DataRow("baduser@microsoft.com", "badpassword", false)]
public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
{
var request = new AuthenticateRequest()
{
Username = testUsername,
Password = testPassword
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
var response = await ProgramTest.NewClient.PostAsync("api/authenticate", jsonContent);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<AuthenticateResponse>();
namespace PublicApiIntegrationTests.AuthEndpoints;
Assert.AreEqual(expectedResult, model.Result);
}
[TestClass]
public class AuthenticateEndpoint
{
[TestMethod]
[DataRow("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
[DataRow("demouser@microsoft.com", "badpassword", false)]
[DataRow("baduser@microsoft.com", "badpassword", false)]
public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
{
var request = new AuthenticateRequest()
{
Username = testUsername,
Password = testPassword
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
var response = await ProgramTest.NewClient.PostAsync("api/authenticate", jsonContent);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<AuthenticateResponse>();
Assert.AreEqual(expectedResult, model!.Result);
}
}

View File

@@ -4,29 +4,28 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
using System.Threading.Tasks;
namespace PublicApiIntegrationTests.CatalogItemEndpoints
namespace PublicApiIntegrationTests.CatalogItemEndpoints;
[TestClass]
public class CatalogItemGetByIdEndpointTest
{
[TestClass]
public class CatalogItemGetByIdEndpointTest
[TestMethod]
public async Task ReturnsItemGivenValidId()
{
[TestMethod]
public async Task ReturnsItemGivenValidId()
{
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<GetByIdCatalogItemResponse>();
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<GetByIdCatalogItemResponse>();
Assert.AreEqual(5, model.CatalogItem.Id);
Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name);
}
Assert.AreEqual(5, model!.CatalogItem.Id);
Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name);
}
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidId()
{
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0");
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidId()
{
var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
}

View File

@@ -8,66 +8,65 @@ using System.Net.Http;
using System.Net;
using System.Threading.Tasks;
namespace PublicApiIntegrationTests.CatalogItemEndpoints
namespace PublicApiIntegrationTests.CatalogItemEndpoints;
[TestClass]
public class CatalogItemListPagedEndpoint
{
[TestClass]
public class CatalogItemListPagedEndpoint
[TestMethod]
public async Task ReturnsFirst10CatalogItems()
{
[TestMethod]
public async Task ReturnsFirst10CatalogItems()
var client = ProgramTest.NewClient;
var response = await client.GetAsync("/api/catalog-items?pageSize=10");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<CatalogIndexViewModel>();
Assert.AreEqual(10, model!.CatalogItems.Count());
}
[TestMethod]
public async Task ReturnsCorrectCatalogItemsGivenPageIndex1()
{
var pageSize = 10;
var pageIndex = 1;
var client = ProgramTest.NewClient;
var response = await client.GetAsync($"/api/catalog-items");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<ListPagedCatalogItemResponse>();
var totalItem = model!.CatalogItems.Count();
var response2 = await client.GetAsync($"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}");
response.EnsureSuccessStatusCode();
var stringResponse2 = await response2.Content.ReadAsStringAsync();
var model2 = stringResponse2.FromJson<ListPagedCatalogItemResponse>();
var totalExpected = totalItem - (pageSize * pageIndex);
Assert.AreEqual(totalExpected, model2!.CatalogItems.Count());
}
[DataTestMethod]
[DataRow("catalog-items")]
[DataRow("catalog-brands")]
[DataRow("catalog-types")]
[DataRow("catalog-items/1")]
public async Task SuccessFullMutipleParallelCall(string endpointName)
{
var client = ProgramTest.NewClient;
var tasks = new List<Task<HttpResponseMessage>>();
for (int i = 0; i < 100; i++)
{
var client = ProgramTest.NewClient;
var response = await client.GetAsync("/api/catalog-items?pageSize=10");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<CatalogIndexViewModel>();
Assert.AreEqual(10, model.CatalogItems.Count());
var task = client.GetAsync($"/api/{endpointName}");
tasks.Add(task);
}
await Task.WhenAll(tasks.ToList());
var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK);
[TestMethod]
public async Task ReturnsCorrectCatalogItemsGivenPageIndex1()
{
var pageSize = 10;
var pageIndex = 1;
var client = ProgramTest.NewClient;
var response = await client.GetAsync($"/api/catalog-items");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<ListPagedCatalogItemResponse>();
var totalItem = model.CatalogItems.Count();
var response2 = await client.GetAsync($"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}");
response.EnsureSuccessStatusCode();
var stringResponse2 = await response2.Content.ReadAsStringAsync();
var model2 = stringResponse2.FromJson<ListPagedCatalogItemResponse>();
var totalExpected = totalItem - (pageSize * pageIndex);
Assert.AreEqual(totalExpected, model2.CatalogItems.Count());
}
[DataTestMethod]
[DataRow("catalog-items")]
[DataRow("catalog-brands")]
[DataRow("catalog-types")]
[DataRow("catalog-items/1")]
public async Task SuccessFullMutipleParallelCall(string endpointName)
{
var client = ProgramTest.NewClient;
var tasks = new List<Task<HttpResponseMessage>>();
for (int i = 0; i < 100; i++)
{
var task = client.GetAsync($"/api/{endpointName}");
tasks.Add(task);
}
await Task.WhenAll(tasks.ToList());
var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK);
Assert.AreEqual(0, totalKO);
}
Assert.AreEqual(0, totalKO);
}
}

View File

@@ -8,62 +8,61 @@ using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace PublicApiIntegrationTests.AuthEndpoints
namespace PublicApiIntegrationTests.AuthEndpoints;
[TestClass]
public class CreateCatalogItemEndpointTest
{
[TestClass]
public class CreateCatalogItemEndpointTest
private int _testBrandId = 1;
private int _testTypeId = 2;
private string _testDescription = "test description";
private string _testName = "test name";
private decimal _testPrice = 1.23m;
[TestMethod]
public async Task ReturnsNotAuthorizedGivenNormalUserToken()
{
private int _testBrandId = 1;
private int _testTypeId = 2;
private string _testDescription = "test description";
private string _testName = "test name";
private decimal _testPrice = 1.23m;
var jsonContent = GetValidNewItemJson();
var token = ApiTokenHelper.GetNormalUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.PostAsync("api/catalog-items", jsonContent);
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}
[TestMethod]
public async Task ReturnsNotAuthorizedGivenNormalUserToken()
[TestMethod]
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()
{
var jsonContent = GetValidNewItemJson();
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.PostAsync("api/catalog-items", jsonContent);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<CreateCatalogItemResponse>();
Assert.AreEqual(_testBrandId, model!.CatalogItem.CatalogBrandId);
Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId);
Assert.AreEqual(_testDescription, model.CatalogItem.Description);
Assert.AreEqual(_testName, model.CatalogItem.Name);
Assert.AreEqual(_testPrice, model.CatalogItem.Price);
}
private StringContent GetValidNewItemJson()
{
var request = new CreateCatalogItemRequest()
{
var jsonContent = GetValidNewItemJson();
var token = ApiTokenHelper.GetNormalUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.PostAsync("api/catalog-items", jsonContent);
CatalogBrandId = _testBrandId,
CatalogTypeId = _testTypeId,
Description = _testDescription,
Name = _testName,
Price = _testPrice
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}
[TestMethod]
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()
{
var jsonContent = GetValidNewItemJson();
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.PostAsync("api/catalog-items", jsonContent);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<CreateCatalogItemResponse>();
Assert.AreEqual(_testBrandId, model.CatalogItem.CatalogBrandId);
Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId);
Assert.AreEqual(_testDescription, model.CatalogItem.Description);
Assert.AreEqual(_testName, model.CatalogItem.Name);
Assert.AreEqual(_testPrice, model.CatalogItem.Price);
}
private StringContent GetValidNewItemJson()
{
var request = new CreateCatalogItemRequest()
{
CatalogBrandId = _testBrandId,
CatalogTypeId = _testTypeId,
Description = _testDescription,
Name = _testName,
Price = _testPrice
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
return jsonContent;
}
return jsonContent;
}
}

View File

@@ -5,34 +5,33 @@ using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace PublicApiIntegrationTests.CatalogItemEndpoints
namespace PublicApiIntegrationTests.CatalogItemEndpoints;
[TestClass]
public class DeleteCatalogItemEndpointTest
{
[TestClass]
public class DeleteCatalogItemEndpointTest
[TestMethod]
public async Task ReturnsSuccessGivenValidIdAndAdminUserToken()
{
[TestMethod]
public async Task ReturnsSuccessGivenValidIdAndAdminUserToken()
{
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.DeleteAsync("api/catalog-items/12");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<DeleteCatalogItemResponse>();
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.DeleteAsync("api/catalog-items/12");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<DeleteCatalogItemResponse>();
Assert.AreEqual("Deleted", model.Status);
}
Assert.AreEqual("Deleted", model!.Status);
}
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()
{
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.DeleteAsync("api/catalog-items/0");
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()
{
var adminToken = ApiTokenHelper.GetAdminUserToken();
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await client.DeleteAsync("api/catalog-items/0");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
}

View File

@@ -2,26 +2,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
namespace PublicApiIntegrationTests
namespace PublicApiIntegrationTests;
[TestClass]
public class ProgramTest
{
[TestClass]
public class ProgramTest
private static WebApplicationFactory<Program> _application = new();
public static HttpClient NewClient
{
private static WebApplicationFactory<Program> _application;
public static HttpClient NewClient
get
{
get
{
return _application.CreateClient();
}
}
[AssemblyInitialize]
public static void AssemblyInitialize(TestContext _)
{
_application = new WebApplicationFactory<Program>();
return _application.CreateClient();
}
}
[AssemblyInitialize]
public static void AssemblyInitialize(TestContext _)
{
_application = new WebApplicationFactory<Program>();
}
}