abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

时间:2023-12-27 18:59:31

Introduction With AspNet MVC Web API EntityFramework and AngularJS

地址:https://aspnetboilerplate.com/Pages/Articles/Introduction-With-AspNet-MVC-Web-API-EntityFramework-and-AngularJs/index.html

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

(github截图)

页面图片:

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

示例应用程序的屏幕截图。

Introduction

在本文中,我将向您展示如何使用以下工具从头开始开发单页Web应用程序(SPA):

In this article, I'll show you how to develop a Single-Page Web Application (SPA) from ground to up using the following tools:

  • ASP.NET MVC and ASP.NET Web API as web framework.
  • AngularJS as SPA framework.
  • EntityFramework as ORM (Object-Relational Mapping) framework
  • Castle Windsor as Dependency Injection framework.
  • Twitter Bootstrap as HTML/CSS framework.
  • Log4Net for logging, AutoMapper for object-to-object mapping.
  • And ASP.NET Boilerplate as startup template and application framework.

ASP.NET MVC和ASP.NET Web API作为Web框架。
AngularJS作为SPA框架。
EntityFramework作为ORM(对象关系映射)框架
温莎城堡作为依赖注入框架。
Twitter Bootstrap作为HTML / CSS框架。
Log4Net用于记录日志,AutoMapper用于对象到对象的映射。
并以ASP.NET Boilerplate作为启动模板和应用程序框架。

ASP.NET Boilerplate [1]是一个开放源代码应用程序框架,该框架将所有这些框架和库组合在一起,使您可以轻松地开始开发应用程序。

它为我们提供了以最佳实践开发应用程序的基础结构。

它自然支持依赖注入,域驱动设计和分层体系结构。 该示例应用程序还实现了验证,异常处理,本地化和响应式设计。

从样板模板创建应用程序

ASP.NET Boilerplate通过提供模板来组合并配置用于构建企业级Web应用程序的最佳工具,从而节省了我们启动新应用程序的时间。

让我们去aspnetboilerplate.com/Templates从模板构建我们的应用程序...

Create the application from boilerplate template

ASP.NET Boilerplate saves our time while starting a new application by providing templates that combines and configures best tools to build enterprise level web applications.

Let's go to aspnetboilerplate.com/Templates to build our application from template...

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

在这里,我选择了带有AngularJS和EntityFramework的ASP.NET MVC 5.x,SPA(单页应用程序)。 还为我的项目名称输入了SimpleTaskSystem。 我不想包含身份验证选项来获得最简单的项目模板。 它创建并下载了我的解决方案。

Here, I selected ASP.NET MVC 5.xSPA (Single Page Application) with AngularJS and EntityFramework. Also entered SimpleTaskSystem for my project name. I didn't want to include authentication options to get the most simple project template. It created and downloaded my solution.

解决方案中包含五个项目。 域(业务)层的核心项目,应用程序层的应用程序项目,用于实现Web Api控制器的WebApi项目,用于表示层的Web项目,最后是用于EntityFramework实现的EntityFramework项目。

There are five projects included in the solution. Core project for domain (business) layer, Application project for application layer, WebApiproject to implement Web Api controllers, Web project for presentation layer and finally EntityFramework project for EntityFramework implementation.

注意:如果下载该Acticle的示例解决方案,则在该解决方案中将看到7个项目。 我改进了模板以支持NHibernate和Durandal也用于相同的应用程序。 如果您对NHibernate或Durandal不感兴趣,请忽略这两个项目。

Note: If you download sample solution for this acticle, you will see 7 projects in the solution. I improved template to support NHibernate and Durandal also for same application. If you don't interest in NHibernate or Durandal, just ignore these 2 projects.

Create entities

我正在创建一个简单的应用程序来创建任务并将这些任务分配给人们。 因此,我需要Task和Person实体。

I'm creating a simple application to create tasks and assing these tasks to people. So, I need Task and Person entities.

任务实体仅定义任务的描述,CreationTime和状态。 它还具有对Person(AssignedPerson)的可选引用:

Task entity simply defines a Description, CreationTime and a State for a Task. It also has an optional reference to a Person (AssignedPerson):

public class Task : Entity<long>
{
[ForeignKey("AssignedPersonId")]
public virtual Person AssignedPerson { get; set; } public virtual int? AssignedPersonId { get; set; } public virtual string Description { get; set; } public virtual DateTime CreationTime { get; set; } public virtual TaskState State { get; set; } public Task()
{
CreationTime = DateTime.Now;
State = TaskState.Active;
}
}

Person entity is simpler and just defines Name of the person:

public class Person : Entity
{
public virtual string Name { get; set; }
}

ASP.NET Boilerplate提供了定义Id属性的Entity类。 我从这个Entity类派生了实体。 因为我从Entity <long>派生以来,任务类有Id类型long。 Person类的ID为int类型。 由于int是默认的主键类型,因此未指定。

ASP.NET Boilerplate provides Entity class that defines Id poperty. I derived entities from this Entity class. Task class has an Id of type long since I derived from Entity<long>. Person class has an Id of type int. Since int is the default primary key type, I did not specified it.

由于实体是域/业务层的一部分,因此我在Core项目中定义了实体。

I defined entities in the Core project since Entities are parts of domain/business layer.

Create DbContext

如您所知,EntityFramework与DbContext类一起使用。 我们应该先定义它。 ASP.NET Boilerplate模板为我们创建了一个DbContext模板。 我刚刚为Task和Person添加了IDbSet。 这是我的DbContext类:

As you know, EntityFramework works with DbContext class. We should first define it. ASP.NET Boilerplate template creates a DbContext template for us. I just added IDbSets for Task and Person. This is my DbContext class:

public class SimpleTaskSystemDbContext : AbpDbContext
{
public virtual IDbSet<Task> Tasks { get; set; } public virtual IDbSet<Person> People { get; set; } public SimpleTaskSystemDbContext()
: base("Default")
{ } public SimpleTaskSystemDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{ }
}

它在web.config中使用默认连接字符串。 它的定义如下所示:

It uses Default connection string in web.config. It's defined as shown below:

<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystem; Trusted_Connection=True;" providerName="System.Data.SqlClient" />

Create Database Migrations(创建数据库迁移)

我们将使用EntityFramework的代码优先迁移来创建和维护数据库架构。 ASP.NET Boilerplate模板默认情况下启用了迁移,并添加了一个Configuration类,如下所示:

We'll use EntityFramework's Code First Migrations to create and maintain the database schema. ASP.NET Boilerplate template has enabled migrations by default and added a Configuration class as shown below:

internalinternal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
} protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context)
{
context.People.AddOrUpdate(
p => p.Name,
new Person {Name = "Isaac Asimov"},
new Person {Name = "Thomas More"},
new Person {Name = "George Orwell"},
new Person {Name = "Douglas Adams"}
);
}
}

在Seed方法中,我添加了四个人作为初始数据。 现在,我将创建初始迁移。 我打开程序包管理器控制台,然后键入以下命令:

In the Seed method, I added four people for initial data. Now, I'll create the initial migration. I opened Package Manager Console and typed the following command:

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

Add-Migration "InitialCreate" command creates a class named InitialCreate as shown below:

public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.StsPeople",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
})
.PrimaryKey(t => t.Id); CreateTable(
"dbo.StsTasks",
c => new
{
Id = c.Long(nullable: false, identity: true),
AssignedPersonId = c.Int(),
Description = c.String(),
CreationTime = c.DateTime(nullable: false),
State = c.Byte(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StsPeople", t => t.AssignedPersonId)
.Index(t => t.AssignedPersonId);
} public override void Down()
{
DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople");
DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" });
DropTable("dbo.StsTasks");
DropTable("dbo.StsPeople");
}
}

我们创建了所需的类来创建数据库,但尚未创建数据库。 为此,我将运行以下命令:

We did create needed classes to create the database, but not created the database yet. To do it, I'll run the following command:

PM> Update-Database

该命令运行迁移,创建数据库并为我们填充初始数据:

This command runs migrations, creates the database and populates the initial data for us:

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

更改Entitiy类时,我们可以使用Add-Migration命令轻松创建新的迁移类,并使用Update-Database命令更新数据库。 要了解有关数据库迁移的更多信息,请参阅实体框架的文档。

When we change Entitiy classes, we can easily create new migration classes using Add-Migration command and update the database with Update-Database command. To learn more about database migrations, see entity framework's documentation.

Define repositories

在域驱动的设计中,存储库用于实现特定于数据库的代码。 ASP.NET Boilerplate使用通用的IRepository接口为每个实体创建一个自动存储库。 IRepository定义了选择,插入,更新,删除以及其他一些常用方法:

In domain driven design, repositories used to implement database-specific codes. ASP.NET Boilerplate creates an automatic repository for each entity using generic IRepository interface. IRepository defines common methods for select, insert, update, delete and a few more:

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

我们可以根据需要扩展这些存储库。 我将对其进行扩展以创建Task信息库。 因为我想将接口与实现分开,所以我首先声明存储库的接口。 这是任务存储库接口:

We can extend these repository upon our needs. I will extend it to create a Task repository. As I want to separate interface from implementation, I declare interfaces for repositories first. Here, is the Task repository interface:

public interface ITaskRepository : IRepository<Task, long>
{
List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
}

它扩展了ASP.NET Boilerplate的通用IRepository接口。 因此,ITaskRepository固有地将所有这些方法定义为默认方法。 当我定义GetAllWithPeople(...)时,它也可以添加自己的方法。

It extends generic IRepository interface of ASP.NET Boilerplate. So, ITaskRepository inherently defines all these methods as default. It can also add it's own methods as I defined GetAllWithPeople(...).

无需为Person创建存储库,因为默认方法对我来说足够了。 ASP.NET Boilerplate提供了一种在不创建存储库类的情况下注入通用存储库的方法。 我们将在“构建应用程序服务”部分的TaskAppService类中看到它。

No need to create a repository for Person since default methods are enough for me. ASP.NET Boilerplate provides a way of injecting generic repositories without creating a repository class. We will see it in TaskAppService class in 'Build application services' section..

我在核心项目中定义了存储库接口,因为它们是域/业务层的一部分。

I defined repository interfaces in the Core project since they are parts of domain/business layer.

Implement repositories

我们应该实现上面定义的ITaskRepository接口。 我正在EntityFramework项目中实现存储库。 因此,域层domain layer变得完全独立于EntityFramework。

We should implement the ITaskRepository interface defined above. I'm implementing repositories in EntityFramework project. Thus, domain layer becomes completely independent from EntityFramework.

创建项目模板时,ASP.NET Boilerplate为项目中的存储库定义了通用基类:

When we created the project template, ASP.NET Boilerplate defined a generic base class for repositories in our project:

SimpleTaskSystemRepositoryBase。 拥有这样的基类是一个好习惯,因为以后我们可以为存储库添加一些通用方法。 您可以在代码中看到此类的定义。 我只是从中获得TaskRepository的实现:

SimpleTaskSystemRepositoryBase. It's a good practice to have such a base class since we can later add some common methods for our repositories. You can see definition of this class in the code. I just derive from it for TaskRepository implementation:

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
} public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
{
//In repository methods, we do not deal with create/dispose DB connections, DbContexes and transactions. ABP handles it. var query = GetAll(); //GetAll() returns IQueryable<T>, so we can query over it.
//var query = Context.Tasks.AsQueryable(); //Alternatively, we can directly use EF's DbContext object.
//var query = Table.AsQueryable(); //Another alternative: We can directly use 'Table' property instead of 'Context.Tasks', they are identical. //Add some Where conditions... if (assignedPersonId.HasValue)
{
query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
} if (state.HasValue)
{
query = query.Where(task => task.State == state);
} return query
.OrderByDescending(task => task.CreationTime)
.Include(task => task.AssignedPerson) //Include assigned person in a single query
.ToList();
}
}

TaskRepository派生自SimpleTaskSystemRepositoryBase,并实现了我们上面定义的ITaskRepository。

TaskRepository is derived from SimpleTaskSystemRepositoryBase and implements ITaskRepository we defined above.

GetAllWithPeople是我们获取任务的特定方法,其中包括(预提取)AssignedPerson并根据某些条件选择过滤任务。 我们可以在存储库中*使用上下文(EF的DBContext)对象和数据库。 ASP.NET Boilerplate为我们管理数据库连接,事务,创建和处置DbContext(有关更多信息,请参见文档)

GetAllWithPeople is our specific method to get tasks where AssignedPerson included (pre-fetched) and optionally filtered by some conditions. We can freely use Context (EF's DBContext) object and database in repositories. ASP.NET Boilerplate manages database connection, transaction, creating and disposing the DbContext for us (See documentation for more information)

Build application services

通过提供外观样式方法,应用程序服务用于将表示层与域层分开。 我在项目的“应用程序”程序集中定义了应用程序服务。 首先,我定义任务应用程序服务的接口:

Application services is used to separate presentation layer from domain layer by providing façade style methods. I defined application services in the Application assembly in the project. First, I define interface for task application service:

public interface ITaskAppService : IApplicationService
{
GetTasksOutput GetTasks(GetTasksInput input);
void UpdateTask(UpdateTaskInput input);
void CreateTask(CreateTaskInput input);
}

ITaskAppService扩展了IApplicationService。 因此,ASP.NET Boilerplate自动为此类提供一些功能(如依赖项注入和验证)。 现在,让我们实现ITaskAppService:

ITaskAppService extends IApplicationService. Thus, ASP.NET Boilerplate automatically provides some features for this class (like dependency injection and validation). Now, let's implement ITaskAppService:

public class TaskAppService : ApplicationService, ITaskAppService
{
//These members set in constructor using constructor injection. private readonly ITaskRepository _taskRepository;
private readonly IRepository<Person> _personRepository; /// <summary>
///In constructor, we can get needed classes/interfaces.
///They are sent here by dependency injection system automatically.
/// </summary>
public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
{
_taskRepository = taskRepository;
_personRepository = personRepository;
} public GetTasksOutput GetTasks(GetTasksInput input)
{
//Called specific GetAllWithPeople method of task repository.
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); //Used AutoMapper to automatically convert List<Task> to List<TaskDto>.
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
} public void UpdateTask(UpdateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService base class.
Logger.Info("Updating a task for input: " + input); //Retrieving a task entity with given id using standard Get method of repositories.
var task = _taskRepository.Get(input.TaskId); //Updating changed properties of the retrieved task entity. if (input.State.HasValue)
{
task.State = input.State.Value;
} if (input.AssignedPersonId.HasValue)
{
task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
} //We even do not call Update method of the repository.
//Because an application service method is a 'unit of work' scope as default.
//ABP automatically saves all changes when a 'unit of work' scope ends (without any exception).
} public void CreateTask(CreateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService class.
Logger.Info("Creating a task for input: " + input); //Creating a new Task entity with given input's properties
var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue)
{
task.AssignedPersonId = input.AssignedPersonId.Value;
} //Saving entity with standard Insert method of repositories.
_taskRepository.Insert(task);
}
}

TaskAppService使用存储库进行数据库操作。 它通过构造函数注入模式在其构造函数中获取引用。 ASP.NET Boilerplate自然地实现了依赖注入,因此我们可以*地使用构造函数注入或属性注入(有关更多信息,请参见ASP.NET Boilerplate文档)。

TaskAppService uses repositories for database operations. It gets references in it's constructor via constructor injection pattern. ASP.NET Boilerplate naturally implements dependency injection, so we can use constructor injection or property injection freely (See more on dependency injection in ASP.NET Boilerplate documentation).

注意,我们通过注入IRepository <Person>来使用PersonRepository。 ASP.NET Boilerplate自动为我们的实体创建存储库。 如果IRepository的默认方法足以满足我们的需求,则不必创建存储库类。

Notice that we're using PersonRepository by injecting IRepository<Person>. ASP.NET Boilerplate automatically creates repositories for our entities. If default methods of IRepository enough for us, we don't have to create repository classes.

应用程序服务方法可与数据传输对象(DTO)一起使用。 这是最佳实践,我绝对建议使用此模式。 但是,只要您可以处理将实体暴露于表示层的问题,就不必这样做。

Application service methods works with Data Transfer Objects (DTOs). It's a best practice and I definitely suggest to use this pattern. But you don't have to do it as long as you can deal with problems of exposing Entities to presentation layer.

在GetTasks方法中,我使用了之前实现的GetAllWithPeople方法。 它返回一个List <Task>,但我需要将List <TaskDto>返回到表示层。 AutoMapper在这里帮助我们将Task对象自动转换为TaskDto对象。 GetTasksInput和GetTasksOutput是为GetTasks方法定义的特殊DTO。

In the GetTasks method, I used the GetAllWithPeople method that I implemented before. It returns a List<Task> but I need to return a List<TaskDto> to presentation layer. AutoMapper helps us here to automatically convert Task objects to TaskDto objects. GetTasksInput and GetTasksOutput are special DTOs defined for GetTasks method.

在UpdateTask方法中,我从数据库中检索了Task(使用IRepository的Get方法),并更改了Task的功能。 注意,我什至没有调用存储库的Update方法。 ASP.NET Boilerplate实现了UnitOfWork模式。 因此,应用程序服务方法中的所有更改都是一个工作单元(原子的),并在方法结束时自动应用于数据库。

In the UpdateTask method, I retrieved the Task from database (using IRepository's Get method) and changed peoperties of the Task. Notice that I did not even called Update method of the repository. ASP.NET Boilerplate implements UnitOfWork pattern. So, all changes in an application service method are a unit of work (atomic) and applied to database at the end of the method automatically.

在CreateTask方法中,我仅创建了一个新Task并使用IRepository的Insert方法将其插入数据库。

In the CreateTask method, I simply created a new Task and inserted to database using the IRepository's Insert method.

ASP.NET Boilerplate的ApplicationService类具有一些使开发应用程序服务更容易的属性。 例如,它定义了用于记录的Logger属性。 因此,我们从ApplicationService派生TaskAppService并在此处使用它的Logger属性。 从此类派生是可选的,但是实现IApplicationService是必需的(请注意ITaskAppService扩展了IApplicationService)。

ASP.NET Boilerplate's ApplicationService class has some properties to make developing application services easier. For example, it defines a Logger property for logging. So, we derived TaskAppService from ApplicationService and used it's Logger property here. It's optional to derive from this class but required to implement IApplicationService (notice that ITaskAppService extends IApplicationService).

Validation

ASP.NET Boilerplate自动验证应用程序服务方法的输入。 CreateTask方法获取CreateTaskInput作为参数:

ASP.NET Boilerplate automatically validates inputs of application service methods. CreateTask method gets CreateTaskInput as parameter:

public class CreateTaskInput
{
public int? AssignedPersonId { get; set; } [Required]
public string Description { get; set; } public override string ToString()
{
return string.Format("[CreateTaskInput > AssignedPersonId = {0}, Description = {1}]", AssignedPersonId, Description);
}
}

在这里,说明被标记为必填。 您可以在此处使用任何“数据注释”属性。 如果要进行一些自定义验证,可以像在UpdateTaskInput中实现的那样实现ICustomValidate:

Here, Description is marked as Required. You can use any Data Annotation attributes here. If you want to make some custom validation, you can implement ICustomValidate as I implemented in UpdateTaskInput:

public class UpdateTaskInput : ICustomValidate
{
[Range(1, long.MaxValue)]
public long TaskId { get; set; } public int? AssignedPersonId { get; set; } public TaskState? State { get; set; } public void AddValidationErrors(List<ValidationResult> results)
{
if (AssignedPersonId == null && State == null)
{
results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" }));
}
} public override string ToString()
{
return string.Format("[UpdateTask > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", TaskId, AssignedPersonId, State);
}
}

AddValidationErrors方法是您可以编写自定义验证代码的地方。

AddValidationErrors method is the place you can write your custom validation code.

Handling exceptions(异常处理)

请注意,我们没有处理任何异常。 ASP.NET Boilerplate自动处理异常,记录日志并向客户端返回适当的错误消息。 同样,在客户端,处理这些错误消息并显示给用户。 实际上,对于ASP.NET MVC和Web API Controller操作,这是正确的。 由于我们将使用Web API公开TaskAppService,因此我们不需要处理异常。 有关详细信息,请参见异常处理文档。

Note that we did not handled any exception. ASP.NET Boilerplate automatically handles exceptions, logs and returns an appropriate error message to the client. Also, in client side, handles these error messages and show to the user. Actually, this is true for ASP.NET MVC and Web API Controller actions. Since we will expose the TaskAppService using Web API, we don't need to handle exceptions. See exception handling document for details.

Build Web API services

我想向远程客户端公开我的应用程序服务。 因此,我的AngularJS应用程序可以使用AJAX轻松调用这些服务方法。

I want to expose my application services to remote clients. Thus, my AngularJS application can easily call these service methods using AJAX.

ASP.NET Boilerplate提供了一种自动方式将应用程序服务方法公开为ASP.NET Web API。 我只使用DynamicApiControllerBuilder,如下所示:

ASP.NET Boilerplate provides an automatic way of exposing application service methods as ASP.NET Web API. I just use DynamicApiControllerBuilder as shown below:

DynamicApiControllerBuilder
.ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
.Build();

对于此示例,ASP.NET Boilerplate在应用程序层程序集中找到所有继承IApplicationService的接口,并为每个应用程序服务类创建一个Web api控制器。 有其他语法可用于精细控制。 我们将看到如何通过AJAX调用这些服务。

For this example, ASP.NET Boilerplate finds all interfaces inherits IApplicationService in Application layer assembly and creates a web api controller for each application service class. There are alternative syntaxes for fine control. We'll see how to call these services via AJAX.

Develop the SPA

我将实现单页Web应用程序作为项目的用户界面。 AngularJS(由Google提供)是最常用的SPA框架之一(最好是排名第一)。

I'll implement a Single-Page Web Application as user interface of my project. AngularJS (by Google) is one (propably the top one) of the most used SPA frameworks.

ASP.NET Boilerplate提供了一个模板,可以轻松地从AngularJS开始。 该模板有两个页面(主页和关于),页面之间平滑过渡。 使用Twitter Bootstrap作为HTML / CSS框架(因此具有响应能力)。 还可以使用ASP.NET Boilerplate的本地化系统将其本地化为英语和土耳其语(您可以轻松添加其他语言或删除其中一种语言)。

ASP.NET Boilerplate provides a template that makes easy to start with AngularJS. The template has two pages (Home and About) with smooth transition between pages. Uses Twitter Bootstrap as HTML/CSS framework (thus, it's responsive). It's also localized into English and Turkish with ASP.NET Boilerplate's localization system (You can easly add other languages or remove one of them).

我们首先更改模板的路线。 ASP.NET Boilerplate模板使用AngularUI-Router,这是AngularJS的实际标准路由器。 它提供基于状态的路由模式。 我们将有两个视图:任务列表和新任务。 因此,我们将在app.js中更改路由定义,如下所示:

We first change route of the template. ASP.NET Boilerplate template uses AngularUI-Router, the de-facto standard router of AngularJS. It provides state based routing modal. We will have two views: task list and new task. So, we will change route definition in app.js as shown below:

app.config([
'$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('tasklist', {
url: '/',
templateUrl: '/App/Main/views/task/list.cshtml',
menu: 'TaskList' //Matches to name of 'TaskList' menu in SimpleTaskSystemNavigationProvider
})
.state('newtask', {
url: '/new',
templateUrl: '/App/Main/views/task/new.cshtml',
menu: 'NewTask' //Matches to name of 'NewTask' menu in SimpleTaskSystemNavigationProvider
});
}
]);

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

app.js是用于配置和启动SPA的主要JavaScript文件。 请注意,我们正在使用cshtml文件作为视图! 通常,html文件在AngularJS中用作视图。 ASP.NET Boilerplate使使用cshtml文件成为可能。 因此,我们将拥有剃刀引擎生成HTML的能力。

app.js is the main JavaScript file to configure and start our SPA. Notice that we're using cshtml files as views! Normally, html files are used as views in AngularJS. ASP.NET Boilerplate makes it possible to use cshtml files. Thus we will have the power of razor engine to generate HTML.

ASP.NET Boilerplate提供了用于在应用程序中创建和显示菜单的基础结构。 它允许在C#中定义菜单,并在C#和JavaScript中使用相同的菜单。 请参阅SimpleTaskSystemNavigationProvider类以创建菜单,并请参见header.js / header.cshtml以有角度的方式显示菜单。

ASP.NET Boilerplate provides an infrastructure to create and show menus in an application. It allows to define menu in C# and use same menu both in C# and JavaScript. See SimpleTaskSystemNavigationProvider class for creating menu and see header.js/header.cshtml for showing menu in the angular way.

First, I'm creating an Angular controller for the task list view:

(function() {
var app = angular.module('app'); var controllerId = 'sts.views.task.list';
app.controller(controllerId, [
'$scope', 'abp.services.tasksystem.task',
function($scope, taskService) {
var vm = this; vm.localize = abp.localization.getSource('SimpleTaskSystem'); vm.tasks = []; $scope.selectedTaskState = 0; $scope.$watch('selectedTaskState', function(value) {
vm.refreshTasks();
}); vm.refreshTasks = function() {
abp.ui.setBusy( //Set whole page busy until getTasks complete
null,
taskService.getTasks({ //Call application service method directly from JavaScript
state: $scope.selectedTaskState > 0 ? $scope.selectedTaskState : null
}).success(function(data) {
vm.tasks = data.tasks;
})
);
}; vm.changeTaskState = function(task) {
var newState;
if (task.state == 1) {
newState = 2; //Completed
} else {
newState = 1; //Active
} taskService.updateTask({
taskId: task.id,
state: newState
}).success(function() {
task.state = newState;
abp.notify.info(vm.localize('TaskUpdatedMessage'));
});
}; vm.getTaskCountText = function() {
return abp.utils.formatString(vm.localize('Xtasks'), vm.tasks.length);
};
}
]);
})();

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

我将控制器的名称定义为“ sts.views.task.list”。 这是我的约定(针对可伸缩代码库),但是您可以简单地将其命名为“ ListController”。 AngularJS也使用依赖注入。 我们在这里注入“ $ scope”和“ abp.services.tasksystem.task”。 第一个是Angular的作用域变量,第二个是为ITaskAppService自动创建的JavaScript服务代理(我们之前在“构建Web API服务”部分中对其进行了构建)。

I defined name of the controller as 'sts.views.task.list'. This my convention (for scalable code-base) but you can simply name it as 'ListController'. AngularJS also uses dependency injection. We're injecting '$scope' and 'abp.services.tasksystem.task' here. First one is Angular's scope variable, second one is the automatically created JavaScript service proxy for ITaskAppService (we built it before in 'Build Web API services' section).

ASP.NET Boilerplate提供了在服务器和客户端中使用相同的本地化文本的基础结构(有关详细信息,请参见其文档)。

ASP.NET Boilerplate provides infrastructure to use same localization texts both in server and client (see it's documentation for details).

vm.taks是将在视图中显示的任务列表。 vm.refreshTasks方法通过使用taskService获取任务来填充此数组。 当selectedTaskState更改时调用(使用$ scope。$ watch观察)。

vm.taks is the list of tasks that will be shown in the view. vm.refreshTasks method fills this array by getting tasks using taskService. It's called when selectedTaskState changes (observed using $scope.$watch).

如您所见,调用应用程序服务方法非常简单明了! 这是ASP.NET Boilerplate的功能。 它生成Web API层和与此Web API层进行通信的JavaScript代理层。 因此,我们将应用程序服务方法称为调用简单的JavaScript方法。 它与AngularJS完全集成(使用Angular的$ http服务)。

As you see, calling an application service method is very easy and straightforward! This is a feature of ASP.NET Boilerplate. It generates Web API layer and JavaScript proxy layer that talks with this Web API layer. Thus, we are calling the application service method as calling a simple JavaScript method. It is completely integrated with AngularJS (uses Angular's $http service).

Let's see the view side of task list:

<div class="panel panel-default" ng-controller="sts.views.task.list as vm">

    <div class="panel-heading" style="position: relative;">
<div class="row"> <!-- Title -->
<h3 class="panel-title col-xs-6">
@L("TaskList") - <span>{{vm.getTaskCountText()}}</span>
</h3> <!-- Task state combobox -->
<div class="col-xs-6 text-right">
<select ng-model="selectedTaskState">
<option value="0">@L("AllTasks")</option>
<option value="1">@L("ActiveTasks")</option>
<option value="2">@L("CompletedTasks")</option>
</select>
</div>
</div>
</div> <!-- Task list -->
<ul class="list-group" ng-repeat="task in vm.tasks">
<div class="list-group-item">
<span class="task-state-icon glyphicon" ng-click="vm.changeTaskState(task)" ng-class="{'glyphicon-minus': task.state == 1, 'glyphicon-ok': task.state == 2}"></span>
<span ng-class="{'task-description-active': task.state == 1, 'task-description-completed': task.state == 2 }">{{task.description}}</span>
<br />
<span ng-show="task.assignedPersonId > 0">
<span class="task-assignedto">{{task.assignedPersonName}}</span>
</span>
<span class="task-creationtime">{{task.creationTime}}</span>
</div>
</ul> </div>

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)

ng-controller属性(在第一行中)将控制器绑定到视图。 @L(“ TaskList”)获取“任务列表”的本地化文本(在呈现HTML时在服务器上工作)。 这是可能的,因为这是一个cshtml文件。

ng-controller attribute (in the first line) binds the controller to the view. @L("TaskList") gets localized text for "task list" (works on server while rendering HTML). It's possible since this is a cshtml file.

ng-model绑定combobox和JavaScript变量。 当变量更改时,组合框将更新。 当组合框更改时,变量将更新。 这是AngularJS的双向绑定。

ng-model binds combobox and the JavaScript variable. When the variable changes, the combobox updated. When the combobox changes, the valiable is updated. This is two-way binding of AngularJS.

ng-repeat是Angular的另一个“指令”,用于为数组中的每个值呈现相同的HTML。 数组更改时(例如,添加了一个项目),它会自动反映到视图中。 这是AngularJS的另一个强大功能。

ng-repeat is another 'directive' of Angular that is used to render same HTML for each value in an array. When the array changes (an item is added for example), it's automatically reflected to the view. This is another powerful feature of AngularJS.

注意:添加JavaScript文件(例如,用于“任务列表”控制器)时,应将其添加到页面中。 可以通过将其添加到模板中的Home \ Index.cshtml中来完成。

Note: When you add a JavaScript file (for example, for the 'task list' controller), you should add it to your page. This can be done by adding it to Home\Index.cshtml in the template.

Localization

ASP.NET Boilerplate提供了一个灵活而强大的本地化系统。 您可以使用XML文件或资源文件作为本地化源。 您还可以定义自定义本地化源。 有关更多信息,请参见文档。 在此示例应用程序中,我使用了XML文件(位于Web应用程序的Localization文件夹下):

ASP.NET Boilerplate provides a flexible and strong localization system. You can use XML files or Resource files as localization source. You can also define custom localization sources. See documentation for more. In this sample application, I used XML files (it's under Localization folder in web application):

<?xml version="1.0" encoding="utf-8" ?>
<localizationDictionary culture="en">
<texts>
<text name="TaskSystem" value="Task System" />
<text name="TaskList" value="Task List" />
<text name="NewTask" value="New Task" />
<text name="Xtasks" value="{0} tasks" />
<text name="AllTasks" value="All tasks" />
<text name="ActiveTasks" value="Active tasks" />
<text name="CompletedTasks" value="Completed tasks" />
<text name="TaskDescription" value="Task description" />
<text name="EnterDescriptionHere" value="Task description" />
<text name="AssignTo" value="Assign to" />
<text name="SelectPerson" value="Select person" />
<text name="CreateTheTask" value="Create the task" />
<text name="TaskUpdatedMessage" value="Task has been successfully updated." />
<text name="TaskCreatedMessage" value="Task {0} has been created successfully." />
</texts>
</localizationDictionary>

Unit testing

ASP.NET Boilerplate设计为可测试的。 我撰写了一篇文章,以展示基于ABP的项目的单元测试和集成测试。 请参阅文章:使用xUnit,Entity Framework,Effort和ASP.NET Boilerplate在C#中进行单元测试。

ASP.NET Boilerplate is designed to be testable. I authored an article to show unit and integration testing for ABP based projects. See the article: Unit testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate.

Source Code

You can get the latest source code here https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/SimpleTaskSystem

总结:

通过翻译此文,对通过abp创建单页系统有了大概了解。

边用翻译软件翻译,边记录耗时5个多小时。

---------------------------------------------------------

系列学习笔记

abp学习(一)
abp学习(二)
abp学习(三)——文档翻译一

abp学习(四)——根据入门教程(aspnetMVC Web API进一步学习)