|
| 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