eShopLegacyApp and SimpleGSMAApp removed

This commit is contained in:
Jose Luis Carrillo Aguilar 2023-06-05 10:33:37 +02:00
Родитель d91b625ecc
Коммит 7563d73b7b
289 изменённых файлов: 0 добавлений и 452633 удалений

Просмотреть файл

@ -1,218 +0,0 @@
# Deploy a Simple GMSA Integrated Workload
This application is provided by Microsoft through the [GMSA on AKS PowerShell Module](https://learn.microsoft.com/virtualization/windowscontainers/manage-containers/gmsa-aks-ps-module). The manifest for this application has been modified to support ingress using Azure private load balancer. After setting up GMSA, the instructions will ask you perform a deployment that grabs the sample application through the PowerShell module. Do not deploy the application through the PowerShell module and please follow the steps below.
Because the infrastructure has been deployed in a private AKS cluster setup with private endpoints for the container registry and other components, you will need to perform the application container build and the publishing to the Container Registry from the Domain Controller in the Hub VNET, connecting via the Bastion Host service. If your computer is connected to the hub network, you may be able to just use that as well. The rest of the steps can be performed on your local machine by using AKS Run commands which allow access into private clusters using RBAC. This will help with improving security and will provide a more user-friendly way of editing YAML files.
## Note on the resource constraints in the workload manifest
The values used for CPU and memory represent the minimums to run a Windows application with GMSA. Windows applications require higher memory thresholds than Linux applications. You should adjust these values when running your own application in this cluster.
## Connecting to the Bastion Host
Follow the instructions [here](https://learn.microsoft.com/azure/bastion/bastion-connect-vm-rdp-windows) to connect to your Domain Controller deployed through the reference architecture via Bastion using RDP or instructions [here](https://learn.microsoft.com/azure/bastion/bastion-connect-vm-ssh-windows) to connect via SSH.
## Prepare your Jumpbox VM with tools (run from local machine)
* Install az cli for windows. You can find latest version [here](https://learn.microsoft.com/cli/azure/install-azure-cli-windows?tabs=azure-cli)
* After installing az cli you will need to install aks add-on. Run az aks install-cli to add support for kubelogin and kubectl.
* Login to Azure
```Powershell
$TENANTID=<tenant id>
az login -t $TENANTID --use-device-code --debug
```
* Ensure you are connected to the correct subscription
```Powershell
az account set --subscription <subscription id>
```
* Get AKS credentials.
```Powershell
az aks get-credentials -n <aks cluster name> -g <resource group>
```
* Validate you can query AKS cluster.
```Powershell
kubectl get ns
```
## Setup GMSA on your AKS cluster
- Follow the steps [here](https://learn.microsoft.com/virtualization/windowscontainers/manage-containers/gmsa-aks-ps-module) to setup GMSA on your cluster. These commands will need to be run on your domain controller or on a domain joined virtual machine (the Windows VM jumpbox from the previous step). Follow all instructions on pages, _gMSA on AKS PowerShell Module_ and _Configure gMSA on AKS with PowerShell Module_. You may optionally follow the instructions on _Validate gMSA on AKS with PowerShell Module_, but it is not required for setup.
- For the keyvault used in the setup, use the keyvault deployed as apart of this reference implementation. The module will check if a keyvault with that name exists before creating the secret with the GMSA credentials.
- For the managed identity used in the setup, use the managed identity deployed as apart of this reference implementation. The module will check if a managed identity with that name exists before creating a new one.
## Setup Group Managed Service Account (GMSA) Integration
* Before enabling GMSA on your AKS cluster, you will need to make the following updates to your architecture:
1. Update the DNS server on the hub and spoke VNETs to from Azure Default to Custom. The IP address will be the IP address of your domain controller (your jumpbox). Reboot all virtual machines, VM Scale Sets, and your domain controller after performing this action.
2. Add your login identity to Key Vault access policy. Assign Secret Management (Get, List, Set, Delete, Recover). This is needed during GMSA setup.
## Common Issues
1. If you are running these commands on your domain controller that is a Windows Server machine, you may have trouble
installing the [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl-windows/) and [kubelogin](https://github.com/Azure/kubelogin). The error would mention "Install-AzAksKubectl". If so, you will need to manually install both following the links above.
## How to validate your GMSA integration before deployment
1. To validate that your cluster is successfully retrieving your GMSA, go into your domain controller local server menu, go to Tools and select Event Viewer. Look under ActiveDirectory events. Look at the contents of the most recent events for a message that says "A caller successfully fetched the password of a group managed service account." The IP address of the caller should match one of your AKS cluster IPs.
## Import simple GMSA application to your container registry
To run the application container image, you will first need to import the Windows Server 2019 LTSC image to your Azure Container Registry that was deployed as a part of the reference implementation. Once this image is imported, you will reference it in the workload's manifest file rather than the public image from Microsoft Container Registry.
```PowerShell
# enter the name of your ACR below
$SPOKERG=<resource group name for spoke>
$ACRNAME=$(az acr show --name <ACR NAME> --resource-group $SPOKERG --query "name" --output tsv)
```
Import the Windows Server 2019 LTSC image into your container registry.
```PowerShell
az acr import -n $ACRNAME --source mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
```
To verify that the image has been imported:
```PowerShell
az acr repository show -n $ACRNAME --image windows/servercore/iis:windowsservercore-ltsc2019
```
## Deploy workload without support for HTTPS
Navigate to "AKS-Landing-Zone-Accelerator/Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/SimpleGMSAAPP/manifests" folder.
1. Create a namespace for your application by running ``` kubectl create namespace simpleapp ```
2. Update the [manifest file](manifests/deployment_sampleapp.yml) for the sample application with your GMSA name (look for **< GMSA Credential Spec Name >** in the manifest) and application container registry name (Look for **< Registry Name >** in the manifest).
3. Run ``` kubectl apply -f deployment_sampleapp.yml -n simpleapp ```
### Check your deployed workload
1. Verify Deployment by executing the following commands on your jumpbox:
```Powershell
kubectl get deployment -n simpleapp
kubectl get pods -n simpleapp
kubectl describe service -n simpleapp
```
2. Copy the ip address displayed by running ``` kubectl describe ingress -n simpleapp ``` on your jumpbox, open a browser, navigate to the IP address obtained above from the ingress controller and explore your website.
## How to Validate Your GMSA Integration after Deployment
1. Check the status of your pods by running ``` kubectl get pods -n simpleapp``` on your jumpbox. If the status is *Running*, you're good to go. If the status of your pods is *CrashLoopBackOff*, run ``` kubectl logs <pod name> ``` to debug. This status likely means that your credential spec file is misconfigured or the cluster permissions to your KeyVault are misconfigured. An example credential spec can be found [here](https://learn.microsoft.com/virtualization/windowscontainers/manage-containers/manage-serviceaccounts#create-a-credential-spec). When you look through your credential spec file, ensure that the DnsName, NetBiosName and GroupManagedServiceAccounts match the values from your domain controller.
2. To check if the container is connected to the domain your GMSA is running under, run the following commands:
To login to the pod running your workload:
```Powershell
kubectl get pods -n simpleapp
kubectl exec -it "pod name" powershell
```
```Powershell
nltest /sc_verify:lzacc.com
```
## Deploy the Ingress with HTTPS support
**Please note: This section is still in development**
A fully qualified DNS name and a certificate are needed to configure HTTPS support on the the front end of the web application. You are welcome to bring your own certificate and DNS if you have them available, however a simple way to demonstrate this is to use a self-signed certificate with an FQDN configured on the IP address used by the Application Gateway.
**Objectives**
1. Configure the Public IP address of your Application Gateway to have a DNS name. It will be in the format of customPrefix.region.cloudapp.azure.com
2. Create a certificate using the FQDN and store it in Key Vault.
### Creating Public IP address for your Application Gateway
1. Find your application gateway in your landing zone resource group and click on it. By default it should be in the spoke resource group.
2. Click on the *Frontend public IP address*
3. Click on configuration in the left blade of the resulting page.
4. Enter a unique DNS name in the field provided and click **Save**.
### Create the self-signed certificate using Lets Encrypt
We are going to use Lets Encrypt and Cert-Manager to provide easy to use certificate management for the application within AKS. Cert-Manager will also handle future certificate renewals removing any manual processes.
1. First of all, you will need to install cert-manager into your cluster.
```bash
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.8.0/cert-manager.yaml"
```
First of all this will create a new namespace called cert-manager which is where all of the resources for cert-manager will be kept. This will then go ahead and download some CRDs (CustomResourceDefinitions) which provides extra functionality in the cluster for the creation of certificates.
We will then proceed to test this certificate process with a staging certificate to begin with, before moving on to deploying a production certificate.
2.Edit the 'certificateIssuer.yaml' file and include your email address. This will be used for certificate renewal notifications.
Deploy certificateIssuer.yaml
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f certificateIssuer.yaml -n default" --file certificateIssuer.yaml
```
1. Edit the `deployment_sampleapp.yml` Ingress section with the FQDN of your host that you created earlier on the public IP of the Application Gateway.
Deploy deployment_sampleapp.yml
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f deployment_sampleapp.yml"
```
After updating the ingress, A request will be sent to letsEncrypt to provide a 'staging' certificate. This can take a few minutes. You can check on the progress by running the below command. When the status Ready = True. You should be able to browse to the same URL you configured on the PIP of the Application Gateway earlier.
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl get certificate"
```
If you notice the status is not changing after a few minutes, there could be a problem with your certificate request. You can gather more information by running a describe on the request using the below command.
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl get certificaterequest"
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl describe certificaterequest <certificaterequestname>"
```
Upon navigating to your new FQDN you will see you receive a certificate warning because it is not a production certificate. If you have got this far, continue to the next step to remediate this issue.
4. Edit the 'certificateIssuer.yaml' file and replace the following:
Change the metadata name to letsencrypt-prod
Change the server to <https://acme-v02.api.letsencrypt.org/directory>
change the privateKeySecretRef to letsencrypt-prod
Re-apply the updated file
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f certificateIssuer.yaml" --file certificateIssuer.yaml
```
5. The next step is to change the ingress to point to the production certificateIssuer. At the moment it is still pointing to the old staging issuer.
Edit 'deployment_sampleapp.yml' and replace the following values:
cert-manager.io/issuer: letsencrypt-prod
Re-apply the updated file
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f deployment_sampleapp.yml"
```
Now you can access the website using using your FQDN. When you navigate to the website using your browser you might see a warning stating the destination is not safe. Give it a few minutes and this should clear out. However, for production you want to use Certified Authority (CA) certificates.
# Next Steps
:arrow_forward: [Create the ingress configuration for GMSA](../../Terraform/09-ingress-config.md)

Просмотреть файл

@ -1,106 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
labels:
app: "gmsa-win-demo"
name: "gmsa-win-demo"
namespace: simpleapp
data:
run.ps1: |
$ErrorActionPreference = "Stop"
Write-Output "Configuring IIS with authentication."
# Add required Windows features, since they are not installed by default.
Install-WindowsFeature "Web-Windows-Auth", "Web-Asp-Net45"
# Create simple ASP.Net page.
New-Item -Force -ItemType Directory -Path 'C:\inetpub\wwwroot\app'
Set-Content -Path 'C:\inetpub\wwwroot\app\default.aspx' -Value 'Authenticated as <B><%=User.Identity.Name%></B>, Type of Authentication: <B><%=User.Identity.AuthenticationType%></B>'
# Configure IIS with authentication.
Import-Module IISAdministration
Start-IISCommitDelay
(Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/windowsAuthentication').Attributes['enabled'].value = $true
(Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/anonymousAuthentication').Attributes['enabled'].value = $false
(Get-IISServerManager).Sites[0].Applications[0].VirtualDirectories[0].PhysicalPath = 'C:\inetpub\wwwroot\app'
Stop-IISCommitDelay
Write-Output "IIS with authentication is ready."
C:\ServiceMonitor.exe w3svc
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: "gmsa-win-demo"
name: gmsa-win-demo
namespace: simpleapp
spec:
replicas: 1
selector:
matchLabels:
app: "gmsa-win-demo"
template:
metadata:
labels:
app: "gmsa-win-demo"
spec:
serviceAccountName: gmsa
nodeSelector:
"kubernetes.io/os": windows
securityContext:
windowsOptions:
gmsaCredentialSpecName: < GMSA Credential Spec Name >
runAsNonRoot: true
containers:
- name: iis
image: < Registry Name >.azurecr.io/windows/servercore/iis:windowsservercore-ltsc2019
imagePullPolicy: IfNotPresent
command:
- powershell
args:
- -File
- /gmsa-demo/run.ps1
volumeMounts:
- name: gmsa-win-demo
mountPath: /gmsa-demo
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 500m
memory: 1024Mi
volumes:
- name: gmsa-win-demo
configMap:
defaultMode: 420
name: gmsa-win-demo
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: agentpool
operator: In
values:
- winpl
---
apiVersion: v1
kind: Service
metadata:
name: gmsa-win-demo
namespace: simpleapp
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "< Subnet Name >"
spec:
type: LoadBalancer
selector:
app: gmsa-win-demo
ports:
- protocol: TCP
port: 80

Просмотреть файл

@ -1,24 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: simpleapp
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gmsa
namespace: simpleapp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gmsa
namespace: simpleapp
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gmsaspec-role
subjects:
- kind: ServiceAccount
name: gmsa
namespace: simpleapp

Просмотреть файл

@ -1,248 +0,0 @@
**This workload is still in development. Feel free to try it out, fix issues and PR to the repo.**
# Deploy a GMSA Integrated .NET Legacy Workload
This application is provided by Microsoft to demonstrate a Legacy .NET 4.7 application on Windows Containers. Follow the instructions in the documentation through generating and applying the credential spec file to the AKS cluster. After you configure GMSA, you will walk through the steps of deploying the application to the cluster.
Because the infrastructure has been deployed in a private AKS cluster setup with private endpoints for the container registry and other components, you will need to perform the application container build and the publishing to the Container Registry from the Dev Jumpbox in the Hub VNET, connecting via the Bastion Host service. If your computer is connected to the hub network, you may be able to just use that as well. The rest of the steps can be performed on your local machine by using AKS Run commands which allow access into private clusters using RBAC. This will help with improving security and will provide a more user-friendly way of editing YAML files.
1. ## Prepare your Jumpbox VM with tools (run from local machine)
* Add a rule in the Firewall to allow internet access to the VM's private IP. Verify VM's private IP and update if necessary
```PowerShell
az network firewall network-rule create --collection-name 'VM-egress' --destination-ports '*' --firewall-name 'vnet-ESLZ-firewall' --name 'Allow-Internet' --protocols Any --resource-group 'ESLZ-HUB' --action Allow --dest-addr '*' --priority 201 --source-addresses '10.0.3.4/32'
```
* Add a rule in the Firewall to allow internet access to the your VM or computer's IP. Verify VM's private IP and update if necessary
```PowerShell
az network firewall network-rule create --collection-name 'VM-egress' --destination-ports '*' --firewall-name 'AZFW' --name 'Allow-Internet' --protocols Any --resource-group 'ESLZ-HUB' --action Allow --dest-addr '*' --priority 201 --source-addresses '<your vm or computer's ip>'
```
## Connecting to the Bastion Host
1. Use Bastion Host to connect to the jumpbox.
2. Enter the username and password. If you have used a public key, then select upload private key (corresponding to the public key) to connect.
3. Once you connect ensure you permit the site to read the content of your clipboard
* Clone it on the jumpbox.
```Git bash
git clone https://github.com/Azure/AKS-Landing-Zone-Accelerator
```
* Run the script below to install the required tools (Az CLI, Docker, Kubectl, Helm etc). Navigate to "AKS-Landing-Zone-Accelerator/Scenarios/AKS-Secure-Baseline-PrivateCluster/Terraform/04-Network-Hub" folder.
``` PowerShell
cd AKS-Landing-Zone-Accelerator/Scenarios/AKS-Secure-Baseline-PrivateCluster/Terraform/04-Network-Hub
./Install-Tools.ps1
```
* Login to Azure
```PowerShell
$TENANTID=<tenant id>
az login -t $TENANTID --debug
```
* Ensure you are connected to the correct subscription
```PowerShell
az account set --subscription <subscription id>
```
## Build Container Images
The Dockerfile for the eShop application is in the repository you've already cloned to your jumpbox in the previous step.
Navigate to the [eShop Dockerfile](../eshopLegacyApp/application/eshop.Dockerfile), build and tag the containers with the name of your Azure Container Registry and push the images to ACR. // Make sure it is the correct ACR
```PowerShell
# enter the name of your ACR below
$SPOKERG=<resource group name for spoke>
$ACRNAME=$(az acr show --name <ACR NAME> --resource-group $SPOKERG --query "name" --output tsv)
cd AKS-Landing-Zone-Accelerator/Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/eshopLegacyApp/application
docker build -t $ACRNAME.azurecr.io/eshopapp:v1 -f eshop.Dockerfile .
```
Log into ACR
```PowerShell
az acr login -n $ACRNAME
```
Push the images into the container registry. Ensure you are logged into the Azure Container Registry, you should show a successful login from the command above.
```PowerShell
docker push $ACRNAME.azurecr.io/eshopapp:v1
```
To verify they have been pushed run the following commands:
```PowerShell
az acr repository show -n $ACRNAME --image eshopapp:v1
```
## Infrastructure Additions
To deploy the eShop application, you will need to add a SQL database to your base architecture and a Windows Server 2022 nodepool to your AKS cluster.
### Azure SQL Database
In addition to the base private architecture that you've deployed, you will need to deploy a SQL database to hold the product information.
1. Create a SQL server and database in your Spoke resource group in the same location.
2. Using the query editor in Azure Portal or SQL Server Management Studio (SSMS), run the [eShop Database Query](/Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/eshopLegacyApp/database%20scripts/insertdata.sql) on your database to create the necessary tables and hydrate the tables with data.
3. Copy and paste your SQL database connection string in the placeholder in the [Web.config](/Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/eshopLegacyApp/application/src/eShopModernizedMVC/Web.config) for the eShop Application. DO NOT commit this connection string to your public Git repository.
### AKS Windows Server 2022 Nodepool
The eShop container image uses Windows Server 2022 nodes. The base architecture deploys a default Windows nodepool which is Windows Server 2019. Update your existing cluster by adding a Windows Server 2022 nodepool through Terraform. Make sure you do this step before setting up GMSA because the setup needs the name of all Windows nodepools that need access to the KeyVault for pulling the domain credentials.
## Setup Group Managed Service Account (GMSA) Integration
Before enabling GMSA on your AKS cluster, you will need to make the following updates to your architecture:
1. Update the DNS server on the hub and spoke VNETs to from Azure Default to Custom. The IP address will be the IP address of your domain controller. Reboot all virtual machines and your domain controller after performing this action.
2. Enable the application gateway add-on for your AKS cluster. This will allow the application to be accessed through the public IP address of the gateway.
Follow the steps [here](https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/gmsa-aks-ps-module) to setup your environment and setup GMSA on your cluster. These commands will need to be run on your domain controller or on a domain joined virtual machine.
### Common Issues
1. If you are running these commands on your domain controller that is a Windows Server machine, you may have trouble installing the [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl-windows/) and [kubelogin](https://github.com/Azure/kubelogin). The error would mention "Install-AzAksKubectl". If so, you will need to manually install both following the links above.
### How to Validate Your GMSA Integration
1. Check the status of your pods by running ``` kubectl get pods ```. If the status is *Running*, you're good to go. If the status of your pods is *CrashLoopBackOff*, run ``` kubectl logs <pod name> ``` to debug. This status likely means that your credential spec file is misconfigured or the cluster permissions to your KeyVault are misconfigured. If you believe your cred spec is correct, check the logs from the above command to verify the pod was able to pull down the image from the Azure Container Registry (ACR).
2. To validate that your cluster is successfully retrieving your GMSA, go into your domain controller local server menu, go to Tools and select Event Viewer. Look under ActiveDirectory events. Look at the contents of the most recent events for a message that says "A caller successfully fetched the password of a group managed service account." The IP address of the caller should match one of your AKS cluster IPs.
### Deploy workload
Navigate to "Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/eshopLegacyApp" folder.
1. Update the [manifest file](/Scenarios/AKS-Secure-Baseline-PrivateCluster/Apps/eshopLegacyApp/manifests/deployment.yml) for the sample application with your GMSA name and image name.
2. Run ``` kubectl apply -f ingress.yml ``` to setup the Application Gateway for ingress on the cluster.
3. Run ``` kubectl apply -f deployment.yml ``` to deploy the application to the Windows 2022 nodes.
## **(Optional)** Deploy the Ingress without support for HTTPS
This step is optional. If you would like to go straight to using https which is the secure option, skip this section and go straight to the **Update the Ingress to support HTTPS traffic** section.
It is important to first configure the NSG for the Application Gateway to accept traffic on port 80 if using the HTTP option. Run the following command to allow HTTP.
```PowerShell
$APPGWSUBNSG=<Name of NSG for AppGwy>
az network nsg rule create -g $SPOKERG --nsg-name $APPGWSUBNSG -n AllowHTTPInbound --priority 1000 \
--source-address-prefixes '*' --source-port-ranges '*' \
--destination-address-prefixes '*' --destination-port-ranges 80 --access Allow \
--protocol Tcp --description "Allow Inbound traffic through the Application Gateway on port 80"
```
You will also need to update the firewall rules to allow http. Go into the portal and add a firewall rule that allows http on port 80.
### Check your deployed workload
1. Copy the ip address displayed by ``` kubectl get ingress ```, open a browser inside your domain controller or a domain joined virtual machine, navigate to the IP address obtained above from the ingress controller and explore your website
It is important to delete the rule that allows HTTP traffic to keep the cluster safe since we have completed the test.
```PowerShell
az network nsg rule delete -g $SPOKERG --nsg-name $APPGWSUBNSG -n AllowHTTPInbound
```
**the optional steps end here**
## Deploy the Ingress with HTTPS support
**Please note: This section is still in development**
A fully qualified DNS name and a certificate are needed to configure HTTPS support on the the front end of the web application. You are welcome to bring your own certificate and DNS if you have them available, however a simple way to demonstrate this is to use a self-signed certificate with an FQDN configured on the IP address used by the Application Gateway.
**Objectives**
1. Configure the Public IP address of your Application Gateway to have a DNS name. It will be in the format of customPrefix.region.cloudapp.azure.com
2. Create a certificate using the FQDN and store it in Key Vault.
### Creating Public IP address for your Application Gateway
1. Find your application gateway in your landing zone resource group and click on it. By default it should be in the spoke resource group.
2. Click on the *Frontend public IP address*
![front end public ip address](../../media/front-end-pip-link.png)
3. Click on configuration in the left blade of the resulting page.
4. Enter a unique DNS name in the field provided and click **Save**.
![creating nds](../../media/dns-created.png)
### Create the self-signed certificate using Lets Encrypt
We are going to use Lets Encrypt and Cert-Manager to provide easy to use certificate management for the application within AKS. Cert-Manager will also handle future certificate renewals removing any manual processes.
1. First of all, you will need to install cert-manager into your cluster.
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.8.0/cert-manager.yaml"
```
First of all this will create a new namespace called cert-manager which is where all of the resources for cert-manager will be kept. This will then go ahead and download some CRDs (CustomResourceDefinitions) which provides extra functionality in the cluster for the creation of certificates.
We will then proceed to test this certificate process with a staging certificate to begin with, before moving on to deploying a production certificate.
2. Edit the 'certificateIssuer.yaml' file and include your email address. This will be used for certificate renewal notifications.
Deploy certificateIssuer.yaml
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f certificateIssuer.yaml -n default" --file certificateIssuer.yaml
```
1. Edit the 'ingress.yml' with the FQDN of your host that you created earlier on the public IP of the Application Gateway.
Deploy ingress.yml
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f ingress.yml"
```
After updating the ingress, A request will be sent to letsEncrypt to provide a 'staging' certificate. This can take a few minutes. You can check on the progress by running the below command. When the status Ready = True. You should be able to browse to the same URL you configured on the PIP of the Application Gateway earlier.
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl get certificate"
```
If you notice the status is not changing after a few minutes, there could be a problem with your certificate request. You can gather more information by running a describe on the request using the below command.
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl get certificaterequest"
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl describe certificaterequest <certificaterequestname>"
```
Upon navigating to your new FQDN you will see you receive a certificate warning because it is not a production certificate. If you have got this far, continue to the next step to remediate this issue.
![deployed workload https](../../media/deployed-workload-https.png)
4. Edit the 'certificateIssuer.yaml' file and replace the following:
Change the metadata name to letsencrypt-prod
Change the server to https://acme-v02.api.letsencrypt.org/directory
change the privateKeySecretRef to letsencrypt-prod
Re-apply the updated file
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f certificateIssuer.yaml" --file certificateIssuer.yaml
```
5. The next step is to change the ingress to point to the production certificateIssuer. At the moment it is still pointing to the old staging issuer.
Edit 'deployment.yml' and replace the following values:
cert-manager.io/issuer: letsencrypt-prod
Re-apply the updated file
```PowerShell
az aks command invoke --resource-group $ClusterRGName --name $ClusterName --command "kubectl apply -f deployment.yml"
```
Now you can access the website using using your FQDN. When you navigate to the website using your browser you might see a warning stating the destination is not safe. Give it a few minutes and this should clear out. However, for production you want to use Certified Authority (CA) certificates.
![deployed workload https more secure](../../media/deployed-workload-https-secure.png)

Просмотреть файл

@ -1,34 +0,0 @@
param (
[string]$webConfig = "c:\inetpub\wwwroot\Web.config"
)
$doc = (Get-Content $webConfig) -as [Xml];
$modified = $FALSE;
$appSettingPrefix = "APPSETTING_";
$connectionStringPrefix = "CONNSTR_";
Get-ChildItem env:* | ForEach-Object {
if ($_.Key.StartsWith($appSettingPrefix)) {
$key = $_.Key.Substring($appSettingPrefix.Length);
$appSetting = $doc.configuration.appSettings.add | Where-Object {$_.key -eq $key};
if ($appSetting) {
$appSetting.value = $_.Value;
Write-Host "Replaced appSetting" $_.Key $_.Value;
$modified = $TRUE;
}
}
if ($_.Key.StartsWith($connectionStringPrefix)) {
$key = $_.Key.Substring($connectionStringPrefix.Length);
$connStr = $doc.configuration.connectionStrings.add | Where-Object {$_.name -eq $key};
if ($connStr) {
$connStr.connectionString = $_.Value;
Write-Host "Replaced connectionString" $_.Key $_.Value;
$modified = $TRUE;
}
}
}
if ($modified) {
$doc.Save($webConfig);
}

Просмотреть файл

@ -1,27 +0,0 @@
$ErrorActionPreference = "Stop"
Write-Output "Configuring IIS with authentication."
# Add required Windows features, since they are not installed by default.
Install-WindowsFeature "Web-Windows-Auth", "Web-Asp-Net45"
# Configure IIS with authentication.
Import-Module IISAdministration
Start-IISCommitDelay
(Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/windowsAuthentication').Attributes['enabled'].value = $true
(Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/anonymousAuthentication').Attributes['enabled'].value = $false
(Get-IISServerManager).Sites[0].Applications[0].VirtualDirectories[0].PhysicalPath = 'C:\inetpub\wwwroot\'
Stop-IISCommitDelay
Write-Output "IIS with authentication is ready."
C:\ServiceMonitor.exe w3svc
.\Set-WebConfigSettings.ps1 -webConfig c:\inetpub\wwwroot\Web.config
If (Test-Path Env:\ASPNET_ENVIRONMENT)
{
\WebConfigTransformRunner.1.0.0.1\Tools\WebConfigTransformRunner.exe \inetpub\wwwroot\Web.config "\inetpub\wwwroot\Web.$env:ASPNET_ENVIRONMENT.config" \inetpub\wwwroot\Web.config
}
Write-Host "IIS Started..."
while ($true) { Start-Sleep -Seconds 3600 }

Просмотреть файл

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Windows</DockerTargetOS>
<ProjectGuid>bc5d671b-dceb-4f3d-b307-db47ada44577</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>http://{ServiceIPAddress}</DockerServiceUrl>
<DockerServiceName>eshopmodernizedmvc</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,5 +0,0 @@
version: '3'
services:
eshop.modernized.mvc:
image: eshop/modernizedmvc

Просмотреть файл

@ -1,10 +0,0 @@
version: '3'
services:
eshop.modernized.mvc:
image: eshop/modernizedmvc
depends_on:
- sql.data
sql.data:
image: microsoft/mssql-server-windows-developer

Просмотреть файл

@ -1,22 +0,0 @@
version: '3'
services:
eshop.modernized.mvc:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
- UseMockData=False
- UseCustomizationData=False
- UseAzureStorage=False
- StorageConnectionString=${ESHOP_AZURE_STORAGE_CONNECTION_STRING}
- AppInsightsInstrumentationKey=${APP_INSIGHTS_INSTRUMENTATION_KEY}
- UseAzureActiveDirectory=false
- AzureActiveDirectoryClientId=${AZURE_ACTIVE_DIRECTORY_CLIENT_ID}
- AzureActiveDirectoryTenant=${AZURE_ACTIVE_DIRECTORY_TENANT}
- PostLogoutRedirectUri=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP:-10.0.75.1}:5115/
ports:
- "5115:80"
networks:
default:
external:
name: nat

Просмотреть файл

@ -1,33 +0,0 @@
version: '3.4'
services:
eshop.modernized.mvc:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
- UseMockData=True
- UseCustomizationData=False
- UseAzureStorage=False
- StorageConnectionString=${ESHOP_AZURE_STORAGE_CONNECTION_STRING}
- AppInsightsInstrumentationKey=${APP_INSIGHTS_INSTRUMENTATION_KEY}
- UseAzureActiveDirectory=false
- AzureActiveDirectoryClientId=${AZURE_ACTIVE_DIRECTORY_CLIENT_ID}
- AzureActiveDirectoryTenant=${AZURE_ACTIVE_DIRECTORY_TENANT}
- PostLogoutRedirectUri=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP:-10.0.75.1}:5115/
ports:
- "5115:80"
sql.data:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"
healthcheck:
test: [ "CMD", "sqlcmd", "-U", "sa", "-P", "Pass@word", "-Q", "select 1" ]
interval: 1s
retries: 20
networks:
default:
external:
name: nat

Просмотреть файл

@ -1,13 +0,0 @@
version: '3.4'
services:
eshop.modernized.mvc:
image: eshop/modernizedmvc
build:
context: .\src\eShopModernizedMVC
dockerfile: Dockerfile
depends_on:
- sql.data
sql.data:
image: microsoft/mssql-server-windows-developer

Просмотреть файл

@ -1,41 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopModernizedMVC", "src\eShopModernizedMVC\eShopModernizedMVC.csproj", "{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Debug|x64.Build.0 = Debug|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Release|Any CPU.Build.0 = Release|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Release|x64.ActiveCfg = Release|Any CPU
{C4E50A92-30E2-4592-9A9A-A9D2B1B13A7E}.Release|x64.Build.0 = Release|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Debug|x64.ActiveCfg = Debug|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Debug|x64.Build.0 = Debug|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Release|Any CPU.Build.0 = Release|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Release|x64.ActiveCfg = Release|Any CPU
{BC5D671B-DCEB-4F3D-B307-DB47ADA44577}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CF2C2E3F-22E9-45EB-8EC1-A670ADC9762D}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -1,52 +0,0 @@
# escape=`
# # ================================================================================================
# # Builder Stage
# # ================================================================================================
FROM mcr.microsoft.com/dotnet/framework/sdk:4.7.2-windowsservercore-ltsc2019 as builder
# Copy files
RUN md c:\build
WORKDIR c:/build
COPY . c:/build
RUN nuget restore
RUN msbuild eShopModernizedMVC.sln
# ================================================================================================
# Runtime Stage
# ================================================================================================
FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2022
# Install Chocolatey
RUN @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:ChocolateyUseWindowsCompression='false'; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
WORKDIR c:/build
COPY --from=builder C:\build\ c:\build\
# Install build tools
RUN powershell add-windowsfeature web-asp-net45 `
&& choco install microsoft-build-tools -y --allow-empty-checksums -version 14.0.23107.10 `
&& choco install dotnet4.6-targetpack --allow-empty-checksums -y `
&& c:\build\nuget.exe install MSBuild.Microsoft.VisualStudio.Web.targets -Version 14.0.0.3 `
&& c:\build\nuget.exe install WebConfigTransformRunner -Version 1.0.0.1
EXPOSE 80
# Install LogMonitor.exe
RUN powershell New-Item -ItemType Directory C:\LogMonitor; $downloads = @( @{ uri = 'https://github.com/microsoft/windows-container-tools/releases/download/v1.1/LogMonitor.exe'; outFile = 'C:\LogMonitor\LogMonitor.exe' }, @{ uri = 'https://raw.githubusercontent.com/microsoft/iis-docker/master/windowsservercore-insider/LogMonitorConfig.json'; outFile = 'C:\LogMonitor\LogMonitorConfig.json' } ); $downloads.ForEach({ Invoke-WebRequest -UseBasicParsing -Uri $psitem.uri -OutFile $psitem.outFile })
RUN powershell remove-item C:\inetpub\wwwroot\iisstart.*
RUN xcopy c:\build\src\eShopModernizedMVC\* c:\inetpub\wwwroot /s
# Enable ETW logging for Default Web Site on IIS
RUN c:\windows\system32\inetsrv\appcmd.exe set config -section:system.applicationHost/sites /"[name='Default Web Site'].logFile.logTargetW3C:"File,ETW"" /commit:apphost
# Start "C:\LogMonitor\LogMonitor.exe C:\ServiceMonitor.exe w3svc and application"
ENTRYPOINT ["powershell", ".\\Startup.ps1", "C:\\LogMonitor\\LogMonitor.exe", "C:\\ServiceMonitor.exe w3svc"]

Просмотреть файл

@ -1,3 +0,0 @@
*
!obj\Docker\publish\*
!obj\Docker\empty\

Просмотреть файл

@ -1,34 +0,0 @@
using System.Web.Optimization;
namespace eShopModernizedMVC
{
public class BundleConfig
{
// For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*"));
// Use the development version of Modernizr to develop with and learn from. Then, when you're
// ready for production, use the build tool at https://modernizr.com to pick only the tests you need.
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js",
"~/Scripts/site.js"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
"~/Content/custom.css",
"~/Content/base.css",
"~/Content/site.css"));
}
}
}

Просмотреть файл

@ -1,93 +0,0 @@
using System.Configuration;
namespace eShopModernizedMVC
{
public class CatalogConfiguration
{
public static bool UseMockData
{
get
{
return IsEnabled("UseMockData");
}
}
public static bool UseAzureStorage
{
get
{
return IsEnabled("UseAzureStorage");
}
}
public static bool UseManagedIdentity
{
get
{
return IsEnabled("UseAzureManagedIdentity");
}
}
public static bool UseCustomizationData
{
get
{
return IsEnabled("UseCustomizationData");
}
}
public static string StorageConnectionString
{
get
{
return ConfigurationManager.AppSettings["StorageConnectionString"];
}
}
public static string AppInsightsInstrumentationKey
{
get
{
return ConfigurationManager.AppSettings["AppInsightsInstrumentationKey"];
}
}
public static bool UseAzureActiveDirectory
{
get
{
return IsEnabled("UseAzureActiveDirectory");
}
}
public static string AzureActiveDirectoryClientId
{
get
{
return ConfigurationManager.AppSettings["AzureActiveDirectoryClientId"];
}
}
public static string AzureActiveDirectoryTenant
{
get
{
return ConfigurationManager.AppSettings["AzureActiveDirectoryTenant"];
}
}
public static string PostLogoutRedirectUri
{
get
{
return ConfigurationManager.AppSettings["PostLogoutRedirectUri"];
}
}
private static bool IsEnabled(string configurationKey)
{
return bool.Parse(ConfigurationManager.AppSettings[configurationKey]);
}
}
}

Просмотреть файл

@ -1,20 +0,0 @@
using eShopModernizedMVC.Filters;
using System.Web.Mvc;
namespace eShopModernizedMVC
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ActionTracerFilter());
filters.Add(new HandleErrorAttribute());
filters.Add(new OutputCacheAttribute
{
VaryByParam = "*",
Duration = 0,
NoStore = true,
});
}
}
}

Просмотреть файл

@ -1,16 +0,0 @@
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
namespace eShopModernizedMVC
{
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry?.Context?.Cloud == null) return;
requestTelemetry.Context.Cloud.RoleName = "eShopModernizedMVC";
}
}
}

Просмотреть файл

@ -1,24 +0,0 @@
using Microsoft.Configuration.ConfigurationBuilders;
using System;
using System.Collections.Specialized;
namespace eShopModernizedMVC
{
/// <summary>
/// A version of <see cref="AzureKeyVaultConfigBuilder"/> that correctly handles optional. See https://github.com/aspnet/MicrosoftConfigurationBuilders/pull/56
/// for general fix.
/// </summary>
public class OptionalKeyVaultConfigurationBuilder : AzureKeyVaultConfigBuilder
{
protected override void LazyInitialize(string name, NameValueCollection config)
{
try
{
base.LazyInitialize(name, config);
}
catch (Exception) when (Optional)
{
}
}
}
}

Просмотреть файл

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace eShopModernizedMVC
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("Pics/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "CatalogRegisterUser", action = "LogIn", id = UrlParameter.Optional }
);
}
}
}

Просмотреть файл

@ -1,44 +0,0 @@
using Microsoft.Azure.Services.AppAuthentication;
using System.Configuration;
using System.Data.SqlClient;
namespace eShopModernizedMVC
{
public interface ISqlConnectionFactory
{
SqlConnection CreateConnection();
}
public class ManagedIdentitySqlConnectionFactory : ISqlConnectionFactory
{
private readonly AzureServiceTokenProvider _provider;
public ManagedIdentitySqlConnectionFactory()
{
_provider = new AzureServiceTokenProvider();
}
public SqlConnection CreateConnection()
{
return new SqlConnection
{
AccessToken = AccessToken,
ConnectionString = ConfigurationManager.ConnectionStrings["CatalogDBContext"].ConnectionString
};
}
private string AccessToken
=> _provider.GetAccessTokenAsync("https://database.windows.net/").ConfigureAwait(false).GetAwaiter().GetResult();
}
public class AppSettingsSqlConnectionFactory : ISqlConnectionFactory
{
public SqlConnection CreateConnection()
{
return new SqlConnection
{
ConnectionString = ConfigurationManager.ConnectionStrings["CatalogDBContext"].ConnectionString
};
}
}
}

Просмотреть файл

@ -1,67 +0,0 @@
//----------------------------------------------------------------------------------------------
// Copyright 2014 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//----------------------------------------------------------------------------------------------
using System;
using Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Configuration;
using System.Globalization;
using System.Threading.Tasks;
namespace eShopModernizedMVC
{
public partial class Startup
{
//
// The Client ID is used by the application to uniquely identify itself to Azure AD.
// The Metadata Address is used by the application to retrieve the signing keys used by Azure AD.
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
// The Authority is the sign-in URL of the tenant.
// The Post Logout Redirect Uri is the URL where the user will be redirected after they sign out.
//
private static string clientId = CatalogConfiguration.AzureActiveDirectoryClientId;
private static string aadInstance = ConfigurationManager.AppSettings["AzureActiveDirectoryInstance"];
private static string tenant = CatalogConfiguration.AzureActiveDirectoryTenant;
private static string postLogoutRedirectUri = CatalogConfiguration.PostLogoutRedirectUri;
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant),
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
}

Просмотреть файл

@ -1,141 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
<InstrumentationKey>cd0e6345-f2bf-459a-9c37-ecc10e76ee71</InstrumentationKey>
<TelemetryInitializers>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.HttpDependenciesParsingTelemetryInitializer, Microsoft.AI.DependencyCollector"/>
<Add Type="Microsoft.ApplicationInsights.ServiceFabric.FabricTelemetryInitializer, Microsoft.AI.ServiceFabric"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureWebAppRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.BuildInfoConfigComponentVersionTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.Web.WebTestTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SyntheticUserAgentTelemetryInitializer, Microsoft.AI.Web">
<!-- Extended list of bots:
search|spider|crawl|Bot|Monitor|BrowserMob|BingPreview|PagePeeker|WebThumb|URL2PNG|ZooShot|GomezA|Google SketchUp|Read Later|KTXN|KHTE|Keynote|Pingdom|AlwaysOn|zao|borg|oegp|silk|Xenu|zeal|NING|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|Java|JNLP|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|vortex|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|voyager|archiver|Icarus6j|mogimogi|Netvibes|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|wsr-agent|http client|Python-urllib|AppEngine-Google|semanticdiscovery|facebookexternalhit|web/snippet|Google-HTTP-Java-Client-->
<Filters>search|spider|crawl|Bot|Monitor|AlwaysOn</Filters>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ClientIpHeaderTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationNameTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationCorrelationTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.UserTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AuthenticatedUserIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AccountIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SessionTelemetryInitializer, Microsoft.AI.Web"/>
</TelemetryInitializers>
<TelemetryModules>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule, Microsoft.AI.DependencyCollector">
<ExcludeComponentCorrelationHttpHeadersOnDomains>
<!--
Requests to the following hostnames will not be modified by adding correlation headers.
Add entries here to exclude additional hostnames.
NOTE: this configuration will be lost upon NuGet upgrade.
-->
<Add>core.windows.net</Add>
<Add>core.chinacloudapi.cn</Add>
<Add>core.cloudapi.de</Add>
<Add>core.usgovcloudapi.net</Add>
</ExcludeComponentCorrelationHttpHeadersOnDomains>
<IncludeDiagnosticSourceActivities>
<Add>Microsoft.Azure.EventHubs</Add>
<Add>Microsoft.Azure.ServiceBus</Add>
</IncludeDiagnosticSourceActivities>
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
<!--
Use the following syntax here to collect additional performance counters:
<Counters>
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
...
</Counters>
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
NOTE: performance counters configuration will be lost upon NuGet upgrade.
The following placeholders are supported as InstanceName:
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
??APP_CLR_PROC?? - instance name of the application CLR process for .NET counters.
-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryModule, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AppServicesHeartbeatTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureInstanceMetadataTelemetryModule, Microsoft.AI.WindowsServer">
<!--
Remove individual fields collected here by adding them to the ApplicationInsighs.HeartbeatProvider
with the following syntax:
<Add Type="Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing.DiagnosticsTelemetryModule, Microsoft.ApplicationInsights">
<ExcludedHeartbeatProperties>
<Add>osType</Add>
<Add>location</Add>
<Add>name</Add>
<Add>offer</Add>
<Add>platformFaultDomain</Add>
<Add>platformUpdateDomain</Add>
<Add>publisher</Add>
<Add>sku</Add>
<Add>version</Add>
<Add>vmId</Add>
<Add>vmSize</Add>
<Add>subscriptionId</Add>
<Add>resourceGroupName</Add>
<Add>placementGroupId</Add>
<Add>tags</Add>
<Add>vmScaleSetName</Add>
</ExcludedHeartbeatProperties>
</Add>
NOTE: exclusions will be lost upon upgrade.
-->
</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.DeveloperModeWithDebuggerAttachedTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnhandledExceptionTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnobservedExceptionTelemetryModule, Microsoft.AI.WindowsServer">
<!--</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.FirstChanceExceptionStatisticsTelemetryModule, Microsoft.AI.WindowsServer">-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.RequestTrackingTelemetryModule, Microsoft.AI.Web">
<Handlers>
<!--
Add entries here to filter out additional handlers:
NOTE: handler configuration will be lost upon NuGet upgrade.
-->
<Add>Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler</Add>
<Add>System.Web.StaticFileHandler</Add>
<Add>System.Web.Handlers.AssemblyResourceLoader</Add>
<Add>System.Web.Optimization.BundleHandler</Add>
<Add>System.Web.Script.Services.ScriptHandlerFactory</Add>
<Add>System.Web.Handlers.TraceHandler</Add>
<Add>System.Web.Services.Discovery.DiscoveryRequestHandler</Add>
<Add>System.Web.HttpDebugHandler</Add>
</Handlers>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AspNetDiagnosticTelemetryModule, Microsoft.AI.Web"/>
</TelemetryModules>
<ApplicationIdProvider Type="Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId.ApplicationInsightsApplicationIdProvider, Microsoft.ApplicationInsights"/>
<TelemetrySinks>
<Add Name="default">
<TelemetryProcessors>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryProcessor, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.Extensibility.AutocollectedMetricsExtractor, Microsoft.ApplicationInsights"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<ExcludedTypes>Event</ExcludedTypes>
</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<IncludedTypes>Event</IncludedTypes>
</Add>
</TelemetryProcessors>
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
</Add>
</TelemetrySinks>
<!--
Learn more about Application Insights configuration with ApplicationInsights.config here:
http://go.microsoft.com/fwlink/?LinkID=513840
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
--></ApplicationInsights>

Просмотреть файл

@ -1,276 +0,0 @@
/* Slider */
.esh-app-hero {
background-image: url("../Images/main_banner.png");
background-size: cover;
height: 12.5vw;
width: 100%;
}
/* Header */
.esh-header {
height: 100%;
}
.esh-header:before {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
}
.esh-header-brand {
max-width: 1440px;
margin-left: auto;
margin-right: auto;
}
.esh-header-title {
font-family: Montserrat, sans-serif;
position: relative;
font-size: 4vw;
font-weight: 600;
color: #FFFFFF;
display: inline-block;
vertical-align: middle;
}
.esh-header-title > span {
font-weight: 300;
}
/* Footer */
.esh-app-footer {
background-color: #000000;
border-top: 1px solid #EEEEEE;
margin-top: 2.5rem;
padding-bottom: 2.5rem;
padding-top: 2.5rem;
width: 100%;
float: left;
}
.esh-app-footer-brand {
height: 50px;
width: 230px;
}
/* Body */
.esh-body-title {
font-family: Montserrat, sans-serif;
padding-left: 3rem;
padding-top: 1rem;
padding-bottom: 1.5rem;
margin: 0px;
margin-bottom: 2rem;
background-color: #00A69C;
font-size: 3rem;
color: #FFFFFF;
}
/* Table */
.esh-table {
max-width: 1440px;
padding-top: 10px;
margin-left: auto;
margin-right: auto;
}
.esh-table-header {
border-bottom: none;
}
.esh-table th {
font-size: 1rem;
text-transform: uppercase;
}
.esh-table td {
font-size: 1rem;
font-weight: 300;
}
.esh-table-price {
font-size: 1em;
font-weight: 600;
}
.esh-table-link {
color: inherit;
font-weight: 600;
cursor: pointer;
transition: color 0.35s;
}
.esh-table-link:hover {
color: #75b918;
transition: color 0.35s;
}
.esh-login-link {
color: inherit;
font-weight: 600;
cursor: pointer;
transition: color 0.35s;
}
.esh-login-link:hover {
background-color: transparent !important;
text-decoration: underline !important;
color: #75b918;
transition: color 0.35s;
}
.esh-thumbnail {
max-width: 120px;
height: auto;
}
.esh-picture {
max-width: 370px;
height: auto;
width: 100%;
}
.esh-link-wrapper {
line-height: 3rem;
position: relative;
text-align: left;
display: flex;
justify-content: space-between;
}
}
.esh-link-item {
color: inherit;
font-size: 2rem;
font-weight: 600;
cursor: pointer;
transition: color 0.35s;
}
.esh-link-item:hover {
color: #75b918;
transition: color 0.35s;
}
.esh-link-item--margin {
margin-left: 1rem;
}
.esh-link-list {
font-size: 2rem;
margin-left: 1rem;
text-align: right;
}
.esh-button-actions .esh-button + .esh-button {
margin-left: 1rem;
}
.esh-button {
border: 0;
border-radius: 0;
color: #FFFFFF;
display: inline-block;
font-size: 1rem;
font-weight: 400;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all 0.35s;
}
.esh-button-primary {
background-color: #83D01B;
}
.esh-button-primary:hover {
background-color: #4a760f;
color: white;
}
.esh-button-secondary {
background-color: #E52638;
}
.esh-button-secondary:hover {
background-color: #b20000;
color: white;
}
.esh-button-primary:hover {
background-color: #4a760f;
transition: all 0.35s;
}
.esh-form-information {
padding-top: 7px;
color: #888888;
}
.esh-button-upload {
margin-top: 1vw;
}
.esh-loader {
border: 8px solid #f3f3f3;
border-top: 8px solid #00A69C;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 2s linear infinite;
margin-top: 0.8vw;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/*Pager*/
.esh-pager-wrapper {
padding-top: 1rem;
text-align: center;
}
.esh-pager-item {
margin: 0 5vw;
color: #333333;
}
.esh-pager-item--hidden {
opacity: 0;
pointer-events: none;
}
.esh-pager-item--navigable {
cursor: pointer;
display: inline-block;
}
.esh-pager-item--navigable:hover {
color: #83D01B;
}
@media screen and (max-width: 1280px) {
.esh-pager-item {
font-size: 0.85rem;
}
}
@media screen and (max-width: 1024px) {
.esh-pager-item {
margin: 0 2.5vw;
}
.esh-app-hero {
height: 20vw;
}
}

Просмотреть файл

@ -1,29 +0,0 @@
@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-size: 16px;
font-family: Montserrat, sans-serif;
}
body {
padding-top: 10px;
padding-bottom: 20px;
font-weight: 300;
z-index: 10;
}
/* Set padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,331 +0,0 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,8 +0,0 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,27 +0,0 @@
/* Override the default bootstrap behavior where horizontal description lists
will truncate terms that are too long to fit in the left column
*/
.dl-horizontal dt {
white-space: normal;
font-size: 20px;
}
.dl-horizontal dd {
margin-left: 5%;
}
/* Tables */
.table thead > tr > th,
.table tbody > tr > th,
.table tfoot > tr > th,
.table thead > tr > td,
.table tbody > tr > td,
.table tfoot > tr > td {
border: none;
white-space: nowrap;
}
.table tbody tr > td {
vertical-align: middle;
border-bottom: 1px solid #eee;
}

Просмотреть файл

@ -1,38 +0,0 @@
using log4net;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Web;
using System.Web.Mvc;
namespace eShopModernizedMVC.Controllers
{
public class AccountController : Controller
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public void SignIn()
{
_log.Info($"Now processing... AccountController.SignIn");
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void SignOut()
{
_log.Info($"Now processing... AccountController.SignOut");
// Send an OpenID Connect sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void EndSession()
{
_log.Info($"Now processing... AccountController.EndSession");
// If AAD sends a single sign-out message to the app, end the user's session, but don't redirect to AAD for sign out.
HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
}
}
}

Просмотреть файл

@ -1,198 +0,0 @@
using System.Collections.Generic;
using System.Net;
using System.Web.Mvc;
using eShopModernizedMVC.Models;
using eShopModernizedMVC.Services;
using System.IO;
using log4net;
namespace eShopModernizedMVC.Controllers
{
public class CatalogController : Controller
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly ICatalogService _service;
private readonly IImageService _imageService;
public CatalogController(ICatalogService service, IImageService imageService)
{
_service = service;
_imageService = imageService;
}
// GET /[?pageSize=3&pageIndex=10]
public ActionResult Index(int pageSize = 10, int pageIndex = 0)
{
_log.Info($"Now loading... /Catalog/Index?pageSize={pageSize}&pageIndex={pageIndex}");
var paginatedItems = _service.GetCatalogItemsPaginated(pageSize, pageIndex);
ChangeUriPlaceholder(paginatedItems.Data);
return View(paginatedItems);
}
// GET: Catalog/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
_log.Info($"Now loading... /Catalog/Details?id={id}");
CatalogItem catalogItem = _service.FindCatalogItem(id.Value);
if (catalogItem == null)
{
return HttpNotFound();
}
AddUriPlaceHolder(catalogItem);
return View(catalogItem);
}
// GET: Catalog/Create
[Authorize]
public ActionResult Create()
{
_log.Info($"Now loading... /Catalog/Create");
ViewBag.CatalogBrandId = new SelectList(_service.GetCatalogBrands(), "Id", "Brand");
ViewBag.CatalogTypeId = new SelectList(_service.GetCatalogTypes(), "Id", "Type");
ViewBag.UseAzureStorage = CatalogConfiguration.UseAzureStorage;
return View(new CatalogItem()
{
PictureUri = _imageService.UrlDefaultImage()
});
}
// POST: Catalog/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[Authorize]
public ActionResult Create([Bind(Include = "Id,Name,Description,Price,PictureFileName,CatalogTypeId,CatalogBrandId,AvailableStock,RestockThreshold,MaxStockThreshold,OnReorder,TempImageName")] CatalogItem catalogItem)
{
_log.Info($"Now processing... /Catalog/Create?catalogItemName={catalogItem.Name}");
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(catalogItem.TempImageName))
{
var fileName = Path.GetFileName(catalogItem.TempImageName);
catalogItem.PictureFileName = fileName;
}
_service.CreateCatalogItem(catalogItem);
if (!string.IsNullOrEmpty(catalogItem.TempImageName))
{
_imageService.UpdateImage(catalogItem);
}
return RedirectToAction("Index");
}
ViewBag.CatalogBrandId = new SelectList(_service.GetCatalogBrands(), "Id", "Brand", catalogItem.CatalogBrandId);
ViewBag.CatalogTypeId = new SelectList(_service.GetCatalogTypes(), "Id", "Type", catalogItem.CatalogTypeId);
ViewBag.UseAzureStorage = CatalogConfiguration.UseAzureStorage;
return View(catalogItem);
}
// GET: Catalog/Edit/5
[Authorize]
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
_log.Info($"Now loading... /Catalog/Edit?id={id}");
CatalogItem catalogItem = _service.FindCatalogItem(id.Value);
if (catalogItem == null)
{
return HttpNotFound();
}
AddUriPlaceHolder(catalogItem);
ViewBag.CatalogBrandId = new SelectList(_service.GetCatalogBrands(), "Id", "Brand", catalogItem.CatalogBrandId);
ViewBag.CatalogTypeId = new SelectList(_service.GetCatalogTypes(), "Id", "Type", catalogItem.CatalogTypeId);
ViewBag.UseAzureStorage = CatalogConfiguration.UseAzureStorage;
return View(catalogItem);
}
// POST: Catalog/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[Authorize]
public ActionResult Edit([Bind(Include = "Id,Name,Description,Price,PictureFileName,CatalogTypeId,CatalogBrandId,AvailableStock,RestockThreshold,MaxStockThreshold,OnReorder,TempImageName")] CatalogItem catalogItem)
{
_log.Info($"Now processing... /Catalog/Edit?id={catalogItem.Id}");
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(catalogItem.TempImageName))
{
_imageService.UpdateImage(catalogItem);
var fileName = Path.GetFileName(catalogItem.TempImageName);
catalogItem.PictureFileName = fileName;
}
_service.UpdateCatalogItem(catalogItem);
return RedirectToAction("Index");
}
ViewBag.CatalogBrandId = new SelectList(_service.GetCatalogBrands(), "Id", "Brand", catalogItem.CatalogBrandId);
ViewBag.CatalogTypeId = new SelectList(_service.GetCatalogTypes(), "Id", "Type", catalogItem.CatalogTypeId);
ViewBag.UseAzureStorage = CatalogConfiguration.UseAzureStorage;
return View(catalogItem);
}
// GET: Catalog/Delete/5
[Authorize]
public ActionResult Delete(int? id)
{
_log.Info($"Now loading... /Catalog/Delete?id={id}");
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CatalogItem catalogItem = _service.FindCatalogItem(id.Value);
if (catalogItem == null)
{
return HttpNotFound();
}
AddUriPlaceHolder(catalogItem);
return View(catalogItem);
}
// POST: Catalog/Delete/5
[HttpPost, ActionName("Delete")]
[Authorize]
public ActionResult DeleteConfirmed(int id)
{
_log.Info($"Now processing... /Catalog/DeleteConfirmed?id={id}");
CatalogItem catalogItem = _service.FindCatalogItem(id);
_service.RemoveCatalogItem(catalogItem);
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_service.Dispose();
}
base.Dispose(disposing);
}
private void ChangeUriPlaceholder(IEnumerable<CatalogItem> items)
{
foreach (var catalogItem in items)
{
AddUriPlaceHolder(catalogItem);
}
}
private void AddUriPlaceHolder(CatalogItem item)
{
item.PictureUri = _imageService.BuildUrlImage(item);
}
}
}

Просмотреть файл

@ -1,128 +0,0 @@
using eShopModernizedMVC.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using eShopModernizedMVC.Services;
using log4net;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
using eShopModernizedMVC.Models;
namespace eShopModernizedMVC.Controllers
{
public class CatalogRegisterUserController : Controller
{
string strCon = WebConfigurationManager.ConnectionStrings["CatalogDBContext"].ToString();
SqlConnection con;
SqlCommand cmd;
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly ICatalogService _service;
public CatalogRegisterUserController(ICatalogService service, IImageService imageService)
{
_service = service;
}
// GET: CatalogRegisterUser
public ActionResult Index()
{
return View();
}
public ActionResult LogIn()
{
return View();
}
[HttpPost]
public ActionResult LogIn(CatalogRegisterUser catalogRegister)
{
using (con = new SqlConnection(strCon))
{
con.Open();
string strCmd = "Select * from CatalogRegisterUser where firstname='" + catalogRegister.firstname + "' and password='" + catalogRegister.password + "'";
using (cmd = new SqlCommand(strCmd, con))
{
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
string name = dr["firstname"].ToString();
return RedirectToAction("Index", "Catalog");
}
}
con.Close();
}
ModelState.Clear();
return View();
}
// GET: CatalogRegisterUser/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: CatalogRegisterUser/Create
public ActionResult Create()
{
CatalogRegisterUser catalogRegisterUsermodel = new CatalogRegisterUser();
return View();
}
// POST: CatalogRegisterUser/Create
[HttpPost]
[Authorize]
public ActionResult Create([Bind(Include = "id,firstname,lastname,password,confirmpassword")] CatalogRegisterUser catalogRegisterUser)
{
// ViewBag.Message = "User Details Saved";
// return View("Register");
// return RedirectToAction("Index")
_log.Info($"Now processing... /CatalogRegisterUser/LogIn?catalogItemName={catalogRegisterUser.id}");
if (ModelState.IsValid)
{
_service.CreateCatalogRegisterUser(catalogRegisterUser);
return RedirectToAction("LogIn");
}
ViewBag.CatalogRegisterUserId = new SelectList(_service.GetCatalogRegisterUser(), "id", "RegisterUser", catalogRegisterUser.id);
ViewBag.UseAzureStorage = CatalogConfiguration.UseAzureStorage;
return View(catalogRegisterUser);
}
// GET: CatalogRegisterUser/Edit/5
public ActionResult Edit(int id)
{
return View();
}
// POST: CatalogRegisterUser/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
// GET: CatalogRegisterUser/Delete/5
public ActionResult Delete(int id)
{
return View();
}
}
}

Просмотреть файл

@ -1,68 +0,0 @@
using eShopModernizedMVC.Services;
using log4net;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
namespace eShopModernizedMVC.Controllers
{
public class PicController : Controller
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ImageFormat[] ValidFormats = { ImageFormat.Jpeg, ImageFormat.Png, ImageFormat.Gif };
private readonly IImageService _imageService;
public PicController(ICatalogService service, IImageService imageService)
{
_imageService = imageService;
}
[HttpPost]
[Route("uploadimage")]
public ActionResult UploadImage()
{
_log.Info($"Now processing... /Pic/UploadImage");
HttpPostedFile image = System.Web.HttpContext.Current.Request.Files["HelpSectionImages"];
var itemId = System.Web.HttpContext.Current.Request.Form["itemId"];
if (!IsValidImage(image))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "image is not valid");
}
int.TryParse(itemId, out var catalogItemId);
var urlImageTemp = _imageService.UploadTempImage(image, catalogItemId);
var tempImage = new
{
name = new Uri(urlImageTemp).PathAndQuery,
url = urlImageTemp
};
return Json(tempImage);
}
private bool IsValidImage(HttpPostedFile file)
{
bool isValidImage = true;
try
{
using (var img = Image.FromStream(file.InputStream))
{
isValidImage = ValidFormats.Contains(img.RawFormat);
}
}
catch (Exception)
{
isValidImage = false;
}
return isValidImage;
}
}
}

Просмотреть файл

@ -1,6 +0,0 @@
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.7.2
ARG source
WORKDIR /inetpub/wwwroot
EXPOSE 80
COPY ${source:-obj/Docker/publish} .

Просмотреть файл

@ -1,14 +0,0 @@
using System.Diagnostics;
using System.Web.Mvc;
namespace eShopModernizedMVC.Filters
{
public class ActionTracerFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Trace.TraceInformation($"Received request for action {filterContext.ActionDescriptor.ActionName} in controller {filterContext.Controller.GetType().Name}.");
base.OnActionExecuting(filterContext);
}
}
}

Просмотреть файл

@ -1 +0,0 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="eShopModernizedMVC.MvcApplication" Language="C#" %>

Просмотреть файл

@ -1,127 +0,0 @@
using Autofac;
using Autofac.Integration.Mvc;
using eShopModernizedMVC.Models;
using eShopModernizedMVC.Models.Infrastructure;
using eShopModernizedMVC.Modules;
using eShopModernizedMVC.Services;
using log4net;
using Microsoft.ApplicationInsights.Extensibility;
using System;
using System.Data.Entity;
using System.Diagnostics;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace eShopModernizedMVC
{
public class MvcApplication : System.Web.HttpApplication
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
IContainer container;
protected void Application_Start()
{
container = RegisterContainer();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigDataBase();
InitializeCatalogImages();
InitializePipeline();
}
/// <summary>
/// Track the machine name and the start time for the session inside the current session
/// </summary>
protected void Session_Start(Object sender, EventArgs e)
{
HttpContext.Current.Session["MachineName"] = Environment.MachineName;
HttpContext.Current.Session["SessionStartTime"] = DateTime.Now;
}
/// <summary>
/// http://docs.autofac.org/en/latest/integration/mvc.html
/// </summary>
protected IContainer RegisterContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterModule(new ApplicationModule(CatalogConfiguration.UseMockData, CatalogConfiguration.UseAzureStorage, CatalogConfiguration.UseManagedIdentity));
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
return container;
}
protected void Application_Error(Object sender, EventArgs e)
{
var raisedException = Server.GetLastError();
Trace.TraceError($"Unhandled exeption: {raisedException}");
_log.Error($"Unhandled exception typeof({raisedException.GetType().Name}): {raisedException.Message}");
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
//set the property to our new object
LogicalThreadContext.Properties["activityid"] = new ActivityIdHelper();
LogicalThreadContext.Properties["requestinfo"] = new WebRequestInfo();
_log.Debug("WebApplication_BeginRequest");
}
private void ConfigDataBase()
{
if (!CatalogConfiguration.UseMockData)
{
Database.SetInitializer<CatalogDBContext>(container.Resolve<CatalogDBInitializer>());
}
}
private void InitializeCatalogImages()
{
var imageService = container.Resolve<IImageService>();
imageService.InitializeCatalogImages();
}
private void InitializePipeline()
{
TelemetryConfiguration.Active.TelemetryInitializers
.Add(new MyTelemetryInitializer());
var environmentKey = CatalogConfiguration.AppInsightsInstrumentationKey;
if (!string.IsNullOrEmpty(environmentKey))
{
TelemetryConfiguration.Active.InstrumentationKey = environmentKey;
}
}
}
public class ActivityIdHelper
{
public override string ToString()
{
if (Trace.CorrelationManager.ActivityId == Guid.Empty)
{
Trace.CorrelationManager.ActivityId = Guid.NewGuid();
}
return Trace.CorrelationManager.ActivityId.ToString();
}
}
public class WebRequestInfo
{
public override string ToString()
{
return HttpContext.Current?.Request?.RawUrl + ", " + HttpContext.Current?.Request?.UserAgent;
}
}
}

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.0 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 851 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.6 KiB

Просмотреть файл

@ -1,23 +0,0 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin;
namespace eShopModernizedMVC.Middleware
{
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
var identity = new ClaimsIdentity("cookies");
identity.AddClaim(new Claim("iat", "1234"));
context.Authentication.User = new ClaimsPrincipal();
context.Authentication.User.AddIdentity(identity);
await Next.Invoke(context);
}
}
}

Просмотреть файл

@ -1,8 +0,0 @@
namespace eShopModernizedMVC.Models
{
public class CatalogBrand
{
public int Id { get; set; }
public string Brand { get; set; }
}
}

Просмотреть файл

@ -1,111 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace eShopModernizedMVC.Models
{
public class CatalogDBContext : DbContext
{
public CatalogDBContext() { }
public CatalogDBContext(ISqlConnectionFactory provider)
: base(provider.CreateConnection(), true)
{
}
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
public DbSet<CatalogRegisterUser> CatalogRegisterUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
ConfigureCatalogType(builder.Entity<CatalogType>());
ConfigureCatalogBrand(builder.Entity<CatalogBrand>());
ConfigureCatalogItem(builder.Entity<CatalogItem>());
ConfigureCatalogRegisterUsers(builder.Entity<CatalogRegisterUser>());
base.OnModelCreating(builder);
}
void ConfigureCatalogRegisterUsers(EntityTypeConfiguration<CatalogRegisterUser> builder)
{
builder.ToTable("CatalogRegisterUser");
builder.HasKey(ci => ci.id);
builder.Property(ci => ci.firstname)
.IsRequired().HasMaxLength(100);
builder.Property(cb => cb.lastname)
.IsRequired()
.HasMaxLength(100);
builder.Property(cb => cb.password)
.IsRequired()
.HasMaxLength(100);
}
void ConfigureCatalogType(EntityTypeConfiguration<CatalogType> builder)
{
builder.ToTable("CatalogType");
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id)
.IsRequired();
builder.Property(cb => cb.Type)
.IsRequired()
.HasMaxLength(100);
}
void ConfigureCatalogBrand(EntityTypeConfiguration<CatalogBrand> builder)
{
builder.ToTable("CatalogBrand");
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id)
.IsRequired();
builder.Property(cb => cb.Brand)
.IsRequired()
.HasMaxLength(100);
}
void ConfigureCatalogItem(EntityTypeConfiguration<CatalogItem> builder)
{
builder.ToTable("Catalog");
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
.IsRequired();
builder.Property(ci => ci.Name)
.IsRequired()
.HasMaxLength(50);
builder.Property(ci => ci.Price)
.IsRequired();
builder.Property(ci => ci.PictureFileName);
builder.Ignore(ci => ci.PictureUri);
builder.Ignore(ci => ci.TempImageName);
builder.HasRequired<CatalogBrand>(ci => ci.CatalogBrand)
.WithMany()
.HasForeignKey(ci => ci.CatalogBrandId);
builder.HasRequired<CatalogType>(ci => ci.CatalogType)
.WithMany()
.HasForeignKey(ci => ci.CatalogTypeId);
}
}
}

Просмотреть файл

@ -1,60 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace eShopModernizedMVC.Models
{
public class CatalogItem
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
// decimal(18,2)
[RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")]
[Range(0, 1000000)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[Display(Name = "Picture name")]
public string PictureFileName { get; set; }
public string PictureUri { get; set; }
[Display(Name = "Type")]
public int CatalogTypeId { get; set; }
[Display(Name = "Type")]
public CatalogType CatalogType { get; set; }
[Display(Name = "Brand")]
public int CatalogBrandId { get; set; }
[Display(Name = "Brand")]
public CatalogBrand CatalogBrand { get; set; }
// Quantity in stock
[Range(0, 10000000, ErrorMessage = "The field Stock must be between 0 and 10 million.")]
[Display(Name = "Stock")]
public int AvailableStock { get; set; }
// Available stock at which we should reorder
[Range(0, 10000000, ErrorMessage = "The field Restock must be between 0 and 10 million.")]
[Display(Name = "Restock")]
public int RestockThreshold { get; set; }
// Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses)
[Range(0, 10000000, ErrorMessage = "The field Max stock must be between 0 and 10 million.")]
[Display(Name = "Max stock")]
public int MaxStockThreshold { get; set; }
/// <summary>
/// True if item is on reorder
/// </summary>
public bool OnReorder { get; set; }
public string TempImageName { get; set; }
}
}

Просмотреть файл

@ -1,32 +0,0 @@
using System;
using System.Linq;
namespace eShopModernizedMVC.Models
{
public class CatalogItemHiLoGenerator
{
private const int HiLoIncrement = 10;
private int _sequenceId = -1;
private int _remainningLoIds = 0;
private object sequenceLock = new object();
public int GetNextSequenceValue(CatalogDBContext db)
{
lock (sequenceLock)
{
if (_remainningLoIds == 0)
{
var rawQuery = db.Database.SqlQuery<Int64>("SELECT NEXT VALUE FOR catalog_hilo;");
_sequenceId = (int)rawQuery.Single();
_remainningLoIds = HiLoIncrement - 1;
return _sequenceId;
}
else
{
_remainningLoIds--;
return ++_sequenceId;
}
}
}
}
}

Просмотреть файл

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace eShopModernizedMVC.Models
{
public class CatalogRegisterUser
{
public int id { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "First Name is Required.")]
public string firstname { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Last Name is Required.")]
public string lastname { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Password is Required.")]
[DataType(DataType.Password)]
public string password { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Confirm Password is Required.")]
[Compare("password", ErrorMessage = "Password and Confirm Password should be same")]
[DataType(DataType.Password)]
public string confirmpassword { get; set; }
private object catalogRegisterUser;
public int CatalogRegisterUserId { get; set; }
public object GetCatalogRegisterUser()
{
return catalogRegisterUser;
}
internal void SetCatalogRegisterUser(object value)
{
catalogRegisterUser = value;
}
}
}

Просмотреть файл

@ -1,8 +0,0 @@
namespace eShopModernizedMVC.Models
{
public class CatalogType
{
public int Id { get; set; }
public string Type { get; set; }
}
}

Просмотреть файл

@ -1,354 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Hosting;
namespace eShopModernizedMVC.Models.Infrastructure
{
public class CatalogDBInitializer : CreateDatabaseIfNotExists<CatalogDBContext>
{
private const string DBCatalogSequenceName = "catalog_type_hilo";
private const string DBBrandSequenceName = "catalog_brand_hilo";
private const string CatalogItemHiLoSequenceScript = @"Models\Infrastructure\dbo.catalog_hilo.Sequence.sql";
private const string CatalogBrandHiLoSequenceScript = @"Models\Infrastructure\dbo.catalog_brand_hilo.Sequence.sql";
private const string CatalogTypeHiLoSequenceScript = @"Models\Infrastructure\dbo.catalog_type_hilo.Sequence.sql";
private CatalogItemHiLoGenerator indexGenerator;
private bool useCustomizationData;
public CatalogDBInitializer(CatalogItemHiLoGenerator indexGenerator)
{
this.indexGenerator = indexGenerator;
useCustomizationData = CatalogConfiguration.UseCustomizationData;
}
protected override void Seed(CatalogDBContext context)
{
ExecuteScript(context, CatalogItemHiLoSequenceScript);
ExecuteScript(context, CatalogBrandHiLoSequenceScript);
ExecuteScript(context, CatalogTypeHiLoSequenceScript);
AddCatalogTypes(context);
AddCatalogBrands(context);
AddCatalogItems(context);
AddCatalogItemPictures();
}
private void AddCatalogTypes(CatalogDBContext context)
{
var preconfiguredTypes = useCustomizationData
? GetCatalogTypesFromFile()
: PreconfiguredData.GetPreconfiguredCatalogTypes();
int sequenceId = GetSequenceIdFromSelectedDBSequence(context, DBCatalogSequenceName);
foreach (var type in preconfiguredTypes)
{
type.Id = sequenceId;
context.CatalogTypes.Add(type);
sequenceId++;
}
context.SaveChanges();
}
private void AddCatalogBrands(CatalogDBContext context)
{
var preconfiguredBrands = useCustomizationData
? GetCatalogBrandsFromFile()
: PreconfiguredData.GetPreconfiguredCatalogBrands();
int sequenceId = GetSequenceIdFromSelectedDBSequence(context, DBBrandSequenceName);
foreach (var brand in preconfiguredBrands)
{
brand.Id = sequenceId;
context.CatalogBrands.Add(brand);
sequenceId++;
}
context.SaveChanges();
}
private void AddCatalogItems(CatalogDBContext context)
{
var preconfiguredItems = useCustomizationData
? GetCatalogItemsFromFile(context)
: PreconfiguredData.GetPreconfiguredCatalogItems();
foreach (var item in preconfiguredItems)
{
var sequenceId = indexGenerator.GetNextSequenceValue(context);
item.Id = sequenceId;
context.CatalogItems.Add(item);
}
context.SaveChanges();
}
private IEnumerable<CatalogType> GetCatalogTypesFromFile()
{
var contentRootPath = HostingEnvironment.ApplicationPhysicalPath;
string csvFileCatalogTypes = Path.Combine(contentRootPath, "Setup", "CatalogTypes.csv");
if (!File.Exists(csvFileCatalogTypes))
{
return PreconfiguredData.GetPreconfiguredCatalogTypes();
}
string[] csvheaders;
string[] requiredHeaders = { "catalogtype" };
csvheaders = GetHeaders(csvFileCatalogTypes, requiredHeaders);
return File.ReadAllLines(csvFileCatalogTypes)
.Skip(1) // skip header row
.Select(x => CreateCatalogType(x))
.Where(x => x != null);
}
static CatalogType CreateCatalogType(string type)
{
type = type.Trim('"').Trim();
if (String.IsNullOrEmpty(type))
{
throw new Exception("catalog Type Name is empty");
}
return new CatalogType
{
Type = type,
};
}
static IEnumerable<CatalogBrand> GetCatalogBrandsFromFile()
{
var contentRootPath = HostingEnvironment.ApplicationPhysicalPath;
string csvFileCatalogBrands = Path.Combine(contentRootPath, "Setup", "CatalogBrands.csv");
if (!File.Exists(csvFileCatalogBrands))
{
return PreconfiguredData.GetPreconfiguredCatalogBrands();
}
string[] csvheaders;
string[] requiredHeaders = { "catalogbrand" };
csvheaders = GetHeaders(csvFileCatalogBrands, requiredHeaders);
return File.ReadAllLines(csvFileCatalogBrands)
.Skip(1) // skip header row
.Select(x => CreateCatalogBrand(x))
.Where(x => x != null);
}
static CatalogBrand CreateCatalogBrand(string brand)
{
brand = brand.Trim('"').Trim();
if (String.IsNullOrEmpty(brand))
{
throw new Exception("catalog Brand Name is empty");
}
return new CatalogBrand
{
Brand = brand,
};
}
static IEnumerable<CatalogItem> GetCatalogItemsFromFile(CatalogDBContext context)
{
var contentRootPath = HostingEnvironment.ApplicationPhysicalPath;
string csvFileCatalogItems = Path.Combine(contentRootPath, "Setup", "CatalogItems.csv");
if (!File.Exists(csvFileCatalogItems))
{
return PreconfiguredData.GetPreconfiguredCatalogItems();
}
string[] csvheaders;
string[] requiredHeaders = { "catalogtypename", "catalogbrandname", "description", "name", "price", "pictureFileName" };
string[] optionalheaders = { "availablestock", "restockthreshold", "maxstockthreshold", "onreorder" };
csvheaders = GetHeaders(csvFileCatalogItems, requiredHeaders, optionalheaders);
var catalogTypeIdLookup = context.CatalogTypes.ToDictionary(ct => ct.Type, ct => ct.Id);
var catalogBrandIdLookup = context.CatalogBrands.ToDictionary(ct => ct.Brand, ct => ct.Id);
return File.ReadAllLines(csvFileCatalogItems)
.Skip(1) // skip header row
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
.Select(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup))
.Where(x => x != null);
}
static CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary<String, int> catalogTypeIdLookup, Dictionary<String, int> catalogBrandIdLookup)
{
if (column.Count() != headers.Count())
{
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
}
string catalogTypeName = column[Array.IndexOf(headers, "catalogtypename")].Trim('"').Trim();
if (!catalogTypeIdLookup.ContainsKey(catalogTypeName))
{
throw new Exception($"type={catalogTypeName} does not exist in catalogTypes");
}
string catalogBrandName = column[Array.IndexOf(headers, "catalogbrandname")].Trim('"').Trim();
if (!catalogBrandIdLookup.ContainsKey(catalogBrandName))
{
throw new Exception($"type={catalogTypeName} does not exist in catalogTypes");
}
string priceString = column[Array.IndexOf(headers, "price")].Trim('"').Trim();
if (!Decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out Decimal price))
{
throw new Exception($"price={priceString}is not a valid decimal number");
}
var catalogItem = new CatalogItem()
{
CatalogTypeId = catalogTypeIdLookup[catalogTypeName],
CatalogBrandId = catalogBrandIdLookup[catalogBrandName],
Description = column[Array.IndexOf(headers, "description")].Trim('"').Trim(),
Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(),
Price = price,
PictureFileName = column[Array.IndexOf(headers, "picturefilename")].Trim('"').Trim(),
};
int availableStockIndex = Array.IndexOf(headers, "availablestock");
if (availableStockIndex != -1)
{
string availableStockString = column[availableStockIndex].Trim('"').Trim();
if (!String.IsNullOrEmpty(availableStockString))
{
if (int.TryParse(availableStockString, out int availableStock))
{
catalogItem.AvailableStock = availableStock;
}
else
{
throw new Exception($"availableStock={availableStockString} is not a valid integer");
}
}
}
int restockThresholdIndex = Array.IndexOf(headers, "restockthreshold");
if (restockThresholdIndex != -1)
{
string restockThresholdString = column[restockThresholdIndex].Trim('"').Trim();
if (!String.IsNullOrEmpty(restockThresholdString))
{
if (int.TryParse(restockThresholdString, out int restockThreshold))
{
catalogItem.RestockThreshold = restockThreshold;
}
else
{
throw new Exception($"restockThreshold={restockThreshold} is not a valid integer");
}
}
}
int maxStockThresholdIndex = Array.IndexOf(headers, "maxstockthreshold");
if (maxStockThresholdIndex != -1)
{
string maxStockThresholdString = column[maxStockThresholdIndex].Trim('"').Trim();
if (!String.IsNullOrEmpty(maxStockThresholdString))
{
if (int.TryParse(maxStockThresholdString, out int maxStockThreshold))
{
catalogItem.MaxStockThreshold = maxStockThreshold;
}
else
{
throw new Exception($"maxStockThreshold={maxStockThreshold} is not a valid integer");
}
}
}
int onReorderIndex = Array.IndexOf(headers, "onreorder");
if (onReorderIndex != -1)
{
string onReorderString = column[onReorderIndex].Trim('"').Trim();
if (!String.IsNullOrEmpty(onReorderString))
{
if (bool.TryParse(onReorderString, out bool onReorder))
{
catalogItem.OnReorder = onReorder;
}
else
{
throw new Exception($"onReorder={onReorderString} is not a valid boolean");
}
}
}
return catalogItem;
}
static string[] GetHeaders(string csvfile, string[] requiredHeaders, string[] optionalHeaders = null)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() < requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is bigger then csv header count '{csvheaders.Count()}' ");
}
if (optionalHeaders != null)
{
if (csvheaders.Count() > (requiredHeaders.Count() + optionalHeaders.Count()))
{
throw new Exception($"csv header count '{csvheaders.Count()}' is larger then required '{requiredHeaders.Count()}' and optional '{optionalHeaders.Count()}' headers count");
}
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader.ToLowerInvariant()))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
private static int GetSequenceIdFromSelectedDBSequence(CatalogDBContext context, string dBSequenceName)
{
var rawQuery = context.Database.SqlQuery<Int64>($"SELECT NEXT VALUE FOR {dBSequenceName}");
var sequenceId = (int)rawQuery.Single();
return sequenceId;
}
private void ExecuteScript(CatalogDBContext context, string scriptFile)
{
var scriptFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, scriptFile);
context.Database.ExecuteSqlCommand(File.ReadAllText(scriptFilePath));
}
private void AddCatalogItemPictures()
{
if (!useCustomizationData)
{
return;
}
var contentRootPath = HostingEnvironment.ApplicationPhysicalPath;
DirectoryInfo picturePath = new DirectoryInfo(Path.Combine(contentRootPath, "Pics"));
foreach (FileInfo file in picturePath.GetFiles())
{
file.Delete();
}
string zipFileCatalogItemPictures = Path.Combine(contentRootPath, "Setup", "CatalogItems.zip");
ZipFile.ExtractToDirectory(zipFileCatalogItemPictures, picturePath.ToString());
}
}
}

Просмотреть файл

@ -1,57 +0,0 @@
using System.Collections.Generic;
namespace eShopModernizedMVC.Models.Infrastructure
{
public static class PreconfiguredData
{
public static List<CatalogItem> GetPreconfiguredCatalogItems()
{
return new List<CatalogItem>()
{
new CatalogItem { Id =1, CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureFileName = "1.png" },
new CatalogItem { Id =2, CatalogTypeId = 1, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureFileName = "2.png" },
new CatalogItem { Id =3, CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureFileName = "3.png" },
new CatalogItem { Id =4, CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureFileName = "4.png" },
new CatalogItem { Id =5, CatalogTypeId = 3, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureFileName = "5.png" },
new CatalogItem { Id =6, CatalogTypeId = 2, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureFileName = "6.png" },
new CatalogItem { Id =7, CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureFileName = "7.png" },
new CatalogItem { Id =8, CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureFileName = "8.png" },
new CatalogItem { Id =9, CatalogTypeId = 1, CatalogBrandId = 5, AvailableStock = 100, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureFileName = "9.png" },
new CatalogItem { Id =10, CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureFileName = "10.png" },
new CatalogItem { Id =11, CatalogTypeId = 3, CatalogBrandId = 2, AvailableStock = 100, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureFileName = "11.png" },
new CatalogItem { Id =12, CatalogTypeId = 2, CatalogBrandId = 5, AvailableStock = 100, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureFileName = "12.png" },
};
}
public static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
{
return new List<CatalogBrand>()
{
new CatalogBrand() { Id =1, Brand = "Azure"},
new CatalogBrand() { Id =2, Brand = ".NET" },
new CatalogBrand() { Id =3, Brand = "Visual Studio" },
new CatalogBrand() { Id =4, Brand = "SQL Server" },
new CatalogBrand() { Id =5, Brand = "Other" }
};
}
public static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()
{
return new List<CatalogType>()
{
new CatalogType() { Id =1, Type = "Mug"},
new CatalogType() { Id =2, Type = "T-Shirt" },
new CatalogType() { Id =3, Type = "Sheet" },
new CatalogType() { Id =4, Type = "USB Memory Stick" }
};
}
public static IEnumerable<CatalogRegisterUser> GetPreconfiguredCatalogRegisterUsers()
{
return new List<CatalogRegisterUser>()
{
new CatalogRegisterUser() { id =1,firstname ="Muzz", lastname = "user",password="123456"}
};
}
}
}

Просмотреть файл

@ -1,9 +0,0 @@
USE [Microsoft.eShopOnContainers.Services.CatalogDb]
/****** Object: Sequence [dbo].[catalog_brand_hilo] Script Date: 16/08/2017 11:21:49 ******/
CREATE SEQUENCE [dbo].[catalog_brand_hilo]
AS [bigint]
START WITH 1
INCREMENT BY 10
MINVALUE -9223372036854775808
MAXVALUE 9223372036854775807
CACHE

Просмотреть файл

@ -1,10 +0,0 @@
USE [Microsoft.eShopOnContainers.Services.CatalogDb]
/****** Object: Sequence [dbo].[catalog_hilo] Script Date: 16/08/2017 11:21:49 ******/
CREATE SEQUENCE [dbo].[catalog_hilo]
AS [bigint]
START WITH 1
INCREMENT BY 10
MINVALUE -9223372036854775808
MAXVALUE 9223372036854775807
CACHE

Просмотреть файл

@ -1,9 +0,0 @@
USE [Microsoft.eShopOnContainers.Services.CatalogDb]
/****** Object: Sequence [dbo].[catalog_type_hilo] Script Date: 16/08/2017 11:21:49 ******/
CREATE SEQUENCE [dbo].[catalog_type_hilo]
AS [bigint]
START WITH 1
INCREMENT BY 10
MINVALUE -9223372036854775808
MAXVALUE 9223372036854775807
CACHE

Просмотреть файл

@ -1,72 +0,0 @@
using Autofac;
using eShopModernizedMVC.Models;
using eShopModernizedMVC.Models.Infrastructure;
using eShopModernizedMVC.Services;
namespace eShopModernizedMVC.Modules
{
public class ApplicationModule : Module
{
private bool useMockData;
private bool useAzureStorage;
private bool useManagedIdentity;
public ApplicationModule(bool useMockData, bool useAzureStorage, bool useManagedIdentity)
{
this.useMockData = useMockData;
this.useAzureStorage = useAzureStorage;
this.useManagedIdentity = useManagedIdentity;
}
protected override void Load(ContainerBuilder builder)
{
if (this.useMockData)
{
builder.RegisterType<CatalogServiceMock>()
.As<ICatalogService>()
.SingleInstance();
}
else
{
builder.RegisterType<CatalogService>()
.As<ICatalogService>()
.InstancePerLifetimeScope();
}
if (this.useAzureStorage)
{
builder.RegisterType<ImageAzureStorage>()
.As<IImageService>()
.InstancePerLifetimeScope();
}
else
{
builder.RegisterType<ImageMockStorage>()
.As<IImageService>()
.InstancePerLifetimeScope();
}
builder.RegisterType<CatalogDBContext>()
.InstancePerLifetimeScope();
builder.RegisterType<CatalogDBInitializer>()
.InstancePerLifetimeScope();
builder.RegisterType<CatalogItemHiLoGenerator>()
.SingleInstance();
if (this.useManagedIdentity)
{
builder.RegisterType<ManagedIdentitySqlConnectionFactory>()
.As<ISqlConnectionFactory>()
.SingleInstance();
}
else
{
builder.RegisterType<AppSettingsSqlConnectionFactory>()
.As<ISqlConnectionFactory>()
.SingleInstance();
}
}
}
}

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 148 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 223 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 212 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 165 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 41 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 170 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 152 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 226 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 179 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 167 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 40 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 848 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.4 KiB

Просмотреть файл

@ -1,37 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("eShopModernizedMVC")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("eShopModernizedMVC")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fdd609f1-1164-407a-9b3c-bfec8c1bdb8c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.xml")]

Просмотреть файл

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<PublishProvider>FileSystem</PublishProvider>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<publishUrl>$(docker_publish_root)obj\Docker\publish</publishUrl>
<DeleteExistingFiles>False</DeleteExistingFiles>
</PropertyGroup>
</Project>

Просмотреть файл

@ -1,219 +0,0 @@
<!-- IGNORE THE HTML BLOCK BELOW, THE INTERESTING PART IS AFTER IT -->
<h1 align="center">Popper.js</h1>
<p align="center">
<strong>A library used to position poppers in web applications.</strong>
</p>
<p align="center">
<a href="https://travis-ci.org/FezVrasta/popper.js/branches" target="_blank"><img src="https://travis-ci.org/FezVrasta/popper.js.svg?branch=master" alt="Build Status"/></a>
<img src="http://img.badgesize.io/https://unpkg.com/popper.js/dist/popper.min.js?compression=gzip" alt="Stable Release Size"/>
<a href="https://www.bithound.io/github/FezVrasta/popper.js"><img src="https://www.bithound.io/github/FezVrasta/popper.js/badges/score.svg" alt="bitHound Overall Score"></a>
<a href="https://codeclimate.com/github/FezVrasta/popper.js/coverage"><img src="https://codeclimate.com/github/FezVrasta/popper.js/badges/coverage.svg" alt="Istanbul Code Coverage"/></a>
<a href="https://gitter.im/FezVrasta/popper.js" target="_blank"><img src="https://img.shields.io/gitter/room/nwjs/nw.js.svg" alt="Get support or discuss"/></a>
<br />
<a href="https://saucelabs.com/u/popperjs" target="_blank"><img src="https://badges.herokuapp.com/browsers?labels=none&googlechrome=latest&firefox=latest&microsoftedge=latest&iexplore=11,10&safari=latest&iphone=latest" alt="SauceLabs Reports"/></a>
</p>
<img src="https://raw.githubusercontent.com/FezVrasta/popper.js/master/popperjs.png" align="right" width=250 />
<!-- 🚨 HEY! HERE BEGINS THE INTERESTING STUFF 🚨 -->
## Wut? Poppers?
A popper is an element on the screen which "pops out" from the natural flow of your application.
Common examples of poppers are tooltips, popovers and drop-downs.
## So, yet another tooltip library?
Well, basically, **no**.
Popper.js is a **positioning engine**, its purpose is to calculate the position of an element
to make it possible to position it near a given reference element.
The engine is completely modular and most of its features are implemented as **modifiers**
(similar to middlewares or plugins).
The whole code base is written in ES2015 and its features are automatically tested on real browsers thanks to [SauceLabs](https://saucelabs.com/) and [TravisCI](https://travis-ci.org/).
Popper.js has zero dependencies. No jQuery, no LoDash, nothing.
It's used by big companies like [Twitter in Bootstrap v4](https://getbootstrap.com/), [Microsoft in WebClipper](https://github.com/OneNoteDev/WebClipper) and [Atlassian in AtlasKit](https://aui-cdn.atlassian.com/atlaskit/registry/).
### Popper.js
This is the engine, the library that computes and, optionally, applies the styles to
the poppers.
Some of the key points are:
- Position elements keeping them in their original DOM context (doesn't mess with your DOM!);
- Allows to export the computed informations to integrate with React and other view libraries;
- Supports Shadow DOM elements;
- Completely customizable thanks to the modifiers based structure;
Visit our [project page](https://fezvrasta.github.io/popper.js) to see a lot of examples of what you can do with Popper.js!
Find [the documentation here](/docs/_includes/popper-documentation.md).
### Tooltip.js
Since lots of users just need a simple way to integrate powerful tooltips in their projects,
we created **Tooltip.js**.
It's a small library that makes it easy to automatically create tooltips using as engine Popper.js.
Its API is almost identical to the famous tooltip system of Bootstrap, in this way it will be
easy to integrate it in your projects.
The tooltips generated by Tooltip.js are accessible thanks to the `aria` tags.
Find [the documentation here](/docs/_includes/tooltip-documentation.md).
## Installation
Popper.js is available on the following package managers and CDNs:
| Source | |
|:-------|:---------------------------------------------------------------------------------|
| npm | `npm install popper.js --save` |
| yarn | `yarn add popper.js` |
| NuGet | `PM> Install-Package popper.js` |
| Bower | `bower install popper.js --save` |
| unpkg | [`https://unpkg.com/popper.js`](https://unpkg.com/popper.js) |
| cdnjs | [`https://cdnjs.com/libraries/popper.js`](https://cdnjs.com/libraries/popper.js) |
Tooltip.js as well:
| Source | |
|:-------|:---------------------------------------------------------------------------------|
| npm | `npm install tooltip.js --save` |
| yarn | `yarn add tooltip.js` |
| Bower* | `bower install tooltip.js=https://unpkg.com/tooltip.js --save` |
| unpkg | [`https://unpkg.com/tooltip.js`](https://unpkg.com/tooltip.js) |
| cdnjs | [`https://cdnjs.com/libraries/popper.js`](https://cdnjs.com/libraries/popper.js) |
\*: Bower isn't officially supported, it can be used to install Tooltip.js only trough the unpkg.com CDN. This method has the limitation of not being able to define a specific version of the library. Bower and Popper.js suggests to use npm or Yarn for your projects.
For more info, [read the related issue](https://github.com/FezVrasta/popper.js/issues/390).
### Dist targets
Popper.js is currently shipped with 3 targets in mind: UMD, ESM and ESNext.
- UMD - Universal Module Definition: AMD, RequireJS and globals;
- ESM - ES Modules: For webpack/Rollup or browser supporting the spec;
- ESNext: Available in `dist/`, can be used with webpack and `babel-preset-env`;
Make sure to use the right one for your needs. If you want to import it with a `<script>` tag, use UMD.
## Usage
Given an existing popper DOM node, ask Popper.js to position it near its button
```js
var reference = document.querySelector('.my-button');
var popper = document.querySelector('.my-popper');
var anotherPopper = new Popper(
reference,
popper,
{
// popper options here
}
);
```
### Callbacks
Popper.js supports two kinds of callbacks, the `onCreate` callback is called after
the popper has been initalized. The `onUpdate` one is called on any subsequent update.
```js
const reference = document.querySelector('.my-button');
const popper = document.querySelector('.my-popper');
new Popper(reference, popper, {
onCreate: (data) => {
// data is an object containing all the informations computed
// by Popper.js and used to style the popper and its arrow
// The complete description is available in Popper.js documentation
},
onUpdate: (data) => {
// same as `onCreate` but called on subsequent updates
}
});
```
### Writing your own modifiers
Popper.js is based on a "plugin-like" architecture, most of its features are fully encapsulated "modifiers".
A modifier is a function that is called each time Popper.js needs to compute the position of the popper. For this reason, modifiers should be very performant to avoid bottlenecks.
To learn how to create a modifier, [read the modifiers documentation](docs/_includes/popper-documentation.md#modifiers--object)
### React, Vue.js, Angular, AngularJS, Ember.js (etc...) integration
Integrating 3rd party libraries in React or other libraries can be a pain because
they usually alter the DOM and drive the libraries crazy.
Popper.js limits all its DOM modifications inside the `applyStyle` modifier,
you can simply disable it and manually apply the popper coordinates using
your library of choice.
For a comprehensive list of libraries that let you use Popper.js into existing
frameworks, visit the [MENTIONS](/MENTIONS.md) page.
Alternatively, you may even override your own `applyStyles` with your custom one and
integrate Popper.js by yourself!
```js
function applyReactStyle(data) {
// export data in your framework and use its content to apply the style to your popper
};
const reference = document.querySelector('.my-button');
const popper = document.querySelector('.my-popper');
new Popper(reference, popper, {
modifiers: {
applyStyle: { enabled: false },
applyReactStyle: {
enabled: true,
fn: applyReactStyle,
order: 800,
},
},
});
```
### Migration from Popper.js v0
Since the API changed, we prepared some migration instructions to make it easy to upgrade to
Popper.js v1.
https://github.com/FezVrasta/popper.js/issues/62
Feel free to comment inside the issue if you have any questions.
### Performances
Popper.js is very performant. It usually takes 0.5ms to compute a popper's position (on an iMac with 3.5G GHz Intel Core i5).
This means that it will not cause any [jank](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/anatomy-of-jank), leading to a smooth user experience.
## Notes
### Libraries using Popper.js
The aim of Popper.js is to provide a stable and powerful positioning engine ready to
be used in 3rd party libraries.
Visit the [MENTIONS](/MENTIONS.md) page for an updated list of projects.
### Credits
I want to thank some friends and projects for the work they did:
- [@AndreaScn](https://github.com/AndreaScn) for his work on the GitHub Page and the manual testing he did during the development;
- [@vampolo](https://github.com/vampolo) for the original idea and for the name of the library;
- [Sysdig](https://github.com/Draios) for all the awesome things I learned during these years that made it possible for me to write this library;
- [Tether.js](http://github.hubspot.com/tether/) for having inspired me in writing a positioning library ready for the real world;
- [The Contributors](https://github.com/FezVrasta/popper.js/graphs/contributors) for their much appreciated Pull Requests and bug reports;
- **you** for the star you'll give to this project and for being so awesome to give this project a try 🙂
### Copyright and license
Code and documentation copyright 2016 **Federico Zivolo**. Code released under the [MIT license](LICENSE.md). Docs released under Creative Commons.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,159 +0,0 @@
/**
* @fileoverview This file only declares the public portions of the API.
* It should not define internal pieces such as utils or modifier details.
*
* Original definitions by: edcarroll <https://github.com/edcarroll>, ggray <https://github.com/giladgray>, rhysd <https://rhysd.github.io>, joscha <https://github.com/joscha>, seckardt <https://github.com/seckardt>, marcfallows <https://github.com/marcfallows>
*/
/**
* This kind of namespace declaration is not necessary, but is kept here for backwards-compatibility with
* popper.js 1.x. It can be removed in 2.x so that the default export is simply the Popper class
* and all the types / interfaces are top-level named exports.
*/
declare namespace Popper {
export type Position = 'top' | 'right' | 'bottom' | 'left';
export type Placement = 'auto-start'
| 'auto'
| 'auto-end'
| 'top-start'
| 'top'
| 'top-end'
| 'right-start'
| 'right'
| 'right-end'
| 'bottom-end'
| 'bottom'
| 'bottom-start'
| 'left-end'
| 'left'
| 'left-start';
export type Boundary = 'scrollParent' | 'viewport' | 'window';
export type Behavior = 'flip' | 'clockwise' | 'counterclockwise';
export type ModifierFn = (data: Data, options: Object) => Data;
export interface BaseModifier {
order?: number;
enabled?: boolean;
fn?: ModifierFn;
}
export interface Modifiers {
shift?: BaseModifier;
offset?: BaseModifier & {
offset?: number | string,
};
preventOverflow?: BaseModifier & {
priority?: Position[],
padding?: number,
boundariesElement?: Boundary | Element,
escapeWithReference?: boolean
};
keepTogether?: BaseModifier;
arrow?: BaseModifier & {
element?: string | Element,
};
flip?: BaseModifier & {
behavior?: Behavior | Position[],
padding?: number,
boundariesElement?: Boundary | Element,
};
inner?: BaseModifier;
hide?: BaseModifier;
applyStyle?: BaseModifier & {
onLoad?: Function,
gpuAcceleration?: boolean,
};
computeStyle?: BaseModifier & {
gpuAcceleration?: boolean;
x?: 'bottom' | 'top',
y?: 'left' | 'right'
};
[name: string]: (BaseModifier & Record<string, any>) | undefined;
}
export interface Offset {
top: number;
left: number;
width: number;
height: number;
}
export interface Data {
instance: Popper;
placement: Placement;
originalPlacement: Placement;
flipped: boolean;
hide: boolean;
arrowElement: Element;
styles: CSSStyleDeclaration;
boundaries: Object;
offsets: {
popper: Offset,
reference: Offset,
arrow: {
top: number,
left: number,
},
};
}
export interface PopperOptions {
placement?: Placement;
positionFixed?: boolean;
eventsEnabled?: boolean;
modifiers?: Modifiers;
removeOnDestroy?: boolean;
onCreate?(data: Data): void;
onUpdate?(data: Data): void;
}
export interface ReferenceObject {
clientHeight: number;
clientWidth: number;
getBoundingClientRect(): ClientRect;
}
}
// Re-export types in the Popper namespace so that they can be accessed as top-level named exports.
// These re-exports should be removed in 2.x when the "declare namespace Popper" syntax is removed.
export type Position = Popper.Position;
export type Placement = Popper.Placement;
export type Boundary = Popper.Boundary;
export type Behavior = Popper.Behavior;
export type ModifierFn = Popper.ModifierFn;
export type BaseModifier = Popper.BaseModifier;
export type Modifiers = Popper.Modifiers;
export type Offset = Popper.Offset;
export type Data = Popper.Data;
export type PopperOptions = Popper.PopperOptions;
export type ReferenceObject = Popper.ReferenceObject;
declare class Popper {
static modifiers: (BaseModifier & { name: string })[];
static placements: Placement[];
static Defaults: PopperOptions;
options: PopperOptions;
constructor(reference: Element | ReferenceObject, popper: Element, options?: PopperOptions);
destroy(): void;
update(): void;
scheduleUpdate(): void;
enableEventListeners(): void;
disableEventListeners(): void;
}
export default Popper;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше