ASP.NET MVC和Web API中的Angular2 - 第2部分
内容
第1部分:Visual Studio 2017中的Angular2设置,基本CRUD应用程序,第三方模态弹出控件
第2部分:使用Angular2管道进行过滤/搜索,全局错误处理,调试客户端
介绍
在 ASP.NET MVC和Web API - 第1部分中,我们学习了ASP.NET MVC中的基本Angular2设置。在这部分我们将学习:
- 我们如何
UserComponent通过FirstName,LastName或Gender使用Angular2 来实现搜索/过滤功能来搜索用户pipe? - 如何通过扩展
ErrorHandler类来实现全局错误处理? - 如何使用Firefox调试器调试客户端代码?
开始吧
- 首先,浏览 ASP.NET MVC和Web API - 第1部分 ,并下载附件Angular2MVC_Finish.zip文件。将其 提取 到您计算机中所需的位置,然后双击
Angular2MVC.sln打开解决方案Visual Studio(2015年更新3或2017版)。
- 由于
Angular2MVC解决方案不需要NuGet和node_modules包,请转到Build菜单并选择Rebuild Solution,Visual Studio将下载所有列出的.NET包packages.config和客户端包中提到的package.json。
- 您将找到
packages包含所有必需的.NET软件包和node_modules包含所有客户端软件包的文件夹:
Compile和run应用程序,你不应该收到任何错误。- 由于现在已经下载了所有项目依赖关系,因此可以根据用户输入开始实现搜索/过滤数据功能,过滤器实现后的最终输出页面如下:
- 从屏幕截图,您可能已经了解了这个搜索/过滤器功能如何工作,一旦用户开始在搜索文本框中输入文本,数据将在匹配用户输入的文本之后的列表中开始在列表中过滤
First Name,last Name并且Gender领域。由于客户端过滤,这是非常方便快捷的。我喜欢这个功能,因为它避免了丑陋的文本框,其旁边的搜索按钮和服务器端的复杂过滤查询。 - 因此,在接下来的步骤中,我们将学习如何实现此功能。我们将使用Angular2
pipe来过滤数据,但在跳转到编码之前,让我们来了解一下什么是pipe如何使用它。 - 虽然Angular2文档对
pipes这里有很简单和全面的解释,但对于我那种懒惰的人,让我总结一下。pipe将数据转换为有意义的表示,例如,您12/03/2016从数据库获取日期并将其转换为Dec 03, 2016,您可以通过它进行操作pipe。其他内置的Pipes是Uppercase,lowercase,json等这是不言自明他们的名字。为什么它被调用pipe,因为我认为我们使用|符号将它们应用于变量或值。例如{{Value | uppercase}}。您可以按照pipes要求按照|符号分隔的某个值申请许多{{ birthdate | date | uppercase }}。您还可以指定参数传递给pipe由: (colon)例如日期过滤器可以采取格式参数,{{birthdate | date : ‘MM/dd/yyyy’}} - Now that we got the basic idea of
pipe, let’s implement the user search functionality throughpipe. Just like built inpipesavailable in Angular2, we can also implement our own custompipes, all we need is to implement thePipeTransforminterface and develop custom logic intransformmethod that takes two parameters,data(to be filtered from) andoptional argumentse.g. user input string to be searched in the data. To read more about custompipes, click here. - Let’s create the
userfilter pipe, right click on theappfolder and selectAdd -> New Folder, name the folder asfilter(orpipe, whatever you prefer):
- Right click on newly created
filterfolder and selectAdd -> TypeScript File:
user.pipe.ts在Item name文本框中输入名称,然后单击OK按钮:
- 将以下代码粘贴到新添加的
user.pipe.ts文件中:
隐藏 复制代码
import { PipeTransform, Pipe } from '@angular/core';
import { IUser } from '../Model/user';
@Pipe({
name: 'userFilter'
})
export class UserFilterPipe implements PipeTransform {
transform(value: IUser[], filter: string): IUser[] {
filter = filter ? filter.toLocaleLowerCase() : null;
return filter ? value.filter((app: IUser) =>
app.FirstName != null && app.FirstName.toLocaleLowerCase().indexOf(filter) != -1
|| app.LastName != null && app.LastName.toLocaleLowerCase().indexOf(filter) != -1
|| app.Gender != null && app.Gender.toLocaleLowerCase().indexOf(filter) != -1
) : value;
}
}
- 让我们明白我们刚加入的内容
user.pipe.ts:- 在第一行中,我们正在导入
PipeTransform和Pipe实现的接口以实现过滤功能。 - 在第二行中,我们导入
IUser我们在第一部分中创建的接口来保存用户列表。在这里,我们还使用它来保存作为过滤源数据的用户列表。 - 在下一行中,我们将指定我们将使用管道的管道
selector/名称userFilter(您将在以后的步骤中找到如何)。 - 接下来,我们正在创建实现
UserFilterPipe接口的类PipeTransform(实现接口意味着为接口中提到的所有方法提供机构)。 - 右键单击
PipeTransform并选择选项Go To Definition:
- 在第一行中,我们正在导入
- 您将登陆到
pipe_transform_d.ts文件,在那里您将找到一个很好的简要说明如何使用管道与transform我们必须实现的示例和方法:
- 您将登陆到
- 所以让我们回到
user.pipe.ts哪里可以看到我们有transform第一个参数的方法作为IUser数组,第二个被命名为filter在IUser数组中要搜索的输入字符串。 - 在
transform方法中,第一行只是检查过滤器是否不null。 - The next statement is the actual implementation of search, if you are C# developer, you can compare it to the
LINQ to Object. We are calling Array’sfiltermethod, checking through conditional operator that if any ofIUsermember (FirstName,LastNameorGender) is matching with user input search string and if YES, returning the filtered result.toLocaleLowerCasemethod is converting string to lower case, to read more about it, click here. If there is no matching record in User list, we are returning the all rows.
- 所以让我们回到
- Now that we have our filter ready, let’s add it to the
AppModuleto use it in our application, double click onapp.module.tsfile inappfolder to edit it:
- Update the
AppModuleaccording to the following screenshot:
- 我们在声明部分加上
UserFilterPipe声明的import引用。只是为了修改,declaration部分中的组件相互认识,这意味着我们可以UserFilterPipe在UserComponent(或任何其他组件)中使用,而不添加引用UserComponent本身。我们可以宣布components,pipesetc.indeclaration部分。 - 所以,我们的用户过滤器/搜索功能已经准备就绪,下一步是使用它,
UserComponent而不是直接使用它UserComponent,让我们创建SearchComponent所有组件可以共享的共享,这将有助于我们了解- 之间的相互作用
parent(UserComponent)和child(的SearchComponent)分量。 - 如何通过输入参数发送
@Input并通过@Output别名获取值 。
- 之间的相互作用
- 右键单击
Shared主app文件夹中的文件夹,然后选择Add -> TypeScript File:
- 输入
Item nameassearch.component.ts和点击OK按钮:
- 复制
search.component.ts文件中的以下代码,让我们一步一步了解:
隐藏 收缩 复制代码
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'search-list',
template: `<div class="form-inline">
<div class="form-group">
<label><h3>{{title}}</h3></label>
</div>
<div class="form-group">
<div class="col-lg-12">
<input class="input-lg" placeholder="Enter any text to filter" (paste)="getPasteData($event)" (keyup)="getEachChar($event.target.value)" type="text" [(ngModel)]="listFilter" /><img src="../../images/cross.png" class="cross-btn" (click)="clearFilter()" *ngIf="listFilter"/>
</div>
</div>
<div class="form-group">
<div *ngIf='listFilter'>
<div class="h3 text-muted">Filter by: {{listFilter}}</div>
</div>
</div>
</div> `
})
export class SearchComponent {
listFilter: string;
@Input() title: string;
@Output() change: EventEmitter<string> = new EventEmitter<string>();
getEachChar(value: any) {
this.change.emit(value);
}
clearFilter() {
this.listFilter = null;
this.change.emit(null);
}
getPasteData(value: any) {
let pastedVal = value.clipboardData.getData('text/plain');
this.change.emit(pastedVal);
value.preventDefault();
}
}
- 第一行我们是导入
Input,Output接口和EventEmitter类。Input和Output接口是不言自明的,从UserComponent(在我们的例子中,来自用户的搜索字符串)的输入参数,Output是将值发送回来,SearchComponent但没有什么有趣的,输出是通过使用EventEmitter类的事件发回的。这将在进一步的步骤中变得更加清楚。 - In next line, we are providing the Component metadata, i.e.
selector(tag name through which we will useSearchComponentinUserComponente.g.<search-list></search-list>).templateis the HTML part of component. You can also put it in separate HTML file and specify thetemplateUrlproperty instead but since this is quite slim, I would prefer to have it in the same file. - In
SearchComponentclass, we are declaring one local variablelistFilterthat is search string we will use to display here<div class="h3 text-muted">Filter by: {{listFilter}}</div>. That is only for cosmetic purpose to show what we are searching. - Second variable
titleis with@Inputdecorator, we will send search textbox title from UserComponent. Third variablechangeis with@Outputdecorator and ofEventEmittertype. This is how we send data back to parent component.change EventEmitter<string>means change is an event that parent component needs to subscribe and will get string argument type. We will explicitly callemitfunction (i.e.change.emit(“test”)) to send the value back to the parent component. getEachChar(value: any): this function will be called for every character user will enter in search textbox. We are only callingthis.change.emit(value);that is sending that character to parent component where it is being sent to theUserFilterPipepipe to be filtered from User list. Just for revision, inUserPipeFilterwe are comparing that character withFirstName,LastNameandGenderand returning only those records where this character(s) exist. So as long the user would be entering characters in Search textbox, data would be filtering on runtime.clearFilter(): Will clear the filter to reset the User list to default without any filtering.getPasteData(value: any): This is little interesting function that will take care if user would copy search string from somewhere and paste it in search textbox to filter the Users list. Throughvalue.clipboardData.getData('text/plain')we are getting the pasted data and sending it throughchange.emit(value)function to parent component.- Now, we got some idea about these function, if you jump back to
SearchComponent template(HTML). We are callinggetEachCharonkeyupevent that will trigger every time user would type in Search textbox,getPasteDatais being called onpasteevent that will occur when user would paste value in Search textbox,clearFilterfunction would be called on clicking the cross image that would only be visible if search textbox would have at least one character.
- 第一行我们是导入
- 所以我们完成了创建
SearchComponent,希望你有一个想法如何工作,让我们添加它,AppModule以便我们可以使用它,双击app -> app.module.ts编辑它:
- 添加以下
import语句:
隐藏 复制代码
import { SearchComponent } from './Shared/search.component';
- 在声明部分添加SearchComponent以在任何组件中使用它:
隐藏 复制代码
declarations: [AppComponent, UserComponent, HomeComponent, UserFilterPipe, SearchComponent],
- 所以现在我们
SearchComponent已经准备好使用了,我们在这里使用它UserComponent。双击app -> Components -> user.component.html进行编辑:
- 我们将添加
SearchComponent在用户列表的顶部,因此在Add按钮顶部添加以下div :
隐藏 复制代码
<div> <search-list [title]='searchTitle' (change)="criteriaChange($event)"></search-list> </div>
- 让我们明白一下,它看起来像普通的HTML,但是
search-list标签。如果你记得,这是我们在文件中定义的selector属性。如果您在第1部分中记住,我们了解到,用于将数据从父组件发送到子组件。我们通过我们定义的变量来评价孩子的组件变量的价值。二是事件绑定,我们创建的事件,我们提供的功能中,当发生变化的事件将执行每一次。将保存事件发送的任何值,在我们的例子中,我们发送每个字符用户将进入搜索文本框(参见功能)。SearchComponentsearch.component.tsProperty Binding [ ]titlesearchTitleUserComponent( )changeSearchComponentcriteriaChangeUserComponent$eventchangegetEachCharSearchComponent - 由于我们
criteriaChange在事件绑定中指定了函数,search-list所以我们将其添加到UserComponent。双击app -> Components -> user.component.ts进行编辑:
- 添加以下功能
user.component.ts:
隐藏 复制代码
criteriaChange(value: string): void {
if (value != '[object Event]')
this.listFilter = value;
}
- 您可以看到我们从
change事件中获取输入参数值(用户在搜索文本框中输入的文本),并将其分配给listFilter我们将用于pipe过滤器的变量。让我们继续声明listFilter变量。添加以下行与其他变量声明语句:
隐藏 复制代码
listFilter: string;
- 到目前为止,我们已经创建了
SearchComponent一个具有交叉图像按钮的文本框,以便按照用户搜索文本的只读显示来清除搜索。在父母中UserComponent,我们订阅了change事件,并在搜索文本框中获取用户输入的每个字符,并将其分配给listFilter变量,其中它被累积(例如,用户输入字符'a'),将被发送到过滤器,其中所有包含“a”的记录将被过滤后,如果用户将其他任何字符(如'f')'a',那么'a'和'f'都将被发送为“af”进行过滤,并且所有具有“af”组合的记录将被过滤,因此上)。一旦你开始使用它,或者你可以调试它,我将在即将到来的步骤解释)。所以,最后一步是如何根据在搜索文本框中输入的搜索文本来过滤用户列表?<tr *ngFor="let user of users">因此,从刷新前面的步骤你管知识和更新在app->Components -> user.component.html到<tr *ngFor="let user of users | userFilter:listFilter">。userFilter我们在前面的步骤中创建的过滤器在哪里,是要过滤listFilter的输入参数。 - 由于我们使用定义的双向数据绑定
[(ngModel)]的listFilter变量FormsModule,我们将其添加到其中AppModule,以便更新
隐藏 复制代码
import { ReactiveFormsModule } from '@angular/forms';
至
隐藏 复制代码
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; in AppModule.
- 添加
formsModule在导入部分。
- 在任何浏览器中编译和运行项目(建议使用Firefox或Chrome)。转到
UserManagement页面,现在可能有很少的记录,你可以继续添加15,20多个。开始输入First Name,Last Name或Gender在搜索文本框中,您将看到运行时过滤的记录:
- 这就是我们的过滤。
- 接下来,我们将了解Angular2中的错误处理,我将保持简单,不会在每个错误类型中出现,但是让您知道如何对每个错误类型都有自定义错误。有关Angular2
ErrorHandler类的快速参考,请点击此处。 - 对于自定义错误处理程序类,我们可以扩展
ErrorHandlerhasconstructor和handleError使用error参数的方法。误差参数具有完整的错误的信息,例如status,error code,error text等等取决于错误类型(HTTP,应用等)。这真的有助于自定义错误消息。ErrorHandler处理任何类型的错误,例如未声明的变量/函数,任何数据异常或HTTP错误。此外ErrorHandler,我还将解释如何在Firefox浏览器中调试代码(您也可以在Chrome或IE中进行调试)。 - 我们将从中删除错误处理代码
UserService,UserComponent以便我们可以捕获ErrorHandler类中的所有错误,然后我们使用Firefox检查错误debugger。所以,让我们开始吧。 - 首先,我们创建自定义错误处理程序类。右键单击
app -> Shared文件夹,然后选择Add -> TypeScript File:
- 输入名称
errorhandler.ts并点击OK按钮:
- 将以下代码粘贴到新创建的文件中:
隐藏 复制代码
import { ErrorHandler } from '@angular/core';
export default class AppErrorHandler extends ErrorHandler {
constructor() {
// We rethrow exceptions, so operations like 'bootstrap' will result in an error
// when an error happens. If we do not rethrow, bootstrap will always succeed.
super(true);
}
handleError(error: any) {
debugger;
alert(error);
super.handleError(error);
}
}
- 代码是非常不言自明的评论,即为什么我们正在调用
super(true)构造函数。AppErrorHandler是我们的定制类,它正在扩展Angular2ErrorHandler类并实现该handleError功能。在handleError,我把调试器显示出来会有什么错误,以及如何自定义它。我们通过简单的JavaScript警报功能显示错误消息。 - 首先让我们看看
HTTP错误。假设我们在加载数据库中的所有用户之前都有认证逻辑,并且请求以某种方式未被认证,我们not authorized (401)将从ASP.NET Web API向Angular2 发送错误。让我们得到这个错误AppErrorHandler并检查它。 - 接下来,添加
AppErrorHandlerinAppModule来捕获所有错误。添加以下import语句:
AppErrorHandler从... 导入'./Shared/errorhandler';
- 更新提供程序部分以具有
ErrorHandler:
隐藏 复制代码
providers: [{ provide: ErrorHandler, useClass: AppErrorHandler },{ provide: APP_BASE_HREF, useValue: '/' }, UserService]
- 我们告诉我们的模块使用我们的自定义错误处理程序来处理任何错误。不要忘记在
ErrorHandler语句中添加类@angular2/coreimport引用:
- 我们来评论
UserComponent.ts文件app -> Components夹中的错误处理。双击进行编辑。转到LoadUsers功能并更新它如下:
隐藏 复制代码
LoadUsers(): void {
this.indLoading = true;
this._userService.get(Global.BASE_USER_ENDPOINT)
.subscribe(users => { this.users = users; this.indLoading = false; }
//,error => this.msg = <any>error
);
}
- 您可以看到我已经注释掉了在
msg屏幕底部显示的变量中保存的错误语句。 - 接下来我们来评论
user.service.ts文件中的错误处理,在文件夹中找到它app -> Service并双击它进行编辑。更新get方法如下,我评论了catch语句:
隐藏 复制代码
get(url: string): Observable<any> {
return this._http.get(url)
.map((response: Response) => <any>response.json());
// .do(data => console.log("All: " + JSON.stringify(data)))
// .catch(this.handleError);
}
- 现在我们的客户端代码已经准备好捕获HTTP异常,我们来添加
unauthorized异常代码UserAPIController(基本上我们将添加它BaseAPIController并调用它UserAPIController)。 - 转到
Controllers文件夹并双击BaseAPIController.cs进行编辑:
- 添加以下
ErrorJson函数实际上是ToJson方法的副本,但只是使用Unauthorized状态代码(我刚刚创建了样例,应该为HTTP调用创建更专业的错误处理代码):
隐藏 复制代码
protected HttpResponseMessage ErrorJson(dynamic obj)
{
var response = Request.CreateResponse(HttpStatusCode.Unauthorized);
response.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
return response;
}
- 由于我迄今没有任何身份验证逻辑,所以在
UserAPIController我只是更新Get()方法如下,只是替换了这个ToJson功能ErrorJson,现在API将永远抛出Unauthorized我们将尝试加载用户的excleion:
隐藏 复制代码
public HttpResponseMessage Get()
{
return ErrorJson(UserDB.TblUsers.AsEnumerable());
}
- 编译并运行项目,转到
User Management页面。几分钟后,您会看到如下所示的丑恶警报信息:
- 很好,所以我们的测试环境成功创建。我们将这个错误从
UserAPIGet()方法发送到我们的定制中被捕获的客户端AppErrorHandler。 - 我们来调试错误,在
errorhandler.ts文件中,点击旁边的灰色条debugger来设置断点:
- 在Firefox中运行应用程序,按
Ctrl+Shift+S或单击打开menu button => Developer => Debugger:
- 你应该结束以下屏幕:
- 转到
User Management页面,稍后,您将看到执行停止在debugger:
- 鼠标悬停在
error上方,您会看到所有参数错误:
- 由于这是HTTP错误,你可以看到的
HTTPStatusCode是401 (Unauthorized request),主体部分仍然有一定你永远也不会发回的数据,而不是你可以在这里发送用户友好的错误消息。 - 通过考虑这些错误参数,我们可以通过检查状态代码来扩展我们的错误处理。我们开始做吧。
- 更新
handleError在以下errorhandler.ts文件:
隐藏 复制代码
handleError(error: any) {
debugger;
if (error.status == '401')
alert("You are not logged in, please log in and come back!")
else
alert(error);
super.handleError(error);
}
Compile与run应用程序再次,转到User Management页一次。您现在将看到以下用户友好的错误消息:
- Firefox
debugger是调试客户端代码的好工具,花一些时间来探索更多有用的功能。你可以步骤next line,into the function或step out通过突出显示的按钮:
- 接下来我们来弄清我们的应用程序,并通过Firefox调试检查错误变量。双击
app => Components => home.component.ts进行编辑。 - 在模板部分输入以下html:
隐藏 复制代码
<button class="btn btn-primary" (click)="IdontExist()">ErrorButton</button>
- 最终模板应如下:
- 我添加了一个带有
IdontExist()不存在的调用函数的click事件的按钮HomeComponent。 - 让我们运行应用程序,然后运行调试器,你会
ErrorButton在屏幕中间看到愚蠢:
- 点击
ErrorButton,再次看到执行停止在debugger(断点),鼠标悬停错误,浏览参数弹出或点击watch链接在底部放错误变量在右侧Variables部分:
- 您可以看到这一堆新的信息,展开该
originalError部分,您将看到实际的错误:
- 您可以看到非常详细的信息来挖掘复杂的错误。
- 按
Resume左侧的按钮继续执行:
- 您会看到简短的错误信息:
- 调试是在客户端获取完整信息的好工具。
Thanks & Best Regards!
Javi Zhu 朱佳辉
Mobile: 15900467108
Email: Javi.zhu@outlook.com

浙公网安备 33010602011771号