A website needs an SSL certificate in order to keep user data secure, verify ownership of the website, prevent attackers from creating a fake version of the site, and gain user trust.
This document introduces how to get and use SSL certificate(HTTPS) for your application.
This document introduces how to get and use an SSL certificate(HTTPS) for your application.
## Get a SSL Certificate from a Certificate Authority
## Get an SSL Certificate from a Certificate Authority
You can get a SSL certificate from a certificate authority (CA) such as [Let's Encrypt](https://letsencrypt.org/) or [Cloudflare](https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/) and so on.
@ -14,21 +16,137 @@ Once you have a certificate, you need to configure your web server to use it. Th
* [Host ASP.NET Core on Linux with Nginx: HTTPS configuration](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx)
* [How to Set Up SSL on IIS 7 or later](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis)
## Create a Self-Signed Certificate
You can create a self-signed certificate for testing purposes or internal use.
There is an article about [how to create a self-signed certificate](https://learn.microsoft.com/en-us/dotnet/core/additional-tools/self-signed-certificates-guide), If you are using IIS, you can use the following this document to [obtain a Certificate](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis#obtain-a-certificate)
### How to get a free SSL certificate from Let's Encrypt?
Let's Encrypt is **a free, automated, and open certificate authority (CA)**. It gives the digital certificates to enable HTTPS (SSL/TLS) for websites. To get a free SSL certificate, we will use [acme.sh](https://github.com/acmesh-official/acme.sh) and Cloudflare DNS API to get a free SSL certificate from [Let's Encrypt](https://letsencrypt.org/).
> If you have any problem with the following steps, you can read the [acme.sh](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) tutorial.
Ensure that you have `curl` command in your terminal. And run the following command on your terminal:
```bash
curl https://get.acme.sh | sh -s email=my@example.com
```
#### [Cloudflare DNS API token](https://dash.cloudflare.com/profile/api-tokens)
You will need to create an API token which either:
(i) has permission to edit a single specific DNS zone; or
(ii) has permission to edit multiple DNS zones.
You can do this via your Cloudflare profile page under the API Tokens section. When you create the token, under Permissions, select Zone > DNS > Edit, and under Zone Resources, only include the specific DNS zones within which you need to perform ACME DNS challenges.
The API token is a 40-character string that may contain uppercase letters, lowercase letters, numbers, and underscores. You must provide it to acme.sh by setting the environment variable CF_Token to its value, e.g. run export CF_Token="Y_jpG9AnfQmuX5Ss9M_qaNab6SQwme3HWXNDzRWs".
**(i) Single DNS zone**
You must give acme.sh the zone ID of the DNS zone it needs to edit. This is a 32-character hexadecimal string (e.g. 763eac4f1bcebd8b5c95e9fc50d010b4), and should not be confused with the zone name (e.g. example.com). This zone ID can be found via the Cloudflare dashboard on the zone's Overview page in the right-hand sidebar.
You provide this info by setting the environment variable CF_Zone_ID to this zone ID, e.g. run export CF_Zone_ID="763eac4f1bcebd8b5c95e9fc50d010b4".
**(ii) Multiple DNS zones**
You must give acme.sh the account ID of the Cloudflare account to which the relevant DNS zones belong. This is a 32-character hexadecimal string, and should not be confused with other account identifiers, such as the account email address (e.g. alice@example.com) or global API key (which is also a 32-character hexadecimal string). This account ID can be found via the Cloudflare dashboard, as the end of the URL when logged in, or on the Overview page of any of your zones, in the right-hand sidebar, beneath the zone ID.
You provide this info by setting the environment variable CF_Account_ID to this account ID, e.g. run export CF_Account_ID="763eac4f1bcebd8b5c95e9fc50d010b4".
#### Issue a certificate
```bash
> export CF_Token='your_token'
> export CF_Account_ID='your_account_id'
> export CF_Zone_ID='your_zone_id'
> acme.sh --issue --dns dns_cf -d getabp.net
[Info] Domains have changed.
[Info] Using CA: https://acme.zerossl.com/v2/DV90
[Info] Single domain='getabp.net'
[Info] Getting webroot for domain='getabp.net'
[Info] Adding TXT value: 1uEeVFfmwXM7N21Wi9PitgEnhJbl4W4dHeRkapGkRSs for domain: _acme-challenge.getabp.net
[Info] Adding record
[Info] Added, OK
[Info] The TXT record has been successfully added.
[Info] Let's check each DNS record now. Sleeping for 20 seconds first.
[Info] You can use '--dnssleep' to disable public dns checks.
[Info] And the full-chain cert is in: fullchain.cer
```
#### Convert the certificate to PFX format(IIS format)
```bash
openssl pkcs12 -export \
-in getabp.net.cer \
-inkey getabp.net.key \
-out getabp.net.pfx \
-passout pass:
```
If you want to set a password for the PFX file, you can set the password with `-passout pass:your_password`.
## Common Exceptions
If you encounter the following exceptions, it means your **certificate is not trusted by the client or the certificate is not valid**.
You will may see the following SSL certificate errors in your browser when you try to access the website.
```cs
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch
```
## Common Problems
```cs
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
```
### The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
This error may occur when using IIS. You need to trust your certificate by `Manage computer certificates`.
@ -7,7 +7,7 @@ ABP was designed to support to build fully modular applications and systems wher
* This document introduces the basics of the module system.
* [Module development best practice guide](../best-practices) explains some **best practices** to develop **re-usable application modules** based on **DDD** principles and layers. A module designed based on this guide will be **database independent** and can be deployed as a **microservice** if needed.
* [Pre-built application modules](../../../modules) are **ready to use** in any kind of application.
* [Module startup template](../../../solution-templates/layered-web-application) is a jump start way to **create a new module**.
* [Module startup template](../../../solution-templates/application-module) is a jump start way to **create a new module**.
* [ABP CLI](../../../cli/index.md) has commands to support modular development.
* All other framework features are compatible to the modularity system.
When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. For most of the claims, that may not be a problem since claims are not frequently changing values. However, some claims may be changed and these changes should be reflected to the current session immediately. For example, we can revoke a role from a user and that should be immediately effective, otherwise user will continue to use that role's permissions until re-login to the application.
When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. That is not a problem for most claims since the claim values do not frequently change. However, for some claims, it may be required to immediately see the impact after the claim values change in the current session. For example, if a role is revoked from a user, you want to see its effect in the next request. Otherwise, the user will continue to use that role's permissions until re-login to the application.
ABP's dynamic claims feature is used to automatically and dynamically override the configured claim values in the client's authentication token/cookie by the latest values of these claims.
ABP's dynamic claims feature dynamically overrides the configured claim values in the client's authentication token/cookie with the latest values of these claims.
## How to Use
@ -10,7 +10,7 @@ This feature is disabled by default. You should enable it for your application a
> **Beginning from the v8.0, all the [startup templates](../../solution-templates) are pre-configured and the dynamic claims feature is enabled by default. So, if you have created a solution with v8.0 and above, you don't need to make any configuration. Follow the instructions only if you've upgraded from a version lower than 8.0.**
### Enabling the Dynamic Claims
### Enabling / Disabling the Dynamic Claims
You can enable it by the following code:
@ -19,7 +19,7 @@ public override void ConfigureServices(ServiceConfigurationContext context)
@ -10,12 +10,14 @@ First, install the [ABP CLI](../cli) if you haven't installed before:
dotnet tool install -g Volo.Abp.Studio.Cli
````
Then use the `abp new` command in an empty folder to create a new solution:
Then use the `abp new` command in an empty folder to create a new solution (with the `--old` parameter at the end of the command):
````bash
abp new Acme.MyConsoleApp -t console
abp new Acme.MyConsoleApp -t console --old
````
> **Note**: Since this startup template is not provided by the new ABP Studio Templates yet, you need to pass the `--old` parameter at the end of the command to use the old CLI & templating system for this startup template.
`Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming.
@ -10,12 +10,14 @@ First, install the [ABP CLI](../cli) if you haven't installed before:
dotnet tool install -g Volo.Abp.Studio.Cli
````
Then use the `abp new` command in an empty folder to create a new solution:
Then use the `abp new` command in an empty folder to create a new solution (with the `--old` parameter at the end of the command):
````bash
abp new Acme.MyWpfApp -t wpf
abp new Acme.MyWpfApp -t wpf --old
````
> **Note**: Since this startup template is not provided by the new ABP Studio Templates yet, you need to pass the `--old` parameter at the end of the command to use the old CLI & templating system for this startup template.
`Acme.MyWpfApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming.
> **Update all the application, gateway and microservice deployment.yaml files.**
**For AuthServer, also add the WildCardDomains that is used to handle subdomain *redirect* and *post_logout redirect* URIs to the authserver-deployment.yaml file:**
{%{
```yaml
... Removed for brevity
- name: "TenantDomain"
@ -348,6 +353,7 @@ Navigate to applications, gateways and microservices' **x-deployment.yaml** file
Afterwards, update the **values.yaml** file for all the sub-charts (administration, authserver etc):
@ -383,7 +389,7 @@ wildCardDomains:
After updating the **deployment.yaml** and **values.yaml** files of all the application, gateway and microservices' navigate to the **mystore chart values.yaml** file located under the k8s/Mystore/values.yaml that overrides all the sub-charts:
**AuthServer:**
{%{
```yaml
# auth-server sub-chart override
authserver:
@ -402,11 +408,13 @@ authserver:
saasService: "https://{0}.saas.mystore.dev"
productService: "https://{0}.product.mystore.dev"
```
}%}
You may also get CORS error when authenticating SwaggerUI of your gateways or microservices. Add Override the AuthServer CORS values with the subdomain to solve this problem:
The ABP Commercial MVC `v8.2.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
The ABP Commercial MVC `v8.3.0` application template has been tested against security vulnerabilities by the [OWASP ZAP v2.14.0](https://www.zaproxy.org/) tool. The demo web application was started on the `https://localhost:44349` address. The below alerts have been reported by the pentest tool. These alerts are sorted by the risk level as high, medium, and low. The informational alerts are not mentioned in this document.
Many of these alerts are **false-positive**, meaning the vulnerability scanner detected these issues, but they are not exploitable. It's clearly explained for each false-positive alert why this alert is a false-positive.
@ -10,14 +10,15 @@ In the next sections, you will find the affected URLs, attack parameters (reques
There are high _(red flag)_, medium _(orange flag)_, low _(yellow flag)_, and informational _(blue flag)_ alerts.
@ -49,6 +50,18 @@ SQL injection may be possible. SQL injection is a web security vulnerability tha
ABP uses Entity Framework Core and LINQ. **It's safe against SQL Injection because it passes all data to the database via SQL parameters.** LINQ queries are not composed by using string manipulation or concatenation, that's why they are not susceptible to traditional SQL injection attacks. Therefore, this is a **false-positive** alert.
* *[POST] — https://localhost:44349/Account/Login* (attack: **false AND 1=1 --**)
**Description**:
SQL injection may be possible on a login page, potentially allowing the application's authentication mechanism to be bypassed.
**Solution**:
This alert indicates that we must not trust client side input (even if there is client side validation in place) and check all data on the server side. ABP Framework already does that and makes server-side validations while authenticating a user. Therefore this is a **false-positive** alert.
### Absence of Anti-CSRF Tokens [Risk: Medium] — False Positive
- *[GET] — https://localhost:44349/Abp/Languages/Switch?culture=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A&returnUrl=%2F&uiCulture=ar* (with combination of different parameters)
- *[GET] — https://localhost:44349/Abp/ApplicationLocalizationScript?cultureName=ZAP%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%25n%25s%0A* (with combination of different parameters)
**Description:**
@ -148,19 +161,21 @@ Injection using XSL transformations may be possible and may allow an attacker to
**Explanation**:
This is a **false-positive** alert. v8.2.0 uses .NET 8 and the XSLT transformation is not possible on .NET5 or higher.
This is a **false-positive** alert. v8.3.0 uses .NET 8 and the XSLT transformation is not possible on .NET5 or higher.
The reported page contains an error/warning message that may disclose sensitive information like the location of the file that produced the unhandled exception. This information can be used to launch further attacks against the web application. The alert could be a false positive if the error message is found inside a documentation page.
The reported pages contain an error/warning message that may disclose sensitive information like the location of the file that produced the unhandled exception. This information can be used to launch further attacks against the web application. The alert could be a false positive if the error message is found inside a documentation page.
**Explanation:**
This vulnerability was reported as a **positive** alert because the application ran in `Development` mode. ABP Framework throws exceptions for developers in the `Development` environment. We set the environment to `Production` and re-run the test, then the server sent a *500-Internal Error* without the error disclosed. Therefore this alert is **false-positive**. Further information can be found in the following issue: [github.com/abpframework/abp/issues/14177](https://github.com/abpframework/abp/issues/14177#issuecomment-1268206947).
This vulnerability was reported as a **positive** alert because the application ran in `Development` mode. ABP throws exceptions for developers in the `Development` environment. We set the environment to `Production` and re-run the test, then the server sent a *500-Internal Error* without the error disclosed. Therefore this alert is **false-positive**. Further information can be found in the following issue: [github.com/abpframework/abp/issues/14177](https://github.com/abpframework/abp/issues/14177#issuecomment-1268206947).
### Cookie No `HttpOnly` Flag [Risk: Low] — Positive (No need for a fix)
@ -326,89 +341,10 @@ The `X-Content-Type-Options` header allows you to avoid MIME type sniffing by sa
You can add the [ABP's Security Header Middleware](../framework/ui/mvc-razor-pages/security-headers.md#security-headers-middleware) into the request pipeline to set the `X-Content-Type-Options` as *no-sniff*. Also, this middleware adds other pre-defined security headers to your application, including `X-XSS-Protection`, `X-Frame-Options` and `Content-Security-Policy` (if it's enabled). Read [Security Headers](../framework/ui/mvc-razor-pages/security-headers.md) documentation for more info.
## Other Alerts
The following alerts are reported by the community or our customers in v8.1+.
### Disclosed Microsoft Client Secret [Risk: Medium] - Positive (No need for a fix)
Secrets shall never be exposed to unauthorized parties. This exposure can result from improper storage, insecure transmission, or inadequate access controls. In this specific case the owner of the user account is authorized to read and modify the secret. In case of administrative accounts, it could lead to further damages, by performing lateral movements, by using the credentials to access other services.
**Explanation**:
The endpoint `/setting-management/` requires permission to be visited and can only be accessed via authorized users. It is the setting page to configure the application settings including the *default localization language*, *timezone*, *layout type*, *password settings* and more...
Application logout functionality does not terminate the user's session. This increases the risk of unauthorized application access via successful session hijacking attacks, users leaving their computers unattended, and/or a local attacker utilizing the browser history. On logout, user sessions should be invalidated and all relevant session identifiers, authentication tokens and application state information deleted or overwritten both on server and on client side.
**Explanation**:
You can track the status of this case at [github.com/abpframework/abp/issues/19576](https://github.com/abpframework/abp/issues/19576).
### Information Disclosure via Configuration Scripts [Risk: Low] - Positive (No need for a fix)
## Other Alerts (Fixed)
- *[GET] — https://localhost:44349/Abp/ApplicationConfigurationScript* or *https://localhost:44349/api/abp/application-configuration*
When users authenticate to the application, their browsers issue requests to 2 endpoints that host configuration scripts for the application framework. The first contains information about the passwords that are accepted by the application. This information can be used by the attackers to narrow down their dictionaries and only focus on the possible passwords for their
attacks. The second, on the other hand, discloses some endpoints that are unavailable to the users with low privileges.
**Explanation**:
* **Application Configuration Script**:
These 2 endpoints are used by ABP application templates. The first one `/Abp/ApplicationConfigurationScript` provides configuration and user based definitions with JSON format. This data is important for SPA based applications to get the current language, localization texts, policies, settings, user info, current tenant or time zone information. This is not a data leak. User specific data can only be accessed after user logon. Other data are application-wide used not dangerous for unauthenticated users. For more information check out the [Application Configuration](../framework/api-development/standard-apis/configuration.md) document.
* **Service Proxy Script**:
This endpoint provides auto-generated JavaScript AJAX call methods for the backend operations. This may disclosure information about the host API methods. On the other hand, it makes easy to consume the HTTP APIs from JavaScript side. ABP Application Services are automatically converted to JavaScript proxies. But it does not mean that these JavaScript methods can be executed anonymously. The attacker still needs to log in to perform operations. For more information check out the [Service Proxy Script](../framework/ui/mvc-razor-pages/dynamic-javascript-proxies.md) document. If you want to disable this functionality, check out [github.com/abpframework/abp/issues/12297](https://github.com/abpframework/abp/issues/12297)
### User E-mail Address Enumeration [Risk: Low] - Positive
It is possible to collect valid email addresses by interacting with the "Forgot Password" function of the
application. This vulnerability is useful to increase the efficiency of brute force attacks.
**Explanation**:
If the email is known, it is easier to find the corresponding password. With the "Forgot Password" function, the attacker can enumerate valid email addresses as the function returns `Cannot find the given email` error, when there is no user registered with the provided e-mail address. This vulnerability has been fixed with v8.2, see the related issue for more info: [github.com/abpframework/abp/issues/19588](https://github.com/abpframework/abp/issues/19588).
### Software Version Disclosure [Risk: Low] - Positive (No need for a fix)
* *[GET] — https://localhost:44349/*
**Description**:
The assessed web server discloses its version number within the HTTP response headers. This information facilitates attackers in planning future attacks and can be used in the automation of the attack process. It is unnecessary to share this information with the clients of the web application. The vulnerability can be verified by issuing HTTP requests and inspecting HTTP response headers. HTTP header "Server" contains the version information.
The following header was received in server responses: `Server: Microsoft-IIS/10.0` or `Server: Microsoft-HTTPAPI/2.0`.
**Explanation**:
This is not directly related to ABP. It's a header added by the IIS server. So you can disable this header with the `web.config` file:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<security>
<requestFilteringremoveServerHeader="true"/>
</security>
</system.webServer>
</configuration>
```
The following alerts were reported by the community or our customers in v8.2 and fixed:
The following issue has been opened for this vulnerability, you can follow it at [github.com/abpframework/abp/issues/19589](https://github.com/abpframework/abp/issues/19589).
This command opens a new dialog to define the properties of the new module. You can use the following values to create a new module named `ModularCrm.Products`:
We will create an application service to manage the `Order` entities.
### Defining the Application Service Contract
We're gonna create the `IOrderAppService` interface under the `ModularCrm.Ordering.Contracts` project but first, we need to add `Volo.Abp.Ddd.Application.Contracts` package reference.
Right-click the `ModularCrm.Ordering.Contracts` project in the *Solution Explorer* panel and select the *Add Package Reference* command:
Select the *NuGet* tab, type `Volo.Abp.Ddd.Application.Contracts` as the *Package name* and write the version of the package you want to install. Please be sure that you are installing the same version as the other ABP packages you are already using.
Click the *Ok* button. Now you can check the *Packages* under the `ModularCrm.Ordering.Contracts` project *Dependencies* to see the `Volo.Abp.Ddd.Application.Contracts` package is installed:
Return to your IDE, open the `ModularCrm.Ordering` module's .NET solution and create an `IOrderAppService` interface under the `Services` folder for `ModularCrm.Ordering.Contracts` project:
````csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace ModularCrm.Ordering.Contracts.Services;
public interface IOrderAppService : IApplicationService
{
Task<List<OrderDto>> GetListAsync();
Task CreateAsync(OrderCreationDto input);
}
````
### Defining Data Transfer Objects
The `GetListAsync` and `CreateAsync` methods will use data transfer objects (DTOs) to communicate with the client. We will create two DTO classes for that purpose.
Create a `OrderCreationDto` class under the `ModularCrm.Ordering.Contracts` project:
````csharp
using System;
using System.ComponentModel.DataAnnotations;
namespace ModularCrm.Ordering.Contracts.Services;
public class OrderCreationDto
{
[Required]
[StringLength(150)]
public string CustomerName { get; set; }
[Required]
public Guid ProductId { get; set; }
}
````
Create a `OrderDto` class under the `ModularCrm.Ordering.Contracts` project:
````csharp
using System;
using ModularCrm.Ordering.Contracts.Enums;
namespace ModularCrm.Ordering.Contracts.Services;
public class OrderDto
{
public Guid Id { get; set; }
public string CustomerName { get; set; }
public Guid ProductId { get; set; }
public OrderState State { get; set; }
}
````
The new files under the `ModularCrm.Ordering.Contracts` project should be like the following figure:
Select the *NuGet* tab, enter `Volo.Abp.Ddd.Application` as the *Package name*, and specify the version of the package you wish to install. Afterward, you can add the `Volo.Abp.AutoMapper` package in the same dialog. Ensure that you install the same version as the other ABP packages you are already using.
Click the *OK* button. Now we should configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` object. We will create a class named `OrderingApplicationAutoMapperProfile` under the `ModularCrm.Ordering` project:
````csharp
using AutoMapper;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
namespace ModularCrm.Ordering;
public class OrderingApplicationAutoMapperProfile : Profile
{
public OrderingApplicationAutoMapperProfile()
{
CreateMap<Order,OrderDto>();
}
}
````
Since this is a non-layered module, we can use entities and repositories directly on the user interface. If you think that is not a good practice, then use the layered module template as we've already done for the *Products* module. But for the Ordering module, we will keep it very simple for this tutorial to show it is also possible.
And configure the `OrderingWebModule` class to use the `OrderingApplicationAutoMapperProfile`:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
Now, we can implement the `IOrderAppService` interface. Create an `OrderAppService` class under the `Services` folder of the `ModularCrm.Ordering` project:
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ModularCrm.Ordering.Contracts.Enums;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace ModularCrm.Ordering.Services;
public class OrderAppService : ApplicationService, IOrderAppService
public async Task CreateAsync(OrderCreationDto input)
{
var order = new Order
{
CustomerName = input.CustomerName,
ProductId = input.ProductId,
State = OrderState.Placed
};
await _orderRepository.InsertAsync(order);
}
}
````
Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method:
This section will create a few example orders using the [Swagger UI](../../framework/api-development/swagger.md). Thus, we will have some sample orders to show on the UI.
Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the order module and the main application are built and ready to run.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface.
Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Orders` API:
Expand the `/api/app/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:
public IndexModel(IRepository<Order,Guid> orderRepository)
public IndexModel(IOrderAppService orderAppService)
{
_orderRepository = orderRepository;
_orderAppService = orderAppService;
}
public async Task OnGetAsync()
{
Orders = await _orderRepository.GetListAsync();
Orders = await _orderAppService.GetListAsync();
}
}
}
@ -310,14 +514,6 @@ Here, we are injecting a repository to query `Order` entities from the database
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](../book-store/index.md).
### Creating Some Sample Data
You can open the database and manually create a few order records to show on the UI:
You can get `ProductId` values from the `Products` table and [generate](https://www.guidgenerator.com/) some random GUIDs for other GUID fields.
### Building the Application
Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, and select the *Build* -> *Graph Build* command:
`OrderingMenuContributor` implements the `IMenuContributor` interface, which forces us to implement the `ConfigureMenuAsync` method. In that method, we can manipulate the menu items (add new menu items, remove existing menu items or change the properties of existing menu items). The `ConfigureMenuAsync` method is executed whenever the menu is rendered on the UI, so you can dynamically decide how to manipulate the menu items.
After creating such a class, we should configure the `AbpNavigationOptions` to add that contributor. Open the `OrderingWebModule` class in the `ModularCrm.Ordering` project and add the following configuration code into the `ConfigureServices` method (if there is no `ConfigureServices` method, first create it as shown below):
After creating such a class, we should configure the `AbpNavigationOptions` to add that contributor. Open the `OrderingWebModule` class in the `ModularCrm.Ordering` project and add the following configuration code into the `ConfigureServices` method:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -145,68 +145,121 @@ ABP Studio adds the package reference and arranges the [module](../../framework/
Now, we can inject and use `IProductIntegrationService` in the Ordering module codebase.
Open the `IndexModel` class (the `IndexModel.cshtml.cs` file under the `Pages/Orders` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and change its content as like the following code block:
Open the `OrderAppService` class (the `OrderAppService.cs` file under the `Services` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and change its content as like the following code block:
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
using ModularCrm.Ordering.Entities;
using ModularCrm.Products.Integration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ModularCrm.Ordering.Contracts.Enums;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
using ModularCrm.Products.Integration;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace ModularCrm.Ordering.Pages.Orders
namespace ModularCrm.Ordering.Services;
public class OrderAppService : ApplicationService, IOrderAppService
public async Task CreateAsync(OrderCreationDto input)
{
var order = new Order
{
// Getting the orders from this module's database
Orders = await _orderRepository.GetListAsync();
CustomerName = input.CustomerName,
ProductId = input.ProductId,
State = OrderState.Placed
};
await _orderRepository.InsertAsync(order);
}
}
````
// Prepare a list of products we need
var productIds = Orders.Select(o => o.ProductId).Distinct().ToList();
And also, open the `OrderDto` class (the `OrderDto.cs` file under the `Services` folder of the `ModularCrm.Ordering.Contracts` project of the `ModularCrm.Ordering` .NET solution) and add a `ProductName` property to it:
// Request the related products from the product integration service
var products = await _productIntegrationService
.GetProductsByIdsAsync(productIds);
````csharp
using System;
using ModularCrm.Ordering.Contracts.Enums;
// Create a dictionary to get a product name easily by its id
ProductNames = products.ToDictionary(p => p.Id, p => p.Name);
}
namespace ModularCrm.Ordering.Contracts.Services;
public class OrderDto
{
public Guid Id { get; set; }
public string CustomerName { get; set; }
public Guid ProductId { get; set; }
public string ProductName { get; set; } // New property
public OrderState State { get; set; }
}
````
Lastly, open the `OrderingApplicationAutoMapperProfile` class (the `OrderingApplicationAutoMapperProfile.cs` file under the `Services` folder of the `ModularCrm.Ordering` project of the `ModularCrm.Ordering` .NET solution) and ignore the `ProductName` property in the mapping configuration:
````csharp
using AutoMapper;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
using Volo.Abp.AutoMapper;
namespace ModularCrm.Ordering;
public class OrderingApplicationAutoMapperProfile : Profile
{
public OrderingApplicationAutoMapperProfile()
{
CreateMap<Order,OrderDto>()
.Ignore(x => x.ProductName); // New line
}
}
````
Let's see what we've changed:
* We have defined a `ProductNames` dictionary. We will use it on the UI to convert product IDs to product names. We are filling that dictionary with products from the product integration service.
* We've added a `ProductName` property to the `OrderDto` class to store the product name.
* Injecting the `IProductIntegrationService` interface so we can use it to request products.
* In the `OnGetAsync` method;
* In the `GetListAsync` method;
* First getting the orders from the ordering module's database just like done before.
* Next, we are preparing a unique list of product IDs since the `GetProductsByIdsAsync` method requests it.
* Then we are calling the `IProductIntegrationService.GetProductsByIdsAsync` method to get a `List<ProductDto>` object.
* In the last line, we are converting the product list to a dictionary, where the key is `Guid Id` and the value is `string Name`. That way, we can easily find a product's name with its ID.
* Finally, we are mapping the orders to `OrderDto` objects and setting the product name by looking up the product ID in the dictionary.
Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.ProductNames[order.ProductId]` to write the product name instead of the product ID. The final `Index.cshtml` content should be the following:
Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.ProductName` to write the product name instead of the product ID. The final `Index.cshtml` content should be the following:
````html
@page
@ -219,11 +272,11 @@ Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.
@ -241,4 +294,3 @@ In the way explained in this section, you can easily create integration services
> **Design Tip**
>
> It is suggested that you keep that type of communication to a minimum and not couple your modules with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering module frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Products module. Especially if you consider converting your system to a microservice solution in the future, too many direct integration API calls can be a performance bottleneck.
The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering module has no functionality to create new orders.
In Part 3, we used ABP's Auto HTTP API Controller feature to expose HTTP APIs from application services automatically. In this section, we will create an ASP.NET Core API controller class to create a new order. In that way, you will also see that it is not different from creating a regular ASP.NET Core controller.
Open the `ModularCrm.Ordering` module's .NET solution, create a `Controllers` folder in the `ModularCrm.Ordering` project and place a controller class named `OrdersController` in that new folder. The final folder structure should be like that:
The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering module has no functionality to create new orders. Let's change that and place an order, for that purpose open the `ModularCrm.Ordering` module's .NET solution, and update the `OrderAppService` as follows:
````csharp
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ModularCrm.Ordering.Contracts.Enums;
using ModularCrm.Ordering.Contracts.Events;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc;
using ModularCrm.Products.Integration;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
namespace ModularCrm.Ordering.Controllers
namespace ModularCrm.Ordering.Services;
public class OrderAppService : ApplicationService, IOrderAppService