In the previous part, we created a custom Ordering module and installed it into the main application. However, the Ordering module has no functionality now. In this part, we will create an `Order` entity and add functionality to create and list the orders.
In the previous part, we created Ordering module and installed it into the main application. However, the Ordering module has no functionality now. In this part, we will create an `Order` entity and add functionality to create and list the orders.
## Creating an `Order` Entity
@ -22,37 +22,9 @@ Open the `ModularCrm.Ordering` .NET solution in your IDE.
> Tip: You can open the folder of a module's .NET solution by right-clicking the related module in ABP Studio and selecting the *Open with* -> *Explorer* command.
The following figure shows the `ModularCrm.Ordering` module in the *Solution Explorer* panel of Visual Studio:
As you see in the preceding figure, the solution structure is very minimal. It also has a minimal ABP dependency. ABP Framework has [hundreds of NuGet packages](https://abp.io/packages), but the `ModularCrm.Ordering` project only has the [`Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared`](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared) package reference:
We will create an [entity class](../../framework/architecture/domain-driven-design/entities.md). ABP defines base entity classes and other related infrastructure in the [`Volo.Abp.Ddd.Domain`](https://abp.io/package-detail/Volo.Abp.Ddd.Domain) package. So, we need to add a reference to that NuGet package.
You can add that package and manually arrange the [module](../../framework/architecture/modularity/basics.md) class dependency. However, we will use ABP Studio as a more practical option.
Return to ABP Studio, right-click the `ModularCrm.Ordering` package in the *Solution Explorer* and select the *Add Package Reference* command:
Select the *NuGet* tab, type `Volo.Abp.Ddd.Domain` 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. ABP Studio will provide an easier way to select the package and its version in future ABP versions.
Click the *OK* button. Now you can check the *Packages* under the `ModularCrm.Ordering` module *Dependencies* to see the `Volo.Abp.Ddd.Domain` package is installed:
Now, you can return your IDE and add an `Order` class to the `ModularCrm.Ordering` project (open an `Entities` folder and place the `Order.cs` into that folder):
Create an `Order` class to the `ModularCrm.Ordering` project (open an `Entities` folder and place the `Order.cs` into that folder):
````csharp
using System;
@ -77,7 +49,7 @@ We allow users to place only a single product within an order. The `Order` entit
We used an `OrderState` enumeration that has not yet been defined. Open an `Enums` folder in the `ModularCrm.Ordering.Contracts` project and create an `OrderState.cs` file inside it:
````csharp
namespace ModularCrm.Ordering.Contracts.Enums;
namespace ModularCrm.Ordering.Enums;
public enum OrderState : byte
{
@ -93,48 +65,64 @@ The final structure of the Ordering module should be similar to the following fi
## Configuring the Database Mapping
The `Order` entity has been created. Now, we need to configure the database mapping for that entity. We will first install the Entity Framework Core package, define the database table mapping, create a database migration and update the database.
### Installing the Entity Framework Core Package
> In this section, we will install the [`Volo.Abp.EntityFrameworkCore`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore) package to the Ordering module. That package is DBMS-independent and leaves the DBMS selection to the final application. If you want, you can install a DBMS-specific package instead. For example, you can install the [`Volo.Abp.EntityFrameworkCore.SqlServer`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore.SqlServer) package if you are using SQL Server and want to make SQL Server specific configuration for your module's database.
> You can search for other packages on the [abp.io/packages](https://abp.io/packages) page.
The `Order` entity has been created. Now, we need to configure the database mapping for that entity. We will first define the database table mapping, create a database migration and update the database.
Stop the web application if it is still running. Return to ABP Studio, right-click the `ModularCrm.Ordering` package on the *Solution Explorer* panel and select the *Add Package Reference* command:
Entity Framework Core requires defining a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. That way, we can control the database migrations at a single point, ensure database transactions on multi-module operations, and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object because it doesn't depend on the main application, and we don't want to establish such a dependency.
Select the *NuGet* tab, type `Volo.Abp.EntityFrameworkCore` as the *Package name* and specify a *Version* that is compatible with the ABP version used by your solution:
As a solution, we will use `DbContext` interface in the Ordering module which is then implemented by the main module's `DbContext`.
Open your IDE, in `Data` folder under the `ModularCrm.Ordering` project, and edit `IOrderingDbContext` interface as shown:
Once you click the *OK* button, the NuGet package reference is added.
````csharp
using Microsoft.EntityFrameworkCore;
using ModularCrm.Ordering.Entities;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
### Defining the Database Mappings
namespace ModularCrm.Ordering.Data;
Entity Framework Core requires defining a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. That way, we can control the database migrations at a single point, ensure database transactions on multi-module operations, and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object because it doesn't depend on the main application, and we don't want to establish such a dependency.
As a solution, we will define a `DbContext` interface in the Ordering module which is then implemented by the main module's `DbContext`.
We can inject and use the `IOrderingDbContext` in the Ordering module. However, we will not usually directly use that interface. Instead, we will use ABP's [repositories](../../framework/architecture/domain-driven-design/repositories.md), which internally uses that interface.
It is best to configure the database table mapping for the `Order` entity in the Ordering module. We will create an extension method that will be called by the main application later. Create a class named`OrderingDbContextModelCreatingExtensions` in the same `Data` folder:
It is best to configure the database table mapping for the `Order` entity in the Ordering module. We will use the`OrderingDbContextModelCreatingExtensions` in the same `Data` folder:
````csharp
using Microsoft.EntityFrameworkCore;
@ -142,26 +130,26 @@ using ModularCrm.Ordering.Entities;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace ModularCrm.Ordering.Data
namespace ModularCrm.Ordering.Data;
public static class OrderingDbContextModelCreatingExtensions
{
public static class OrderingDbContextModelCreatingExtensions
public static void ConfigureOrdering(
this ModelBuilder builder)
{
public static void ConfigureOrdering(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
Check.NotNull(builder, nameof(builder));
builder.Entity<Order>(b =>
{
//Configure table name
b.ToTable("Orders");
builder.Entity<Order>(b =>
{
//Configure table name
b.ToTable("Orders");
//Always call this method to set base entity properties
b.ConfigureByConvention();
//Always call this method to set base entity properties
@ -238,35 +226,22 @@ 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:
We're gonna create the `IOrderAppService` interface under the `ModularCrm.Ordering.Contracts` project. 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;
using System;
using System.ComponentModel.DataAnnotations;
namespace ModularCrm.Ordering.Contracts.Services;
namespace ModularCrm.Ordering.Services;
public interface IOrderAppService : IApplicationService
public class OrderCreationDto
{
Task<List<OrderDto>> GetListAsync();
Task CreateAsync(OrderCreationDto input);
[Required]
[StringLength(150)]
public string CustomerName { get; set; }
[Required]
public Guid ProductId { get; set; }
}
````
@ -297,9 +272,9 @@ Create a `OrderDto` class under the `ModularCrm.Ordering.Contracts` project:
````csharp
using System;
using ModularCrm.Ordering.Contracts.Enums;
using ModularCrm.Ordering.Enums;
namespace ModularCrm.Ordering.Contracts.Services;
namespace ModularCrm.Ordering.Services;
public class OrderDto
{
@ -316,72 +291,43 @@ The new files under the `ModularCrm.Ordering.Contracts` project should be like t
### Implementing the Application Service
Before creating the `OrderAppService` class, we need to add the `Volo.Abp.Ddd.Application` and `Volo.Abp.AutoMapper` packages to the Ordering module.
Right-click the `ModularCrm.Ordering` package in the *Solution Explorer* panel and select the *Add Package Reference* command:
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:
Now we should configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` object. We will use the `OrderingAutoMapperProfile` under the `ModularCrm.Ordering` project:
````csharp
using AutoMapper;
using ModularCrm.Ordering.Contracts.Services;
using ModularCrm.Ordering.Entities;
using ModularCrm.Ordering.Services;
namespace ModularCrm.Ordering;
public class OrderingApplicationAutoMapperProfile : Profile
public class OrderingAutoMapperProfile : Profile
{
public OrderingApplicationAutoMapperProfile()
public OrderingAutoMapperProfile()
{
CreateMap<Order,OrderDto>();
}
}
````
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.Enums;
using ModularCrm.Ordering.Entities;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace ModularCrm.Ordering.Services;
public class OrderAppService : ApplicationService, IOrderAppService
public class OrderAppService : OrderingAppService, IOrderAppService
@ -442,32 +388,17 @@ If you check the database, you should see the entities created in the *Orders* t
## Creating the User Interface
### Creating a `_ViewImports.cshtml` File
Open the `ModularCrm.Ordering` .NET solution in your favorite IDE, locate the `ModularCrm.Ordering` project and create a `Pages` folder under that project. Then add a `_ViewImports.cshtml` file under that `Pages` folder with the following content:
Create an `Orders` folder under the `Pages` folder and add an `Index.cshtml` Razor Page inside that new folder. Then replace the `Index.cshtml.cs` content with the following code block:
Replace the `Index.cshtml.cs` content in the `Pages/Ordering` folder of the `ModularCrm.Ordering.UI` project with the following code block:
Here, we are injecting a repository to query `Order` entities from the database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block:
Here, we are injecting `IOrderAppService` to query `Order` entities from the database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block:
@ -514,87 +445,65 @@ 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).
### 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:
We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE), then right-click the `ModularCrm.Web` application and select the *Run* -> *Build & Start*. This approach can be faster if you have too many modules and you make a change in one of the modules.
### Running the Application
Run the main application on ABP Studio, manually type `/Orders` to the end of your application's URL to open the *Orders* page:
Great! We can see the list of orders. However, there are two problems:
1. The Order page has no menu item on the main menu. This is because we haven't configured the [navigation menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) yet.
2. We see Product's GUID ID instead of its name. This is because the Ordering module has no integration with the Products module and doesn't have access to Product module's database to perform a JOIN query.
We will solve the second problem in the [next part](part-06.md), but we can easily add a menu item for the Orders page now.
### Adding a Menu Item
### Editing the Menu Item
ABP provides a modular navigation [menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) where each module can contribute to the main menu dynamically.
Open the `ModularCrm.Ordering` .NET solution in your IDE and add the following`OrderingMenuContributor` class into the `ModularCrm.Ordering` project:
Edit the `OrderingMenuContributor` class into the `ModularCrm.Ordering.UI` project:
````csharp
using System.Threading.Tasks;
using Volo.Abp.UI.Navigation;
namespace ModularCrm.Ordering
namespace ModularCrm.Ordering.UI.Menus;
public class OrderingMenuContributor : IMenuContributor
{
public class OrderingMenuContributor : IMenuContributor
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
public Task ConfigureMenuAsync(MenuConfigurationContext context)
`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:
> You can check the [menu documentation](../../framework/ui/mvc-razor-pages/navigation-menu.md) to learn more about manipulating menu items.
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
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:
That's all. You can stop the main application (if it is already working), make a graph build on the main application, run it again on ABP Studio's *Solution Runner* panel and *Browse* it to see the result:
We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE), then right-click the `ModularCrm.Web` application and select the *Run* -> *Build & Start*. This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm.Web` application and selecting the *Run* -> *Start* command.
The *Orders* menu item is added under the *Products* menu item.
Great! We can see the list of orders. However, there is a problems:
> You can check the [menu documentation](../../framework/ui/mvc-razor-pages/navigation-menu.md) to learn more about manipulating menu items.
1. We see Product's GUID ID instead of its name. This is because the Ordering module has no integration with the Products module and doesn't have access to Product module's database to perform a JOIN query.
## Summary
We will solve this problem in the [next part](part-06.md).
In this part of the *Modular CRM* tutorial, we've built the functionality inside the Ordering module we created in the [previous part](part-04.md). Since we've created the Ordering module from scratch (with the *Empty Module* template), we had to implement many aspects manually, add ABP packages, create some configuration classes, etc. It is good to do all these manually for one time to learn the things, but it is better to use the other module templates (that pre-configure the fundamentals for us) for a more comfortable development experience.
## Summary
In the next part, we will work on establishing communication between the Orders module and the Products module.
In this part of the *Modular CRM* tutorial, we've built the functionality inside the Ordering module we created in the [previous part](part-04.md). In the next part, we will work on establishing communication between the Orders module and the Products module.