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

Angular 2 和 .NET Core 从零开始开发 Web 应用程序 第 2 部分:实现前端部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2017 年 1 月 29 日

CPOL

6分钟阅读

viewsIcon

24851

downloadIcon

358

一篇很棒的文章,教你如何使用 Angular2 和 .NET CORE WEB API 从零开始创建 Web 应用程序

目录

引言

在准备好服务器端之后,我们将开始使用 Angular 2 框架构建客户端(前端)部分。

我们的应用程序是一个由四个模块组成的单页应用程序

  • home:主页,用于向客户展示可用产品。
  • Login:认证页面,允许用户登录应用程序并访问后端管理界面。
  • Subscription:订阅页面,提供一个表单来创建新的用户帐户。
  • Product Management:此模块提供一个表单,用于将新产品插入数据库。

在本文中,我参考了以下链接

背景

为了更好地理解此演示,建议您对以下内容有很好的了解:

  • C#、JavaScript 和 HTML 编程
  • Angular 2
  • MVC 架构
  • 数据绑定
  • 实体框架
  • Visual Studio Code(可选)

必备组件

Using the Code

A) 创建和配置 Angular 2 项目

首先,使用 CMD 创建一个名为“angular2fromscratch”的新文件夹(新项目),您可以在其中从 Angular 2 官方文档下载 Angular 2 项目。

之后,您应该创建相同的配置文件(基于 Angular 2 官方文档)

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

接下来,您应该使用 npm 安装 TypeScript、typings、webpack、cookies 服务和 angular2-material

  • Npm install –g typescript
  • Npm install -g typings
  • Npm install angular2-cookie
  • npm install @angular2-material/sidenav
  • npm install @angular2-material/input
  • npm install @angular2-material/button
  • npm install @angular2-material/core
  • npm install @angular2-material/card
  • npm install @angular2-material/icon
  • npm install @angular2-material/toolbar
  • npm install @angular2-material/progress-circle
  • npm install @angular2-material/sidenav

最终的 treeview 应该如下所示

在开始实施之前,您应该对 systemjs.config.js 文件进行一些更改,以定义 Angular 2 包的别名。

(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',
      '@angular2-material/input': 'npm:@angular2-material/input/input.umd.js',
      '@angular2-material/core': 'npm:@angular2-material/core/core.umd.js',
      '@angular2-material/card': 'npm:@angular2-material/card/card.umd.js',
      '@angular2-material/button': 'npm:@angular2-material/button/button.umd.js',
      '@angular2-material/icon': 'npm:@angular2-material/icon/icon.umd.js',
      '@angular2-material/toolbar': 'npm:@angular2-material/toolbar/toolbar.umd.js',
      '@angular2-material/progress-circle': 
      'npm:@angular2-material/progress-circle/progress-circle.umd.js',
      '@angular2-material/sidenav': 'npm:@angular2-material/sidenav/sidenav.umd.js',
       'angular2-cookie': 'npm:angular2-cookie',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
    },
    // 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'
      },
      'angular-in-memory-web-api': {
        main: './index.js',
        defaultExtension: 'js'
      },
       'angular2-cookie': {
        main: './core.js',
        defaultExtension: 'js'
      },
    }
  });
})(this);

B) 设置 Angular 2 项目

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

  • app.module.ts

    此文件用于:

    • 使用路由器模块定义路由:“RouterModule
    • 通过关键字导入所需的 Angular 2 模块:“imports
    • 通过关键字声明组件:“declarations
    • 通过关键字声明服务:“providers
    • 通过关键字指定要包含在 index.html 文件中的根组件:“bootstrap
    import { NgModule }             from '@angular/core';
    import { BrowserModule }        from '@angular/platform-browser';
    import { FormsModule }          from '@angular/forms';
    import { RouterModule, Routes } from '@angular/router';
    import { MdInputModule } from '@angular2-material/input';
    import { MdCardModule } from '@angular2-material/card';
    import { MdButtonModule } from '@angular2-material/button';
    import { MdIconModule } from '@angular2-material/icon';
    import { MdToolbarModule } from '@angular2-material/toolbar';
    import { MdIconRegistry } from '@angular2-material/icon';
    import { CookieService } from 'angular2-cookie/services/cookies.service';
    import { AppComponent }   from './app.component';
    import { loginComponenent }   from './app.loginComponenent';
    import { homeComponenent }   from './app.homeComponenent';
    import { subscriptionComponenent }   from './app.subscriptionComponenent';
    import { productManageComponent }   from './app.productManageComponent';
    import { PageNotFoundComponent }   from './app.pageNotFoundComponent';
    import { MdProgressCircleModule } from '@angular2-material/progress-circle';
    import { MdSidenavModule } from '@angular2-material/sidenav';
    
    import { AuthentificationService } from './services/authentificationService';
    import { ManageProductService } from './services/manageProductService'
    
    const appRoutes: Routes = [
      { path: 'home', component: homeComponenent },
      { path: 'login', component: loginComponenent },
      { path: 'subscription', component: subscriptionComponenent },
      { path: 'productManagement', component: productManageComponent },
      { path: '', redirectTo: '/home',  pathMatch: 'full'},
      { path: '**', component: PageNotFoundComponent }
    ];
    
    @NgModule({
    
       imports:  [ FormsModule , BrowserModule,MdSidenavModule, 
          MdProgressCircleModule, MdInputModule, MdToolbarModule, 
          MdCardModule, MdButtonModule, MdIconModule,  RouterModule.forRoot(appRoutes)],
        declarations: [ AppComponent, loginComponenent, homeComponenent, 
        subscriptionComponenent, productManageComponent, PageNotFoundComponent],
        providers: [ CookieService, MdIconRegistry, 
                     AuthentificationService, ManageProductService ],
        bootstrap: [ AppComponent ]
    })
    export class AppModule { }
  • app.component.ts

    此文件用作 app.html 模板的代码隐藏文件,您将在其中实现:

    • constructor:用于初始化我们的主页面(app.html),并声明路由器监听器,根据当前会话值(会话由 cookies 管理)控制用户导航。
    • logOut 事件:此事件用于通过清除 cookies 释放当前会话,并将用户重定向到主页。
    import { Component } from '@angular/core';
    import {MdToolbar} from '@angular2-material/toolbar';
    import {CookieService} from 'angular2-cookie/core';
    import {Router, NavigationStart, NavigationEnd} from '@angular/router'
    @Component({
      selector: 'my-app',
      templateUrl: 'app/templates/app.html',
         
    })
    export class AppComponent {
      title = 'Angular 2 Application';
      showMenu : boolean = false;
      login : string = "";
      constructor(private _cookieService: CookieService, private router : Router){
          this.showMenu = false;
          router.events.subscribe((val) => {
            // see also 
            if(val instanceof NavigationStart) {
                this.login = this._cookieService.get("login");
                if(this.login != null && this.login != ""){
                    this.showMenu = true;
                    if(val.url.endsWith("login") || val.url.endsWith("subscription")){
                        router.navigateByUrl("/home");
                    }
                } 
                else{
                    this.showMenu = false;
                    if(val.url.endsWith("productManagement")){
                          router.navigateByUrl("/home");
                      }
                }
            }
         });
      }
      public logOut(right){
        this.showMenu = false;
        right.close();
        this._cookieService.remove("login");
        location.reload();
        //this.router.navigateByUrl("/home");
      } 
    }
  • app.homeComponent.ts

    构成我们 home 模板的代码隐藏文件,您将在其中实现:

    • ngOnInit 事件:在每次页面初始化时刷新产品列表
    • Remove 事件:通过调用 “_service 变量”的“Delete 方法”删除现有产品(通过其“唯一键”识别)
    • loadProducts event:通过调用 _service 变量的 loadProducts 方法从远程服务器加载所有可用产品
    import { Component , OnInit} from '@angular/core';
    import { Product } from './model/product'
    import { ManageProductService } from './services/manageProductService'
    import {CookieService} from 'angular2-cookie/core';
    
    @Component({
      templateUrl: 'app/templates/home.html'
    })
    export class homeComponenent extends OnInit {
      title = 'Products';
      showProgress : boolean = false; 
      showError : boolean = false;
      showEmpty : boolean = false;
      isLogged  : boolean = false;
      listProduct : Product[];
    
      constructor(private _service : ManageProductService, 
                  private _cookieService: CookieService){
             super();
             console.log("constructor");
      }
    
      ngOnInit() {
         this.loadProducts();
    
         this.isLogged = (this._cookieService.get("login")!= null)?true: false;
      }
    
      public Remove(id: number) {
        var result = confirm("Do you want to continue the suppression process ?");
        let _self = this;
        _self.showError = false;
        _self.showEmpty = false;
        if (result == true) {
           
               this._service.deleteProduct(id).then(function(){
                    _self.listProduct = _self.listProduct.filter(value=> {
                        return (value.id != id);
                    });
                    if(_self.listProduct.length==0){
                        _self.showEmpty = true;
                    }
               }).catch(function(){
                   _self.showError = true;
               });
        } else {
            
        }
      }
      public loadProducts(){
          
          let _self = this;
          _self.showError = false;
          _self.showEmpty = false;
          _self.showProgress = true;
          _self._service.loadProducts().then(function(response){
            
             _self.showProgress = false;
             if(response.length >0){
             
               _self.listProduct = response;
          
             }else{
                _self.showEmpty = true;
             }
             
          }).catch(function(error: any){
             _self.showProgress = false;
              _self.showError = true;
          });
      }
    }
  • app.loginComponent.ts

    构成 login 模板的代码隐藏文件,您将在其中实现:

    • tryToConnect 事件:使用用户凭据(电子邮件、密码)与服务器建立新会话
    import { Component } from '@angular/core';
    import {Router} from '@angular/router'
    import { User } from './model/user';
    import { AuthentificationService } from './services/authentificationService';
    import {CookieService} from 'angular2-cookie/core';
    
    @Component({
      templateUrl: 'app/templates/login.html'
    })
    
    export class loginComponenent {
      public title = 'Login';
      public model : User = new User();
      constructor(private _service : AuthentificationService, 
                  private _cookieService:CookieService, private router: Router){
    
      }
    
      tryToConnect(){
         let _self = this;
          _self._service.Login(_self.model).then(function (response){
              
              _self._cookieService.put("login", _self.model.login);
              _self.router.navigateByUrl('/home');
           })
           .catch(function(error : any){
              alert("Fail to login");
           });
      }
    }
  • app.productManageComponent.ts

    构成我们 productManagement 模板的代码隐藏文件,您将在其中实现:

    • addNewProduct 事件:当用户点击提交按钮以提交表单数据注册新产品时调用。它将调用 _service 变量的 addProduct 方法。
     import { Component } from '@angular/core';
    
    import { Product } from './model/Product';
    import { ManageProductService } from './services/manageProductService';
    @Component({
      templateUrl: 'app/templates/productManagement.html'
    })
    export class productManageComponent {
      title = 'Manage Product';
      model : Product;
      constructor(private _service : ManageProductService){
          this.model = new Product();
      }
      onChange(event) {
            var file = event.srcElement.files[0];
            this.model.pictureFile = file;
        
        }
      public addNewProduct(){
    
        let _self = this;
        let formData = new FormData();
        formData.append("title",_self.model.title);
        formData.append("fullDescription",_self.model.fullDescription);
        formData.append("price",_self.model.price);
        formData.append("file",_self.model.pictureFile, _self.model.pictureFile.name);
        _self._service.addProduct(formData).then(function(response){
          alert("new product was successfully created");
        }).catch(function(error: any){
          alert("Server error");
        });
      }
    }
  • app.subscriptionComponent.ts

    构成我们 signup 模板的代码隐藏文件,您将在其中实现:

    • Subscription 事件:当用户尝试提交新用户详细信息时触发。它将调用 _service 变量的 addProduct 方法。
    import { Component } from '@angular/core';
    import { User } from './model/user';
    import { AuthentificationService } from './services/authentificationService';
    import {  NgModule }  from '@angular/core';
    @Component({
      templateUrl: 'app/templates/singup.html'
    })
    export class subscriptionComponenent {
        title = 'Subscription';
      public model : User;
    
      constructor(private _service : AuthentificationService){
        this.model = new User();
      }
         
      public Subscription(){
        let _self = this;
        this._service.Signup(this.model).then(function(response){
     
           alert("Congratulation, the user with login : 
                 "+ _self.model.login  + " has been created");
        })
         .catch(function(error : any){
              alert("Fail to create new user");
          });
      }
    }

接下来,您应该创建不同的文件夹和文件

1) model 文件夹

  • product.ts

    用于将 JSON 数据反序列化为 product 对象。

    export class Product{
         id : number;
         title : string = '';
         fullDescription : string = '';
         price : number = 0;
         picture : string;
         pictureFile : any;
    }
  • user.ts

    用于将 JSON 数据反序列化为 user 对象。

    export class User{
         login : string = '';
         pwd : string = '';
         pwdConfirmation : string = '';
    }

2) services 文件夹

  • authentificationService.ts

    实现确保用户身份验证和用户注册所需的方法

    • Login:调用外部 web 服务 "api/Services/Login" 以创建与服务器的会话。
    • Signup:调用远程 web 服务 "api/Services/Subscription" 以注册用户。
    import { Injectable } from '@angular/core';
    import { Http, Response, RequestOptions, Headers } from '@angular/http';
    import 'rxjs/add/operator/toPromise';
    import {User} from '../model/user';
    
    @Injectable()
    export class AuthentificationService {
    
        constructor( private _http : Http){
        }
    
         Login(model : User): Promise<any> {
                
                let _url = "https://:5000/api/Services/Login";
    
                let bodyString = JSON.stringify(model);
                let headers    = new Headers
                ({ 'Content-Type': 'application/json' }); // ... Set content type 
                                                          // to JSON
                let options    = new RequestOptions({ headers: headers });
        
                return this._http.post(_url, bodyString, options)
                .toPromise(); 
        }
        
        Signup(model : any): Promise<any> {
                
                let _url = "https://:5000/api/Services/Subscription";
    
                let bodyString = JSON.stringify(model);
                let headers    = new Headers
                ({ 'Content-Type': 'application/json' }); // ... Set content 
                                                          // type to JSON
                let options    = new RequestOptions({ headers: headers });
        
                return this._http.post(_url, bodyString, options)
                .toPromise();
        }
     
        protected handleErrorPromise(error: any): Promise<void> {
    
            try {
                error = JSON.parse(error._body);
            } catch (e) {
            }
            
            return Promise.reject(error);
        } 
    }
  • manageProductService.ts

    此类提供管理产品的服务,例如:

    • loadProducts:通过调用地址为 "api/Services/GetProducts" 的外部 web 服务加载可用产品列表。
    • addProduct:它调用外部 web 服务 "api/Services/AddProduct" 来创建一个新产品。
    • deleteProduct:通过调用现有 web 服务 "api/Services/DeleteProduct/" 从数据库中删除特定产品。
    import { Injectable } from '@angular/core';
    import { Http, Response, RequestOptions, Headers } from '@angular/http';
    import { Product} from '../model/Product';
    import 'rxjs/add/operator/toPromise';
    
    @Injectable()
    export class ManageProductService {
        constructor(private _http: Http) { }
    
       loadProducts(): Promise<Product[]> {
                    
           let _url = "https://:5000/api/Services/GetProducts";
            return this._http.get(_url)
                .toPromise()
                .then(response => this.extractArray(response));
        }      
    
        addProduct(formData: any): Promise<any>{
              let _url = "https://:5000/api/Services/AddProduct";
            return new Promise(function (resolve, reject) {
    
                let xhr = new XMLHttpRequest();
                xhr.open('POST', _url, true);
    
                xhr.onload = function (e) {
                    resolve(JSON.parse(xhr.response));
                };
                xhr.onerror = function (e) {
                    reject(e);
                }
    
                xhr.send(formData);
            });
        }
      
        deleteProduct(id: number) {
            let _url = "https://:5000/api/Services/DeleteProduct/";
            return this._http.delete(_url+'?id=' + id).toPromise();
        }    
    
        protected extractArray(res: Response, showprogress: boolean = true) {
            let data = res.json();
            return data || [];
        }
    }

3) templates 文件夹

  • app.html

    这是一个 HTML 模板,用作主页面。它包含:

    • md-toolbar:它被视为导航菜单,您可以从中导航到主页、登录和订阅页面。
    • md-sidenav:右侧导航菜单,显示以下信息:
      • 当前用户的电子邮件
      • 导航按钮:用于重定向到 productManagement 页面
      • 注销按钮:用于删除当前会话并重定向到 home 页面
    • router-outlet:这是一个占位符,用于根据路由器状态显示特定视图(了解更多关于 router-outlet 的信息)。
<md-toolbar >
  <button md-icon-button  class="md-raised md-primary" routerLink="/home">
      <md-icon >home</md-icon>
  </button> 

  <span>{{title}}</span>
     <span flex></span>
    
        <a md-button routerLink="/login"   *ngIf="showMenu == false"  
                     routerLinkActive="active">
            <i md-icon></i>Login
        </a>
        <a md-button routerLink="/subscription"  *ngIf="showMenu == false"  
           routerLinkActive="active">
                <i md-icon></i>Sign up
        </a>
 
      <button md-icon-button *ngIf="showMenu == true"  
              class="md-raised md-primary" (click)="right.toggle()">
          <md-icon  class="md-24">list</md-icon>  
    </button>
</md-toolbar>
 <md-sidenav-layout>
 
    <md-sidenav  #right align="end" layout-padding>
      <h5 style="text-align:center"><md-icon>account_circle</md-icon></h5>
      <h6 style="text-align:center"> {{login}}</h6>
      <hr>
          <div style="text-align:center">
            <button md-button  routerLink="/productManagement"  
             class="md-icon-button" style="width: 100%" (click)="right.close()" 
             routerLinkActive="active">
                      <h5> Manage</h5>
              </button>
        </div>
       <div style="text-align:center">
            <button  md-button    class="md-icon-button"  
             style="width: 100%" 
             (click)="logOut(right)" routerLinkActive="active">
                   <h5>Logout</h5>
            </button>
         </div>
    </md-sidenav>
    <div  layout-padding>
             <router-outlet></router-outlet>    
    </div>
  </md-sidenav-layout>
  • home.html

    通过此页面,用户可以看到可用产品的列表及详细信息。如果用户已连接,他将有可能从当前列表中删除某些产品。

    <div >
        <h1>{{title}}</h1>
        <div class="row">
        
            <md-progress-circle color="primary" mode="indeterminate" 
             *ngIf="showProgress" [style.width]="'40px'"  
             [style.margin]="'0 auto'" ></md-progress-circle>
            <div *ngIf="showEmpty" class="alert alert-info">
                <strong>Info :</strong> Empty Result
            </div>
    
            <div *ngIf="showError" class="alert alert-danger">
                <strong>Error :</strong> Problem happened in server side.
            </div>
        <div>
     
        <div class="row"  >
            <div  *ngFor="let obj of listProduct" 
                  class="col col-lg-4" style="margin-top:1px;  " > 
                 <md-card >
                    <md-card-title-group>
                        <img md-card-md-image  [src]="obj.picture">
                        <md-card-title>{{obj.title}}</md-card-title>
                        <md-card-subtitle>Price : {{obj.price}} $</md-card-subtitle>
                      
                    </md-card-title-group>
                    
                    <md-card-actions *ngIf="isLogged == true" 
                     style="text-align: center">
                          <button md-icon-button  
                           class="md-primary" (click)="Remove(obj.id)" >
                                <md-icon    class="md-24" >delete</md-icon>
                          </button>
                    </md-card-actions>
                </md-card> 
                <div class="clearfix" ></div>
            </div>
        </div>
    </div>
  • login.html

    此页面提供了一个连接表单,允许用户输入其凭据(电子邮件和密码)并打开新会话。

    <div>
        <h1>{{title}}</h1>
        <div >
            <form>
                <div> 
                    <label class="vSpace">  Login :</label>  
                    <md-input type="email" name="login" 
                    [(ngModel)]="model.login" placeholder="Email" >  </md-input>
                </div>
                <div> 
                     <label class="vSpace"> Password : </label>  
                     <md-input type="password" name="pwd" 
                     [(ngModel)]="model.pwd" placeholder="Password" >  </md-input>
                </div>
                <div> 
                    <button type="button" md-raised-button   
                    [disabled]="(model.login == '' || model.pwd =='')"  
                    (click)="tryToConnect()"  class="md-primary" value="submit"  > 
                            submit
                    </button>
                </div>
            </form>
        </div>
    </div>
  • productManagement.html

    此页面为用户提供了通过表单添加新产品的可能性。他必须提供有关产品名称(标题)、描述、价格、缩略图(图片)的信息。

    <div>
        <h1>{{title}}</h1>
        <div >
            <form>
                <div> 
                    <label class="vSpace">  Title :</label>  
                    <md-input type="text" name="title" 
                    [(ngModel)]="model.title" placeholder="title">  </md-input>
                </div>
                 <div> 
                    <label class="vSpace">  Full description :</label>  
                    <md-input type="text"name="fullDescription" 
                    [(ngModel)]="model.fullDescription" 
                    placeholder="Description">  </md-input>
                </div>
                <div> 
                     <label class="vSpace"> Price ($) : </label>  
                     <md-input type="number" name="price" 
                     [(ngModel)]="model.price"  placeholder="Price" ></md-input>
                </div>
                <div> 
                    <label class="vSpace"> Piture : </label>
                    <label class="btn btn-default btn-file">
                        Load File <input type="file" id="file" 
                        name="pictureFile" (change)="onChange($event)" 
                        class="md-primary" class="hidden">
                    </label>
                </div>
                <div> 
       
                    <button type="button" md-raised-button  
                    [disabled]="(model.title == '' || model.fullDescription =='')" 
                    (click)="addNewProduct()"   class="md-primary" value="submit"  > 
                            Add
                    </button>
                </div>
            </form>
        </div>
    </div>
  • signup.html

    通过此页面,任何匿名用户都可以创建自己的帐户,以获得访问应用程序后端管理界面的权限。

    要创建新帐户,用户应填写订阅表单,输入以下信息:登录名、密码。

    <div>
        <h1>{{title}}</h1>
        <div>
              <form id="newProductForm" >
                <div> 
                <label class="vSpace">Login :</label> 
                     <md-input type="email"  name="login" 
                     [(ngModel)]="model.login" placeholder="Email" >  </md-input>
                </div>
                <div> 
                <label class="vSpace">Password :</label>
                     <md-input type="password" name="pwd"  
                     [(ngModel)]="model.pwd" placeholder="Password" >  </md-input>
                </div>
                <div> 
                <label class="vSpace">Confirm password :</label> 
                   <md-input type="password"  name="pwdConfirmation"  
                   [(ngModel)]="model.pwdConfirmation" 
                   placeholder="Confirm password" >  </md-input>
                </div>
                <div> 
     
                <button md-raised-button type="button"  
                [disabled]="(model.login == '' || model.pwd =='' || 
                model.pwdConfirmation != model.pwd)" (click)="Subscription()"  
                class="md-primary" value="submit"  > 
                                submit
                </button>
                </div>
            </form>
        </div>
    </div>

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

  • npm start:将 TS 文件转换为 JavaScript 文件并启动应用程序。

当您尝试通过给定的 URL (https://:3000) 打开应用程序时,您将得到以下结果

要获得此结果,您应该启动您的服务器应用程序(请参阅本文第 1 部分:服务器端实现)。确保您的 Web 服务可以从该地址访问:https://:5000。

参考文献

关注点

希望您喜欢这一系列文章。请尝试下载源代码,我期待您的问题和评论。

历史

  • v1 2017 年 1 月 29 日:初始版本
© . All rights reserved.