@ -30,7 +38,7 @@ This tutorial is organized as the following parts:
## Download the Source Code
After logging in to the ABP website, you can download the source code from [here](https://abp.io/api/download/samples/cloud-crm-mvc-ef).
After logging in to the ABP website, you can download the source code from {{if UI == "MVC"}} [here](https://abp.io/api/download/samples/cloud-crm-mvc-ef) {{else if UI == "NG"}} [here](https://abp.io/api/download/samples/cloud-crm-ng-ef) {{else}} [here](https://abp.io/api/download/samples/cloud-crm-blazor-ef) {{end}}.
Follow the *[Get Started](../../get-started/microservice.md)* guide to create a new layered web application with the following configurations:
* **Solution name**: `CloudCrm`
{{if DB == "EF"}}
* **Database Provider**: Entity Framework Core
* **Database Management System**: SQL Server
{{end}}
{{if DB == "Mongo"}}
* **Database Provider**: MongoDB
{{end}}
{{if UI == "MVC"}}
* **UI Framework**: MVC / Razor Pages
{{end}}
{{if UI == "Blazor"}}
* **UI Framework**: Blazor WebAssembly
{{end}}
{{if UI == "BlazorServer"}}
* **UI Framework**: Blazor Server
{{end}}
{{if UI == "BlazorWebApp"}}
* **UI Framework**: Blazor Web App
{{end}}
{{if UI == "NG"}}
* **UI Framework**: Angular
{{end}}
* **Mobile framework**: None
* **Public website**: Selected
You can select the other options based on your preference.
> **Please complete the *[Get Started](../../get-started/microservice.md)* guide and run the applications before going further.** You can skip the sections after the *Running the Solution* section, if you don't prefer to complete all.
> **Please complete the *[Get Started](../../get-started/layered-web-application.md)* guide and run the web application before going further.** You can skip the sections after the *Running the Solution* section, if you don't prefer to complete all.
The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*:
> ABP Studio will perform a few additional steps after creating your solution. **Please wait until all the background tasks are completed** before going further.
@ -37,4 +75,4 @@ You can see the *[Microservice Solution Template](../../solution-templates/micro
## Summary
In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md).
In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md).
This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `CatalogService`:
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial.
Select *Entity Framework Core* option and proceed the *Next* step.
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button.
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial.
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button.
## Exploring the New Catalog Microservice
In this section, we will investigate the new microservice in overall.
@ -62,7 +91,7 @@ In this section, we will investigate the new microservice in overall.
The new microservice is added under the `services` folder in the `CloudCrm` ABP Studio solution:
The new microservice has its own separate .NET solution that includes three packages (.NET projects):
@ -74,11 +103,11 @@ The new microservice has its own separate .NET solution that includes three pack
You can open the new microservice in your favorite IDE for development. As a shortcut, you can right-click it in ABP Studio, select the *Open with* -> *Visual Studio* command for example:
The `CloudCrm` microservice solution contains more than one .NET solution. Typically, each ABP Studio module represents a separate .NET solution (see the [concepts](../../studio/concepts.md) document). ABP Suite works on a single .NET solution to generate code, so we should select a module here.
Select the `CloudCrm.CatalogService` module and click the *OK* button. It will open ABP Suite as shown below:
@ -71,7 +79,7 @@ That's all. You can click the *Save and generate* button to start the code gener
ABP Suite will generate the necessary code for you. It will take some time to complete the process. After the process is completed, you will see a success message, click the *OK* button.
We can now start the `CloudCrm.CatalogService` application by clicking the *Start* button (or alternatively, directly clicking the *run* icon) in the *Solution Runner* panel.
@ -79,39 +87,65 @@ We can now start the `CloudCrm.CatalogService` application by clicking the *Star
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.CatalogService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Products* controller in the Swagger UI.
{{if UI == "NG"}}
### Generating the UI Proxy
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
ABP Suite automatically generates the UI proxy for the `Angular` project. If you want to create manually, run this command under the `Angular` project folder:
```bash
abp generate-proxy -t ng -m catalog -u http://localhost:44384 --target catalog-service
For more information, please refer to the [Service Proxies](https://abp.io/docs/latest/framework/ui/angular/service-proxies) documentation.
{{end}}
It will open the *Generate C# Proxies* window. Select the `CloudCrm.CatalogService` application, and it will automatically populate the *URL* field. Select the *catalog* module, set the service type to *application*, and check the *Without contracts* checkbox, as the `CloudCrm.Web` project already depends on the `CloudCrm.CatalogService.Contracts` package:
> To be able to select the *Application*, you must *Start* the related application beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md) as explained in the previous parts.
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the UI project. Since we are using `BlazorWebApp`, we will generate client proxies in both the `CloudCrm.Blazor` and `CloudCrm.Blazor.Client` projects, as pages are sometimes rendered on the server side and sometimes on the client side.
Lastly, we need to configure the use of a static HTTP client for the `CatalogService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method:
This will open the Generate C# Proxies window. Select the `CloudCrm.CatalogService` application this will automatically fill in the *URL* field. Next, choose the catalog module, set the service type to *Application*, and check the *Without contracts* option, since the `CloudCrm.Blazor` project already references the `CloudCrm.CatalogService.Contracts` package.
This will open the Generate C# Proxies window. Select the `CloudCrm.CatalogService` application this will automatically fill in the *URL* field. Next, choose the catalog module, set the service type to *Application*, and check the *Without contracts* option, since the `CloudCrm.Blazor.Client` project already references the `CloudCrm.CatalogService.Contracts` package.
{{else}}
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the UI project. Right-click the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "BlazorServer"}} `CloudCrm.Blazor` {{else if UI == "Blazor"}} `CloudCrm.Blazor.Client` {{end}} [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
It will open the *Generate C# Proxies* window. Select the `CloudCrm.CatalogService` application, and it will automatically populate the *URL* field. Select the *catalog* module, set the service type to *application*, and check the *Without contracts* checkbox, as the {{if UI == "MVC"}} `CloudCrm.Web` {{else}} `CloudCrm.Blazor` {{end}} project already depends on the `CloudCrm.CatalogService.Contracts` package:
> To be able to select the *Application*, you must *Start* the related application beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md) as explained in the previous parts.
{{end}}
public override void ConfigureServices(ServiceConfigurationContext context)
Now, stop any application running in the *Solution Runner* panel, and then run the applications by clicking the *Start All* button on the root item in the *Solution Runner* panel:
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.Web` application to open it in the ABP Studio's pre-integrated browser:
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "NG"}} `CloudCrm.Angular` {{else}} `CloudCrm.Blazor` {{end}} application to open it in the ABP Studio's pre-integrated browser:
This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `OrderingService`:
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial.
Select *Entity Framework Core* option and proceed the *Next* step.
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button.
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial.
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button.
## Exploring the New Ordering Microservice
In this section, we will investigate the new microservice in overall.
@ -62,7 +91,7 @@ In this section, we will investigate the new microservice in overall.
Just like the Catalog microservice, the Ordering microservice is a .NET solution that contains multiple projects. You can see the solution structure in the *Solution Explorer* panel:
* `CloudCrm.OrderingService` is the main project that you will implement your service. It typically contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), [application services](../../framework/architecture/domain-driven-design/application-services.md), API controllers, etc.
* `CloudCrm.OrderingService.Contracts` project can be shared with the other services and applications. It typically contains interfaces of your [application services](../../framework/architecture/domain-driven-design/application-services.md), [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md), and some other types you may want to share with the clients of this microservice.
@ -76,13 +105,13 @@ You can run the solution using ABP Studio's *Solution Runner*. It will also run
Click the *Play* button near to the solution root:
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.OrderingService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Orders* controller in the Swagger UI:
@ -18,7 +26,7 @@ In the previous part, we created the Ordering microservice. In this part, we wil
## Creating the Order Entity
We will start by creating the `Order` entity, which will represent an order in our system. We'll add this entity to the `CloudCrm.OrderingService` project. Create a new folder named `Entities` and add a class named `Order` inside it:
We will start by creating the `Order` entity, which will represent an order in our system. We'll add this entity to the `CloudCrm.OrderingService` project. Create a new folder named `Entities` and create a file `Order.cs` inside it:
```csharp
using CloudCrm.OrderingService.Enums;
@ -38,7 +46,7 @@ To keep this example simple, we allow users to include only a single product wit
### Adding the OrderState Enum
We also need to define the `OrderState` enum. In the `CloudCrm.OrderingService.Contracts` project, create a folder named `Enums` and add an `OrderState` enum inside it:
We also need to define the `OrderState` enum. In the `CloudCrm.OrderingService.Contracts` project, create a folder named `Enums` and create a file `OrderState.cs` inside it:
```csharp
namespace CloudCrm.OrderingService.Enums;
@ -53,7 +61,7 @@ public enum OrderState : byte
The final solution structure should look like this:
The changes will be applied to the database during the next application startup. For more details, refer to the [database migrations on service startup](../../solution-templates/microservice/database-configurations.md#database-migrations-on-service-startup) section.
@ -198,7 +206,7 @@ public class OrderDto
The final solution structure should look like this:
@ -269,7 +277,7 @@ public class OrderingServiceApplicationAutoMapperProfile : Profile
Now, we can test the `OrderAppService` class using the Swagger UI. Open the Solution Runner and right-click to `CloudCrm.OrderingService` project and select the *Start* command. After the application starts, you can open the Swagger UI by clicking to the [Browse](../../studio/running-applications.md#monitoring) command:
Expand the `api/ordering/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button:
@ -283,6 +291,7 @@ If you check the database, you should see the entities created in the `Orders` t
## Creating the User Interface
{{if UI == "MVC"}}
Now, we will create the user interface for the Ordering module. We will use the `CloudCrm.Web` project to create the user interface. Open the `CloudCrm.Web` .NET solution in your favorite IDE.
### Creating the Orders Page
@ -339,38 +348,120 @@ Here, we inject the `IOrderAppService` into the `Index` Razor Page. We use this
```
This page shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../../tutorials/book-store/index.md).
To select the *Application* during proxy generation, ensure that the `CloudCrm.OrderingService` is *Started* beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md).
Now, we will create the user interface for the Ordering module. We will use the {{if UI == "BlazorServer"}} `CloudCrm.Blazor.Server` {{else}} `CloudCrm.Blazor.Client` {{end}} project to create the user interface. Open the {{if UI == "BlazorServer"}} `CloudCrm.Blazor.Server` {{else}} `CloudCrm.Blazor.Client` {{end}} .NET solution in your favorite IDE.
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
It will open the *Generate C# Proxies* window. Select the `CloudCrm.OrderingService` application, and it will automatically populate the *URL* field. Choose the *ordering* module and service type is *application* lastly check the *Without contracts* checkbox, since we already have a dependency on the `CloudCrm.OrderingService.Contracts` package in the `CloudCrm.Web` project:
Create `Orders.razor` file under the `Components/Pages` folder in the `CloudCrm.Blazor.Server` project.
Lastly, we need to configure the use of a static HTTP client for the `OrderingService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method:
Create `Orders.razor` file under the `Pages` folder in the `CloudCrm.Blazor.Client` project.
```csharp
//...
using CloudCrm.OrderingService;
{{end}}
public override void ConfigureServices(ServiceConfigurationContext context)
To select the *Application* during proxy generation, ensure that the `CloudCrm.OrderingService` is *Started* beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md).
{{if UI == "BlazorWebApp"}}
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the UI project. Since we are using `BlazorWebApp`, we will generate client proxies in both the `CloudCrm.Blazor` and `CloudCrm.Blazor.Client` projects, as pages are sometimes rendered on the server side and sometimes on the client side.
This will open the Generate C# Proxies window. Select the `CloudCrm.CatalogService` application this will automatically fill in the *URL* field. Next, choose the catalog module, set the service type to *Application*, and check the *Without contracts* option, since the `CloudCrm.Blazor` project already references the `CloudCrm.CatalogService.Contracts` package.
This will open the Generate C# Proxies window. Select the `CloudCrm.CatalogService` application this will automatically fill in the *URL* field. Next, choose the catalog module, set the service type to *Application*, and check the *Without contracts* option, since the `CloudCrm.Blazor.Client` project already references the `CloudCrm.CatalogService.Contracts` package.
{{else}}
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the UI project. Right-click the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "BlazorServer"}} `CloudCrm.Blazor.Server` {{else if UI == "Blazor"}} `CloudCrm.Blazor.Client` {{end}} [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
It will open the *Generate C# Proxies* window. Select the `CloudCrm.OrderingService` application, and it will automatically populate the *URL* field. Choose the *ordering* module and service type is *application* lastly check the *Without contracts* checkbox, since we already have a dependency on the `CloudCrm.OrderingService.Contracts` package in the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "BlazorServer"}} `CloudCrm.Blazor.Server` {{else if UI == "Blazor"}} `CloudCrm.Blazor.Client` {{end}} project:
> ABP provides a modular navigation [menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) that allows you to define the menu items in a modular way.
Finally, we need to add a menu item to the sidebar to navigate to the `Orders` page. Open the `CloudCrmMenus` file in the `Navigation` folder of the `CloudCrm.Web` project and edit with the following code:
Finally, we need to add a menu item to the sidebar to navigate to the `Orders` page. Open the `CloudCrmMenus` file in the `Navigation` folder of the {{if UI == "MVC"}} `CloudCrm.Web` {{else}} `CloudCrm.Blazor.Client` {{end}} project and edit with the following code:
```csharp
namespace CloudCrm.Web.Navigation;
@ -391,7 +482,7 @@ public class CloudCrmMenus
}
```
Then, open the `CloudCrmMenuContributor` class in the `CloudCrm.Web` project, located in the `Navigation` folder, and add the following code to `ConfigureMainMenuAsync` method:
Then, open the `CloudCrmMenuContributor` class in the {{if UI == "MVC"}} `CloudCrm.Web` {{else}} `CloudCrm.Blazor` {{end}} project, located in the `Navigation` folder, and add the following code to `ConfigureMainMenuAsync` method:
* Open the `projects/ordering-service/config/src/ordering-service-config.module.ts` file and add `ORDERS_ORDER_ROUTE_PROVIDER` to the `providers` array as following code:
*ordering-service-config.module.ts*
```typescript
import { ModuleWithProviders, NgModule } from '@angular/core';
import { ORDERING_SERVICE_ROUTE_PROVIDERS } from './providers/route.provider';
import { ORDERS_ORDER_ROUTE_PROVIDER } from './providers/order-route.provider';
* Create `order.component.html` file under the `projects/ordering-service/src/lib/order` folder as following code:
```html
<divclass="card">
<divclass="card-body">
<tableclass="table table-bordered">
<thead>
<tr>
<th>Order ID</th>
<th>Product Id</th>
<th>Customer Name</th>
</tr>
<tr*ngFor="let item of items">
<td>{%{{{item.id}}}%}</td>
<td>{%{{{item.productId}}}%}</td>
<td>{%{{{item.customerName}}}%}</td>
</tr>
</thead>
</table>
</div>
</div>
```
{{end}}
## Building and Running the Application
Now, we can run the application to see the changes. Please stop the applications if they are running. Then open the *Solution Runner* panel, right-click the `CloudCrm` root item, and select the *Start* command:
@ -22,7 +30,7 @@ In this section, we will integrate the Ordering service with the Catalog service
In a microservices architecture, each service is responsible for its own data and business logic. However, services often need to communicate with each other to fulfill their responsibilities. This communication can be synchronous or asynchronous, depending on the requirements.
In our case, the Ordering service needs to display the product name instead of the product ID. To achieve this, we need to call the Catalog service to retrieve the product details based on the product ID. This is a typical example of a synchronous communication pattern between microservices. As a solution to that problem, we will use an [integration service](../../framework/api-development/integration-services.md) that will handle the communication with the Catalog service. Integration service concept in ABP is designed for request/response style inter-module (in modular applications) and inter-microservice (in distributed systems) communication.
@ -111,11 +119,11 @@ Now that we have created the `IProductIntegrationService` interface and the `Pro
First, we need to add a reference to the `CloudCrm.OrderingService` package in the Ordering service. Open the ABP Studio, and stop the application(s) if it is running. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select *Add* -> *Package Reference* command:
In the *Add Package Reference* window, select the `CloudCrm.CatalogService.Contracts` package from the *This solution* tab. Click the *OK* button to add the reference:
ABP Studio adds the package reference and arranges the [module](../../framework/architecture/modularity/basics.md) dependency.
@ -236,25 +244,13 @@ Let's explain the changes we made:
We have created the `IProductIntegrationService` interface and the `ProductIntegrationService` class in the `CloudCrm.CatalogService` solution. Now, we need to generate the proxy classes for the integration service in the `CloudCrm.OrderingService` package. First, *Start* the `CloudCrm.CatalogService` application in ABP Studio *Solution Runner*. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
It opens the *Generate C# proxies* window. Select the `CloudCrm.CatalogService` application from the *Application* dropdown list. Then, choose the *catalog* module from the *Module* dropdown list and choose the *integration* service from the *Service type* dropdown list. Check the *Without contracts* checkbox and click the *Generate* button:
We have generated the proxy classes for the `IProductIntegrationService` interface. Now, we must add the *Remote Service* url to the `appsettings.json` file of the `CloudCrm.OrderingService` project. Open the `appsettings.json` file (the `appsettings.json` file of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and add the *CatalogService* section following configuration:
> **BaseUrl** refers to the base URL of the Catalog service. You can use the *Copy Url* option from the Catalog service's context menu in the ABP Studio **Solution Runner** to paste it here.
Proxy classes for the `IProductIntegrationService` interface have been generated.
Lastly, open the `CloudCrmOrderingServiceModule` class (the `CloudCrmOrderingServiceModule.cs` file under the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and add the following code to the `ConfigureServices` method:
@ -271,6 +267,8 @@ public override void ConfigureServices(ServiceConfigurationContext context)
### Updating the UI to Display the Product Name
{{if UI == "MVC"}}
Open the `Index.cshtml` file (the `Index.cshtml` file under the `Pages/Orders` folder of the `CloudCrm.Web` project of the `CloudCrm.Web` .NET solution) and update the table content to display the product name instead of the product ID:
```html
@ -295,9 +293,49 @@ Open the `Index.cshtml` file (the `Index.cshtml` file under the `Pages/Orders` f
Now, the Ordering service displays the product name instead of the product ID. We have successfully integrated the Ordering service with the Catalog service using HTTP API calls.
@ -318,4 +356,4 @@ Open the `etc\helm\cloudcrm\charts\ordering\templates\ordering.yaml` file in a t
value: "http://{%{{{ .Release.Name }}}%}-catalog"
````
With this simple configuration, now the Ordering module can discover the catalog microservice's URL inside your Kubernetes cluster.
With this simple configuration, now the Ordering module can discover the catalog microservice's URL inside your Kubernetes cluster.
@ -135,21 +143,21 @@ Since the `OrderPlacedEto` class is in the `CloudCrm.OrderingService.Contracts`
Open the ABP Studio UI and stop the applications if they are running. Then, open the *Solution Explorer* panel and right-click on the `CloudCrm.CatalogService`. Select *Import Module* from the context menu:


In the opening dialog, find and select the `CloudCrm.OrderingService` module, check the *Install this module* option, click the *OK* button:
Here, select the `CloudCrm.OrderingService.Contracts` package on the left side (because we want to add that package reference) and `CloudCrm.CatalogService` package on the middle area (because we want to add the package reference to that project).
You can check the ABP Studio's *Solution Explorer* panel to see the module and the project reference (dependency):
@ -205,7 +213,7 @@ To keep this tutorial simple, we will not implement a user interface for creatin
Once the application is running and ready, [Browse](../../studio/running-applications.md#c-application) the `CloudCrm.OrderingService` application. Use the `POST /api/ordering/order` endpoint to create a new order:


Find the *Order* API, click the *Try it out* button, enter a sample value the *Request body* section, and click the *Execute* button:
@ -218,9 +226,9 @@ Find the *Order* API, click the *Try it out* button, enter a sample value the *R
> **IMPORTANT:** Here, you should type a valid Product Id from the Products table of your database!
Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is listed. You can also check the `/Products` page to see if the stock count of the related product is decreased by one in the `CloudCrm.Web` application.
Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is listed. You can also check the `/Products` page to see if the stock count of the related product is decreased by one in the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "NG"}} `Angular` {{else}} `CloudCrm.Blazor` {{end}} application.
Here are sample screenshots from the Orders and Products pages of the `CloudCrm.Web` application:
Here are sample screenshots from the Orders and Products pages of the {{if UI == "MVC"}} `CloudCrm.Web` {{else if UI == "NG"}} `Angular` {{else}} `CloudCrm.Blazor` {{end}} application: