65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Angular 2 和 Web API 在 ASP.NET Core MVC 中进行 CRUD 操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (31投票s)

2016 年 10 月 31 日

CPOL

6分钟阅读

viewsIcon

174219

downloadIcon

6248

一个关于如何使用 ASP.NET CORE、Angular2 和 WebApi 构建 CRUD Web 应用程序的优秀示例

引言

通过本文,您将了解 Web CRUD 应用程序的实现。CRUD 应用程序意味着一个可以对数据源(如数据库、XML 文件等)进行记录的创建 (Create)、读取 (Read)、更新 (Update) 和删除 (Delete) 的应用程序。

本文的主要目标是教您以下几点:

  • 创建 ASP.NET Core MVC 应用程序。
  • 使用 npm 安装所需的软件包(Angular2、Typings 等)。
  • 对已安装在 SQL Server 上的现有数据库进行反向工程(使用 Entity Framework 模型优先方法)。
  • 使用 Web Api 创建一个 RestFul 服务器。
  • 在 Angular 2 中创建组件、模板、服务和类。
  • 使用 webpack 编译 Angular2 项目。
  • 运行 ASP.NETCORE MVC Web 应用程序。

在本文中,我参考了以下链接来构建我的应用程序:

背景

为了更好地理解此演示,最好您对以下内容有扎实的了解:

  • C#、JavaScript 和 HTML 编程。
  • MVC 架构。
  • SQL 语言。
  • 数据绑定。
  • Entity Framework。
  • Visual Studio Code。

必备组件

在开始实现 Web 应用程序之前,您需要安装:

Using the Code

构建 Web 应用程序

在本节中,我将一步一步地解释如何轻松构建一个 CRUD Web 应用程序。

A) 设置 ASP.NETCore MVC 项目

打开您的 CMD 控制台(我建议您以管理员身份运行),然后运行以下命令:

  • mkdir dotnetcoreapp.
  • cd dotnetcoreapp.
  • dotnet new -t web 用于创建 Web 应用程序。
  • dotnet restore 用于加载依赖项。
  • dotnet run 用于启动应用程序。

结果将如下面的图片所示:

使用浏览器导航到给定的 URL (https://:5000),您应该会看到与下图相同的显示:

B) 创建和配置 Angular2 项目

根据官方 Angular2 文档,您需要创建相同的配置文件。

  • package.json
  • tsconfig.json
  • typings.json

接下来,您应该使用 npm 安装 typescripttypingswebpack

  • Npm install –g typescript
  • Npm install -g typings
  • Npm install -g webpack

最后,您需要对 Startup.cs 文件进行一些更改,以支持 Angular2 单页应用程序。您需要将默认的 ASP.NETCore MVC 路由指向 Angular2 项目的 index 页(wwwroot/index.html)。为此,您需要在 Configure 方法中进行一些更改,特别是在路由代码中。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
     
           if (env.IsDevelopment())
            {
                //read error details
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            } 
//point on the index page of the Angular2 project
           app.Use(async (context, next) =>
            {
                await next();

                if (context.Response.StatusCode == 404
                    && !Path.HasExtension(context.Request.Path.Value))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            }); 
            app.UseStaticFiles();
            app.UseMvc();
        }

C) 设置数据库

1) 创建数据库

以下步骤将帮助您创建一个非空数据库(在我的例子中,我使用了 SQL Server 2014 来本地托管我的数据库)。

  • 在您的 SQL Server 中创建一个名为 DataBaseDotnetCore 的新数据库。
  • 执行以下 SQL 脚本来创建一个名为 Product 的表并插入一些数据。
        CREATE TABLE [dbo].[Product](
    	[id] [int] IDENTITY(1,1) NOT NULL,
    	[_name] [varchar](250) NULL,
    	[_description] [varchar](250) NULL,
        Primary key(id),
        );
    
        insert into Product values ('Juice','Juice description'), _
        ('Orange','Orange description')
2) 使用 Entity Framework 模型优先方法

在本节中,您将进行反向工程,以从现有数据库创建 Entity Framework 模型。为此,您需要遵循以下步骤:

  • 导航到应用程序的根文件夹(dotnetcoreapp 文件夹)。
  • 导入所需的依赖项和工具:您需要修改 project.json 文件,方法是:
    1. 添加以下依赖项:
         "Microsoft.EntityFrameworkCore":"1.0.0",
         "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
         "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0",  
         "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", 
    2. 添加工具:
      "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    3. 保存更改并执行以下命令行:
dotnet -restore
  • 编写以下命令行以启动反向工程过程:
    dotnet ef dbcontext scaffold "Server=[serverName];Database=[databaseName];
    Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models

    例如

    dotnet ef dbcontext scaffold "Server=LFRUL-013;Database=DataBaseDotnetCore;
    Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models
  • 最后,修改 Startup.cs 文件中的 ConfigureServices 方法以创建数据库连接。
    public void ConfigureServices(IServiceCollection services)
    {            
        var connection = @"Server=LFRUL-013;Database=DataBaseDotnetCore;Trusted_Connection=True;";
        services.AddDbContext<DataBaseDotnetCoreContext>(options => options.UseSqlServer(connection));
        services.AddMvc();
    }

D) 设置 RESTful Web API

在此步骤中,您将创建一个名为 ProductsController.cs 的控制器,该控制器实现了几个 HTTP verb

  • Get:从数据库检索 product 列表。
  • Post:创建一个新的 Product
  • Put:更新指定的 Product
  • Delete:通过传入的 id 删除指定的 Product
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using angularapp.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace WebApplication
{
    [Route("api/[controller]")]
    [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true, Duration = -1)] 
    public class ProductsController : Controller
    {
        private DataBaseDotnetCoreContext _context; 

           public ProductsController(DataBaseDotnetCoreContext context)
        {
            _context = context;
        }

        [HttpGet]
        public IEnumerable<dynamic> Get()
        {
                return _context.Product.ToList();
        }

        [HttpPost]
        public string Post([FromBody] Product product)
        {
            Response.StatusCode = 200;
    
            try{
                    angularapp.Models.Product newProduct = new Product();
                    newProduct.Name = product.Name; 
                    newProduct.Description = product.Description; 
                     _context.Product.Add(newProduct);
                     _context.SaveChanges();
              
            }catch(Exception e){
                Response.StatusCode = 400;
                return e.ToString();
            }
            return "OK";
        }
        [HttpPut]
        public string Put([FromBody] Product product)
        {
            Response.StatusCode = 200;
    
            try{
                   
                    product.Name = product.Name; 
                    product.Description = product.Description; 
                     _context.Product.Attach(product);
                      _context.Entry(product).State = EntityState.Modified;
                     _context.SaveChanges();
              
            }catch(Exception e){
                Response.StatusCode = 400;
                return e.ToString();
            }
            return "OK";
        }

        [HttpDelete]
        public String Delete(int id)
        {
            Response.StatusCode = 200;
              
            try{
                    angularapp.Models.Product newProduct = new Product();
                    newProduct.Id = id; 
                    _context.Product.Remove(newProduct);
                    _context.SaveChanges();
        
            }catch(Exception e){
                 return e.ToString();
            }
            return "OK";
        }
    }
}

当您在此级别运行项目时,实现的 Web API 将在以下 URL 上公开:https://:5000/api/products

E) 设置 Angular2 项目(前端部分)

wwwroot/app 文件夹中,创建以下文件:

a) product.service.ts

这是 Angular 服务类,它实现了所需的方法和接口,以确保与已开发的 Web API 的通信。它由以下函数组成:

  • AnnounceChange:向观察者发送通知,以在视图列表中刷新数据。
  • LoadData:从数据库加载数据,通过调用现有的 Web 服务 [/api/products]。
  • Add:调用外部 Web 服务 [/api/products],该服务将一个新的 Product 对象添加到数据库中。
  • Update:通过调用现有的 Web 服务 [/api/products],更新数据库中现有产品的某些内容。
  • Delete:通过调用现有的 Web 服务 [/api/products],从数据库中删除指定的 product。

 

 

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import { Observable, Subject } from 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class ProductService {
    constructor(private _http: Http) { }
    private RegenerateData = new Subject<number>();
    // Observable string streams
    RegenerateData$ = this.RegenerateData.asObservable();
   
    AnnounceChange(mission: number) {
            
          this.RegenerateData.next(mission);
    }
    
    LoadData(): Promise<iproduct[]> {
        return this._http.get('/api/products')
            .toPromise()
            .then(response => this.extractArray(response))
            .catch(this.handleErrorPromise);
    }    

    Add(model) {
        let headers = new Headers({ 'Content-Type': 
        'application/json; charset=utf-8' });
        let options = new RequestOptions({ headers: headers });
        delete model["id"];
        let body = JSON.stringify(model);
        return this._http.post('/api/products/', body, 
               options).toPromise().catch(this.handleErrorPromise);
    }
    Update(model) {
        let headers = new Headers({ 'Content-Type': 
        'application/json; charset=utf-8' });
        let options = new RequestOptions({ headers: headers });
        let body = JSON.stringify(model);
        return this._http.put('/api/products/', body, 
               options).toPromise().catch(this.handleErrorPromise);
    }
    Delete(id: number) {  
        return this._http.delete('/api/products/?id=' + 
        id).toPromise().catch(this.handleErrorPromise);
    }    

    protected extractArray(res: Response, showprogress: boolean = true) {
        let data = res.json();
        
        return data || [];
    }

    protected handleErrorPromise(error: any): Promise<void> {
        try {
            error = JSON.parse(error._body);
        } catch (e) {
        }

        let errMsg = error.errorMessage
            ? error.errorMessage
            : error.message
                ? error.message
                : error._body
                    ? error._body
                    : error.status
                        ? `${error.status} - ${error.statusText}`
                        : 'unknown server error';

        console.error(errMsg);
        return Promise.reject(errMsg);
    }
}
b) IProduct.ts 

构成 Product 实体的 interface

export interface IProduct { id : number , name : string , description : string }
c) app.componentHW.ts

这是主组件。它包含应用程序的模板和实现。

  • refresh:通过调用 _service 变量的 "loadData method" 从外部服务接收数据,刷新现有的 product 视图列表。

  • onUpdate:通过调用 "_service variable" 的 "Update method" 更新数据库中现有的 product。
  • onDelete:通过调用 "_service variable" 的 "Delete method" 删除由其 "唯一键" 标识的现有 product。
import { Component, OnInit } from '@angular/core';
import { ProductService, IProduct } from './product.service';
import { ProductForm } from './productForm';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'myhw',
  template: `
      <div class='row'>
        <pform></pform>
      </div>
      <div class='row'>
       <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class='panel-heading'>Products List</div>
        <div class='panel-body'>
          <table class='table table-condensed'>
            <thead>
              <th>Id</th>
              <th>Name</th>
              <th>Description</th>
                <th></th>
            </thead>
             <tbody>
              <tr *ngFor="let person of persons"  >
                  <td> {{person.id}}  </td>
                  <td> <input type="text"  
                  [(ngModel)]="person.name" 
                                            name="pname" 
                                            class="form-control" /> </td>
                  <td> <input type="text"  
                  [(ngModel)]="person.description" 
                                            name="pdescription"  
                                            class="form-control" /> </td>
                  <td> <input type="button" 
                  value="update" class="btn btn-default" 
                         (click)="onUpdate(person)"/> 
                         <input type="button" value="remove" 
                          class="btn btn-danger"  
                          (click)="onDelete(person.id)"/></td>
                </tr> 
            <tbody>
           </table>
           </div>
           </div>
        </div>
        `     
      })
export class HwComponent extends OnInit {
    subscription: Subscription;
    
     refresh(){
         this._service.loadData().then(data => {
            this.persons = data;
        })
    }
    constructor(private _service: ProductService) {
        super();
          this.subscription = _service.RegenerateData$.subscribe(
          mission => {
              console.log("Good !! ", mission);
               this.refresh();
           });
    }

    ngOnInit() {
        this.Refresh();
    }
    onUpdate(elem){
        console.log(elem); 
        this._service.Update(elem).then(data => {
         })
    }
    onDelete(elem : number){
        console.log("Delete Form ! ");
        console.log(elem); 
        this._service.Delete(elem).then(data => {
              this.Refresh();
        })
    }
    persons: IProduct[] = [];

     ngOnDestroy() {
    // prevent memory leak when component destroyed
       this.subscription.unsubscribe();
     }
}
d) Product.ts

此类包含 product 项的详细信息。

export class Product {
      constructor(
        public id : number,      
        public name : string,      
        public description : string
      ){

      }
}
e) productForm.component.html

这是我们 Product 表单使用的 HTML 模板。

 <div>
    <h3>Product Form</h3>
    <form>
      <div class="form-group">
        <label for="name">Name *</label>
        <input type="text" class="form-control"  
        [(ngModel)]="model.name" name="name" required>
      </div>
      <div class="form-group">
        <label for="description">Description *</label>
        <input type="text" class="form-control"   
        [(ngModel)]="model.description"  name="email">
      </div>
      <button type="button"  (click)="onSubmit()" 
      class="btn btn-primary">Add</button>
    </form>
  </div>  
f) productForm.ts

构成 productForm 模板的后台代码,您将在其中实现:

  • onSumbit method:此事件用于调用 ProductServiceAdd 方法。
  • model attribute:用于将产品表单字段与模型绑定。
import { Component, OnInit } from '@angular/core';
import { Product } from './Product';
import { ProductService, IProduct } from './product.service';

@Component({
    moduleId: __filename,
    selector: 'pform',
    templateUrl: './app/productForm.component.html'    
 })

export class ProductForm {
    constructor(private _service: ProductService) {
     
    }
    model = new Product(0,'','');
    submitted = false;
    onSubmit() { 
        console.log("Sumbitted Form ! ");
        this.submitted = true; 
        this._service.Add(this.model).then(data => {
           this._service.AnnounceChange(1212);
        })
    }
    
  // TODO: Remove this when we're done
    get diagnostic() { return JSON.stringify(this.model); }
}
g) app.module.ts

此文件将用于:

  • 通过 "imports" 关键字导入所需的 Angular2 模块。
  • 通过 "declarations" 关键字声明创建的组件。
  • 通过 "providers" 关键字声明所需的全局服务。
  • 在 "bootstrap" 数组中指定根组件,该组件必须包含在 index.html 文件中。
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HwComponent }   from './app.componentHW';
import { ProductService } from './product.service';
import {ProductForm } from './productForm';
 
@NgModule({
  imports:      [ BrowserModule,
                  FormsModule,
                  HttpModule],
  declarations: [ HwComponent, ProductForm],
  providers: [
        ProductService
  ],
  bootstrap:    [  HwComponent]
})
export class AppModule { }

导航到 wwwroot 文件夹,您将在其中开始配置前端项目。

h) systemjs.config.js

此文件用于加载使用 TypeScript 编译器编译的模块。

/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': '../node_modules/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',
            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 
                 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 
                 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
            // other libraries
            'rxjs': 'npm:rxjs',
        },
        meta: {
            './app/main.js': {
                format: 'global'
            }
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './main.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        }
    });
})(this); 
i) index.html

此 HTML 文件是应用程序的入口点,我们将在此包含所有必需的 JS、CSS 文件来渲染我们的组件。

<html>
    <head>
        <title>Angular 2 QuickStart</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="css/site.css">
        <link rel="stylesheet" href="css/bootstrap.min.css">
        <!-- 1. Load libraries -->
        <script src="js/core.js"></script>
        <script src="js/zone.js"></script>
        <script src="js/reflect.js"></script>
        <script src="js/system.js"></script>
        <!-- 2. Configure SystemJS -->
        <script src="systemjs.config.js"></script>
        <script>
          System.import('app').catch(function(err){ console.error(err); });
        </script>
    </head>
    <!-- 3. Display the application -->
    <body>

        <div class="container">    
          <myhw>Loading ...</myhw>
        <div>
    </body>
</html>

最后,您应该配置 webpack 来:

  • 将所需的 node_modules 导出到 wwwroot/js 文件夹。
  • main.ts 转译为名为 main.js 的 JavaScript 文件。

为此,请导航到根文件夹dotnetcoreapp/),然后创建 webpack.config.js

 module.exports = [
  {
    entry: {
      core: './node_modules/core-js/client/shim.min.js',
      zone: './node_modules/zone.js/dist/zone.js',
      reflect: './node_modules/reflect-metadata/Reflect.js',
      system: './node_modules/systemjs/dist/system.src.js'
    },
    output: {
      filename: './wwwroot/js/[name].js'
    },
    target: 'web',
    node: {
      fs: "empty"
    }
  },
  {
    entry: {
      app: './wwwroot/app/main.ts'
    },
    output: {
      filename: './wwwroot/app/main.js'
    },
    devtool: 'source-map',
    resolve: {
      extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
    },
    module: {
      loaders: [
        { test: /\.ts$/, loader: 'ts-loader' }
      ]
    }
  }];   

F) 运行 Web 应用程序

要运行演示,您应该使用 CMD 编写以下命令行,但首先请确保您位于 .net core Web 应用程序的根目录下:

  • webpack:将 TS 文件转译为 JavaScript 文件。
  • dotnet run:编译项目并运行服务器。

如果编译成功,您可以在浏览器中打开给定的 URL 来查看正在运行的 Web 应用程序。

 

参考文献

关注点

希望您喜欢这篇帖子。尝试下载源代码,不要犹豫留下您的问题和评论。

历史

  • v1 30/10/2016:初始版本
© . All rights reserved.