Merge remote-tracking branch 'eShopOnContainers/dev' into dev

# Conflicts:
#	README.md
#	docs/Enterprise-Application-Patterns-using-XamarinForms.pdf
#	docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf
This commit is contained in:
Eduard Tomas 2017-05-05 10:05:51 +02:00
Родитель d9f7b3de15 c79336cda2
Коммит 2af88e74c8
158 изменённых файлов: 3122 добавлений и 1202 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -6,6 +6,7 @@
*.user
*.userosscache
*.sln.docstates
.vscode/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

134
KUBERNETES.md Normal file
Просмотреть файл

@ -0,0 +1,134 @@
# Kubernetes 101
## Docker vs. Kubernetes
Docker helps you package applications into images, and execute them in containers. Kubernetes is a robust platform for containerized applications. It abstracts away the underlying network infrastructure and hardware required to run them, simplifying their deployment, scaling, and management.
## Kubernetes from the container up
### Pods
The basic unit of a Kubernetes deployment is the **Pod**. A Pod encapsulates one or more containers. For example, the `basket` Pod specifies two containers:
>`deployments.yaml`
>
>The first container runs the `eshop/basket.api` image:
>```yaml
>spec:
> containers:
> - name: basket
> image: eshop/basket.api
> env:
> - name: ConnectionString
> value: 127.0.0.1
>```
>Note the `ConnectionString` environment variable: containers within a Pod are networked via `localhost`. The second container runs the `redis` image:
>```yaml
>- name: basket-data
> image: redis:3.2-alpine
> ports:
> - containerPort: 6379
>```
Placing `basket` and `basket-data` in the same Pod is reasonable here because the former requires the latter, and owns all its data. If we wanted to scale the service, however, it would be better to place the containers in separate Pods because the basket API and redis scale at different rates.
If the containers were in separate Pods, they would no longer be able to communicate via `localhost`; a **Service** would be required.
### Services
Services expose Pods to external networks. For example, the `basket` Service exposes Pods with labels `app=eshop` and `component=basket` to the cluster at large:
>`services.yaml`
>```yaml
>kind: Service
>metadata:
> ...
> name: basket
>spec:
> ports:
> - port: 80
> selector:
> app: eshop
> component: basket
>```
Kubernetes's built-in DNS service resolves Service names to cluster-internal IP addresses. This allows the nginx frontend to proxy connections to the app's microservices by name:
>`nginx.conf`
>```
>location /basket-api {
> proxy_pass http://basket;
>```
The frontend Pod is different in that it needs to be exposed outside the cluster. This is accomplished with another Service:
>`frontend.yaml`
>```yaml
>spec:
> ports:
> - port: 80
> targetPort: 8080
> selector:
> app: eshop
> component: frontend
> type: LoadBalancer
>```
`type: LoadBalancer` tells Kubernetes to expose the Service behind a load balancer appropriate for the cluster's platform. For Azure Container Service, this creates an Azure load balancer rule with a public IP.
### Deployments
Kubernetes uses Pods to organize containers, and Services to network them. It uses **Deployments** to organize creating, and modifying, Pods. A Deployment describes a state of one or more Pods. When a Deployment is created or modified, Kubernetes attempts to realize that state.
The Deployments in this project are basic. Still, `deploy.ps1` shows some more advanced Deployment capabilities. For example, Deployments can be paused. Each Deployment of this app is paused at creation:
>`deployments.yaml`
>```yaml
>kind: Deployment
>spec:
> paused: true
>```
This allows the deployment script to change images before Kubernetes creates the Pods:
>`deploy.ps1`
>```powershell
>kubectl set image -f deployments.yaml basket=$registry/basket.api ...
>kubectl rollout resume -f deployments.yaml
>```
### ConfigMaps
A **ConfigMap** is a collection of key/value pairs commonly used to provide configuration information to Pods. The deployment script uses one to store the frontend's configuration:
>`deploy.ps1`
>```
>kubectl create configmap config-files from-file=nginx-conf=nginx.conf
>```
This creates a ConfigMap named `config-files` with key `nginx-conf` whose value is the content of nginx.conf. The frontend Pod mounts that value as `/etc/nginx/nginx.conf`:
>`frontend.yaml`
>```yaml
>spec:
> containers:
> - name: nginx
> ...
> volumeMounts:
> - name: config
> mountPath: /etc/nginx
> volumes:
> - name: config
> configMap:
> name: config-files
> items:
> - key: nginx-conf
> path: nginx.conf
>```
This facilitates rapid iteration better than other techniques, e.g. building an image to bake in configuration.
The script also stores public URLs for the app's components in a ConfigMap:
>`deploy.ps1`
>```powershell
>kubectl create configmap urls --from-literal=BasketUrl=http://$($frontendUrl)/basket-api ...
>```
>Here's how the `webspa` Deployment uses it:
>
>`deployments.yaml`
>```yaml
>spec:
> containers:
> - name: webspa
> ...
> env:
> ...
> - name: BasketUrl
> valueFrom:
> configMapKeyRef:
> name: urls
> key: BasketUrl
>```
### Further reading
* [Kubernetes Concepts](https://kubernetes.io/docs/concepts/)
* [kubectl for Docker Users](https://kubernetes.io/docs/user-guide/docker-cli-to-kubectl/)
* [Kubernetes API reference](https://kubernetes.io/docs/api-reference/v1.5/)

28
README.k8s.md Normal file
Просмотреть файл

@ -0,0 +1,28 @@
# eShopOnContainers on Kubernetes
The k8s directory contains Kubernetes configuration for the eShopOnContainers app and a PowerShell script to deploy it to a cluster. Each eShopOnContainers microservice has a deployment configuration in `deployments.yaml`, and is exposed to the cluster by a service in `services.yaml`. The microservices are exposed externally on individual routes (`/basket-api`, `/webmvc`, etc.) by an nginx reverse proxy specified in `frontend.yaml` and `nginx.conf`.
## Prerequisites
* A Kubernetes cluster. Follow Azure Container Service's [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough) to create one.
* A private Docker registry. Follow Azure Container Registry's [guide](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) to create one.
* Optionally, previous steps can be skipped if you run gen-k8s-env.ps1 script to automatically create the azure environment needed for kubernetes deployment. Azure cli 2.0 must be previously installed [installation guide](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). For example:
>```
>./gen-k8s-env -resourceGroupName k8sGroup -location westeurope -registryName k8sregistry -orchestratorName k8s-cluster -dnsName k8s-dns
>```
* A Docker development environment with `docker` and `docker-compose`.
* Visit [docker.com](https://docker.com) to download the tools and set up the environment. Docker's [installation guide](https://docs.docker.com/engine/getstarted/step_one/#step-3-verify-your-installation) covers verifying your Docker installation.
* The Kubernetes command line client, `kubectl`.
* This can be installed with the `az` tool as described in the Azure Container Service [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough). `az` is also helpful for getting the credentials `kubectl` needs to access your cluster. For other installation options, and information about configuring `kubectl` yourself, see the [Kubernetes documentation](https://kubernetes.io/docs/tasks/kubectl/install/).
## Deploy the application with the deployment script
1. Open a PowerShell command line at the `k8s` directory of your local eShopOnContainers repository.
1. Ensure `docker`, `docker-compose`, and `kubectl` are on the path, and configured for your Docker machine and Kubernetes cluster.
1. Run `deploy.ps1` with your registry information. The Docker username and password are provided by Azure Container Registry, and can be retrieved from the Azure portal. Optionally, ACR credentials can be obtained by running the following command:
>```
>az acr credential show -n eshopregistry
>```
Once the user and password are retrieved, run the following script for deployment. For example:
>```
>./deploy.ps1 -registry myregistry.azurecr.io -dockerUser User -dockerPassword SecretPassword
>```
The script will build the code and corresponding Docker images, push the latter to your registry, and deploy the application to your cluster. You can watch the deployment unfold from the Kubernetes web interface: run `kubectl proxy` and open a browser to [http://localhost:8001/ui](http://localhost:8001/ui)

120
README.md
Просмотреть файл

@ -1,25 +1,121 @@
# eShopOnContainers for Azure
Azure-based version og the eShopOnContainers reference application
**Please refer to the [original eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) for general info about the project**
# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**BETA state** - Visual Studio 2017 and CLI environments compatible)
Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. <p>
**Note for Pull Requests**: We accept pull request from the community. When doing it, please do it onto the DEV branch which is the consolidated work-in-progress branch. Do not request it onto Master, if possible.
> ### DISCLAIMER
> **IMPORTANT:** The current state of this sample application is **BETA**, consider it version a 0.1 foundational version, therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. **Feedback with improvements and pull requests from the community will be highly appreciated and accepted.**
>
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. The chosen domain is an eShop/eCommerce but simply because it is a well-know domain by most people/developers.
However, this sample application should not be considered as an "eCommerce reference model", at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
> <p>For example, the next step (still not covered in eShopOnContainers) after understanding Docker containers and microservices development with .NET Core, is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes).
> Additional steps would be to move your databases to HA cloud services, or to implement your EventBus with Azure Service Bus or any other production ready Service Bus in the market.
> <p> In the future we might fork this project and make multiple versions targeting specific microservice cluster/orchestrators plus using additional cloud infrastructure. <p>
> <img src="img/exploring-to-production-ready.png">
> Read the planned <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>Roadmap and Milestones for future releases of eShopOnContainers</a> within the Wiki for further info about possible new implementations and provide feedback at the <a href='https://github.com/dotnet/eShopOnContainers/issues'>ISSUES section</a> if you'd like to see any specific scenario implemented or improved. Also, feel free to discuss on any current issue.
## WHAT IS ON THIS REPO?
This repo contains an implementation of eShopOnContainers using Azure services when applicable. Its code is synced periodically with the code of [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers). Also unique features using Azure services will be implemented in this repo (while generic features will be
implemented on _eShopOnContainers_ and integrated in this repo).
**Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol.
<p>
It also supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
<p>
<img src="img/eshop_logo.png">
<img src="img/eShopOnContainers_Architecture_Diagram.png">
<p>
The microservices are different in type, meaning different internal architecture patterns approaches depending on it purpose, as shown in the image below.
<p>
<img src="img/eShopOnContainers_Types_Of_Microservices.png">
<p>
<p>
Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :)
This repo also contains scripts for an autometed deploy of all Azure resources and information about how to configure a CI/CD pipeline.
> ### Important Note on Database Servers/Containers
> In this solution's current configuration for a development environment, the SQL databases are automatically deployed with sample data into a single SQL Server for Linux container (a single shared Docker container for SQL databases) so the whole solution can be up and running without any dependency to any cloud or specific server. Each database could also be deployed as a single Docker container, but then you'd need more then 8GB or memory RAM assigned to Docker in your development machine in order to be able to run 3 SQL Server Docker containers in your Docker Linux host in "Docker for Windows" or "Docker for Mac" development environments.
> <p> A similar case is defined in regards Redis cache running as a container for the development environment.
> <p> However, in a real production environment it is recommended to have your databases (SQL Server and Redis, in this case) in HA (High Available) services like Azure SQL Database, Redis as a service or any other clustering system. If you want to change to a production configuration, you'll just need to change the connection strings once you have set up the servers in a HA cloud or on-premises.
Status of this repo is in **early development**.
## Related documentation and guidance
While developing this reference application, we've been creating a reference <b>Guide/eBook</b> focusing on <b>architecting and developing containerized and microservice based .NET Applications</b> (download link available below) which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
<p>
There are also additional eBooks focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published plus an additional eBook focusing on Enterprise Apps Patterns with Xamarin.Forms.
You can download them and start reviewing these Guides/eBooks here:
<p>
## HOW TO CONTRIBUTE
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
| ------------ | ------------| ------------|
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (Early DRAFT, still work in progress)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Preview Edition) </a> </sup> |
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
<p>
However, we encourage to download and review the "Architecting & Developing eBook" because the architectural styles and architectural patterns and technologies explained in the guidance are using this reference application when explaining many pattern implementations, so you'll understand much better the context, design and decisions taken in the current architecture and internal designs.
## Overview of the application code
In this repo you can find a sample reference application that will help you to understand how to implement a microservice architecture based application using <b>.NET Core</b> and <b>Docker</b>.
The example business domain or scenario is based on an eShop or eCommerce which is implemented as a multi-container application. Each container is a microservice deployment (like the basket-microservice, catalog-microservice, ordering-microservice and the identity-microservice) which are developed using ASP.NET Core running on .NET Core so they can run either on Linux Containers and Windows Containers.
The screenshot below shows the VS Solution structure for those microservices/containers and client apps.
- (*Recommended when getting started*) Open <b>eShopOnContainers-ServicesAndWebApps.sln</b> for a solution containing just the server-side projects related to the microservices and web applications.
- Open <b>eShopOnContainers-MobileApps.sln</b> for a solution containing just the client mobile app projects (Xamarin mobile apps only). It works independently based on mocks, too.
- Open <b>eShopOnContainers.sln</b> for a solution containing all the projects (All client apps and services).
<img src="img/vs-solution-structure.png">
Finally, those microservices are consumed by multiple client web and mobile apps, as described below.
<br>
<b>*MVC Application (ASP.NET Core)*</b>: Its an MVC application where you can find interesting scenarios on how to consume HTTP-based microservices from C# running in the server side, as it is a typical ASP.NET Core MVC application. Since it is a server-side application, access to other containers/microservices is done within the internal Docker Host network with its internal name resolution.
<img src="img/eshop-webmvc-app-screenshot.png">
<br>
<b>*SPA (Single Page Application)*</b>: Providing similar "eShop business functionality" but developed with Angular 2, Typescript and slightly using ASP.NET Core MVC. This is another approach for client web applications to be used when you want to have a more modern client behavior which is not behaving with the typical browser round-trip on every action but behaving like a Single-Page-Application which is more similar to a desktop app usage experience. The consumption of the HTTP-based microservices is done from TypeScript/JavaScript in the client browser, so the client calls to the microservices come from out of the Docker Host internal network (Like from your network or even from the Internet).
<img src="img/eshop-webspa-app-screenshot.png">
<br>
<b>*Xamarin Mobile App (For iOS, Android and Windows/UWP)*</b>: It is a client mobile app supporting the most common mobile OS platforms (iOS, Android and Windows/UWP). In this case, the consumption of the microservices is done from C# but running on the client devices, so out of the Docker Host internal network (Like from your network or even the Internet).
<img src="img/xamarin-mobile-App.png">
## Setting up your development environment for eShopOnContainers
### Visual Studio 2017 and Windows based
This is the more straightforward way to get started:
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
### CLI and Windows based
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
https://github.com/dotnet/eShopOnContainers/wiki/03.-Setting-the-eShopOnContainers-solution-up-in-a-Windows-CLI-environment-(dotnet-CLI,-Docker-CLI-and-VS-Code)
### CLI and Mac based
For those who prefer the CLI on a Mac, using dotnet CLI, docker CLI and VS Code for Mac
(Instructions still TBD, but similar to Windows CLI):
https://github.com/dotnet/eShopOnContainers/wiki/04.-Setting-eShopOnContainer-solution-up-in-a-Mac,-VS-Code-and-CLI-environment--(dotnet-CLI,-Docker-CLI-and-VS-Code)
> ### Note on tested Docker Containers/Images
> Most of the development and testing of this project was (as of early March 2017) done <b> on Docker Linux containers</b> running in development machines with "Docker for Windows" and the default Hyper-V Linux VM (MobiLinuxVM) installed by "Docker for Windows".
The <b>Windows Containers scenario is currently being implemented/tested yet</b>. The application should be able to run on Windows Nano Containers based on different Docker base images, as well, as the .NET Core services have also been tested running on plain Windows (with no Docker).
The app was also partially tested on "Docker for Mac" using a development MacOS machine with .NET Core and VS Code installed, which is still a scenario using Linux containers running on the VM setup in the Mac by the "Docker for Windows" setup. But further testing and feedback on Mac environments and Windows Containers, from the community, will be appreciated.
## Kubernetes
The k8s directory contains Kubernetes configuration for the eShopOnContainers app and a PowerShell script to deploy it to a cluster. Each eShopOnContainers microservice has a deployment configuration in `deployments.yaml`, and is exposed to the cluster by a service in `services.yaml`. The microservices are exposed externally on individual routes (`/basket-api`, `/webmvc`, etc.) by an nginx reverse proxy specified in `frontend.yaml` and `nginx.conf`.
### Prerequisites
* A Kubernetes cluster. Follow Azure Container Service's [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough) to create one.
* A private Docker registry. Follow Azure Container Registry's [guide](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) to create one.
* A Docker development environment with `docker` and `docker-compose`.
* Visit [docker.com](https://docker.com) to download the tools and set up the environment. Docker's [installation guide](https://docs.docker.com/engine/getstarted/step_one/#step-3-verify-your-installation) covers verifying your Docker installation.
* The Kubernetes command line client, `kubectl`.
* This can be installed with the `az` tool as described in the Azure Container Service [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough). `az` is also helpful for getting the credentials `kubectl` needs to access your cluster. For other installation options, and information about configuring `kubectl` yourself, see the [Kubernetes documentation](https://kubernetes.io/docs/tasks/kubectl/install/).
### Deploy the application with the deployment script
1. Open a PowerShell command line at the `k8s` directory of your local eShopOnContainers repository.
1. Ensure `docker`, `docker-compose`, and `kubectl` are on the path, and configured for your Docker machine and Kubernetes cluster.
1. Run `deploy.ps1` with your registry information. The Docker username and password are provided by Azure Container Registry, and can be retrieved from the Azure portal. For example:
>```
>./deploy.ps1 -registry myregistry.azurecr.io -dockerUser User -dockerPassword SecretPassword
>```
The script will build the code and corresponding Docker images, push the latter to your registry, and deploy the application to your cluster. You can watch the deployment unfold from the Kubernetes web interface: run `kubectl proxy` and open a browser to [http://localhost:8001/ui](http://localhost:8001/ui)
## Sending feedback and pull requests
As mentioned, we'd appreciate to your feedback, improvements and ideas.
You can create new issues at the issues section, do pull requests and/or send emails to **eshop_feedback@service.microsoft.com**
## Questions
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):
https://github.com/dotnet/eShopOnContainers/issues/107

37
cli-linux/build-bits-linux.sh Normal file → Executable file
Просмотреть файл

@ -1,18 +1,17 @@
projectList=(
"/src/Services/Catalog/Catalog.API"
"/src/Services/Basket/Basket.API"
"/src/Services/Ordering/Ordering.API"
"/src/Services/Identity/Identity.API"
"/src/Web/WebMVC"
"/src/Web/WebSPA"
"/src/Web/WebStatus
#!/bin/bash
declare -a projectList=(
'../src/Services/Catalog/Catalog.API'
'../src/Services/Basket/Basket.API'
'../src/Services/Ordering/Ordering.API'
'../src/Services/Identity/Identity.API'
'../src/Web/WebMVC'
'../src/Web/WebSPA'
'../src/Web/WebStatus'
)
# Build SPA app
pushd $(pwd)/src/Web/WebSPA
npm rebuild node-sass
npm run build:prod
# pushd $(pwd)../src/Web/WebSPA
# npm run build:prod
for project in "${projectList[@]}"
do
@ -28,13 +27,13 @@ do
done
# remove old docker images:
#images=$(docker images --filter=reference="eshop/*" -q)
#if [ -n "$images" ]; then
# docker rm $(docker ps -a -q) -f
# echo "Deleting eShop images in local Docker repo"
# echo $images
# docker rmi $(docker images --filter=reference="eshop/*" -q) -f
#fi
images=$(docker images --filter=reference="eshop/*" -q)
if [ -n "$images" ]; then
docker rm $(docker ps -a -q) -f
echo "Deleting eShop images in local Docker repo"
echo $images
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
fi
# No need to build the images, docker build or docker compose will
# do that using the images and containers defined in the docker-compose.yml file.

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

@ -11,8 +11,7 @@ if ([string]::IsNullOrEmpty($rootPath)) {
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
$SolutionFilePath = $rootPath + "eShopOnContainers-ServicesAndWebApps.sln"
$SolutionFilePath = [IO.Path]::Combine($rootPath, "eShopOnContainers-ServicesAndWebApps.sln")
dotnet restore $SolutionFilePath

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

@ -2,7 +2,7 @@ version: '2.1'
services:
basket.api:
image: eshop/basket.api
image: eshop/basket.api-win
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile.nanowin
@ -11,7 +11,7 @@ services:
- identity.api
catalog.api:
image: eshop/catalog.api
image: eshop/catalog.api-win
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile.nanowin
@ -19,7 +19,7 @@ services:
- sql.data
identity.api:
image: eshop/identity.api
image: eshop/identity.api-win
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile.nanowin
@ -27,7 +27,7 @@ services:
- sql.data
ordering.api:
image: eshop/ordering.api
image: eshop/ordering.api-win
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile.nanowin
@ -35,7 +35,7 @@ services:
- sql.data
webspa:
image: eshop/webspa
image: eshop/webspa-win
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile.nanowin
@ -44,7 +44,7 @@ services:
- basket.api
webmvc:
image: eshop/webmvc
image: eshop/webmvc-win
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile.nanowin
@ -58,18 +58,18 @@ services:
image: microsoft/mssql-server-windows
basket.data:
image: redis
build:
context: ./_docker/redis
dockerfile: Dockerfile.nanowin
image: eshop/redis-win
# build:
# context: ./_docker/redis
# dockerfile: Dockerfile.nanowin
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq
build:
context: ./_docker/rabbitmq
dockerfile: Dockerfile.nanowin
image: eshop/rabbitmq-win
# build:
# context: ./_docker/rabbitmq
# dockerfile: Dockerfile.nanowin
ports:
- "5672:5672"

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

@ -6,5 +6,5 @@ services:
volumes:
- .:/src
working_dir: /src
command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && pushd ./../../.. && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"
command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"

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

@ -45,7 +45,6 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5102
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://basket.api:5103
- EventBusConnection=rabbitmq
ports:
- "5102:5102"

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

@ -50,7 +50,6 @@ services:
- ASPNETCORE_URLS=http://0.0.0.0:5102
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://basket.api:5103
- EventBusConnection=rabbitmq
ports:
- "5102:5102"

Двоичные данные
docs/Enterprise-Application-Patterns-using-XamarinForms.pdf Normal file

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

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

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

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.12
VisualStudioVersion = 15.0.26403.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -62,18 +62,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationEventLogEF", "sr
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.Data", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj", "{7804FC60-23E6-490C-8E08-F9FEF829F184}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resilience", "Resilience", "{FBF43D93-F2E7-4FF8-B4AB-186895949B88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resilience.Http", "src\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj", "{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -712,54 +714,6 @@ Global
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU
{9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.AppStore|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|ARM.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhone.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x64.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.ActiveCfg = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Debug|x86.Build.0 = Debug|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|Any CPU.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|ARM.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhone.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x64.Build.0 = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.ActiveCfg = Release|Any CPU
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E}.Release|x86.Build.0 = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -808,54 +762,6 @@ Global
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU
{942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.AppStore|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|ARM.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhone.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x64.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.ActiveCfg = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Debug|x86.Build.0 = Debug|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|Any CPU.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|ARM.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhone.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x64.Build.0 = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.ActiveCfg = Release|Any CPU
{7804FC60-23E6-490C-8E08-F9FEF829F184}.Release|x86.Build.0 = Release|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@ -952,6 +858,150 @@ Global
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x64.Build.0 = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.ActiveCfg = Release|Any CPU
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502}.Release|x86.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.ActiveCfg = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.Build.0 = Debug|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.Build.0 = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.ActiveCfg = Release|Any CPU
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.Build.0 = Debug|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU
{4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.AppStore|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|ARM.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhone.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x64.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.ActiveCfg = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Debug|x86.Build.0 = Debug|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|Any CPU.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|ARM.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhone.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x64.Build.0 = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.ActiveCfg = Release|Any CPU
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -981,11 +1031,12 @@ Global
{8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{DF8367F8-E6BD-4D07-99D2-E416BF8AB01E} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{7804FC60-23E6-490C-8E08-F9FEF829F184} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
{FBF43D93-F2E7-4FF8-B4AB-186895949B88} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{D1C47FF1-91F1-4CAF-9ABB-AD642B821502} = {FBF43D93-F2E7-4FF8-B4AB-186895949B88}
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
EndGlobalSection
EndGlobal

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

До

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

После

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

Двоичные данные
img/ebook_arch_dev_microservices_containers_cover_LARGE.png Normal file

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

После

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

Двоичные данные
img/ebook_arch_dev_microservices_containers_cover_OLD.png Normal file

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

После

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

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

До

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

После

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

80
k8s/deploy.ps1 Normal file
Просмотреть файл

@ -0,0 +1,80 @@
Param(
[parameter(Mandatory=$true)][string]$registry,
[parameter(Mandatory=$true)][string]$dockerUser,
[parameter(Mandatory=$true)][string]$dockerPassword
)
$requiredCommands = ("docker", "docker-compose", "kubectl")
foreach ($command in $requiredCommands) {
if ((Get-Command $command -ErrorAction SilentlyContinue) -eq $null) {
Write-Host "$command must be on path" -ForegroundColor Red
exit
}
}
Write-Host "Logging in to $registry" -ForegroundColor Yellow
docker login -u $dockerUser -p $dockerPassword $registry
if (-not $LastExitCode -eq 0) {
Write-Host "Login failed" -ForegroundColor Red
exit
}
# create registry key secret
kubectl create secret docker-registry registry-key `
--docker-server=$registry `
--docker-username=$dockerUser `
--docker-password=$dockerPassword `
--docker-email=not@used.com
# start sql, rabbitmq, frontend deployments
kubectl create configmap config-files --from-file=nginx-conf=nginx.conf
kubectl label configmap config-files app=eshop
kubectl create -f sql-data.yaml -f rabbitmq.yaml -f services.yaml -f frontend.yaml
Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow
dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln
dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln
Write-Host "Building Docker images..." -ForegroundColor Yellow
docker-compose -p .. -f ../docker-compose.yml build
Write-Host "Pushing images to $registry..." -ForegroundColor Yellow
$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "webmvc", "webspa")
foreach ($service in $services) {
docker tag eshop/$service $registry/eshop/$service
docker push $registry/eshop/$service
}
Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow
while ($true) {
$frontendUrl = kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"
if ([bool]($frontendUrl -as [ipaddress])) {
break
}
Start-Sleep -s 15
}
kubectl create configmap urls `
--from-literal=BasketUrl=http://$($frontendUrl)/basket-api `
--from-literal=CatalogUrl=http://$($frontendUrl)/catalog-api `
--from-literal=IdentityUrl=http://$($frontendUrl)/identity `
--from-literal=OrderingUrl=http://$($frontendUrl)/ordering-api `
--from-literal=MvcClient=http://$($frontendUrl)/webmvc `
--from-literal=SpaClient=http://$($frontendUrl)
kubectl label configmap urls app=eshop
Write-Host "Creating deployments..."
kubectl apply -f deployments.yaml
# update deployments with the private registry before k8s tries to pull images
# (deployment templating, or Helm, would obviate this)
kubectl set image -f deployments.yaml `
basket=$registry/eshop/basket.api `
catalog=$registry/eshop/catalog.api `
identity=$registry/eshop/identity.api `
ordering=$registry/eshop/ordering.api `
webmvc=$registry/eshop/webmvc `
webspa=$registry/eshop/webspa
kubectl rollout resume -f deployments.yaml
Write-Host "WebSPA is exposed at http://$frontendUrl, WebMVC at http://$frontendUrl/webmvc" -ForegroundColor Yellow

236
k8s/deployments.yaml Normal file
Просмотреть файл

@ -0,0 +1,236 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: basket
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: basket
spec:
containers:
- name: basket
image: eshop/basket.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/basket-api
- name: ConnectionString
value: 127.0.0.1
- name: EventBusConnection
value: rabbitmq
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
ports:
- containerPort: 80
- name: basket-data
image: redis:3.2-alpine
ports:
- containerPort: 6379
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: catalog
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: catalog
spec:
containers:
- name: catalog
image: eshop/catalog.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/catalog-api
- name: ConnectionString
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word"
- name: EventBusConnection
value: rabbitmq
- name: ExternalCatalogBaseUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: identity
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: identity
spec:
containers:
- name: identity
image: eshop/identity.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/identity
- name: ConnectionStrings__DefaultConnection
value: "Server=sql-data;Initial Catalog=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word"
- name: MvcClient
valueFrom:
configMapKeyRef:
name: urls
key: MvcClient
- name: SpaClient
valueFrom:
configMapKeyRef:
name: urls
key: SpaClient
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ordering
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: ordering
spec:
containers:
- name: ordering
image: eshop/ordering.api
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/ordering-api
- name: ConnectionString
value: "Server=sql-data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"
- name: EventBusConnection
value: rabbitmq
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: webmvc
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: webmvc
spec:
containers:
- name: webmvc
image: eshop/webmvc
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80/webmvc
- name: BasketUrl
valueFrom:
configMapKeyRef:
name: urls
key: BasketUrl
- name: CallBackUrl
valueFrom:
configMapKeyRef:
name: urls
key: MvcClient
- name: CatalogUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
- name: OrderingUrl
valueFrom:
configMapKeyRef:
name: urls
key: OrderingUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: webspa
spec:
paused: true
template:
metadata:
labels:
app: eshop
component: webspa
spec:
containers:
- name: webspa
image: eshop/webspa
imagePullPolicy: Always
env:
- name: ASPNETCORE_URLS
value: http://0.0.0.0:80
- name: BasketUrl
valueFrom:
configMapKeyRef:
name: urls
key: BasketUrl
- name: CallBackUrl
valueFrom:
configMapKeyRef:
name: urls
key: SpaClient
- name: CatalogUrl
valueFrom:
configMapKeyRef:
name: urls
key: CatalogUrl
- name: IdentityUrl
valueFrom:
configMapKeyRef:
name: urls
key: IdentityUrl
- name: OrderingUrl
valueFrom:
configMapKeyRef:
name: urls
key: OrderingUrl
ports:
- containerPort: 80
imagePullSecrets:
- name: registry-key

47
k8s/frontend.yaml Normal file
Просмотреть файл

@ -0,0 +1,47 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: frontend
name: frontend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: eshop
component: frontend
type: LoadBalancer
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: frontend
spec:
template:
metadata:
labels:
app: eshop
component: frontend
spec:
containers:
- name: nginx
image: nginx:1.11.10-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
volumeMounts:
- name: config
mountPath: /etc/nginx
volumes:
- name: config
configMap:
name: config-files
items:
- key: nginx-conf
path: nginx.conf

25
k8s/gen-k8s-env.ps1 Normal file
Просмотреть файл

@ -0,0 +1,25 @@
Param(
[parameter(Mandatory=$true)][string]$resourceGroupName,
[parameter(Mandatory=$true)][string]$location,
[parameter(Mandatory=$true)][string]$registryName,
[parameter(Mandatory=$true)][string]$orchestratorName,
[parameter(Mandatory=$true)][string]$dnsName
)
# Create resource group
Write-Host "Creating resource group..." -ForegroundColor Yellow
az group create --name=$resourceGroupName --location=$location
# Create Azure Container Registry
Write-Host "Creating Azure Container Registry..." -ForegroundColor Yellow
az acr create -n $registryName -g $resourceGroupName -l $location --admin-enabled true --sku Basic
# Create kubernetes orchestrator
Write-Host "Creating kubernetes orchestrator..." -ForegroundColor Yellow
az acs create --orchestrator-type=kubernetes --resource-group $resourceGroupName --name=$orchestratorName --dns-prefix=$dnsName --generate-ssh-keys
# Retrieve kubernetes cluster configuration and save it under ~/.kube/config
az acs kubernetes get-credentials --resource-group=$resourceGroupName --name=$orchestratorName
# Show ACR credentials
az acr credential show -n $registryName

74
k8s/nginx.conf Normal file
Просмотреть файл

@ -0,0 +1,74 @@
pid /tmp/nginx.pid;
worker_processes 1;
events {
worker_connections 1024;
}
http {
server_tokens off;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_buffers 4 32k;
gzip_types text/plain application/javascript text/css;
gzip_vary on;
keepalive_timeout 65;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
server {
listen 8080;
location /basket-api {
proxy_pass http://basket;
proxy_redirect off;
proxy_set_header Host $host;
}
location /catalog-api {
proxy_pass http://catalog;
proxy_redirect off;
proxy_set_header Host $host;
}
location /identity {
proxy_pass http://identity;
proxy_redirect off;
proxy_set_header Host $host;
}
location /ordering-api {
proxy_pass http://ordering;
proxy_redirect off;
proxy_set_header Host $host;
}
location /webmvc {
proxy_pass http://webmvc;
proxy_redirect off;
proxy_set_header Host $host;
}
location / {
proxy_pass http://webspa;
proxy_redirect off;
proxy_set_header Host $host;
}
}
}

30
k8s/rabbitmq.yaml Normal file
Просмотреть файл

@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: rabbitmq
name: rabbitmq
spec:
ports:
- port: 5672
selector:
app: eshop
component: rabbitmq
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rabbitmq
spec:
template:
metadata:
labels:
app: eshop
component: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:3.6.9-alpine
ports:
- containerPort: 5672

83
k8s/services.yaml Normal file
Просмотреть файл

@ -0,0 +1,83 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: basket
name: basket
spec:
ports:
- port: 80
selector:
app: eshop
component: basket
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: catalog
name: catalog
spec:
ports:
- port: 80
selector:
app: eshop
component: catalog
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: identity
name: identity
spec:
ports:
- port: 80
selector:
app: eshop
component: identity
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: ordering
name: ordering
spec:
ports:
- port: 80
selector:
app: eshop
component: ordering
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: webmvc
name: webmvc
spec:
ports:
- port: 80
selector:
app: eshop
component: webmvc
---
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: webspa
name: webspa
spec:
ports:
- port: 80
selector:
app: eshop
component: webspa

33
k8s/sql-data.yaml Normal file
Просмотреть файл

@ -0,0 +1,33 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: eshop
component: sql-data
name: sql-data
spec:
ports:
- port: 1433
selector:
app: eshop
component: sql-data
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: sql-data
spec:
template:
metadata:
labels:
app: eshop
component: sql-data
spec:
containers:
- name: sql-data
image: microsoft/mssql-server-linux:ctp1-3
env:
- name: ACCEPT_EULA
value: "Y"
- name: SA_PASSWORD
value: Pass@word

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

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\EventBus\EventBus.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

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

@ -0,0 +1,56 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using System;
using System.Linq;
using Xunit;
namespace EventBus.Tests
{
public class InMemory_SubscriptionManager_Tests
{
[Fact]
public void After_Creation_Should_Be_Empty()
{
var manager = new InMemoryEventBusSubscriptionsManager();
Assert.True(manager.IsEmpty);
}
[Fact]
public void After_One_Event_Subscription_Should_Contain_The_Event()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
[Fact]
public void After_All_Subscriptions_Are_Deleted_Event_Should_No_Longer_Exists()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.False(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
}
[Fact]
public void Deleting_Last_Subscription_Should_Raise_On_Deleted_Event()
{
bool raised = false;
var manager = new InMemoryEventBusSubscriptionsManager();
manager.OnEventRemoved += (o, e) => raised = true;
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.RemoveSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(raised);
}
[Fact]
public void Get_Handlers_For_Event_Should_Return_All_Handlers()
{
var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>(() => new TestIntegrationEventHandler());
manager.AddSubscription<TestIntegrationEvent, TestIntegrationOtherEventHandler>(() => new TestIntegrationOtherEventHandler());
var handlers = manager.GetHandlersForEvent<TestIntegrationEvent>();
Assert.Equal(2, handlers.Count());
}
}
}

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

@ -0,0 +1,11 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace EventBus.Tests
{
public class TestIntegrationEvent : IntegrationEvent
{
}
}

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

@ -0,0 +1,23 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace EventBus.Tests
{
public class TestIntegrationOtherEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
{
public bool Handled { get; private set; }
public TestIntegrationOtherEventHandler()
{
Handled = false;
}
public async Task Handle(TestIntegrationEvent @event)
{
Handled = true;
}
}
}

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

@ -0,0 +1,23 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace EventBus.Tests
{
public class TestIntegrationEventHandler : IIntegrationEventHandler<TestIntegrationEvent>
{
public bool Handled { get; private set; }
public TestIntegrationEventHandler()
{
Handled = false;
}
public async Task Handle(TestIntegrationEvent @event)
{
Handled = true;
}
}
}

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

@ -1,11 +1,17 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IEventBus
{
void Subscribe<T>(IIntegrationEventHandler<T> handler) where T: IntegrationEvent;
void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent;
void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void Publish(IntegrationEvent @event);
}
}

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

@ -0,0 +1,26 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public interface IEventBusSubscriptionsManager
{
bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved;
void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName);
void Clear();
IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<Delegate> GetHandlersForEvent(string eventName);
}
}

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

@ -0,0 +1,115 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
{
public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{
private readonly Dictionary<string, List<Delegate>> _handlers;
private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager()
{
_handlers = new Dictionary<string, List<Delegate>>();
_eventTypes = new List<Type>();
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
public void AddSubscription<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var key = GetEventKey<T>();
if (!HasSubscriptionsForEvent<T>())
{
_handlers.Add(key, new List<Delegate>());
}
_handlers[key].Add(handler);
_eventTypes.Add(typeof(T));
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var handlerToRemove = FindHandlerToRemove<T, TH>();
if (handlerToRemove != null)
{
var key = GetEventKey<T>();
_handlers[key].Remove(handlerToRemove);
if (!_handlers[key].Any())
{
_handlers.Remove(key);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == key);
if (eventType != null)
{
_eventTypes.Remove(eventType);
RaiseOnEventRemoved(eventType.Name);
}
}
}
}
public IEnumerable<Delegate> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
}
public IEnumerable<Delegate> GetHandlersForEvent(string eventName) => _handlers[eventName];
private void RaiseOnEventRemoved(string eventName)
{
var handler = OnEventRemoved;
if (handler != null)
{
OnEventRemoved(this, eventName);
}
}
private Delegate FindHandlerToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
if (!HasSubscriptionsForEvent<T>())
{
return null;
}
var key = GetEventKey<T>();
foreach (var func in _handlers[key])
{
var genericArgs = func.GetType().GetGenericArguments();
if (genericArgs.SingleOrDefault() == typeof(TH))
{
return func;
}
}
return null;
}
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return HasSubscriptionsForEvent(key);
}
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.Single(t => t.Name == eventName);
private string GetEventKey<T>()
{
return typeof(T).Name;
}
}
}

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

@ -10,18 +10,18 @@ using System.Net.Sockets;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class DefaultRabbitMQPersisterConnection
: IRabbitMQPersisterConnection
public class DefaultRabbitMQPersistentConnection
: IRabbitMQPersistentConnection
{
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<DefaultRabbitMQPersisterConnection> _logger;
private readonly ILogger<DefaultRabbitMQPersistentConnection> _logger;
IConnection _connection;
bool _disposed;
object sync_root = new object();
public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersisterConnection> logger)
public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersistentConnection> logger)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@ -87,13 +87,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_connection.CallbackException += OnCallbackException;
_connection.ConnectionBlocked += OnConnectionBlocked;
_logger.LogInformation($"RabbitMQ persister connection acquire a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
_logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
return true;
}
else
{
_logger.LogCritical("FATAL ERROR: RabbitMQ connections can't be created and opened");
_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
return false;
}

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

@ -1,4 +1,5 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@ -21,32 +22,50 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
const string BROKER_NAME = "eshop_event_bus";
private readonly IRabbitMQPersisterConnection _persisterConnection;
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers
= new Dictionary<string, List<IIntegrationEventHandler>>();
private readonly List<Type> _eventTypes
= new List<Type>();
private IModel _consumerChannel;
private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger<EventBusRabbitMQ> logger)
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, IEventBusSubscriptionsManager subsManager)
{
_persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection));
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_consumerChannel = CreateConsumerChannel();
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
}
private void SubsManager_OnEventRemoved(object sender, string eventName)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
public void Publish(IntegrationEvent @event)
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
@ -56,7 +75,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_logger.LogWarning(ex.ToString());
});
using (var channel = _persisterConnection.CreateModel())
using (var channel = _persistentConnection.CreateModel())
{
var eventName = @event.GetType()
.Name;
@ -77,75 +96,49 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
public void Subscribe<T, TH>(Func<TH> handler)
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = typeof(T).Name;
if (_handlers.ContainsKey(eventName))
var containsKey = _subsManager.HasSubscriptionsForEvent<T>();
if (!containsKey)
{
_handlers[eventName].Add(handler);
}
else
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
_handlers.Add(eventName, new List<IIntegrationEventHandler>());
_handlers[eventName].Add(handler);
_eventTypes.Add(typeof(T));
}
}
_subsManager.AddSubscription<T, TH>(handler);
}
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
public void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var eventName = typeof(T).Name;
_subsManager.RemoveSubscription<T, TH>();
}
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
private static Func<IIntegrationEventHandler> FindHandlerByType(Type handlerType, IEnumerable<Func<IIntegrationEventHandler>> handlers)
{
foreach (var func in handlers)
{
_handlers[eventName].Remove(handler);
if (_handlers[eventName].Count == 0)
if (func.GetMethodInfo().ReturnType == handlerType)
{
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
if (!_persisterConnection.IsConnected)
{
_persisterConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_handlers.Keys.Count == 0)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
return func;
}
}
return null;
}
public void Dispose()
@ -155,17 +148,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_consumerChannel.Dispose();
}
_handlers.Clear();
_subsManager.Clear();
}
private IModel CreateConsumerChannel()
{
if (!_persisterConnection.IsConnected)
if (!_persistentConnection.IsConnected)
{
_persisterConnection.TryConnect();
_persistentConnection.TryConnect();
}
var channel = _persisterConnection.CreateModel();
var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
@ -196,15 +189,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message)
{
if (_handlers.ContainsKey(eventName))
{
Type eventType = _eventTypes.Single(t => t.Name == eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
var handlers = _handlers[eventName];
foreach (var handler in handlers)
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handlers = _subsManager.GetHandlersForEvent(eventName);
foreach (var handlerfactory in handlers)
{
var handler = handlerfactory.DynamicInvoke();
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
}

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

@ -3,8 +3,7 @@ using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public interface IRabbitMQPersisterConnection
public interface IRabbitMQPersistentConnection
: IDisposable
{
bool IsConnected { get; }

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

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@ -11,30 +13,34 @@ namespace Microsoft.AspNetCore.HealthChecks
{
public class HealthCheckMiddleware
{
private RequestDelegate _next;
private string _path;
private int? _port;
private IHealthCheckService _service;
private readonly RequestDelegate _next;
private readonly string _path;
private readonly int? _port;
private readonly IHealthCheckService _service;
private readonly TimeSpan _timeout;
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port)
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout)
{
_port = port;
_service = service;
_next = next;
_timeout = timeout;
}
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path)
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout)
{
_path = path;
_service = service;
_next = next;
_timeout = timeout;
}
public async Task Invoke(HttpContext context)
{
if (IsHealthCheckRequest(context))
{
var result = await _service.CheckHealthAsync();
var timeoutTokenSource = new CancellationTokenSource(_timeout);
var result = await _service.CheckHealthAsync(timeoutTokenSource.Token);
var status = result.CheckStatus;
if (status != CheckStatus.Healthy)
@ -60,7 +66,9 @@ namespace Microsoft.AspNetCore.HealthChecks
}
if (context.Request.Path == _path)
{
return true;
}
return false;
}

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

@ -11,15 +11,18 @@ namespace Microsoft.AspNetCore.HealthChecks
{
private string _path;
private int? _port;
private TimeSpan _timeout;
public HealthCheckStartupFilter(int port)
public HealthCheckStartupFilter(int port, TimeSpan timeout)
{
_port = port;
_timeout = timeout;
}
public HealthCheckStartupFilter(string path)
public HealthCheckStartupFilter(string path, TimeSpan timeout)
{
_path = path;
_timeout = timeout;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
@ -27,9 +30,13 @@ namespace Microsoft.AspNetCore.HealthChecks
return app =>
{
if (_port.HasValue)
app.UseMiddleware<HealthCheckMiddleware>(_port);
{
app.UseMiddleware<HealthCheckMiddleware>(_port, _timeout);
}
else
app.UseMiddleware<HealthCheckMiddleware>(_path);
{
app.UseMiddleware<HealthCheckMiddleware>(_path, _timeout);
}
next(app);
};

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

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
@ -8,28 +9,38 @@ namespace Microsoft.AspNetCore.Hosting
{
public static class HealthCheckWebHostBuilderExtension
{
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port)
=> UseHealthChecks(builder, port, DefaultTimeout);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout)
{
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535");
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535.");
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
builder.ConfigureServices(services =>
{
var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}");
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port));
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port, timeout));
});
return builder;
}
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path)
=> UseHealthChecks(builder, path, DefaultTimeout);
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout)
{
Guard.ArgumentNotNull(nameof(path), path);
// REVIEW: Is there a better URL path validator somewhere?
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values");
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /");
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values.");
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'.");
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path)));
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path, timeout)));
return builder;
}
}

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

@ -7,7 +7,7 @@ using System.Data.SqlClient;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckBuilderDataExtensions
public static class HealthCheckBuilderSqlServerExtensions
{
public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString)
{
@ -33,7 +33,7 @@ namespace Microsoft.Extensions.HealthChecks
}
}
}
catch(Exception ex)
catch (Exception ex)
{
return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}");
}

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

@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.HealthChecks
{
public abstract class CachedHealthCheck
{
private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo();
private volatile int _writerCount;
public CachedHealthCheck(string name, TimeSpan cacheDuration)
{
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero.");
Name = name;
CacheDuration = cacheDuration;
}
public IHealthCheckResult CachedResult { get; internal set; }
public TimeSpan CacheDuration { get; }
public DateTimeOffset CacheExpiration { get; internal set; }
public string Name { get; }
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider);
public async ValueTask<IHealthCheckResult> RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken))
{
while (CacheExpiration <= UtcNow)
{
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
// and the waiters who aren't allowed to write will just spin wait for the new value.
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
{
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
continue;
}
try
{
var check = Resolve(serviceProvider);
CachedResult = await check.CheckAsync(cancellationToken);
}
catch (OperationCanceledException)
{
CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out");
}
catch (Exception ex)
{
CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}");
}
CacheExpiration = UtcNow + CacheDuration;
_writerCount = 0;
break;
}
return CachedResult;
}
public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck)
{
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck);
}
public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType)
{
Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType);
Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'.");
return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType);
}
class TypeOrHealthCheck_HealthCheck : CachedHealthCheck
{
private readonly IHealthCheck _healthCheck;
public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration)
=> _healthCheck = healthCheck;
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck;
}
class TypeOrHealthCheck_Type : CachedHealthCheck
{
private readonly Type _healthCheckType;
public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration)
=> _healthCheckType = healthCheckType;
protected override IHealthCheck Resolve(IServiceProvider serviceProvider)
=> (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType);
}
}
}

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

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class CachedHealthCheckExtensions
{
public static ValueTask<IHealthCheckResult> RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider)
{
Guard.ArgumentNotNull(nameof(check), check);
return check.RunAsync(serviceProvider, CancellationToken.None);
}
}
}

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

@ -15,96 +15,102 @@ namespace Microsoft.Extensions.HealthChecks
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<IHealthCheckResult> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, builder.DefaultCacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
}
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNull(nameof(builder), builder);
builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check, cacheDuration));
return builder;
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration);
}
// IHealthCheck versions of AddCheck
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check)
{
Guard.ArgumentNotNull(nameof(builder), builder);
return builder.AddCheck(checkName, check, builder.DefaultCacheDuration);
}
// Type versions of AddCheck
public static HealthCheckBuilder AddCheck<TCheck>(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck
{
Guard.ArgumentNotNull(nameof(builder), builder);
return builder.AddCheck<TCheck>(name, builder.DefaultCacheDuration);
}
}
}

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

@ -14,7 +14,7 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable<T>
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
builder.AddCheck(name, () =>
@ -23,7 +23,7 @@ namespace Microsoft.Extensions.HealthChecks
var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
return HealthCheckResult.FromStatus(
status,
$"{name}: min={minValue}, current={currentValue}",
$"min={minValue}, current={currentValue}",
new Dictionary<string, object> { { "min", minValue }, { "current", currentValue } }
);
});
@ -35,16 +35,16 @@ namespace Microsoft.Extensions.HealthChecks
where T : IComparable<T>
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc);
builder.AddCheck($"{name}", () =>
builder.AddCheck(name, () =>
{
var currentValue = currentValueFunc();
var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
return HealthCheckResult.FromStatus(
status,
$"{name}: max={maxValue}, current={currentValue}",
$"max={maxValue}, current={currentValue}",
new Dictionary<string, object> { { "max", maxValue }, { "current", currentValue } }
);
});

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

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.HealthChecks.Internal;
@ -37,73 +35,12 @@ namespace Microsoft.Extensions.HealthChecks
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrWhitespace(nameof(url), url);
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
var urlCheck = new UrlChecker(checkFunc, url);
builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync());
return builder;
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName)
=> AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => UrlChecker.DefaultUrlCheck(response));
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, CheckStatus.Warning, response => checkFunc(response));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus)
=> AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => UrlChecker.DefaultUrlCheck(response));
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, IHealthCheckResult> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
return AddUrlChecks(builder, urlItems, groupName, partialSuccessStatus, response => new ValueTask<IHealthCheckResult>(checkFunc(response)));
}
public static HealthCheckBuilder AddUrlChecks(this HealthCheckBuilder builder, IEnumerable<string> urlItems, string groupName,
CheckStatus partialSuccessStatus, Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc)
{
var urls = urlItems?.ToArray();
Guard.ArgumentNotNull(nameof(builder), builder);
Guard.ArgumentNotNullOrEmpty(nameof(urlItems), urls);
Guard.ArgumentNotNullOrWhitespace(nameof(groupName), groupName);
var urlChecker = new UrlChecker(checkFunc, urls) { PartiallyHealthyStatus = partialSuccessStatus };
builder.AddCheck($"UrlChecks({groupName})", () => urlChecker.CheckAsync());
return builder;
}
}
}

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

@ -7,7 +7,6 @@ using System.Linq;
namespace Microsoft.Extensions.HealthChecks
{
// REVIEW: Does this need to be thread safe?
/// <summary>
/// Represents a composite health check result built from several results.
/// </summary>
@ -31,17 +30,23 @@ namespace Microsoft.Extensions.HealthChecks
{
var checkStatuses = new HashSet<CheckStatus>(_results.Select(x => x.Value.CheckStatus));
if (checkStatuses.Count == 0)
{
return _initialStatus;
}
if (checkStatuses.Count == 1)
{
return checkStatuses.First();
}
if (checkStatuses.Contains(CheckStatus.Healthy))
{
return _partiallyHealthyStatus;
}
return CheckStatus.Unhealthy;
}
}
public string Description => string.Join(Environment.NewLine, _results.Select(r => r.Value.Description));
public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}"));
public IReadOnlyDictionary<string, object> Data
{
@ -58,23 +63,21 @@ namespace Microsoft.Extensions.HealthChecks
public IReadOnlyDictionary<string, IHealthCheckResult> Results => _results;
// REVIEW: Should description be required? Seems redundant for success checks.
public void Add(string name, CheckStatus status, string description)
=> Add(name, status, description, null);
public void Add(string name, CheckStatus status, string description, Dictionary<string, object> data)
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add unknown status to composite health check result");
Guard.ArgumentNotNullOrWhitespace(nameof(description), description);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result.");
Guard.ArgumentNotNullOrEmpty(nameof(description), description);
_results.Add(name, HealthCheckResult.FromStatus(status, description, data));
}
public void Add(string name, IHealthCheckResult checkResult)
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
Guard.ArgumentNotNull(nameof(checkResult), checkResult);
_results.Add(name, checkResult);

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

@ -9,68 +9,34 @@ namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheck : IHealthCheck
{
private DateTimeOffset _cacheExpiration;
private IHealthCheckResult _cachedResult;
private volatile int _writerCount;
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
{
Guard.ArgumentNotNull(nameof(check), check);
Guard.ArgumentValid(cacheDuration >= TimeSpan.Zero, nameof(cacheDuration), "Cache duration must either be zero (disabled) or a positive value");
Check = check;
CacheDuration = cacheDuration;
}
public TimeSpan CacheDuration { get; }
protected Func<CancellationToken, ValueTask<IHealthCheckResult>> Check { get; }
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
=> Check(cancellationToken);
public async ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken)
{
while (_cacheExpiration <= UtcNow)
{
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
// and the waiters who aren't allowed to write will just spin wait for the new value.
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
{
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
continue;
}
public static HealthCheck FromCheck(Func<IHealthCheckResult> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
try
{
_cachedResult = await Check(cancellationToken).ConfigureAwait(false);
_cacheExpiration = UtcNow + CacheDuration;
break;
}
finally
{
_writerCount = 0;
}
}
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
return _cachedResult;
}
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()));
public static HealthCheck FromCheck(Func<IHealthCheckResult> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)));
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check)
=> new HealthCheck(token => check());
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check()), cacheDuration);
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token)), cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(token => check(), cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration)
=> new HealthCheck(check, cacheDuration);
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check)
=> new HealthCheck(check);
}
}

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

@ -8,33 +8,128 @@ namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckBuilder
{
private readonly Dictionary<string, IHealthCheck> _checks;
private readonly Dictionary<string, CachedHealthCheck> _checksByName;
private readonly HealthCheckGroup _currentGroup;
private readonly Dictionary<string, HealthCheckGroup> _groups;
public HealthCheckBuilder()
{
_checks = new Dictionary<string, IHealthCheck>(StringComparer.OrdinalIgnoreCase);
DefaultCacheDuration = TimeSpan.FromMinutes(5);
_checksByName = new Dictionary<string, CachedHealthCheck>(StringComparer.OrdinalIgnoreCase);
_currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy);
_groups = new Dictionary<string, HealthCheckGroup>(StringComparer.OrdinalIgnoreCase)
{
[string.Empty] = _currentGroup
};
DefaultCacheDuration = TimeSpan.FromMinutes(1);
}
public IReadOnlyDictionary<string, IHealthCheck> Checks => _checks;
/// <summary>
/// This constructor should only be used when creating a grouped health check builder.
/// </summary>
public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup)
{
Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder);
Guard.ArgumentNotNull(nameof(currentGroup), currentGroup);
_checksByName = rootBuilder._checksByName;
_currentGroup = currentGroup;
_groups = rootBuilder._groups;
DefaultCacheDuration = rootBuilder.DefaultCacheDuration;
}
/// <summary>
/// Gets the registered checks, indexed by check name.
/// </summary>
public IReadOnlyDictionary<string, CachedHealthCheck> ChecksByName => _checksByName;
/// <summary>
/// Gets the current default cache duration used when registering checks.
/// </summary>
public TimeSpan DefaultCacheDuration { get; private set; }
public HealthCheckBuilder AddCheck(string name, IHealthCheck check)
{
Guard.ArgumentNotNullOrWhitespace(nameof(name), name);
Guard.ArgumentNotNull(nameof(check), check);
/// <summary>
/// Gets the registered groups, indexed by group name. The root group's name is <see cref="string.Empty"/>.
/// </summary>
public IReadOnlyDictionary<string, HealthCheckGroup> Groups => _groups;
/// <summary>
/// Registers a health check type that will later be resolved via dependency
/// injection.
/// </summary>
public HealthCheckBuilder AddCheck<TCheck>(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck
{
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck));
_checksByName.Add(checkName, namedCheck);
_currentGroup.ChecksInternal.Add(namedCheck);
return this;
}
/// <summary>
/// Registers a concrete health check to the builder.
/// </summary>
public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration)
{
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName);
Guard.ArgumentNotNull(nameof(check), check);
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered.");
var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check);
_checksByName.Add(checkName, namedCheck);
_currentGroup.ChecksInternal.Add(namedCheck);
return this;
}
/// <summary>
/// Creates a new health check group, to which you can add one or more health
/// checks. Uses <see cref="CheckStatus.Unhealthy"/> when the group is
/// partially successful.
/// </summary>
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks)
=> AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy);
/// <summary>
/// Creates a new health check group, to which you can add one or more health
/// checks.
/// </summary>
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks, CheckStatus partialSuccessStatus)
{
Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName);
Guard.ArgumentNotNull(nameof(groupChecks), groupChecks);
Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success.");
Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered.");
Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder.");
var group = new HealthCheckGroup(groupName, partialSuccessStatus);
_groups.Add(groupName, group);
var innerBuilder = new HealthCheckBuilder(this, group);
groupChecks(innerBuilder);
_checks.Add(name, check);
return this;
}
public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration)
{
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration");
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration.");
DefaultCacheDuration = duration;
return this;
}
public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus)
{
_currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus;
return this;
}
}
}

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

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckExtensions
{
public static ValueTask<IHealthCheckResult> CheckAsync(this IHealthCheck healthCheck)
{
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return healthCheck.CheckAsync(CancellationToken.None);
}
}
}

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

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckGroup
{
private CheckStatus _partialSuccessStatus;
public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus)
{
Guard.ArgumentNotNull(nameof(groupName), groupName);
GroupName = groupName;
PartiallyHealthyStatus = partialSuccessStatus;
}
public IReadOnlyList<CachedHealthCheck> Checks => ChecksInternal.AsReadOnly();
internal List<CachedHealthCheck> ChecksInternal { get; } = new List<CachedHealthCheck>();
public string GroupName { get; }
public CheckStatus PartiallyHealthyStatus
{
get => _partialSuccessStatus;
internal set
{
Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success.");
_partialSuccessStatus = value;
}
}
}
}

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.HealthChecks
{

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

@ -3,52 +3,117 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.HealthChecks
{
public class HealthCheckService : IHealthCheckService
{
public IReadOnlyDictionary<string, IHealthCheck> _checks;
private readonly HealthCheckBuilder _builder;
private readonly IReadOnlyList<HealthCheckGroup> _groups;
private readonly HealthCheckGroup _root;
private readonly IServiceProvider _serviceProvider;
private readonly IServiceScopeFactory _serviceScopeFactory;
private ILogger<HealthCheckService> _logger;
public HealthCheckService(HealthCheckBuilder builder, ILogger<HealthCheckService> logger)
public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
{
_checks = builder.Checks;
_logger = logger;
_builder = builder;
_groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList();
_root = GetGroup(string.Empty);
_serviceProvider = serviceProvider;
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var logMessage = new StringBuilder();
var result = new CompositeHealthCheckResult(partiallyHealthyStatus);
foreach (var check in _checks)
using (var scope = GetServiceScope())
{
try
var scopeServiceProvider = scope.ServiceProvider;
var groupTasks = _groups.Select(group => new { Group = group, Task = RunGroupAsync(scopeServiceProvider, group, cancellationToken) }).ToList();
var result = await RunGroupAsync(scopeServiceProvider, _root, cancellationToken).ConfigureAwait(false);
await Task.WhenAll(groupTasks.Select(x => x.Task));
foreach (var groupTask in groupTasks)
{
var healthCheckResult = await check.Value.CheckAsync().ConfigureAwait(false);
logMessage.AppendLine($"HealthCheck: {check.Key} : {healthCheckResult.CheckStatus}");
result.Add(check.Key, healthCheckResult);
}
catch (Exception ex)
{
logMessage.AppendLine($"HealthCheck: {check.Key} : Exception {ex.GetType().FullName} thrown");
result.Add(check.Key, CheckStatus.Unhealthy, $"Exception during check: {ex.GetType().FullName}");
result.Add($"Group({groupTask.Group.GroupName})", groupTask.Task.Result);
}
return result;
}
}
public IReadOnlyList<CachedHealthCheck> GetAllChecks()
=> _builder.ChecksByName.Values.ToList().AsReadOnly();
public CachedHealthCheck GetCheck(string checkName)
=> _builder.ChecksByName[checkName];
public HealthCheckGroup GetGroup(string groupName)
=> _builder.Groups[groupName];
public IReadOnlyList<HealthCheckGroup> GetGroups()
=> _builder.Groups.Values.ToList().AsReadOnly();
private IServiceScope GetServiceScope()
=> _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope();
public async ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
{
using (var scope = GetServiceScope())
{
return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Uses the provided service provider and executes the provided check.
/// </summary>
public ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken))
{
Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider);
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
return healthCheck.RunAsync(serviceProvider, cancellationToken);
}
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
/// </summary>
public async Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
{
using (var scope = GetServiceScope())
return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Uses the provided service provider and executes the checks in the given group.
/// </summary>
public async Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken))
{
var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus);
var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList();
await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task));
foreach (var checkTask in checkTasks)
{
result.Add(checkTask.Check.Name, checkTask.Task.Result);
}
if (logMessage.Length == 0)
logMessage.AppendLine("HealthCheck: No checks have been registered");
_logger.Log((result.CheckStatus == CheckStatus.Healthy ? LogLevel.Information : LogLevel.Error), 0, logMessage.ToString(), null, MessageFormatter);
return result;
}
private static string MessageFormatter(string state, Exception error) => state;
private class UnscopedServiceProvider : IServiceScope
{
public UnscopedServiceProvider(IServiceProvider serviceProvider)
=> ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public void Dispose() { }
}
}
}

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

@ -2,20 +2,28 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.Extensions.HealthChecks;
namespace Microsoft.Extensions.DependencyInjection
{
public static class HealthCheckServiceCollectionExtensions
{
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checkupAction)
private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService);
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checks)
{
var checkupBuilder = new HealthCheckBuilder();
Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once.");
checkupAction.Invoke(checkupBuilder);
var builder = new HealthCheckBuilder();
services.AddSingleton(checkupBuilder);
services.AddSingleton<IHealthCheckService, HealthCheckService>();
services.AddSingleton<IHealthCheckService, HealthCheckService>(serviceProvider =>
{
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
return new HealthCheckService(builder, serviceProvider, serviceScopeFactory);
});
checks(builder);
return services;
}
}

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

@ -1,38 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.HealthChecks
{
public static class HealthCheckServiceExtensions
{
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(CheckStatus.Unhealthy, CancellationToken.None);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(partiallyHealthyStatus, CancellationToken.None);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CancellationToken cancellationToken)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(CheckStatus.Unhealthy, cancellationToken);
}
public static Task<CompositeHealthCheckResult> CheckHealthAsync(this IHealthCheckService service, CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken)
{
Guard.ArgumentNotNull(nameof(service), service);
return service.CheckHealthAsync(partiallyHealthyStatus, cancellationToken);
}
}
}

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

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
@ -9,8 +8,6 @@ namespace Microsoft.Extensions.HealthChecks
{
public interface IHealthCheck
{
TimeSpan CacheDuration { get; }
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken);
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

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

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -8,6 +10,49 @@ namespace Microsoft.Extensions.HealthChecks
{
public interface IHealthCheckService
{
Task<CompositeHealthCheckResult> CheckHealthAsync(CheckStatus partiallyHealthyStatus, CancellationToken cancellationToken);
/// <summary>
/// Runs all registered health checks.
/// </summary>
Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Gets all registered health checks as a flat list.
/// </summary>
IReadOnlyList<CachedHealthCheck> GetAllChecks();
/// <summary>
/// Gets a health check by name.
/// </summary>
CachedHealthCheck GetCheck(string checkName);
/// <summary>
/// Gets all health checks in a group.
/// </summary>
HealthCheckGroup GetGroup(string groupName);
/// <summary>
/// Gets all the health check groups.
/// </summary>
IReadOnlyList<HealthCheckGroup> GetGroups();
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the provided check.
/// </summary>
ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Uses the provided service provider and executes the provided check.
/// </summary>
ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Creates a new resolution scope from the default service provider and executes the checks in the given group.
/// </summary>
Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Uses the provided service provider and executes the checks in the given group.
/// </summary>
Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken));
}
}

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

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
@ -13,55 +12,33 @@ namespace Microsoft.Extensions.HealthChecks.Internal
public class UrlChecker
{
private readonly Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> _checkFunc;
private readonly string[] _urls;
private readonly string _url;
// REVIEW: Cache timeout here?
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, params string[] urls)
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, string url)
{
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc);
Guard.ArgumentNotNullOrEmpty(nameof(urls), urls);
Guard.ArgumentNotNullOrEmpty(nameof(url), url);
_checkFunc = checkFunc;
_urls = urls;
_url = url;
}
public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning;
public Task<IHealthCheckResult> CheckAsync()
=> _urls.Length == 1 ? CheckSingleAsync() : CheckMultiAsync();
public async Task<IHealthCheckResult> CheckSingleAsync()
public async Task<IHealthCheckResult> CheckAsync()
{
var httpClient = CreateHttpClient();
var result = default(IHealthCheckResult);
await CheckUrlAsync(httpClient, _urls[0], (_, checkResult) => result = checkResult).ConfigureAwait(false);
return result;
}
public async Task<IHealthCheckResult> CheckMultiAsync()
{
var composite = new CompositeHealthCheckResult(PartiallyHealthyStatus);
var httpClient = CreateHttpClient();
// REVIEW: Should these be done in parallel?
foreach (var url in _urls)
await CheckUrlAsync(httpClient, url, (name, checkResult) => composite.Add(name, checkResult)).ConfigureAwait(false);
return composite;
}
private async Task CheckUrlAsync(HttpClient httpClient, string url, Action<string, IHealthCheckResult> adder)
{
var name = $"UrlCheck({url})";
try
using (var httpClient = CreateHttpClient())
{
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
var result = await _checkFunc(response);
adder(name, result);
}
catch (Exception ex)
{
adder(name, HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}"));
try
{
var response = await httpClient.GetAsync(_url).ConfigureAwait(false);
return await _checkFunc(response);
}
catch (Exception ex)
{
var data = new Dictionary<string, object> { { "url", _url } };
return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data);
}
}
}
@ -74,8 +51,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
public static async ValueTask<IHealthCheckResult> DefaultUrlCheck(HttpResponseMessage response)
{
// REVIEW: Should this be an explicit 200 check, or just an "is success" check?
var status = response.StatusCode == HttpStatusCode.OK ? CheckStatus.Healthy : CheckStatus.Unhealthy;
var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy;
var data = new Dictionary<string, object>
{
{ "url", response.RequestMessage.RequestUri.ToString() },
@ -83,7 +59,7 @@ namespace Microsoft.Extensions.HealthChecks.Internal
{ "reason", response.ReasonPhrase },
{ "body", await response.Content?.ReadAsStringAsync() }
};
return HealthCheckResult.FromStatus(status, $"UrlCheck({response.RequestMessage.RequestUri}): status code {response.StatusCode} ({(int)response.StatusCode})", data);
return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data);
}
protected virtual HttpClient GetHttpClient()

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

@ -9,37 +9,49 @@ static class Guard
public static void ArgumentNotNull(string argumentName, object value)
{
if (value == null)
{
throw new ArgumentNullException(argumentName);
}
}
public static void ArgumentNotNullOrEmpty<T>(string argumentName, string value)
public static void ArgumentNotNullOrEmpty(string argumentName, string value)
{
if (value == null)
{
throw new ArgumentNullException(argumentName);
}
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Value cannot be an empty string", argumentName);
{
throw new ArgumentException("Value cannot be an empty string.", argumentName);
}
}
// Use IReadOnlyCollection<T> instead of IEnumerable<T> to discourage double enumeration
public static void ArgumentNotNullOrEmpty<T>(string argumentName, IReadOnlyCollection<T> items)
{
if (items == null)
{
throw new ArgumentNullException(argumentName);
}
if (items.Count == 0)
throw new ArgumentException("Collection must contain at least one item", argumentName);
}
public static void ArgumentNotNullOrWhitespace(string argumentName, string value)
{
if (value == null)
throw new ArgumentNullException(argumentName);
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Value must contain a non-whitespace value", argumentName);
{
throw new ArgumentException("Collection must contain at least one item.", argumentName);
}
}
public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage)
{
if (!valid)
{
throw new ArgumentException(exceptionMessage, argumentName);
}
}
public static void OperationValid(bool valid, string exceptionMessage)
{
if (!valid)
{
throw new InvalidOperationException(exceptionMessage);
}
}
}

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

@ -31,7 +31,7 @@ namespace eShopOnContainers.Core.Behaviors
BindableProperty.CreateAttached("EventArgsConverterParameter", typeof(object), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
private Delegate _handler;
protected Delegate _handler;
private EventInfo _eventInfo;
public string EventName

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

@ -17,7 +17,6 @@ namespace eShopOnContainers.Core.Services.Basket
public async Task<CustomerBasket> GetBasketAsync(string guidUser, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
builder.Path = guidUser;
@ -30,18 +29,17 @@ namespace eShopOnContainers.Core.Services.Basket
ServicesHelper.FixBasketItemPictureUri(basket?.Items);
return basket;
}
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
string uri = builder.ToString();
string uri = builder.ToString();
var result = await _requestProvider.PostAsync(uri, customerBasket, token);
var result = await _requestProvider.PostAsync(uri, customerBasket, token);
return result;
return result;
}
public async Task ClearBasketAsync(string guidUser, string token)

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

@ -61,12 +61,5 @@ namespace eShopOnContainers.Core.Services.Catalog
return MockCatalogType;
}
public async Task<CatalogItem> GetCatalogItemAsync(string id)
{
await Task.Delay(500);
return MockCatalog.FirstOrDefault(c => c.Id == id);
}
}
}

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

@ -20,7 +20,6 @@ namespace eShopOnContainers.Core.Services.Catalog
public async Task<ObservableCollection<CatalogItem>> FilterAsync(int catalogBrandId, int catalogTypeId)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = string.Format("api/v1/catalog/items/type/{0}/brand/{1}", catalogTypeId, catalogBrandId);
@ -34,12 +33,10 @@ namespace eShopOnContainers.Core.Services.Catalog
return catalog?.Data.ToObservableCollection();
else
return new ObservableCollection<CatalogItem>();
}
public async Task<ObservableCollection<CatalogItem>> GetCatalogAsync()
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = "api/v1/catalog/items";
@ -57,36 +54,27 @@ namespace eShopOnContainers.Core.Services.Catalog
}
else
return new ObservableCollection<CatalogItem>();
}
public Task<CatalogItem> GetCatalogItemAsync(string id)
{
throw new NotImplementedException();
}
public async Task<ObservableCollection<CatalogBrand>> GetCatalogBrandAsync()
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = "api/v1/catalog/catalogbrands";
builder.Path = "api/v1/catalog/catalogbrands";
string uri = builder.ToString();
string uri = builder.ToString();
IEnumerable<CatalogBrand> brands =
await _requestProvider.GetAsync<IEnumerable<CatalogBrand>>(uri);
if (brands != null)
return brands?.ToObservableCollection();
else
return new ObservableCollection<CatalogBrand>();
IEnumerable<CatalogBrand> brands =
await _requestProvider.GetAsync<IEnumerable<CatalogBrand>>(uri);
if (brands != null)
return brands?.ToObservableCollection();
else
return new ObservableCollection<CatalogBrand>();
}
public async Task<ObservableCollection<CatalogType>> GetCatalogTypeAsync()
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = "api/v1/catalog/catalogtypes";
@ -100,7 +88,6 @@ namespace eShopOnContainers.Core.Services.Catalog
return types.ToObservableCollection();
else
return new ObservableCollection<CatalogType>();
}
}
}

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

@ -10,6 +10,5 @@ namespace eShopOnContainers.Core.Services.Catalog
Task<ObservableCollection<CatalogItem>> FilterAsync(int catalogBrandId, int catalogTypeId);
Task<ObservableCollection<CatalogType>> GetCatalogTypeAsync();
Task<ObservableCollection<CatalogItem>> GetCatalogAsync();
Task<CatalogItem> GetCatalogItemAsync(string id);
}
}

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

@ -5,8 +5,6 @@ using Xamarin.Forms;
using eShopOnContainers.Core.Models.Catalog;
using eShopOnContainers.Core.Services.Catalog;
using System.Windows.Input;
using eShopOnContainers.Core.Services.Basket;
using eShopOnContainers.Core.Services.User;
namespace eShopOnContainers.Core.ViewModels
{

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

@ -14,7 +14,7 @@ namespace eShopOnContainers.TestRunner.Droid
AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly);
// or in any reference assemblies getting the Assembly from any type/class
AddTestAssembly(typeof(UnitTests.DummyTests).Assembly);
AddTestAssembly(typeof(UnitTests.CatalogViewModelTests).Assembly);
base.OnCreate(bundle);
}

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

@ -20,6 +20,7 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<AndroidTlsProvider></AndroidTlsProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>

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

@ -12,7 +12,7 @@ namespace eShopOnContainers.TestRunner.Windows
{
// Otherwise you need to ensure that the test assemblies will
// become part of the app bundle
AddTestAssembly(typeof(UnitTests.DummyTests).GetTypeInfo().Assembly);
AddTestAssembly(typeof(UnitTests.CatalogViewModelTests).GetTypeInfo().Assembly);
}
}
}

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

@ -17,7 +17,7 @@ namespace eShopOnContainers.TestRunner.iOS
// Otherwise you need to ensure that the test assemblies will
// become part of the app bundle
AddTestAssembly(typeof(UnitTests.DummyTests).Assembly);
AddTestAssembly(typeof(UnitTests.CatalogViewModelTests).Assembly);
return base.FinishedLaunching(app, options);
}

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

@ -155,6 +155,12 @@
<HintPath>..\..\..\..\packages\xunit.runner.utility.2.2.0-beta4-build3444\lib\netstandard1.1\xunit.runner.utility.dotnet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Plugin.Settings.Abstractions">
<HintPath>..\..\..\..\packages\Xam.Plugins.Settings.2.6.0.12-beta\lib\Xamarin.iOS10\Plugin.Settings.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Plugin.Settings">
<HintPath>..\..\..\..\packages\Xam.Plugins.Settings.2.6.0.12-beta\lib\Xamarin.iOS10\Plugin.Settings.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="AppDelegate.cs.txt" />

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

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonServiceLocator" version="1.3" targetFramework="xamarinios10" />
<package id="Xam.Plugins.Settings" version="2.6.0.12-beta" targetFramework="xamarinios10" />
<package id="Xamarin.Forms" version="2.3.3.175" targetFramework="xamarinios10" />
<package id="xunit" version="2.2.0-beta4-build3444" targetFramework="xamarinios10" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="xamarinios10" />

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

@ -0,0 +1,120 @@
using Xunit;
using Xamarin.Forms;
using System;
using System.Globalization;
namespace eShopOnContainers.UnitTests
{
public class EventToCommandBehaviorTests
{
[Fact]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
[Fact]
public void CommandExecutedWhenEventFiresText()
{
bool executedCommand = false;
var behavior = new MockEventToCommandBehavior
{
EventName = "ItemTapped",
Command = new Command(() =>
{
executedCommand = true;
})
};
var listView = new ListView();
listView.Behaviors.Add(behavior);
behavior.RaiseEvent(listView, null);
Assert.True(executedCommand);
}
[Fact]
public void CommandCanExecuteTest()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "ItemTapped",
Command = new Command(() => Assert.True(false), () => false)
};
var listView = new ListView();
listView.Behaviors.Add(behavior);
behavior.RaiseEvent(listView, null);
}
[Fact]
public void CommandCanExecuteWithParameterShouldNotExecuteTest()
{
bool shouldExecute = false;
var behavior = new MockEventToCommandBehavior
{
EventName = "ItemTapped",
CommandParameter = shouldExecute,
Command = new Command<string>(o => Assert.True(false), o => o.Equals(true))
};
var listView = new ListView();
listView.Behaviors.Add(behavior);
behavior.RaiseEvent(listView, null);
}
[Fact]
public void CommandWithConverterTest()
{
const string item = "ItemProperty";
bool executedCommand = false;
var behavior = new MockEventToCommandBehavior
{
EventName = "ItemTapped",
EventArgsConverter = new ItemTappedEventArgsConverter(false),
Command = new Command<string>(o =>
{
executedCommand = true;
Assert.NotNull(o);
Assert.Equal(item, o);
})
};
var listView = new ListView();
listView.Behaviors.Add(behavior);
behavior.RaiseEvent(listView, new ItemTappedEventArgs(listView, item));
Assert.True(executedCommand);
}
private class ItemTappedEventArgsConverter : IValueConverter
{
private readonly bool _returnParameter;
public bool HasConverted { get; private set; }
public ItemTappedEventArgsConverter(bool returnParameter)
{
_returnParameter = returnParameter;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
HasConverted = true;
return _returnParameter ? parameter : (value as ItemTappedEventArgs)?.Item;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
}

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

@ -1,27 +0,0 @@
using System;
using System.Threading.Tasks;
using Xunit;
namespace eShopOnContainers.UnitTests
{
public class DummyTests
{
[Fact]
public void ThisShouldPass_Sync()
{
Assert.True(true);
}
[Fact]
public async Task ThisShouldPass_Async()
{
await Task.Run(() => { Assert.True(true); });
}
[Fact]
public async Task ThisShouldFail_Async()
{
await Task.Run(() => { throw new Exception("Oops!"); });
}
}
}

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

@ -0,0 +1,12 @@
using eShopOnContainers.Core.Behaviors;
namespace eShopOnContainers.UnitTests
{
public class MockEventToCommandBehavior : EventToCommandBehavior
{
public void RaiseEvent(params object[] args)
{
_handler.DynamicInvoke(args);
}
}
}

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

@ -0,0 +1,53 @@
using eShopOnContainers.Core.ViewModels.Base;
using eShopOnContainers.Core.Validations;
namespace eShopOnContainers.UnitTests
{
public class MockViewModel : ViewModelBase
{
private ValidatableObject<string> _forename;
private ValidatableObject<string> _surname;
public ValidatableObject<string> Forename
{
get
{
return _forename;
}
set
{
_forename = value;
RaisePropertyChanged(() => Forename);
}
}
public ValidatableObject<string> Surname
{
get
{
return _surname;
}
set
{
_surname = value;
RaisePropertyChanged(() => Surname);
}
}
public MockViewModel()
{
_forename = new ValidatableObject<string>();
_surname = new ValidatableObject<string>();
_forename.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Forename is required." });
_surname.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Surname name is required." });
}
public bool Validate()
{
bool isValidForename = _forename.Validate();
bool isValidSurname = _surname.Validate();
return isValidForename && isValidSurname;
}
}
}

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

@ -1,6 +1,5 @@
using eShopOnContainers.Core;
using eShopOnContainers.Core.Services.Order;
using eShopOnContainers.Core.Services.RequestProvider;
using System.Threading.Tasks;
using Xunit;
@ -8,6 +7,15 @@ namespace eShopOnContainers.UnitTests
{
public class OrdersServiceTests
{
[Fact]
public async Task GetFakeOrderTest()
{
var ordersMockService = new OrderMockService();
var order = await ordersMockService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
Assert.NotNull(order);
}
[Fact]
public async Task GetFakeOrdersTest()
{
@ -16,15 +24,5 @@ namespace eShopOnContainers.UnitTests
Assert.NotEqual(0, result.Count);
}
[Fact]
public async Task GetOrdersTest()
{
var requestProvider = new RequestProvider();
var ordersService = new OrderService(requestProvider);
var result = await ordersService.GetOrdersAsync(GlobalSetting.Instance.AuthToken);
Assert.NotEqual(0, result.Count);
}
}
}

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

@ -0,0 +1,223 @@
using Xunit;
using eShopOnContainers.Core.ViewModels;
using eShopOnContainers.Core.ViewModels.Base;
using eShopOnContainers.Core.Services.Catalog;
using eShopOnContainers.Core.Models.Catalog;
using System.Threading.Tasks;
using System.Linq;
namespace eShopOnContainers.UnitTests
{
public class CatalogViewModelTests
{
public CatalogViewModelTests()
{
ViewModelLocator.RegisterDependencies(true);
}
[Fact]
public void AddCatalogItemCommandIsNotNullTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.NotNull(catalogViewModel.AddCatalogItemCommand);
}
[Fact]
public void FilterCommandIsNotNullTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.NotNull(catalogViewModel.FilterCommand);
}
[Fact]
public void ClearFilterCommandIsNotNullTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.NotNull(catalogViewModel.ClearFilterCommand);
}
[Fact]
public void ProductsPropertyIsNullWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.Null(catalogViewModel.Products);
}
[Fact]
public void BrandsPropertyuIsNullWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.Null(catalogViewModel.Brands);
}
[Fact]
public void BrandPropertyIsNullWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.Null(catalogViewModel.Brand);
}
[Fact]
public void TypesPropertyIsNullWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.Null(catalogViewModel.Types);
}
[Fact]
public void TypePropertyIsNullWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.Null(catalogViewModel.Type);
}
[Fact]
public void IsFilterPropertyIsFalseWhenViewModelInstantiatedTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Assert.False(catalogViewModel.IsFilter);
}
[Fact]
public async Task ProductsPropertyIsNotNullAfterViewModelInitializationTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
await catalogViewModel.InitializeAsync(null);
Assert.NotNull(catalogViewModel.Products);
}
[Fact]
public async Task BrandsPropertyIsNotNullAfterViewModelInitializationTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
await catalogViewModel.InitializeAsync(null);
Assert.NotNull(catalogViewModel.Brands);
}
[Fact]
public async Task TypesPropertyIsNotNullAfterViewModelInitializationTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
await catalogViewModel.InitializeAsync(null);
Assert.NotNull(catalogViewModel.Types);
}
[Fact]
public async Task SettingProductsPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
catalogViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Products"))
invoked = true;
};
await catalogViewModel.InitializeAsync(null);
Assert.True(invoked);
}
[Fact]
public async Task SettingBrandsPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
catalogViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Brands"))
invoked = true;
};
await catalogViewModel.InitializeAsync(null);
Assert.True(invoked);
}
[Fact]
public async Task SettingTypesPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
catalogViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Types"))
invoked = true;
};
await catalogViewModel.InitializeAsync(null);
Assert.True(invoked);
}
[Fact]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
bool messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.True(messageReceived);
}
[Fact]
public async Task FilterCommandSendsFilterMessageTest()
{
bool messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
await catalogViewModel.InitializeAsync(null);
catalogViewModel.Brand = catalogViewModel.Brands.FirstOrDefault();
catalogViewModel.Type = catalogViewModel.Types.FirstOrDefault();
Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel>(this, MessageKeys.Filter, (sender) =>
{
messageReceived = true;
});
catalogViewModel.FilterCommand.Execute(null);
Assert.True(messageReceived);
}
[Fact]
public async Task ClearFilterCommandResetsPropertiesTest()
{
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
await catalogViewModel.InitializeAsync(null);
catalogViewModel.ClearFilterCommand.Execute(null);
Assert.Null(catalogViewModel.Brand);
Assert.Null(catalogViewModel.Type);
Assert.NotNull(catalogViewModel.Products);
}
}
}

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

@ -0,0 +1,54 @@
using Xunit;
using eShopOnContainers.Core.ViewModels;
using eShopOnContainers.Core.ViewModels.Base;
using eShopOnContainers.Core.Models.Navigation;
using System.Threading.Tasks;
namespace eShopOnContainers.UnitTests
{
public class MainViewModelTests
{
public MainViewModelTests()
{
ViewModelLocator.RegisterDependencies(true);
}
[Fact]
public void SettingsCommandIsNotNullWhenViewModelInstantiatedTest()
{
var mainViewModel = new MainViewModel();
Assert.NotNull(mainViewModel.SettingsCommand);
}
[Fact]
public async Task ViewModelInitializationSendsChangeTabMessageTest()
{
bool messageReceived = false;
var mainViewModel = new MainViewModel();
var tabParam = new TabParameter { TabIndex = 2 };
Xamarin.Forms.MessagingCenter.Subscribe<MainViewModel, int>(this, MessageKeys.ChangeTab, (sender, arg) =>
{
messageReceived = true;
});
await mainViewModel.InitializeAsync(tabParam);
Assert.True(messageReceived);
}
[Fact]
public void IsBusyPropertyIsFalseWhenViewModelInstantiatedTest()
{
var mainViewModel = new MainViewModel();
Assert.False(mainViewModel.IsBusy);
}
[Fact]
public async Task IsBusyPropertyIsTrueAfterViewModelInitializationTest()
{
var mainViewModel = new MainViewModel();
await mainViewModel.InitializeAsync(null);
Assert.True(mainViewModel.IsBusy);
}
}
}

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

@ -0,0 +1,113 @@
using Xunit;
using eShopOnContainers.Core.ViewModels.Base;
namespace eShopOnContainers.UnitTests
{
public class MockViewModelTests
{
public MockViewModelTests()
{
ViewModelLocator.RegisterDependencies(true);
}
[Fact]
public void CheckValidationFailsWhenPropertiesAreEmptyTest()
{
var mockViewModel = new MockViewModel();
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.Null(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Surname.Value);
Assert.False(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Surname.IsValid);
Assert.NotEmpty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Surname.Errors);
}
[Fact]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.NotNull(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Surname.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Surname.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Surname.Errors);
}
[Fact]
public void CheckValidationPassesWhenOnlySurnameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Surname.Value = "Smith";
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.Null(mockViewModel.Forename.Value);
Assert.NotNull(mockViewModel.Surname.Value);
Assert.False(mockViewModel.Forename.IsValid);
Assert.True(mockViewModel.Surname.IsValid);
Assert.NotEmpty(mockViewModel.Forename.Errors);
Assert.Empty(mockViewModel.Surname.Errors);
}
[Fact]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
bool isValid = mockViewModel.Validate();
Assert.True(isValid);
Assert.NotNull(mockViewModel.Forename.Value);
Assert.NotNull(mockViewModel.Surname.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.True(mockViewModel.Surname.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.Empty(mockViewModel.Surname.Errors);
}
[Fact]
public void SettingForenamePropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var mockViewModel = new MockViewModel();
mockViewModel.Forename.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Value"))
invoked = true;
};
mockViewModel.Forename.Value = "John";
Assert.True(invoked);
}
[Fact]
public void SettingSurnamePropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var mockViewModel = new MockViewModel();
mockViewModel.Surname.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Value"))
invoked = true;
};
mockViewModel.Surname.Value = "Smith";
Assert.True(invoked);
}
}
}

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

@ -0,0 +1,55 @@
using Xunit;
using eShopOnContainers.Core;
using eShopOnContainers.Core.ViewModels;
using eShopOnContainers.Core.ViewModels.Base;
using eShopOnContainers.Core.Services.Order;
using System.Threading.Tasks;
namespace eShopOnContainers.UnitTests
{
public class OrderViewModelTests
{
public OrderViewModelTests()
{
ViewModelLocator.RegisterDependencies(true);
}
[Fact]
public void OrderPropertyIsNullWhenViewModelInstantiatedTest()
{
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
Assert.Null(orderViewModel.Order);
}
[Fact]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.NotNull(orderViewModel.Order);
}
[Fact]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.True(invoked);
}
}
}

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

@ -34,11 +34,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="BasketServiceTests.cs" />
<Compile Include="CatalogServiceTests.cs" />
<Compile Include="DummyTests.cs" />
<Compile Include="OrdersServiceTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Mocks\MockEventToCommandBehavior.cs" />
<Compile Include="Services\BasketServiceTests.cs" />
<Compile Include="Services\CatalogServiceTests.cs" />
<Compile Include="ViewModels\CatalogViewModelTests.cs" />
<Compile Include="ViewModels\MainViewModelTests.cs" />
<Compile Include="ViewModels\OrderViewModelTests.cs" />
<Compile Include="Services\OrdersServiceTests.cs" />
<Compile Include="Behaviors\EventToCommandBehaviorTests.cs" />
<Compile Include="Mocks\MockViewModel.cs" />
<Compile Include="ViewModels\MockViewModelTests.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
@ -57,6 +63,15 @@
<HintPath>..\..\..\..\packages\xunit.extensibility.execution.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.execution.dotnet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Core">
<HintPath>..\..\..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Platform">
<HintPath>..\..\..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Xaml">
<HintPath>..\..\..\..\packages\Xamarin.Forms.2.3.4.231\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -68,6 +83,12 @@
<Name>eShopOnContainers.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Mocks\" />
<Folder Include="Services\" />
<Folder Include="ViewModels\" />
<Folder Include="Behaviors\" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
@ -76,4 +97,5 @@
<Target Name="AfterBuild">
</Target>
-->
<Import Project="..\..\..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\..\..\packages\Xamarin.Forms.2.3.4.231\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
</Project>

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

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="portable46-net451+win81" />
<package id="xunit.assert" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" />
<package id="xunit.core" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" />
<package id="xunit.extensibility.core" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" />
<package id="xunit.extensibility.execution" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" />
<package id="xunit.runner.console" version="2.2.0-beta4-build3444" targetFramework="portable46-net451+win81" developmentDependency="true" />
<package id="Xamarin.Forms" version="2.3.4.231" targetFramework="portable46-net451+win81" />
<package id="xunit" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="portable-net451+win81" requireReinstallation="True" />
<package id="xunit.assert" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" requireReinstallation="True" />
<package id="xunit.core" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" requireReinstallation="True" />
<package id="xunit.extensibility.core" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" requireReinstallation="True" />
<package id="xunit.extensibility.execution" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" requireReinstallation="True" />
<package id="xunit.runner.console" version="2.2.0-beta4-build3444" targetFramework="portable-net451+win81" developmentDependency="true" />
</packages>

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

@ -43,7 +43,6 @@
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>

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

@ -4,11 +4,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Basket.API.Infrastructure.Filters
{
@ -43,12 +39,12 @@ namespace Basket.API.Infrastructure.Filters
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error ocurr.Try it again." }
Messages = new[] { "An error occurred. Try it again." }
};
if (env.IsDevelopment())
{
json.DeveloperMeesage = context.Exception;
json.DeveloperMessage = context.Exception;
}
context.Result = new InternalServerErrorObjectResult(json);
@ -61,7 +57,7 @@ namespace Basket.API.Infrastructure.Filters
{
public string[] Messages { get; set; }
public object DeveloperMeesage { get; set; }
public object DeveloperMessage { get; set; }
}
}
}

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

@ -111,4 +111,3 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
}
}

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

@ -3,6 +3,7 @@ using Basket.API.IntegrationEvents.EventHandling;
using Basket.API.IntegrationEvents.Events;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
@ -19,6 +20,7 @@ using StackExchange.Redis;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System;
namespace Microsoft.eShopOnContainers.Services.Basket.API
{
@ -68,20 +70,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options =>
@ -108,9 +108,16 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
});
services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
RegisterServiceBus(services);
}
private void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<OrderStartedIntegrationEventHandler>();
}
@ -155,11 +162,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
var orderStartedHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices
.GetRequiredService<IEventBus>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<ProductPriceChangedIntegrationEventHandler>());
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<OrderStartedIntegrationEventHandler>());
}
}
}

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

@ -59,7 +59,7 @@
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
</ItemGroup>

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

@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
@ -103,18 +104,19 @@
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<CatalogSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
}

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

@ -63,7 +63,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>

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

@ -61,20 +61,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
[DataMember]
public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
public void AddOrderItem(OrderItemDTO item)
{
_orderItems.Add(item);
}
public CreateOrderCommand()
{
_orderItems = new List<OrderItemDTO>();
}
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
public CreateOrderCommand(List<OrderItemDTO> orderItems, string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration,
string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this()
{
_orderItems = orderItems;
City = city;
Street = street;
State = state;

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

@ -30,25 +30,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{
bool result = false;
bool commandResult = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(command, guid);
result = await _mediator.SendAsync(requestCreateOrder);
commandResult = await _mediator.SendAsync(requestCreateOrder);
}
else
{
// If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients
// that aren't still updated. When all clients were updated this could be removed.
result = await _mediator.SendAsync(command);
commandResult = await _mediator.SendAsync(command);
}
if (result)
{
return Ok();
}
return commandResult ? (IActionResult)Ok() : (IActionResult)BadRequest();
return BadRequest();
}
[Route("{orderId:int}")]

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

@ -27,7 +27,7 @@
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\Ordering.Domain\Ordering.Domain.csproj" />
<ProjectReference Include="..\Ordering.Infrastructure\Ordering.Infrastructure.csproj" />

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

@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
@ -107,18 +108,19 @@
var serviceProvider = services.BuildServiceProvider();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"]
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddOptions();

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

@ -50,6 +50,9 @@
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity item = (Entity)obj;
if (item.IsTransient() || this.IsTransient())

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

@ -252,7 +252,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
// After executing this line all the changes (from the Command Handler and Domain Event Handlers)
// performed thought the DbContext will be commited
// performed throught the DbContext will be commited
var result = await base.SaveChangesAsync();
return true;

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

@ -59,7 +59,6 @@
<ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.Data\Microsoft.Extensions.HealthChecks.Data.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
</ItemGroup>

2
src/Web/WebMVC/wwwroot/css/site.min.css поставляемый

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

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

@ -0,0 +1,58 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "WebSPA"
},
"apps": [
{
"root": "Client",
"outDir": "wwwroot",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"globals.scss",
"../node_modules/bootstrap/scss/bootstrap.scss"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "Client/tsconfig.app.json"
},
{
"project": "Client/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}

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