There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are:
有许多方法可以捆绑&压缩客户端资源(JavaScript和CSS文件). 最常见的方式是:
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/).
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins.
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests.
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...).
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md).
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment).
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example:
对于razor bundle tag helpers, `name`是**可选**. 如果没有定义一个名字,它将根据使用的捆绑文件名自动**计算生成**(they are **concatenated** and **hashed**) 例:
````html
<abp-style-bundle>
@ -80,33 +81,36 @@ The `name` is **optional** for the razor bundle tag helpers. If you don't define
</abp-style-bundle>
````
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not).
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions.
* 可以**有条件地将项目**添加到捆绑包中. 但这可能会导致基于条件的捆绑的存在多种变化.
Advantages of **named** bundles:
**命名** bundles优点:
* Other **modules can contribute** to the bundle by its name (see the sections below).
* 其他模块可以通过其名称为捆绑包做出贡献(参见下面的部分).
#### Single File
#### 单个文件
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example:
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too.
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class.
@ -131,20 +135,20 @@ public class MyWebModule : AbpModule
}
````
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`).
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example:
在定义bundle之后, 可以使用上面定义的相同tag helpers将其包括在页面中. 例如:
````html
<abp-script-bundlename="MyGlobalBundle"/>
````
This time, no file defined in the tag helper definition because the bundle files are defined by the code.
这次tag helper定义中没有定义文件, 因为捆绑文件是由代码定义的.
#### Configuring An Existing Bundle
#### 配置现有的 Bundle
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module.
@ -166,13 +170,13 @@ public class MyWebExtensionModule : AbpModule
}
````
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper.
> 无法通过代码配置未命名的bundle tag helpers, 因为它们的名称在开发时是未知的. 建议始终使用bundle tag helper的名称.
### Bundle Contributors
### Bundle 贡献者
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases.
Contributors can also be used in the bundle tag helpers.
Example:
贡献者也可以在bundle tag helpers中使用.
例如:
````xml
<abp-style-bundle>
@ -211,12 +215,12 @@ Example:
</abp-style-bundle>
````
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle.
A bundle contributor can have one or more dependencies to other contributors.
Example:
bundle贡献者可以与其他贡献者具有一个或多个依赖关系.
例如:
````C#
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
@ -226,19 +230,19 @@ public class MyExtensionStyleBundleContributor : BundleContributor
}
````
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications.
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules.
创建贡献者和定义依赖关系是一种跨不同模块组织包创建的方法.
#### Accessing to the IServiceProvider
#### 访问 IServiceProvider
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method.
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package.
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself.
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle.
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more.
It's suggested to define multiple bundles for an application, each one is used for different purposes.
建议为应用程序定义多个包, 每个包用于不同的目的.
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them.
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice.
* **Module bundles**: For shared resources among the pages of an individual module.
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice.
## ASP.NET Core MVC Client Side Package Management
## ASP.NET Core MVC 客户端包管理
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually.
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions.
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs).
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version.
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more.
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example:
It's suggested to depend on a standard package instead of directly depending on a third-party package.
#### Package Installation
#### 安装包
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies:
依赖于NPM包后, 你应该做的就是从命令行运行**yarn**命令来安装所有包及其依赖项:
````
yarn
````
Alternatively, you can use `npm install` but [Yarn](https://yarnpkg.com/) is suggested as mentioned before.
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted:
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package).
* It should be the **latest stable** version of the package.
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages.
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package.
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created.
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project.
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers.
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies.
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication.
* **clean** section is a list of folders to clean before copying the files.
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package.
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line:
正确配置`abp.resourcemapping.js`文件后, 可以从命令行运行gulp命令:
````
gulp
````
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file.
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively).
In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Entity Framework Core** (EF Core) will be used as the ORM provider (as it comes pre-configured with the [startup template](https://abp.io/Templates)).
Go to the [startup template page](https://abp.io/Templates) and download a new project named `Acme.BookStore`, create the database and run the application by following the [template document](../../Getting-Started-AspNetCore-MVC-Template.md).
Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Domain` project) of the solution. The main entity of the application is the `Book`:
* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for more details and best practices.
* `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class.
* `Guid` is the **primary key type** of the `Book` entity.
* Used **data annotation attributes** in this code for EF Core mappings. Alternatively you could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead.
* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling).
#### BookType Enum
#### BookType枚举
The `BookType` enum used above is defined as below:
下面是所有要用到的`BookType`枚举:
````C#
namespace Acme.BookStore
@ -77,9 +77,9 @@ namespace Acme.BookStore
}
````
#### Add Book Entity to Your DbContext
#### 将Book实体添加到DbContext中
EF Core requires you to relate entities with your DbContext. The easiest way to do this is to add a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below:
EF Core需要你将实体和DbContext建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如:
````C#
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
@ -89,31 +89,31 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
}
````
#### Add New Migration & Update the Database
#### 添加新的Migration并更新数据库
The Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore` as the **default project** and execute the following command:
* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details.
* `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI.
* `BookDto` is derived from the `AuditedEntityDto<Guid>` which has audit properties just like the `Book` class defined above.
* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. In this way, you get automatic convertion of `Book` objects to `BookDto` objects (instead of manually copy all properties).
Define an interface named `IBookAppService` for the book application service:
为应用服务定义一个名为 `IBookAppService` 的接口:
````C#
using System;
@ -186,25 +186,27 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
IAsyncCrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books
CreateUpdateBookDto, //Used to create a new book
CreateUpdateBookDto> //Used to update a book
IAsyncCrudAppService< //定义了CRUD方法
BookDto, //用来展示书籍
Guid, //Book实体的主键
PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序
CreateUpdateBookDto, //用于创建书籍
CreateUpdateBookDto> //用户更新书籍
{
}
}
````
* Defining interfaces for application services is <u>not required</u> by the framework. However, it's suggested as best practice.
* `IAsyncCrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods.
* There are some variations of the `IAsyncCrudAppService` where you can use a single DTO or separated DTOs for each method.
Implement the `IBookAppService` as named `BookAppService`:
创建 `BookAppService` 并实现 `IBookAppService`接口:
````C#
using System;
@ -228,75 +230,76 @@ namespace Acme.BookStore
}
````
* `BookAppService` is derived from `AsyncCrudAppService<...>` which implements all the CRUD methods defined above.
* `BookAppService` injects `IRepository<Book, Guid>` which is the default repository created for the `Book` entity. ABP automatically creates repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md).
* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as object mapping provider. You defined mappings using the `AutoMapFrom` and the `AutoMapTo` attributes above. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details.
You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX.
ABP can **automagically** configures your application services as MVC API Controllers by convention.
ABP可以 **自动地** 将应用服务转换成MVC API Controllers.
#### Swagger UI
The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application and enter `http://localhost:53929/swagger/` as URL on your browser.
It's common to call HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way.
ABP **dynamically** creates JavaScript **proxies** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**.
You can easily test the JavaScript proxy using your favorite browser's **Developer Console** now. Run the application again, open your browser's **developer tools** (shortcut: F12), switch to the **Console** tab, type the following code and press enter:
* `acme.bookStore` is the namespace of the `BookAppService` converted to [camelCase](https://en.wikipedia.org/wiki/Camel_case).
* `book` is the conventional name for the `BookAppService` (removed AppService postfix and converted to camelCase).
* `getList` is the conventional name for the `GetListAsync` method defined in the `AsyncCrudAppService` base class (removed Async postfix and converted to camelCase).
* `{}` argument is used to send an empty object to the `GetListAsync` method which normally expects an object of type `PagedAndSortedResultRequestDto` which is used to send paging and sorting options to the server.
* `getList` function returns a `promise`. So, you can pass a callback to the `done` (or `then`) function to get the result from the server.
You can see the **book list** returned from the server. You can also check the **network** tab of the developer tools to see the client to server communication:
It's time to create something visible and usable! Instead of classic MVC, we will use the new [Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start) approach which is recommended by Microsoft.
Create a new `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project and add a new Razor Page named `Index.html`:
Open the `Index.cshtml` and change the content as shown below:
打开`Index.cshtml`并把内容修改成下面这样:
````html
@page
@ -307,11 +310,11 @@ Open the `Index.cshtml` and change the content as shown below:
<h2>Books</h2>
````
* Change the default inhertitance of the Razor View Page Model so it **inherits** from the `BookStorePageBase` class (instead of `PageModel`). The `BookStorePageBase` class which comes with the startup template and provides some shared properties/methods used by all pages.
@ -339,22 +342,22 @@ Open the `en.json` file and add localization texts for `Menu:BookStore` and `Men
}
````
* ABP's localization system is built on [ASP.NET Core's standard localization](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) system and extends it in many ways. See the [localization document](../../Localization.md) for details.
* Localization key names are arbitrary, you can set any name. We prefer to add `Menu` namespace for menu items to distinguish from other texts. If a text is not defined in the localization file, it **fallbacks** to the localization key (ASP.NET Core's standard behavior).
* ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](../../Localization.md).
When you click to the Books menu item, you are redirected to the new Books page.
点击菜单就会调转到新增书籍的页面.
#### Book List
#### 书籍列表
We will use the [Datatables.net](https://datatables.net/) JQuery plugin to show list of tables on the page. Datatables can completely work via AJAX, so it is fast and provides a good user experience. Datatables plugin is configured in the startup template, so you can directly use it in any page without including any style or script file to your page.
Change the `Pages/Books/Index.cshtml` as following:
将`Pages/Books/Index.cshtml`改成下面的样子:
````html
@page
@ -385,17 +388,17 @@ Change the `Pages/Books/Index.cshtml` as following:
</abp-card>
````
* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning** for example. See the [bundling & minification document](../../AspNetCore/Bundling-Minification.md) for details.
* `abp-card`and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are many tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help of the intellisense and compile time type checking. See the [tag helpers document](../../AspNetCore/Tag-Helpers.md).
* You can **localize** the column names in the localization file as you did for the menu items above.
* `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to Datatable's format.
* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirment to use it, but it simplifies the datatables configuration by providing conventional values for missing options.
* `acme.bookStore.book.getList` is the function to get list of books (you have seen it before).
* See [Datatable's documentation](https://datatables.net/manual/) for more configuration options.