Wednesday, May 20, 2020

ABP CRUD Cheat Sheet: The Distilled, Essential Steps to Add 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
  • : ICreationAudited or : IModificationAudited if you're not doing full audit and want just last modified or creation info
  • : IMustHaveTenant if you're doing multi-tenancy
  • [ForeignKey(nameof(Category))]public int CategoryId { get; set; } in addition to public 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."
Set Default Project in Package Manager Console
  • 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; OR
  • dotnet 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)
Swagger UI

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’),

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

Completed UI for a new product

Conclusion

If you found this useful please tweet at @lprichar, or add a comment and let me know.  If you found a bug, please add a comment.  If you'd rather see this as a github page, please tell me, I'm game.  Otherwise: I hope this helps, and happy coding!

No comments: