[NetCore] Asp.net Core with Angular 6 - Read

建立Asp.net core並使用Angular 6 作為UI

公司產品前端採用Angular,API則使用ASP.NET Core,文章我只以這兩大前後端 framework為主。

 

建立ASP.NET core web application專案

選擇core2.1和Angular

Note:安裝core2.1就沒必要安裝Angular template

預設為 Angular 5,所以須升級相關library為Angular 6,

相關的軟體安裝請參考Installing Angular 6

ng update @angular/cli

可以看到會幫我們更新專案中的package.json

ng update @angular/core
ng update @angular/material

 

專案執行如下

Note:ASP.net core針對開發環境,預設內部使用ng serve,我們不需要額外執行ng serve,

你可以在Output看到類似如下文字:

NG Live Development Server is listening on localhost:13589, open your browser on http://localhost:13589/

 

專案結構如下

ClientApp folder就是Angular UI部分。

 

這裡我會新增EF Entities和Dbcontext netstandard2.0專案,

實現EventLog entity的CRUD,下面我們先來把API部分搞定

新增Entity

public sealed class EventLog
    {
        public long Id { get; set; }
        public int EventID { get; set; }
        public string LogLevel { get; set; }
        public string Message { get; set; }
        public string Exception { get; set; }
        public DateTime CreatedTime { get; set; }
    }

 

新增DemoContext

public class DemoContext : DbContext
    {
        public DbSet<EventLog> EventLogs { get; set; }
 
        public DemoContext(DbContextOptions<DemoContext> options) : base(options)
        {
        }
 
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("dbo");
            modelBuilder.ApplyConfiguration(new EventLogConfiguration());
        }
    }

 

新增IServiceCollectionExtension,用來注入DemoContext

public static class IServiceCollectionExtension
   {
       public static IServiceCollection AddDemoContext(this IServiceCollection services, IConfiguration configuration)
       {
           var connectionString = configuration.GetConnectionString("Demo");
           services.AddDbContextPool<DemoContext>(
               options =>
               {
                   var builder = new SqlConnectionStringBuilder(connectionString)
                   {
                       ConnectTimeout = 30,
                       Pooling = true,
                       ConnectRetryCount = 3,
                       ConnectRetryInterval = 10
                   };
                   options.UseSqlServer(builder.ToString(), b => b.MaxBatchSize(500));
                   options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
                   options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
               });
           return services;
       }
   }

 

Website專案部分,新增Dao、Service、Model folder

新增Model

public class GetEventLogResponse
    {
        public long Id { get; set; }
        public int EventID { get; set; }
        public DateTime CreatedTime { get; set; }
        public string Message { get; set; }
    }

 

新增Dao

public interface IEventLogDao
    {
        Task<List<GetEventLogResponse>> GetEventLogsAsync();
        Task<GetEventLogResponse> GetEventLogAsync(long id);
        Task<int> AddEventLogAsync(IEnumerable<EventLog> eventLogs);
        Task<int> DeleteEventLogAsync(IEnumerable<EventLog> eventLogs);
        Task<int> UpdateEventLogAsync(IEnumerable<EventLog> eventLogs);
    }
 
    internal sealed class EventLogDao : IEventLogDao
    {
        private readonly DemoContext _demoContext;
        public EventLogDao(DemoContext demoContext)
        {
            _demoContext = demoContext;
        }
 
        Task<int> IEventLogDao.AddEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            _demoContext.EventLogs.AddRangeAsync(eventLogs);
            return _demoContext.SaveChangesAsync();
        }
 
        Task<int> IEventLogDao.DeleteEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            _demoContext.EventLogs.RemoveRange(eventLogs);
            return _demoContext.SaveChangesAsync();
        }
 
        Task<GetEventLogResponse> IEventLogDao.GetEventLogAsync(long id)
        {
            return (from e in _demoContext.EventLogs
                    where e.Id == id
                    select new GetEventLogResponse
                    {
                        Id = e.Id,
                        EventID = e.EventID,
                        Message = e.Message,
                        CreatedTime = e.CreatedTime
                    }).FirstOrDefaultAsync();
        }
 
        Task<List<GetEventLogResponse>> IEventLogDao.GetEventLogsAsync()
        {
            return (from e in _demoContext.EventLogs
                    select new GetEventLogResponse
                    {
                        Id = e.Id,
                        EventID = e.EventID,
                        Message = e.Message,
                        CreatedTime = e.CreatedTime
                    }).ToListAsync();
        }
        Task<int> IEventLogDao.UpdateEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            _demoContext.EventLogs.UpdateRange(eventLogs);
            return _demoContext.SaveChangesAsync();
        }
    }

 

新增Service

public interface IEventLogService
    {
        Task<List<GetEventLogResponse>> GetEventLogsAsync();
        Task<GetEventLogResponse> GetEventLogAsync(long id);
        Task<int> CreateEventLogAsync(IEnumerable<EventLog> eventLogs);
        Task<int> RemoveEventLogAsync(IEnumerable<EventLog> eventLogs);
        Task<int> UpdateEventLogAsync(IEnumerable<EventLog> eventLogs);
    }
 
    internal sealed class EventLogService : IEventLogService
    {
        private readonly IEventLogDao _eventLogDao;
        public EventLogService(IEventLogDao eventLogDao)
        {
            _eventLogDao = eventLogDao;
        }
 
        Task<int> IEventLogService.CreateEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            return _eventLogDao.AddEventLogAsync(eventLogs);
        }
 
        Task<GetEventLogResponse> IEventLogService.GetEventLogAsync(long id)
        {
            return _eventLogDao.GetEventLogAsync(id);
        }
 
        Task<List<GetEventLogResponse>> IEventLogService.GetEventLogsAsync()
        {
            return _eventLogDao.GetEventLogsAsync();
        }
 
        Task<int> IEventLogService.RemoveEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            return _eventLogDao.DeleteEventLogAsync(eventLogs);
        }
 
        Task<int> IEventLogService.UpdateEventLogAsync(IEnumerable<EventLog> eventLogs)
        {
            return _eventLogDao.UpdateEventLogAsync(eventLogs);
        }
    }

 

新增Controller

[Route("api/eventlog")]
    [ApiController]
    public class EventLogController : ControllerBase
    {
        private readonly IEventLogService _eventLogService;
        public EventLogController(IEventLogService eventLogService)
        {
            _eventLogService = eventLogService;
        }
 
        [HttpGet]
        [Route("all")]
        public async Task<IEnumerable<GetEventLogResponse>> GetEventLogsAsync()
        {
            return await _eventLogService.GetEventLogsAsync();
        }
 
        [HttpGet]
        [Route("{id}")]
        public async Task<GetEventLogResponse> GetEventLogAsync(long id)
        {
            return await _eventLogService.GetEventLogAsync(id);
        }
    }

 

新增IServiceCollectionExtension

public static class IServiceCollectionExtension
   {
       public static IServiceCollection AddEventLog(this IServiceCollection services)
       {
           services.AddScoped<IEventLogDao, EventLogDao>();
           services.AddScoped<IEventLogService, EventLogService>();
           return services;
       }
   }

 

修改startup.cs,注入相關Service

先簡單用postman測試API

到這裡API就算完成了,下面繼續Angular UI部分。

 

app下新增service folder,並透過CLI新增Angular service

ng g service /service/eventlog

修改eventlog.service.ts

import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';  

@Injectable()
export class EventlogService {
  myAppUrl: string = "";  
  constructor(private _http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
    this.myAppUrl = baseUrl;  
  }

  getEventLogs() {
    // HttpClient.get() applies res.json() automatically and returns 
    return this._http.get(this.myAppUrl + 'api/eventlog/all')
      .catch(this.errorHandler);
  }
 
  getEventLogsById(id: number) {
    return this._http.get(this.myAppUrl + "api/eventlog/" + id)     
      .catch(this.errorHandler)
  }

  errorHandler(error: Response) {
    console.log(error);
    return Observable.throw(error);
  }  

}

 

透過CLI新增Angular component

ng g component eventlog

修改app.module.ts,新增eventlog的router和provider

Import { EventlogService } from './service/eventlog.service' 

修改nav-menu.component.html,新增EventLog menu

<li [routerLinkActive]='["link-active"]'>
            <a [routerLink]='["/eventlog"]' (click)='collapse()'>
              <span class='glyphicon glyphicon-th-list'></span> EventLog
            </a>
          </li>

 

修改eventlog.component.html

<h1>EventLog View</h1>
 
<p *ngIf="!eventlogs"><em>Loading...</em></p>
<p>
  <a [routerLink]="['/eventlog']" (click)="getEventLogs()">Index</a> | 
  <a [routerLink]="['/addeventlog']">Create New</a>
</p>
 
<table class='table' *ngIf="eventlogs">
  <thead>
    <tr>
      <th>Id</th>
      <th>EventID</th>
      <th>Message</th>
      <th>CreatedTime</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let eventlog of eventlogs">
     <td> <a [routerLink]="" (click)="getEventLogsById(eventlog.id)">{{ eventlog.id }}</a></td>
      <td>{{ eventlog.eventID }}</td>
      <td>{{ eventlog.message }}</td>
      <td>{{ eventlog.createdTime }}</td>
      <td>
        <a [routerLink]="['/eventlog/edit/', eventlog.id]">Edit</a> |
        <a [routerLink]="" (click)="delete(eventlog.id)">Delete</a>
      </td>
    </tr>
  </tbody>
</table>

 

修改eventlog.component.ts

import { Component, OnInit, Inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';  
import { HttpClient } from '@angular/common/http';
import { Time } from '@angular/common';
import { EventlogService } from '../service/eventlog.service'  

@Component({
  selector: 'app-eventlog',
  templateUrl: './eventlog.component.html',
  styleUrls: ['./eventlog.component.css']
})
export class EventlogComponent implements OnInit {

  public eventlogs: EventLogs[];

  constructor(public http: HttpClient, private _router: Router, private _eventLogService: EventlogService) {
    this.getEventLogs();
  }

  ngOnInit() {
  }

  getEventLogs() {
    this._eventLogService.getEventLogs().subscribe(result =>
      this.eventlogs = result
    )
  }

  getEventLogsById(id: number) {
    this._eventLogService.getEventLogsById(id).subscribe(result =>
      this.eventlogs = result
    )
  }

}

interface EventLogs {
  id: number;
  eventID: number;
  message: string;
  createdTime: Date;
}

到這裡我們已經完成透過Angular 6來顯示來自webapi的資料。

 

目前Angular專案部分結構

 

結果

篇幅有點長,後面我們在繼續Create、Update、Delete相關實作。

 

 

參考

Use the Angular project template with ASP.NET Core

Upgrade Angular 5 app to Angular 6 with Visual Studio 2017

Use DbContextPooling to improve the performance: .Net Core 2.1 feature

CRUD Operations With ASP.NET Core Using Angular 5 and ADO.NET

Entity Framework Core

Angular 5 and ASP.NET Core