Skip to content

Commit 5ffd530

Browse files
authored
Merge pull request #148 from aspnetzero/Review-Extending-Existing-Entities
Create Extending-Existing-Entities-Core-Angular.md
2 parents 3b54b2b + d114422 commit 5ffd530

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Extending Existing Entities
2+
3+
## Introduction
4+
5+
This tutorial is a step by step guide to learn **how to add new properties to existing entities**, from database layer to UI layer.
6+
7+
In ASP.NET Zero, **Tenant**, **User** and **Role** entities are **abstract** in the framework, others are not. There are some differences between them. So, we separated it into two sections.
8+
9+
*Note: We assume that you have created your project as described in the Getting Started document*
10+
11+
* [Getting Started Angular](Getting-Started-Angular)
12+
13+
14+
## Extending Abstract Entities
15+
16+
As a sample, we will work on **User** entity. We want to add an **Address** property to the User.
17+
18+
### Add New Property To User
19+
20+
Open Authorization\\Users\\**User.cs** (in .Core project) and add the new property:
21+
22+
```csharp
23+
public class User : AbpUser<User>
24+
{
25+
//...existing code
26+
27+
public virtual string Address { get; set; }
28+
}
29+
```
30+
31+
Here, we hided existing code in the User class to show it simpler. You can add Address property after existing properties.
32+
33+
### Add Migration
34+
35+
Since we added new property, our database schema is changed. Whenever we change our entities, we should add a new database migration. Open Package Manager Console, select ".EntityFrameworkCore" project as default project in combobox and write new migration code:
36+
37+
Add-Migration "Added_Address_To_User"
38+
39+
This will create a new Entity Framework migration class:
40+
41+
```csharp
42+
public partial class Added_Address_To_User : Migration
43+
{
44+
protected override void Up(MigrationBuilder migrationBuilder)
45+
{
46+
migrationBuilder.AddColumn<string>(
47+
name: "Address",
48+
table: "AbpUsers",
49+
nullable: true);
50+
}
51+
52+
protected override void Down(MigrationBuilder migrationBuilder)
53+
{
54+
migrationBuilder.DropColumn(
55+
name: "Address",
56+
table: "AbpUsers");
57+
}
58+
}
59+
```
60+
61+
Since it's automatically created, we don't have to know what it does for most cases. Now, we can update our database with this command:
62+
63+
Update-Database
64+
65+
When we check **AbpUsers** table in the database, we can see the new **Address** field:
66+
67+
<img src="images/extend-entities-user-address.png" alt="Address for Users" class="img-thumbnail" width="505" height="102" />
68+
69+
For testing purposes, we can enter some data for existing users by hand.
70+
71+
### Show Address On The UI
72+
73+
Note: The UI part of this document is written for ASP.NET Core Angular version of ASP.NET Zero. For ASP.NET Core MVC & jQuery version, see [related doc](Extending-Existing-Entities-Core.md) .
74+
75+
**GetUsers** method in Authorization\\Users\\**UserAppService.cs** (in .Application project) is used for getting list of users by clients. It returns a list of **UserListDto** (we always use [DTOs](https://aspnetboilerplate.com/Pages/Documents/Data-Transfer-Objects) for client communication). So, we should add the Address property to UserListDto too:
76+
77+
```csharp
78+
public class UserListDto : EntityDto<long>, IPassivable, IHasCreationTime
79+
{
80+
//...existing code
81+
82+
public string Address { get; set; }
83+
}
84+
```
85+
86+
Since UserListDto **auto maps** from User entity, no need to change **UserAppService.GetUsers** method. Now, we can go to UI side to add Address property to the **users table**.
87+
88+
Now we can start ***.Host** project and then run [NSwag](Infrastructure-Angular-NSwag.md).
89+
90+
Open **src\app\admin\users\users.component.html** file and add Address column right after CreationTime column:
91+
92+
```html
93+
//....
94+
<th style="width: 200px" pSortableColumn="creationTime">
95+
{{'CreationTime' | localize}}
96+
<p-sortIcon field="creationTime"></p-sortIcon>
97+
</th>
98+
<th style="width: 200px" pSortableColumn="address">
99+
{{'Address' | localize}}
100+
<p-sortIcon field="address"></p-sortIcon>
101+
</th>
102+
</tr>
103+
</ng-template>
104+
105+
//...
106+
//...
107+
108+
<td style="width: 200px">
109+
<span class="ui-column-title"> {{'CreationTime' | localize}}</span>
110+
{{record.creationTime | momentFormat:'L'}}
111+
</td>
112+
<td style="width: 200px">
113+
<span class="ui-column-title"> {{'address' | localize}}</span>
114+
{{record.address}}
115+
</td>
116+
</tr>
117+
</ng-template>
118+
```
119+
120+
That's all. Now we can start the Angular application and open the **users page**:
121+
122+
<img src="images/extend-entities-user-address-in-table-2.png" alt="Address in table" class="img-thumbnail" width="297" height="240" />
123+
124+
### Add Address On User Create/Edit
125+
126+
We may want to set Address while **creating/editing** a User.
127+
128+
Clients use UserAppService.**GetUserForEdit** method to show user information on edit form. It returns **GetUserForEditOutput** object which contains a **UserEditDto** object that includes user properties.
129+
So, we should add Address to UserEditDto to allow clients to change Address property on create/update:
130+
131+
```csharp
132+
public class UserEditDto : IPassivable
133+
{
134+
//...existing code
135+
136+
public string Address { get; set; }
137+
}
138+
```
139+
140+
Since **UserAppService** use **auto mapping**, no need to manually map Address to the User entity. So, server side code is just that. We can go to UI side to add an Address field to the form:
141+
142+
```html
143+
<div class="form-group">
144+
<label for="Address">{{"Address" | localize}}</label>
145+
<input id="Address" #surnameInput="ngModel" type="text" name="Address" class="form-control" [(ngModel)]="user.address">
146+
</div>
147+
```
148+
149+
This code is written to **src\app\admin\users\create-or-edit-user-modal.component.html**. After adding, new Address field is shown on the create/edit form as shown below:
150+
151+
<img src="images/extend-entities-user-address-in-edit-form.png" alt="Address on user edit form" class="img-thumbnail" width="614" height="906" />
152+
153+
## Extending Non-Abstract Entities
154+
155+
As a sample, we will work on **Edition** entity.
156+
157+
### Derive From Edition Entity
158+
159+
Since Edition is **not abstract** in the framework, we can not direcly add new properties to the Edition class. Instead, we should use OOP patterns like **inheritance** or **composition**. Since inheritance will be simpler, we can create a new class deriving from Edition entity. There is already an entity derived from edition: **SubscribableEdition**. Let we look how it is implemented, and show the steps of how **AnnualPrice** field is added (SubscribableEdition class is under Editions folder under the .Core project):
160+
161+
```csharp
162+
public class SubscribableEdition : Edition
163+
{
164+
//...other fields
165+
166+
public decimal? AnnualPrice { get; set; }
167+
168+
//...other fields
169+
```
170+
171+
172+
Notice that a DbSet property for SubscribableEdition entity is added to DbContext class defined in .EntityFrameworkCore project.
173+
174+
```csharp
175+
public class ProjectNameDbContext : AbpZeroDbContext<Tenant, Role, User>
176+
{
177+
public virtual DbSet<SubscribableEdition> SubscribableEditions { get; set; }
178+
179+
//...other entities
180+
181+
public ProjectNameDbContext()
182+
: base("Default")
183+
{
184+
185+
}
186+
187+
//...other codes
188+
}
189+
```
190+
191+
Also, you can see that a new migration is added after SubscribableEdition is created.
192+
193+
### Show AnnualPrice On The UI
194+
195+
Editions\\**EditionAppService.cs** (in .Application project) is used to get list of editions by clients. It returns a list of **EditionListDto** (we always use [DTOs](https://aspnetboilerplate.com/Pages/Documents/Data-Transfer-Objects) for client communication). So, AnnualPrice property is added to EditionListDto too:
196+
197+
```csharp
198+
public class EditionListDto : EntityDto
199+
{
200+
//...other fields
201+
202+
public decimal? AnnualPrice { get; set; }
203+
204+
//...other fields
205+
}
206+
```
207+
208+
**Auto mapping from MyEdition** is already added. No need to change **EditionAppService.GetEditions** method by the help of auto mapping. Now, you can go to UI side to see how AnnualPrice property is used in the **editions** table in **src\app\admin\editions\editions.component.html**:
209+
210+
```javascript
211+
<td>
212+
<span class="ui-column-title">{{'Price' | localize}}</span>
213+
<span *ngIf="record.monthlyPrice || record.annualPrice">
214+
$ {{record.monthlyPrice}} {{"Monthly" | localize }} / $ {{record.annualPrice}} {{"Annual" | localize }}
215+
</span>
216+
<span *ngIf="!record.monthlyPrice && !record.annualPrice">
217+
{{"Free" | localize}}
218+
</span>
219+
</td>
220+
```
221+
222+
That's all. You can check **EditionCreateDto** and **src\app\admin\editions\create-edition-modal.component.html** to see how the AnnualPrice fields is set during edition creation.

0 commit comments

Comments
 (0)