ABP CRUD Cheat Sheet: The Distilled, Essential Steps for Adding an Entity in ASP.Net Boilerplate with Angular
While the process of making Create, Read, Update, and Delete (CRUD) pages in ASP.Net Boilerplate is well documented on their website, and while I did a detailed walk through in my video E19: Be a Hero on Day 1 with ASP.Net Boilerplate (particularly the demo starting at 40:59), I feel the community could use a high level, quick, reference guide for starting the CRUD process from scratch with a new entity: an ABP CRUD Cheat Sheet.
Here are 15 steps broken down by server-side and client-side. They may seem complicated compared to adding an entity to a SharePoint or Power Apps app, but remember the end product is infinitely customizable, and when something goes wrong you can almost always code your way out of it.
To use this reference I imagine you'd want to skim over it like a checklist each time you add a new entity to ensure you didn't forget anything. That's what I plan to do, anyway. I expect it will save me time and effort and reduce the unnecessary rework that comes with missing a step. Hopefully it will for you, too.
Quick note: For readability when you see "Product" substitute your entity name. When you see "Category" substitute a foreign table. When you see "LeesStore", substitute your project name. These were the entities I used in the video, and on my ABP Demo Site, and I feel they read better than [EntitySingular] or [EntityPlural].
A. Server-Side
A1. Add Entity
LeesStore.Core/Products/Product.cs
public class Product : Entity<int> { ...}
: IFullAudited
if you want a history of who created, deleted, and last edited and when: ISoftDelete
if you're not doing auditing and want soft-deletes: IMustHaveTenant
if you're doing multi-tenancy[ForeignKey(nameof(Category))]public int CategoryId { get; set; }
in addition topublic Category Category { get; set; }
if you want the ID of a foreign key[Required]
to strings and foreign entities as needed[MaxLength(255)]
to strings as needed
A2. Add to DbContext
LeesStoreDbContext.cs
public DbSet<Product> Products { get; set; }
- Migrations will show up empty if you forget to do this
A3. Add Migration
Add-Migration "Add-Products"
- Run from Package Manager Console window. Don't forget to set the Default project to [MyProject].EntityFrameworkCore or else you'll get:
"No DbContext was found in assembly 'LeesStore.Web.Host'. Ensure that you're using the correct assembly and that the type is neither abstract nor generic."
- Alternately:
dotnet ef migrations add Add-Products
from the LeesStore.EntityFrameworkCore directory
A4. Update Database
Either:
Update-Database
in Package Manager Console with Default project set per above; ORdotnet ef database update
from the command line; OR- Run the Migrator project, which is required if you are using one of the multi-database multi-tenant solutions (see Multi-Tenancy is Hard: ASP.Net Boilerplate Makes it Easy)
A5. Add a DTO
LeesStore.Application/Products/Dto/ProductDto.cs
[AutoMapFrom(typeof(Product))]
[AutoMapTo(typeof(Product))]
public class ProductDto : EntityDto<int> { … }
- Copy over only fields that need to be updated to prevent overposting attacks
- AutoMapFrom and AutoMap to if it's a simple mapping. For more complex mappings instead add an auto-mapper profile:
public class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>()
.ForMember(x => x.LastModifiedByUsername, opt => opt.MapFrom(i => i.CreatorUser.UserName));
}
}
[MaxLength(255)]
,[Required]
, and other data annotations for server-side validation: IValidatableObject
for custom validation
A6. Register a Permission
LeesStoreAuthorizationProvider.cs
context.CreatePermission(PermissionNames.Pages_Products, L("Products"), multiTenancySides: MultiTenancySides.Host);
- multiTenancySides is optional but you should treat it as required because the default value is both Host AND Tenant, which is probably wrong.
- Add your entity to LeesStore.xml or else localization will look bad in the UI
<text name="Projects" value="Projects" />
A7. Add an AppService
LeesStore.Application/Products/ProductsAppService.cs
[AbpAuthorize(PermissionNames.Pages_Products)]
public class ProductsAppService : AsyncCrudAppService<Product, ProductDto, int, PagedAndSortedResultRequestDto, ProductDto>
- Either
[AbpAuthorize(SomePermission)]
or[AbpAuthorize]
else it won't require authorization (it will be open to the Internet) - The final generic property should be a CreateEntityDto if creating has different bits
- Override
CreateFilteredQuery
if you need to .Include foreign entities e.g.
protected override IQueryable<Product> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
{
return base.CreateFilteredQuery(input)
.Include(i => i.CreatorUser);
}
A8. Run App, See Swagger Update, Rejoice
- Ctrl+F5 (run without debugging starts your app fast)
Client-Side
B1. Update nSwag
- PC: \angular\nswag\refresh.bat
- Mac:
npx nswag run /runtime:NetCore31
B2. Register Service Proxy
src/shared/service-proxies/service-proxy.module.ts
ApiServiceProxies.ProductsServiceProxy,
B3. Update Left-Hand Nav
src\app\layout\sidebar-nav.component.ts
new MenuItem(this.l('Products'), 'Pages.Products', 'local_grocery_store', '/app/products’),
- Replace
local_grocery_store
with an icon from Material Icons
B4. Duplicate Tenant Folder and Find/Replace "Tenant" with "[Entity]" and "tenant" with "[entity]"
- Within every file name; and
- Inside of each file including .ts files and .html files
B5. Update Route
src\app\app-routing.module.ts
{
path: 'products',
component: ProductsComponent,
data: { permission: 'Pages.Products' },
canActivate: [AppRouteGuard]
},
B6. Register new components in app.module.ts
src\app\app.module.ts
- Add all three components to
declarations:
- Add both modal dialogs to
entryComponents:
B7. Fix Fields, Tidy Up UI, Rejoice
Fix columns in the table of the main component and columns in the create and edit components. Fix linting errors, and anything that doesn't run.
Relax. Smile. You did it. Well done! ?
Conclusion
If you found this useful please tweet at @lprichar and let me know. If you found a bug, please let me know! If you'd rather see this as a github page, please tell me, I'm game. Otherwise: I hope this helped and happy coding!