|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,40 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Creating the Solution", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-01" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> This tutorial is suitable for those who have an ABP Team or a higher [license](https://abp.io/pricing). |
|||
|
|||
## About This Tutorial |
|||
|
|||
> In this tutorial, you will use the [ABP Suite](../../suite/index.md) to generate everything you need to build the **BookStore** application, such as [*Entities*](../../framework/architecture/domain-driven-design/entities.md), [*Domain Services*](../../framework/architecture/domain-driven-design/domain-services.md), [*Application Services*](../../framework/architecture/domain-driven-design/application-services.md), *CRUD pages* and more... |
|||
|
|||
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: |
|||
|
|||
* **{{DB_Value}}** as the database provider. |
|||
* **{{UI_Value}}** as the UI Framework. |
|||
|
|||
This tutorial is organized as the following parts: |
|||
|
|||
- [Part 1: Creating the Solution](part-01.md) |
|||
- [Part 2: Creating the Books](part-02.md) |
|||
- [Part 3: Creating the Authors](part-03.md) |
|||
- [Part 4: Book to Author Relation](part-04.md) |
|||
- [Part 5: Customizing the Generated Code](part-05.md) |
|||
|
|||
### 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/suite-bookstore-mvc-ef). |
|||
@ -0,0 +1,39 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 1: Creating the Solution |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Creating the Books", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-02" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Before starting the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../../get-started/layered-web-application.md). |
|||
|
|||
You can use the following configurations: |
|||
|
|||
* **Solution Name:** `Acme.BookStore` |
|||
* **UI Framework:** {{if UI=="MVC}} ASP.NET Core MVC / Razor Pages {{end}} |
|||
* **UI Theme:** LeptonX |
|||
* **Mobile Framework:** None |
|||
* **Database Provider:** {{if DB=="EF"}} Entity Framework Core {{end}} |
|||
|
|||
You can select the other options based on your preference. |
|||
|
|||
> **Please complete the [Get Started](../../get-started/layered-web-application.md) guide and run the web application before going further.** |
|||
|
|||
The initial solution structure should be like the following in the ABP Studio's [Solution Explorer](../../studio/solution-explorer.md): |
|||
|
|||
 |
|||
|
|||
## Summary |
|||
|
|||
We've created the initial layered monolith solution. In the next part, we will learn how to create entities, and generate CRUD pages based on the specified options (including tests, UI, customizable code support etc.) with [ABP Suite](../../suite/index.md). |
|||
@ -0,0 +1,123 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 2: Creating the Books |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Solution", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-01" |
|||
}, |
|||
"Next": { |
|||
"Name": "Creating the Authors", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-03" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this part, you will create a new entity named `Book` and generate CRUD pages for the related entities with everything that you would normally implement manually (including application services, tests, CRUD pages, database relations and more...) via [ABP Suite](../../suite/index.md) with few clicks. |
|||
|
|||
## Opening the ABP Suite |
|||
|
|||
> Please **stop the application** in ABP Studio's *Solution Runner* panel, if it's currently running, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
After creating the solution in the previous part, now you can open the ABP Suite and start generating CRUD pages. You can select the *ABP Suite -> Open* command on the main menu to open ABP Suite: |
|||
|
|||
 |
|||
|
|||
After clicking the related command, pre-integrated browser of ABP Studio should open, then you can start generating entities and all related codes with a few configurations: |
|||
|
|||
 |
|||
|
|||
## Creating the Book Entity |
|||
|
|||
Let's create a `Book` entity with some properties. First, type `Book` for the *Name* field and leave the other options as is. ABP Suite automatically calculates proper values for the rest of the inputs for you: |
|||
|
|||
 |
|||
|
|||
ABP Suite sets: |
|||
|
|||
* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), |
|||
* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), |
|||
* Primary key type as **Guid**, |
|||
* Plural name, database name, namespace, page title, menu item and more... |
|||
|
|||
You can change the menu-item value as **book** to show a proper icon in the generated UI, and also enable **code customization**, **creating unit & integration tests**, and other options as you wish: |
|||
|
|||
 |
|||
|
|||
After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: |
|||
|
|||
 |
|||
|
|||
Here the details: |
|||
|
|||
* `Name` is **required**, it's a **string** property and maximum length is **128**. |
|||
* `Type` is an **enum** and the enum file path is *\Acme.BookStore.Domain.Shared\Books\BookType.cs* (we will create the file, in the next section). |
|||
* `PublishDate` is a **DateTime** property and **not nullable**. |
|||
* `Price` is a **float** property and **required**. |
|||
|
|||
You can leave the other configurations as default. |
|||
|
|||
> ABP Suite allows you to define properties with a great range of options, for example, you can specify the property type as *string*, *int*, *float*, *Guid*, *DateTime*, and even *File* (for file upload) and also you can set any options while defining your properties, such as specifying it as *required*, or *nullable*, setting *max-min length*, *default value* and more... |
|||
|
|||
While defining the properties, you defined a *Type* property with the type of *enum*. ABP Suite asks for an enum path to read the enum file, and set the namespace, and enum name in the generated code. |
|||
|
|||
For that purpose, you should create a `BookType` enum in the `Acme.BookStore.Domain.Shared` project under the **Books** folder as follows: |
|||
|
|||
```csharp |
|||
namespace Acme.BookStore.Books; |
|||
|
|||
public enum BookType |
|||
{ |
|||
Undefined, |
|||
Adventure, |
|||
Biography, |
|||
Dystopia, |
|||
Fantastic, |
|||
Horror, |
|||
Science, |
|||
ScienceFiction, |
|||
Poetry |
|||
} |
|||
``` |
|||
|
|||
Then, you can specify the enum path for the `Type` property in ABP Suite like in the following figure: |
|||
|
|||
 |
|||
|
|||
> After you select the enum file, ABP Suite automatically sets the namespace and enum name, and lists your enum values in the next section. You can change these values, but for now, you can leave as is. |
|||
|
|||
After that, you can click the **Save and Generate** button to start the code generation process: |
|||
|
|||
 |
|||
|
|||
ABP Suite will generate the necessary code for you. It generates: |
|||
|
|||
* `Book` entity (also `BookBase` class, which is allow you customizing the generated entity), |
|||
* Repository implementation (`EfCoreBookRepository` class), |
|||
* `BookManager` domain service, |
|||
* Input & Output **DTOs** and **application service** implementations (`IBookAppService` & `BookAppService`), |
|||
* **Unit & integration tests**, |
|||
* A new **migration** (and also applies to the database), |
|||
* All related **permission**, **object mapping** and **navigation menu item** configurations, |
|||
* and all required **UI components and pages**. |
|||
|
|||
It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can see the Books page in the following figure with a single record: |
|||
|
|||
 |
|||
|
|||
On this page, you can create a new book, update an existing book, delete a book, export all records (or the filtered records) to excel, filter the records by using the advanced filter section, bulk delete multiple records and so on. |
|||
|
|||
## Summary |
|||
|
|||
In this part, you've created a new entity named `Book` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. |
|||
@ -0,0 +1,79 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 3: Creating the Authors |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Books", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-02" |
|||
}, |
|||
"Next": { |
|||
"Name": "Book to Author Relation", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-04" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous part, you have created the `Book` entity and in this part, you will create a new entity named `Author` and generate all necesssary code via [ABP Suite](../../suite/index.md) with few clicks. After creating the `Author` entity, you will be establishing [one-to-many relationship](../../suite/generating-crud-page.md#step-by-step-creating-a-navigation-property-with-1-to-many-relationship) with *Book* and *Author* entities, in the next part. |
|||
|
|||
## Creating the Author Entity |
|||
|
|||
After generating the all necessary code for the `Book` entity, and testing the *Books* page, by building & starting the application, now you can continue with creating the `Author` entity. |
|||
|
|||
> Before, creating the `Author` entity, please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
Click the entity selection box in the top right of the *CRUD page generation* page, and select the *-New entity-*: |
|||
|
|||
 |
|||
|
|||
Then, you can type `Author` for the *Name* field and leave the other options as is (you can change the menu icon as **pen** for a proper menu icon, and/or other options, if you wish). ABP Suite automatically calculates proper values for the rest of the inputs for you: |
|||
|
|||
 |
|||
|
|||
ABP Suite sets: |
|||
|
|||
* Entity type as **master** (ABP Suite allows you to establish [master-child relationship](../../suite/creating-master-detail-relationship.md)), |
|||
* Base class as **FullAuditedAggregateRoot** ([see other possible values](../../framework/architecture/domain-driven-design/entities.md)), |
|||
* Primary key type as **Guid**, |
|||
* Plural name, database name, namespace, page title, menu item and more... |
|||
* Also, it enables **code customization**, **UI code generation**, **unit & integration test generation** and **bulk delete** by default. |
|||
|
|||
After, specifying the entity metadata, open the *Properties* tab and create the properties shown in the following figure: |
|||
|
|||
 |
|||
|
|||
Here the details: |
|||
|
|||
* `Name` is **required**, it's a **string** property, and it's a **textarea**. Minimum length is **2** and maximum length is **128**. |
|||
* `BirthDate` is a **DateTime** property and **not nullable**. |
|||
* `ShortBio` is a **string** property, it's **required**, maximum length is **256**. |
|||
|
|||
You can leave the other configurations as default. |
|||
|
|||
> **Note:** All properties are marked as **filterable** by default, and they appear in the advanced filter section because of that. You can set any properties you want as **not filterable** and then the related property will be removed from the advanced filter section and code will be generated accordingly. |
|||
|
|||
You can click the **Save and Generate** button to start the code generation process: |
|||
|
|||
 |
|||
|
|||
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, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser and try to add a new author: |
|||
|
|||
 |
|||
|
|||
As you can see, the *Name* field is **required**, the *Birth Date* field shows a datepicker, and the *Short Bio* field is also **required** and it's a **textarea**. You just configured how you want your properties with some options (for example, setting the short bio as **textarea** and **required**), and ABP Suite generated code according that. |
|||
|
|||
## Summary |
|||
|
|||
In this part, you've created a new entity named `Author` and generated the necessary code for it with [ABP Suite](../../suite/index.md) with a few clicks. ABP Suite generated the all code for you, including the **entity**, **application service**, **database relations**, **unit & integration tests**, **UI** and **defined the custom hooks for code customization**. |
|||
|
|||
In the next part, you will establish [one-to-many relation](../../suite/generating-crud-page.md) between the `Book` and `Author` entities. |
|||
@ -0,0 +1,168 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 4: Book to Author Relation |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the Author", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-03" |
|||
}, |
|||
"Next": { |
|||
"Name": "Customizing the Generated Code", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-05" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous parts, you have created the `Book` and `Author` entities (& generated code for all functionalities) for the book store application. However, currently there is no relation between these entities. |
|||
|
|||
In this part, you will establish to **one-to-many relation** between the `Book` and `Author` entities. |
|||
|
|||
## Establishing Relations with ABP Suite |
|||
|
|||
ABP Suite allows establishing both **one-to-many** and [many-to-many](../../suite/creating-many-to-many-relationship.md) relationships. |
|||
|
|||
In this tutorial, you will only establish **one-to-many relation** between `Book` and `Author` entities. It's pretty straightforward to establish a relationship with ABP Suite. You should just need to navigate to the *Navigations* tab, and provide the metadata for navigation property (1-n) or navigation collection (n-n) relations. |
|||
|
|||
## Creating Book to Author Relationship |
|||
|
|||
> Please **stop the application** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it might need to build the solution in some steps and running the solution prevents to build it. |
|||
|
|||
To establish **one-to-many relations** between *Book* and *Author* entities, select the `Book` entity from the entity selection box on the top-right of the *CRUD page generation* page: |
|||
|
|||
 |
|||
|
|||
Then, you can open the *Navigations* tab, and click the **Add navigation property (1-n)** button. After that, a navigation property model will open, and you can fill the fields like in the following figure: |
|||
|
|||
 |
|||
|
|||
Here is the details: |
|||
|
|||
* Selected the entity as `Author`. (ABP Suite will establish one-to-many relation between *Book* and *Author* entities with this configuration) |
|||
* Set the property name as *AuthorId*, it will be set as foreign-key restriction in the database and all related database configurations will be made by ABP Suite. |
|||
* Selected the display property as *Name*, this will be used in the dropdown component to set an author with a book & also it will be shown in the datatable of the *Books* page. |
|||
* Also, made the relation **required** and set it **filterable** so books can be filterable by authors. |
|||
|
|||
After, specifying the metadata, you can click the *Ok* button to close the modal. Then, click the **Save and generate** button to start code generation process. ABP Suite will establish one-to-many relationship between the entities, and will generate all necessary code automatically: |
|||
|
|||
 |
|||
|
|||
It will take some time to complete the process. After the process is completed, you will see a success message, you can click the *Ok* button, and build & start the application by clicking the *Run -> Build & Start* button in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and *Browse* on the application to open it in the ABP Studio's pre-integrated browser. You can first create an author and then create a book with the author for testing: |
|||
|
|||
 |
|||
|
|||
Also, notice that, in the advanced filter section, there is an **Author** dropdown, which you can use to filter books by authors (remember you set **filterable** while defining navigation property and thanks to that, ABP Suite generated the code accordingly): |
|||
|
|||
 |
|||
|
|||
## Unit & Integration Tests |
|||
|
|||
Since you completed the bookstore application, now we can check the generated tests, and run them to see if all of them pass or not. |
|||
|
|||
There are several test projects in the solution: |
|||
|
|||
 |
|||
|
|||
> Test projects slightly differs based on your UI and Database selection. For example, if you select MongoDB, then the `Acme.BookStore.EntityFrameworkCore.Tests` will be `Acme.BookStore.MongoDB.Tests`. |
|||
|
|||
ABP Suite generated unit & integration tests, for the `Book` & `Author` entities. If you open the **Test explorer** in your IDE, you will see the following tests are generated: |
|||
|
|||
 |
|||
|
|||
ABP Suite generated tests for repository implementations & application service implementations for the generated code, if you enable *Create unit & integration tests* option, while creating the entity. Since, you already did that in the previous parts, it generated the all required tests for the entities. |
|||
|
|||
Let's examine one of the generated test classes. Open the *BooksAppServiceTests* (under the *test/Acme.BookStore.Application.Tests/Books/BookApplicationTests.cs*) and check the `CreateAsync` method: |
|||
|
|||
```csharp |
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
// Arrange |
|||
var input = new BookCreateDto |
|||
{ |
|||
Name = "6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3", |
|||
Type = default, |
|||
PublishDate = new DateTime(2006, 8, 21), |
|||
Price = 754882891, |
|||
AuthorId = Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583") |
|||
}; |
|||
|
|||
// Act |
|||
var serviceResult = await _booksAppService.CreateAsync(input); |
|||
|
|||
// Assert |
|||
var result = await _bookRepository.FindAsync(c => c.Id == serviceResult.Id); |
|||
|
|||
result.ShouldNotBe(null); |
|||
result.Name.ShouldBe("6c3d1eda8bf04852b7bd5dfdbbd93224b252478c2e474d4c8faf24fa6b182168ca830d4f80e64e4a8e363f33e151d1d34a04be4709274c7fbf2214f9bb3a16c3"); |
|||
result.Type.ShouldBe(default); |
|||
result.PublishDate.ShouldBe(new DateTime(2006, 8, 21)); |
|||
result.Price.ShouldBe(754882891); |
|||
} |
|||
``` |
|||
|
|||
ABP Suite; |
|||
|
|||
* Create the `BookCreateDto` input DTO object, and fill its values with dummy data to simulate creating a book, |
|||
* Then, it calls the `IBooksAppService.CreateAsync` method to create a book, |
|||
* And finally, asserts the returned result to see if it's as expected or not. |
|||
|
|||
Notice, also the *AuthorId* is set in the `BookCreateDto` object. At that point, you might ask yourself that I haven't created the author with that ID before, should not it throw exception? |
|||
|
|||
No, it will not throw an exception, because ABP Suite also generates simple dummy data for the entities just for the tests! You can see the test data seed contributors under the *Acme.BookStore.Domain.Tests* project: |
|||
|
|||
 |
|||
|
|||
Here is the content of the `AuthorsDataSeedContributor.SeedAsync` method: |
|||
|
|||
```csharp |
|||
public async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
if (IsSeeded) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
await _authorRepository.InsertAsync(new Author |
|||
( |
|||
id: Guid.Parse("602460f6-df6e-456a-89d9-8c5870dfc583"), |
|||
name: "d7bbb3bff0d54ad799477298c4572e9c05fd1175ab21416da17d0001e2b697cd7fef99fdb4414f26a05789667a97442bd65865510ba34c3599e874ccf08b45e4", |
|||
birthDate: new DateTime(2010, 2, 11), |
|||
shortBio: "3c2ff43c18e34d7b9ad3f1b9c444cbb000f90808d3774cb6b7702b957f472d74048597f93df744f6a6fdf507be428e016edec982f1174e09b124982cbc40156290ce6bc9fd7b49b4972741956cc847891cb55ad0942f4534b90aa0561d3e0c200340b613c7ad40c38b4b2f2c39298169a853473faed34341a130b31e1eb57e92" |
|||
)); |
|||
|
|||
await _authorRepository.InsertAsync(new Author |
|||
( |
|||
id: Guid.Parse("6ea5a6b2-919e-4334-9728-13f4872e5e0e"), |
|||
name: "fd332fb58f184716962b08fbaa92f1c3e0963d843ba34c82bb5409517f60da3727c43b05e8d4490f996c5d19265962e53a69ed5e3e144509aad1441e37ce5081", |
|||
birthDate: new DateTime(2010, 6, 10), |
|||
shortBio: "b7808946c46c42e3935c4d8203d82973cfb98c5d81644f1da4ce1e643767849e23e0eb12a92f48be8f7eec0c07aefa043721fdd3fea542cfa644d2b7d428dc8842647180ef8a47139e097f6674c4f0d86c46765c406042a2a858865cb112ecd78d9ef6f5843e444994641f924a38a2d24ee4e212d41444888d3c0861af0cf9dd" |
|||
)); |
|||
|
|||
await _unitOfWorkManager!.Current!.SaveChangesAsync(); |
|||
|
|||
IsSeeded = true; |
|||
} |
|||
``` |
|||
|
|||
Since ABP Suite generated the test data seed contributors for each entity, you have initial data while testing your services. Also, as you would notice, the id in this example (*602460f6-df6e-456a-89d9-8c5870dfc583*) is same as the *authorId* field in the `BooksAppServiceTests.CreateAsync` method. |
|||
|
|||
Let's execute all tests, and see the results: |
|||
|
|||
 |
|||
|
|||
## Summary |
|||
|
|||
So far, you have created the all functionality for the bookstore application without needing to write any single line of code. ABP Suite generated the entities, application services, UI components, unit & integration tests and more... |
|||
|
|||
In the next part, you will write some code and modify the ABP Suite's generated code by writing the code in the specified hookpoints. Thanks to [ABP Suite's Customized Code Support](../../suite/customizing-the-generated-code.md), in the next generation, our custom code will not be overridden and will be preserved. |
|||
@ -0,0 +1,123 @@ |
|||
# Web Application Development (with ABP Suite) Tutorial - Part 5: Customizing the Generated Code |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC"], |
|||
"DB": ["EF"] |
|||
} |
|||
```` |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Book to Author Relation", |
|||
"Path": "tutorials/book-store-with-abp-suite/part-04" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
So far, you have created the all functionality for the bookstore application without needing to write any single line of code. In this part, let's write some code and check one of the great features of the ABP Suite, which is [Customizable Code Support](../../suite/customizing-the-generated-code.md). |
|||
|
|||
## Customizable Code Support |
|||
|
|||
ABP Suite allows you to customize the generated code blocks and preserve your custom code changes in the next CRUD Page Generation. It specifies hook points to allow adding custom code blocks. Then, the code written by you to these hook points will be respected and will not be overridden in the next CRUD Page Generation. |
|||
|
|||
To enable custom code support, you should check the *Customizable code* option in the **CRUD Page Generation** page (it's selected by default), and you enabled it for both entities: |
|||
|
|||
 |
|||
|
|||
## Custom Code Hookpoints |
|||
|
|||
When you enable the *custom code support*, ABP Suite adds some hookpoints that you can write your own custom code without worrying about, are my codes being overridden with the next CRUD page generation. |
|||
|
|||
On the C# side, ABP Suite adds abstract base classes for entities, application services, interfaces, domain services and so on... (and partial classes for interfaces) |
|||
|
|||
You can write your custom code in those classes (with the `*.Extended.cs` extension) and next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected): |
|||
|
|||
 |
|||
|
|||
> For example, you can create a new repository method like in the example above, and in the next CRUD page generation, you will ABP Suite won't override your custom code. |
|||
|
|||
On the UI side, ABP Suite provides convenient comment placeholders within pages for MVC, Blazor, and Angular UIs. These comment sections serve as hook points where you can add your custom code. |
|||
|
|||
For example, if you open the *Books/Index.cshtml* file in your IDE, you will see those placeholders like following: |
|||
|
|||
```xml |
|||
<!-- Code omitted for brevity --> |
|||
|
|||
@section styles |
|||
{ |
|||
@*//<suite-custom-code-block-1>*@ |
|||
@*//</suite-custom-code-block-1>*@ |
|||
} |
|||
|
|||
<!-- ... --> |
|||
``` |
|||
|
|||
You can write your custom codes between the _**<suite-custom-code-block-n></suite-custom-code-block-n>**_ placeholders and you can also extend these placeholders by customizing the [ABP Suite templates](../../suite/editing-templates.md). |
|||
|
|||
> For more information, please refer to [Customizing the Generated Code documentation](../../suite/customizing-the-generated-code.md) |
|||
|
|||
## Implementing Custom Code |
|||
|
|||
Let's see the custom code support in action. We can demonstrate this feature with an easy example. |
|||
|
|||
Assume that we want to show the author's name with his abbreviated name. For example, for the author *John Ronald Reuel Tolkien*, we want to show the name *John Ronald Reuel Tolkien (a.k.a J.R.R.T)*. Achieving that is pretty straightforward. |
|||
|
|||
We just need to open the *src/Acme.BookStore.Application/Books/BooksAppService.Extended.cs* file and override the base `GetListAsync` method, which is called on the books page: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace Acme.BookStore.Books |
|||
{ |
|||
public class BooksAppService : BooksAppServiceBase, IBooksAppService |
|||
{ |
|||
//<suite-custom-code-autogenerated> |
|||
public BooksAppService(IBookRepository bookRepository, BookManager bookManager, IDistributedCache<BookDownloadTokenCacheItem, string> downloadTokenCache, IRepository<Acme.BookStore.Authors.Author, Guid> authorRepository) |
|||
: base(bookRepository, bookManager, downloadTokenCache, authorRepository) |
|||
{ |
|||
} |
|||
//</suite-custom-code-autogenerated> |
|||
|
|||
//Write your custom code... |
|||
public override async Task<PagedResultDto<BookWithNavigationPropertiesDto>> GetListAsync(GetBooksInput input) |
|||
{ |
|||
var result = await base.GetListAsync(input); |
|||
|
|||
foreach (var book in result.Items) |
|||
{ |
|||
var akaName = book.Author.Name.Split(" ").Select(q => q[0]).JoinAsString("."); |
|||
book.Author.Name += $" (a.k.a {akaName})"; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* Here, we have overridden the `GetListAsync` method and changed its result according to our need. |
|||
* Notice, this class is derieved from the `BooksAppServiceBase` class and implements the `IBooksAppService`. |
|||
* Thus, ABP Suite only modifies the `BooksAppServiceBase` class and implements the necessary services in each generation, but does not generate the files with the `*.Extended` postfixes and let you implement your own custom code. |
|||
* You can create new methods, override an existing method and change its behaviour, add custom hookpoints to extend customization capabilities and more... |
|||
|
|||
Now, we can open ABP Suite and try to regenerate the book entity and see if our custom code is gone or not: |
|||
|
|||
 |
|||
|
|||
After the regeneration has been completed, we can check the **BooksAppService.Extended.cs** file and we should see our custom code is there without any modification. ABP Suite didn't override our custom code, and finally, we can run the application to see the final result: |
|||
|
|||
 |
|||
|
|||
ABP Suite's custom code support is not limited to the backend side. You can also override the UI. You can refer to the [Customizing the Generated Code document](../../suite/customizing-the-generated-code.md) for further info. |
|||
|
|||
## Summary |
|||
|
|||
In this tutorial, you created the bookstore application without needing to write a single line of code with ABP Suite. Then, in this last part, you have written custom code in the specified hookpoints and it did not override by ABP Suite. |
|||