ASP.NET Core Angular Tutorial - Part 1
About this tutorial
In this tutorial series, you will build an ABP Commercial application named Acme.BookStore. In this sample project, we will manage a list of books and authors. MongoDB will be used as the ORM provider. And on the front-end side Angular and JavaScript will be used.
Instead of creating this application manually, you can automatically generate the same code using the ABP Suite. However, it is recommended to follow this tutorial to learn the basics of an application development using the ABP Commercial.
The ASP.NET Core Angular tutorial series consists of 3 parts:
- Part-1: Creating the project and book list page (this tutorial)
- Part-2: Creating, updating and deleting books
- Part-3: Integration tests
The completed sample is available: bookstore-angular-mongodb.zip. To be able to download this sample, you need to hold an active ABP Commercial license.
Creating the project
Create a new project named Acme.BookStore where Acme is the company name and BookStore is the project name. You can check out creating a new project document to see how you can create a new project.  We will create the project with ABP CLI. But first of all, we need to login to the ABP Platform to create a commercial project.
Login to the ABP Platform
Run the below command to login with your username, then enter your password when it asks.
abp login <username>
If you get the below error message, it means you are not logged in with a commercial account. You can contact to info@abp.io for the license issues.
Remote server returns '403-Forbidden'. Message: Pro templates require a commercial license! Should login to be able to download a pro template.
Create the project
By running the below command, it creates a new ABP Commercial project with the database provider MongoDB and UI option MVC. To see the other CLI options, check out ABP CLI document.
abp new Acme.BookStore --template app-pro --database-provider mongodb --ui angular

Apply migrations
After creating the project, you need to apply the initial migrations and create the database. To apply migrations, right click on the Acme.BookStore.DbMigrator and click Debug > Start New Instance. This will run the application and apply all migrations. You will see the below result when it successfully completes the process. The application database is  ready!

Alternatively, you can run
Update-Databasecommand in the Visual Studio > Package Manager Console to apply migrations.
Initial database tables

Run the application
To run the project, right click to the   Acme.BookStore.HttpApi.Host  project and click Set As StartUp Project. And run the web project by pressing CTRL+F5 (without debugging and fast) or press F5 (with debugging and slow). You will see the Swagger UI for BookStore API.
Further information, see the running the application section.

To start Angular project, go to the angular folder, open a command line terminal, execute the yarn  command:
yarn
Once all node modules are loaded, execute the yarn start command:
yarn start
The website will be accessible from the following default URL:
http://localhost:4200/
If you see the website's landing page successfully, you can exit Angular hosting by pressing ctrl-c. (We'll later start it again.)
Be aware that, Firefox does not use the Windows Certificate Store, so you'll need to add the self-signed developer certificate to Firefox manually. To do this, open Firefox and navigate to the below URL:
https://localhost:44322/api/abp/application-configuration
If you see the below screen, click the Accept the Risk and Continue button to bypass this warning.
The default login credentials are;
- Username: admin
- Password: 1q2w3E*
Solution structure
This is how the layered solution structure looks like:

Check out the solution structure document to understand the structure in details.
Create the book entity
Domain layer in the startup template is separated into two projects:
- Acme.BookStore.Domaincontains your entities, domain services and other core domain objects.
- Acme.BookStore.Domain.Sharedcontains- constants,- enumsor other domain related objects those can be shared with clients.
Define entities in the domain layer (Acme.BookStore.Domain project) of the solution. The main entity of the application is the Book. Create a class, named Book, in the Acme.BookStore.Domain project as shown below:
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore
{
    public class Book : AuditedAggregateRoot<Guid>
    {
        public string Name { get; set; }
        public BookType Type { get; set; }
        public DateTime PublishDate { get; set; }
        public float Price { get; set; }
        protected Book()
        {
        }
        public Book(Guid id, string name, BookType type, DateTime publishDate, float price) :
            base(id)
        {
            Name = name;
            Type = type;
            PublishDate = publishDate;
            Price = price;
        }
    }
}
- ABP has 2 fundamental base classes for entities: AggregateRootandEntity. Aggregate Root is one of the Domain Driven Design (DDD) concepts. See entity document for details and best practices.
- Bookentity inherits- AuditedAggregateRootwhich adds some auditing properties (- CreationTime,- CreatorId,- LastModificationTime... etc.) on top of the- AggregateRootclass.
- Guidis the primary key type of the- Bookentity.
BookType enum
Create the BookType enum in the Acme.BookStore.Domain.Shared project:
namespace Acme.BookStore
{
    public enum BookType
    {
        Undefined,
        Adventure,
        Biography,
        Dystopia,
        Fantastic,
        Horror,
        Science,
        ScienceFiction,
        Poetry
    }
}
Add book entity to the DbContext
Add a IMongoCollection<Book> Books property to the BookStoreMongoDbContext inside the Acme.BookStore.MongoDB project:
public class BookStoreMongoDbContext : AbpMongoDbContext
{
        public IMongoCollection<AppUser> Users => Collection<AppUser>();
        public IMongoCollection<Book> Books => Collection<Book>();//<--added this line-->
        //...
}
Add seed (sample) data
Adding sample data is optional, but it's good to have initial data in the database for the first run. ABP provides a data seed system. Create a class deriving from the IDataSeedContributor in the *.Domain project:
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
    public class BookStoreDataSeederContributor
        : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<Book, Guid> _bookRepository;
        private readonly IGuidGenerator _guidGenerator;
        public BookStoreDataSeederContributor(
            IRepository<Book, Guid> bookRepository,
            IGuidGenerator guidGenerator)
        {
            _bookRepository = bookRepository;
            _guidGenerator = guidGenerator;
        }
        public async Task SeedAsync(DataSeedContext context)
        {
            if (await _bookRepository.GetCountAsync() > 0)
            {
                return;
            }
            await _bookRepository.InsertAsync(
                new Book(
                    id: _guidGenerator.Create(),
                    name: "1984",
                    type: BookType.Dystopia,
                    publishDate: new DateTime(1949, 6, 8),
                    price: 19.84f
                )
            );
            await _bookRepository.InsertAsync(
                new Book(
                    id: _guidGenerator.Create(),
                    name: "The Hitchhiker's Guide to the Galaxy",
                    type: BookType.ScienceFiction,
                    publishDate: new DateTime(1995, 9, 27),
                    price: 42.0f
                )
            );
        }
    }
}
Create the application service
The next step is to create an application service to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
- Acme.BookStore.Application.Contractsmainly contains your- DTOs and application service interfaces.
- Acme.BookStore.Applicationcontains the implementations of your application services.
BookDto
Create a DTO class named BookDto into the Acme.BookStore.Application.Contracts project:
using System;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore
{
    public class BookDto : AuditedEntityDto<Guid>
    {
        public string Name { get; set; }
        public BookType Type { get; set; }
        public DateTime PublishDate { get; set; }
        public float Price { get; set; }
    }
}
- DTO classes are used to transfer data between the presentation layer and the application layer. See the Data Transfer Objects document for more details.
- BookDtois used to transfer book data to the presentation layer in order to show the book information on the UI.
- BookDtois derived from the- AuditedEntityDto<Guid>which has audit properties just like the- Bookclass defined above.
It will be needed to map Book entities to BookDto objects while returning books to the presentation layer. AutoMapper library can automate this conversion when you define the proper mapping. The startup template comes with AutoMapper configured, so you can just define the mapping in the BookStoreApplicationAutoMapperProfile class in the Acme.BookStore.Application project:
using AutoMapper;
namespace Acme.BookStore
{
    public class BookStoreApplicationAutoMapperProfile : Profile
    {
        public BookStoreApplicationAutoMapperProfile()
        {
            CreateMap<Book, BookDto>();
        }
    }
}
CreateUpdateBookDto
Create a DTO class named CreateUpdateBookDto into the Acme.BookStore.Application.Contracts project:
using System;
using System.ComponentModel.DataAnnotations;
namespace Acme.BookStore
{
    public class CreateUpdateBookDto
    {
        [Required]
        [StringLength(128)]
        public string Name { get; set; }
        [Required]
        public BookType Type { get; set; } = BookType.Undefined;
        [Required]
        public DateTime PublishDate { get; set; }
        [Required]
        public float Price { get; set; }
    }
}
- This DTOclass is used to get book information from the user interface while creating or updating a book.
- It defines data annotation attributes (like [Required]) to define validations for the properties.DTOs are automatically validated by the ABP framework.
Next, add a mapping in BookStoreApplicationAutoMapperProfile from the CreateUpdateBookDto object to the Book entity with the CreateMap<CreateUpdateBookDto, Book>(); command:
using AutoMapper;
namespace Acme.BookStore
{
    public class BookStoreApplicationAutoMapperProfile : Profile
    {
        public BookStoreApplicationAutoMapperProfile()
        {
            CreateMap<Book, BookDto>();
            CreateMap<CreateUpdateBookDto, Book>(); //<--added this line-->
        }
    }
}
IBookAppService
Create an interface named IBookAppService in the Acme.BookStore.Application.Contracts project:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
    public interface IBookAppService : 
        ICrudAppService< //Defines CRUD methods
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books
            CreateUpdateBookDto, //Used to create a new book
            CreateUpdateBookDto> //Used to update a book
    {
    }
}
- Defining interfaces for the application services are not required by the framework. However, it's suggested as a best practice.
- ICrudAppServicedefines common CRUD methods:- GetAsync,- GetListAsync,- CreateAsync,- UpdateAsyncand- DeleteAsync. It's not required to extend it. Instead, you could inherit from the empty- IApplicationServiceinterface and define your own methods manually.
- There are some variations of the ICrudAppServicewhere you can use separated DTOs for each method.
BookAppService
Implement the IBookAppService as named BookAppService in the Acme.BookStore.Application project:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
    public class BookAppService : 
        CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
                       CreateUpdateBookDto, CreateUpdateBookDto>,
        IBookAppService
    {
        public BookAppService(IRepository<Book, Guid> repository) 
            : base(repository)
        {
        }
    }
}
- BookAppServiceis derived from- CrudAppService<...>which implements all the CRUD (create, read, update, delete) methods defined above.
- BookAppServiceinjects- IRepository<Book, Guid>which is the default repository for the- Bookentity. ABP automatically creates default repositories for each aggregate root (or entity). See the repository document.
- BookAppServiceuses- IObjectMapperto map- Bookobjects to- BookDtoobjects and- CreateUpdateBookDtoobjects to- Bookobjects. The Startup template uses the AutoMapper library as the object mapping provider. We have defined the mappings before, so it will work as expected.
Auto API Controllers
We normally create Controllers to expose application services as HTTP API endpoints. This allows browsers or 3rd-party clients to call them via AJAX. ABP can automagically configures your application services as MVC API Controllers by convention.
Swagger UI
The startup template is configured to run the Swagger UI using the Swashbuckle.AspNetCore library. Run the application by pressing CTRL+F5 and navigate to https://localhost:<port>/swagger/ on your browser. (Replace <port> with your own port number.)
You will see some built-in service endpoints as well as the Book service and its REST-style endpoints:

Swagger has a nice interface to test the APIs. You can try to execute the [GET] /api/app/book API to get a list of books.
Localize the menu items
Localization texts are located under the Localization/BookStore folder of the Acme.BookStore.Domain.Shared project:

Open the en.json (English translations) file and add the below localization texts to the end of the file:
{
  "Culture": "en",
  "Texts": {
    "Menu:Home": "Home",
    "Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information visit abp.io.",
    "EndDate": "End date",
    "StartDate": "Start date",
    "Permission:Dashboard": "Dashboard",
    "Menu:Dashboard": "Dashboard",
    "Dashboard": "Dashboard",
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books",
    "Actions": "Actions",
    "Edit": "Edit",
    "PublishDate": "Publish date",
    "NewBook": "New book",
    "Name": "Name",
    "Type": "Type",
    "Price": "Price",
    "CreationTime": "Creation time",
    "AreYouSureToDelete": "Are you sure you want to delete this item?"
  }
}
- ABP's localization system is built on ASP.NET Core's standard localization system and extends it in many ways. See the localization document for details.
- Localization key names are arbitrary. You can set any name. As a best practice, we prefer to add Menu:prefix for menu items to distinguish from other texts. If a text is not defined in the localization file, it fallbacks to the localization key (as ASP.NET Core's standard behavior).
Angular development
Create the books page
It's time to create something visible and usable! There are some tools that we will use when developing ABP Angular frontend application:
- Angular CLI will be used to create modules, components and services.
- NGXS will be used as the state management library.
- Ng Bootstrap will be used as the UI component library.
- Visual Studio Code will be used as the code editor (you can use your favorite editor).
Install NPM packages
Open a new command line interface (terminal window) and go to your angular folder and then run yarn command to install NPM packages:
yarn
BooksModule
Run the following command line to create a new module, named BookModule:
yarn ng generate module book --routing true

Routing
Open the app-routing.module.ts file in src\app folder. Add the new import and add a route as shown below
import { ApplicationLayoutComponent } from '@volo/abp.ng.theme.lepton'; //==> added this line to imports <==
//...added books path with the below to the routes array
{
  path: 'books',
  component: ApplicationLayoutComponent,
  loadChildren: () => import('./book/book.module').then(m => m.BookModule),
  data: {
    routes: {
      name: '::Menu:Books',
      iconClass: 'fas fa-book'
    } as ABP.Route
  },
}
- The ApplicationLayoutComponentconfiguration sets the application layout to the new page. We added thedataobject. Thenameis the menu item name and theiconClassis the icon of the menu item.
Book list component
Run the command below on the terminal in the root folder to generate a new component, named book-list:
yarn ng generate component book/book-list

Open book.module.ts file in the app\book folder and replace the content as below:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookRoutingModule } from './book-routing.module';
import { BookListComponent } from './book-list/book-list.component';
import { SharedModule } from '../shared/shared.module'; //<== added this line ==>
@NgModule({
  declarations: [BookListComponent],
  imports: [
    CommonModule,
    BookRoutingModule,
    SharedModule, //<== added this line ==>
  ],
})
export class BookModule {}
- We imported SharedModuleand added toimportsarray.
Open book-routing.module.ts  file in the app\book folder and replace the content as below:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==>
// <== replaced routes ==>
const routes: Routes = [
  {
    path: '',
    component: BookListComponent,
  },
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class BookRoutingModule { }
- We imported BookListComponentand replacedroutesconst.
Run yarn start and wait for Angular to serve the application:
yarn start
Open the browser and navigate to http://localhost:4200/books. We'll see book-list works! text on the books page:

Create BookState
Run the following command in the terminal to create a new state, named BooksState:
npx @ngxs/cli --name book --directory src/app/book
- This command creates book.state.tsandbook.actions.tsfiles in thesrc/app/book/statefolder. See the NGXS CLI documentation.
Import the BookState to the app.module.ts in the src/app folder and then add the BookState to forRoot static method of NgxsModule as an array element of the first parameter of the method.
// ...
import { BookState } from './book/state/book.state'; //<== imported BookState ==>
@NgModule({
  imports: [
    // other imports
    NgxsModule.forRoot([BookState]), //<== added BookState ==>
    //other imports
  ],
  // ...
})
export class AppModule {}
Generate proxies
ABP CLI provides generate-proxy command that generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running generate-proxy command, your host must be up and running. See the CLI documentation
Run the following command in the angular folder:
abp generate-proxy --module app

The generated files looks like below:

GetBooks Action
Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. See NGXS Actions documentation.
Open the book.actions.ts file in app/book/state folder and replace the content below:
export class GetBooks {
  static readonly type = '[Book] Get';
}
Implement BooksState
Open the book.state.ts file in app/book/state folder and replace the content below:
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from './book.actions';
import { BookService } from '../services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../models';
export class BookStateModel {
  public book: PagedResultDto<BookDto>;
}
@State<BookStateModel>({
  name: 'BookState',
  defaults: { book: {} } as BookStateModel,
})
@Injectable()
export class BookState {
  @Selector()
  static getBooks(state: BookStateModel) {
    return state.book.items || [];
  }
  constructor(private bookService: BookService) {}
  @Action(GetBooks)
  get(ctx: StateContext<BookStateModel>) {
    return this.bookService.getListByInput().pipe(
      tap((booksResponse) => {
        ctx.patchState({
          book: booksResponse,
        });
      })
    );
  }
}
- We added the book property to BookStateModel model.
- We added the GetBooksaction that retrieves the book data viaBookServicethat generated via ABP CLI and patches the state.
- NGXSrequires to return the observable without subscribing it in the get function.
BookListComponent
Open the book-list.component.ts file in app\book\book-list folder and replace the content as below:
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../models';
import { GetBooks } from '../state/book.actions';
import { BookState } from '../state/book.state';
@Component({
  selector: 'app-book-list',
  templateUrl: './book-list.component.html',
  styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
  @Select(BookState.getBooks)
  books$: Observable<BookDto[]>;
  booksType = BookType;
  loading = false;
  constructor(private store: Store) {}
  ngOnInit() {
    this.get();
  }
  get() {
    this.loading = true;
    this.store
      .dispatch(new GetBooks())
      .pipe(finalize(() => (this.loading = false)))
      .subscribe(() => {});
  }
}
- We added the getfunction that updates store to get the books.
- See the Dispatching actions and Select on the NGXSdocumentation for more information on theseNGXSfeatures.
Open the book-list.component.html file in app\book\book-list folder and replace the content as below:
<div class="row entry-row">
    <div class="col-auto">
        <h1 class="content-header-title">{{ '::Menu:Books' | abpLocalization }}</h1>
    </div>
    <div class="col-lg-auto pl-lg-0">
        <abp-breadcrumb></abp-breadcrumb>
    </div>
    <div class="col">
        <div class="text-lg-right pt-2" id="AbpContentToolbar"></div>
    </div>
</div>
<div id="wrapper" class="card">
    <div class="card-body">
      <abp-table
        [value]="books$ | async"
        [abpLoading]="loading"
        [headerTemplate]="tableHeader"
        [bodyTemplate]="tableBody"
        [rows]="10"
        [scrollable]="true"
      >
      </abp-table>
      <ng-template #tableHeader>
        <tr>
          <th>{{ "::Name" | abpLocalization }}</th>
          <th>{{ "::Type" | abpLocalization }}</th>
          <th>{{ "::PublishDate" | abpLocalization }}</th>
          <th>{{ "::Price" | abpLocalization }}</th>
        </tr>
      </ng-template>
      <ng-template #tableBody let-data>
        <tr>
          <td>{{ data.name }}</td>
          <td>{{ booksType[data.type] }}</td>
          <td>{{ data.publishDate | date }}</td>
          <td>{{ data.price }}</td>
        </tr>
      </ng-template>
    </div>
</div>
- We added HTML code of book list page.
Now you can see the final result on your browser:

The file system structure of the project:

In this tutorial we have applied the rules of official Angular Style Guide.
Next Part
See the part 2 for creating, updating and deleting books.

 
                                             
                                    