## Data Transfer Objects Best Practices & Conventions
## 数据传输对象最佳实践&约定
* **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like`EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>`and so on).
* **Do** define DTO members with **public getter and setter**.
* **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service.
* **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary.
public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
````
- **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules.
- **Do not** configure model directly in the `OnModelCreating` method. Instead, create an **extension method** for `ModelBuilder`. Use Configure*ModuleName* as the method name. Example:
public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions
@ -116,9 +115,9 @@ public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfiguratio
}
````
### Repository Implementation
### 仓储实现
- **Do****inherit** the repository from the `EfCoreRepository<TDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example:
public static IQueryable<IdentityUser> IncludeDetails(
@ -172,9 +171,9 @@ public static IQueryable<IdentityUser> IncludeDetails(
}
````
* **Do** use the `IncludeDetails` extension method in the repository methods just like used in the example code above (see FindByNormalizedUserNameAsync).
## Module Development Best Practices & Conventions
## 模块开发最佳实践 & 约定
### Introduction
### 介绍
This document describes the **best practices** and **conventions** for those who want to develop **modules** that satisfy the following specifications:
这篇文档描述了想要满足以下规范的**模块**的**最佳实践**与**约定**:
* Develop modules that conform to the **Domain Driven Design** patterns & best practices.
* Develop modules with **DBMS and ORM independence**.
* Develop modules that can be used as a **remote service / microservice** as well as being compatible with a **monolithic** application.
* 开发应用**领域驱动设计**模式的最佳实践的模块.
* 开发 **DBMS 与 ORM 独立** 的模块.
* 开发可用作 **远程服务 / 微服务** 的模块, 并可以集成到 **单体** 应用程序中.
Also, this guide is mostly usable for general **application development**.
本指南主要用于 **应用程序** 开发.
### Guides
* Overall
* [Module Architecture](Module-Architecture.md)
* Domain Layer
* [Entities](Entities.md)
* [Repositories](Repositories.md)
* [Domain Services](Domain-Services.md)
* Application Layer
* [Application Services](Application-Services.md)
* [Data Transfer Objects](Data-Transfer-Objects.md)
For a while, we were working to design a new major version of the ASP.NET Boilerplate framework. Now, it’s time to share it with the community. We are too excited and we believe that you are too.
# ABP vNext介绍
#### Naming
The name of the framework remains same, except we will call it only as “ABP” instead of “ASP.NET Boilerplate”. Because, the “boilerplate” word leads to misunderstandings and does not reflect that it is a framework (instead of some boilerplate code). We continue to use the “ABP” name since it’s the successor of the current ASP.NET Boilerplate framework, except it’s a rewrite.
We have created a startup template. You can just create a new project from https://abp.io/Templates and start your development. For more information, visit [abp.io](https://abp.io).
### Why A Complete Rewrite?
Why we spent our valuable time to rewrite it from scratch instead of incremental changes and improvements. Why?
## 命名
#### ASP.NET Core
When we first introduced the ABP framework, it was 2013 (5 years ago)! There was no .Net Core & ASP.NET Core and there was no Angular2+. They were all developed from scratch after ABP’s release.
ASP.NET Core introduced many built-in solutions (extension libraries) for dependency injection, logging, caching, localization, configuration and so on. These are actually independent from the ASP.NET Core and usable for any type of application.
We were using 3rd-party libraries and our own solutions for these requirements. We immediately integrated to ASP.NET Core features once they were released. But that was an integration, instead of building the ABP framework on top of these extension libraries. For instance, current ASP.NET Boilerplate still depends on Castle Windsor for dependency injection even it’s integrated to ASP.NET Core’s DI system.
## 如何开始
We wanted to depend on these new extension libraries instead of 3rd-party and custom solutions and this changes fundamental structures of the framework.
While current ABP is already modular itself and consists of dozens of packages, we still wanted to split the functionalities to more fine grained nuget packages.
For example, the core Abp package contains many features like DDD classes, auditing, authorization, background jobs, event bus, json serialization, localization, multi-tenancy, threading, timing and so on… We wanted to split all these functionality into their own packages and make them optional.
## 为什么要完全重写?
#### Dropping Support for Legacy Technologies
Yes, the new ABP framework will not support ASP.NET MVC 5.x, Entity Framework 6.x and other legacy technologies.
为什么我们花了宝贵的时间从头开始重写它而不是增量更改和改进.为什么?
These legacy technologies are maintained by Microsoft but no new feature is being added. So, if you are still using these technologies, you can continue with the current ASP.NET Boilerplate framework. We will continue to maintain it, fix bugs and will add new features.
### ASP.NET Core
Dropping support for these legacy libraries will improve our development speed (since we currently duplicate our work for some features) and concentrate on the .Net Core & ASP.NET Core.
The new ABP framework will be based on .net standard. So, it’s still possible to use full .net framework or .net core with the new ABP framework.
ASP.NET Core引入了许多内置解决方案(扩展库),用于依赖注入,日志记录,缓存,本地化,配置等.它们实际上独立于ASP.NET Core,可用于任何类型的应用程序.
### Goals
We have learnt much from the community and had experience of developing the current ASP.NET Boilerplate framework. New ABP framework has significant and exciting goals.
The first goal is to provide a good infrastructure to develop application modules. We think a module as a set of application features with its own database, its own entities, services, APIs, UI pages, components and so on.
我们希望依赖这些新的扩展库而不是第三方和自定义解决方案,这会改变框架的基本结构.
We will create a module market which will contain free & paid application modules. You will also be able to publish your own modules on the market. More information will be coming soon.
### 自身模块化
#### Microservices
We are designing the new ABP framework to be ready to develop microservices and communicate them to each other.
We are creating a [specification / best practice documentation](https://github.com/abpframework/abp/blob/master/docs/Best-Practices/Index.md) for that.
### 放弃对传统技术的支持
#### Theming and UI Composition
The new ABP framework will provide a theming infrastructure based on the latest Twitter Bootstrap 4.x. We developed a basic theme that only uses the plain Bootstrap 4.x styling. It’s free and open source. We are also developing premium & paid themes.
UI Composition is one of the main goals. For this purpose, theme system will provide menus, toolbars and other extensible areas to allow other modules to contribute.
While current ASP.NET Boilerplate framework has implemented the repository pattern for ORM/Database independence, identity integration module (Abp.Zero* packages) has never worked well with ORMs other than EF.
It currently supports most used input types and more in the development.
目前支持最常用的输入类型. 更多类型正在开发中.
### 虚拟文件系统
#### Virtual File System
Virtual File System allows you to embed views, pages, components, javascript, css, json and other type of files into your module assembly/package (dll) and use your assembly in any application. Your virtual files behave just like physical files in the containing application with complete ASP.NET Core Integration.
Read more [about the Virtual File System](https://medium.com/volosoft/designing-modularity-on-asp-net-core-virtual-file-system-2dd2cc2078bd) and see [its documentation](https://github.com/abpframework/abp/blob/master/docs/Virtual-File-System.md).
Dynamic bundling & minification system works on the virtual file system and allows modules to create, modify and contribute to bundles in a modular, dynamic and powerful way. An example:
This code creates a new style bundle on the fly by including bootstrap (and its dependencies if there are) and two more css files. These files are bundled & minified on production environment, but will be added individually on the development environment.
See [the documentation](https://github.com/abpframework/abp/blob/master/docs/AspNetCore/Bundling-Minification.md) for more.
## ASP.NET Boilerplate(当前版本)和ASP.NET Zero会如何?
#### Distributed Event Bus
In current ABP, there is an IEventBus service to trigger and handle events inside the application. In addition to this local event bus, we are creating a distributed event bus abstraction (and RabbitMQ integration) to implement distributed messaging patterns.
ABP was already creating dynamic javascript proxies for all HTTP APIs. This feature does also exists in the new ABP framework. In addition, it now can create dynamic C# proxies for all HTTP APIs.
All the stuffs mentioned above are already in development. However, we haven’t started some concepts yet.
## 新的ABP可用在生产环境吗?
#### Single Page Applications
We designed the new framework SPAs in mind. However, we haven’t tried it with any SPA framework and we haven’t prepared a startup template for it yet.
还没有.我们的第一个目标是使基本功能稳定,然后逐步完成其他功能.
### What About ASP.NET Boilerplate (Current Version) and ASP.NET Zero?
我们会经常发布新版本,每个新版本都可能会有重大变化.我们将在发行说明中写下重大更改.
We have dedicated development & support teams actively working on the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) and [ASP.NET Zero](https://aspnetzero.com/) projects. These projects have a big community and we are also getting contributions from the community.
We will frequently release new versions and every new version will probably have breaking changes. We will write breaking changes on the release notes.
New ABP framework will start with v1.0 instead of following current ASP.NET Boilerplate's version to reflect the fact that it’s a rewrite.
## 我应该用哪一个?
We will frequently [release](https://github.com/abpframework/abp/releases) it. You can expect many breaking changes until v1.0. Starting with the v1.0, we will pay attention to not introduce breaking changes in 1.x releases.
Current ABP’s package names start with [Abp](https://www.nuget.org/packages/Abp) prefix (like Abp.EntityFrameworkCore). New package names start with [Volo.Abp](https://www.nuget.org/packages/Volo.Abp.Core) prefix (like Volo.Abp.EntityFrameworkCore).
If you are creating a new project, we suggest to continue with the current ASP.NET Boilerplate framework since it’s very mature, feature rich and production ready.
## 贡献
If you are open to breaking changes and want to have experience on the new framework, you can start with the new ABP. We don’t suggest it yet for projects with close deadlines and go to the production in a short term.
就像当前的ABP框架一样,你可为新框架做出贡献.
### Contribution
Just like the current ABP framework, the new framework is available for your contribution.
* 你可以发送代码或文档的拉取请求.
* 你可以撰写关于它的博客文章或教程.
* 你可以尝试并分享你的经验.
* 你可以提出改进和功能请求.
* 你可以报告错误和其他问题.
* You can send pull requests for code or documentation.
* You can write blog posts or tutorials about it.
* You can try it and share your experiences.
* You can create enhancement and feature requests.
ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) library (Microsoft.Extensions.DependencyInjection nuget package). So, it's documentation is valid in ABP too.
Since ABP is a modular framework, every module defines it's own services and registers via dependency injection in it's own seperate [module class](Module-Development-Basics.md). Example:
public override void ConfigureServices(ServiceConfigurationContext context)
{
//register dependencies here
//在此处注入依赖项
}
}
````
### Conventional Registration
### 依照约定的注册
ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method.
@ -30,7 +30,7 @@ public class BlogModule : AbpModule
}
````
Once you skip auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example:
``ExposeServicesAttribute`` is used to control which services are provided by the related class. Example:
``ExposeServicesAttribute``用于控制相关类提供了什么服务.例:
````C#
[ExposeServices(typeof(ITaxCalculator))]
@ -122,18 +122,18 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie
}
````
``TaxCalculator`` class only exposes ``ITaxCalculator`` interface. That means you can only inject ``ITaxCalculator``, but can not inject ``TaxCalculator`` or ``ICalculator`` in your application.
* The class itself is exposed by default. That means you can inject it by ``TaxCalculator`` class.
* Default interfaces are exposed by default. Default interfaces are determined by naming convention. In this example, ``ICalculator`` and ``ITaxCalculator`` are default interfaces of ``TaxCalculator``, but ``ICanCalculate`` is not.
Combining attributes and interfaces is possible as long as it's meaningful.
只要有意义,就可以组合属性和接口.
````C#
[Dependency(ReplaceServices = true)]
@ -144,31 +144,31 @@ public class TaxCalculator : ITaxCalculator, ITransientDependency
}
````
#### Manually Registering
#### 手动注册
In some cases, you may need to register a service to the `IServiceCollection` manually, especially if you need to use custom factory methods or singleton instances. In that case, you can directly add services just as [Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) describes. Example:
There are three common ways of using a service that has already been registered.
使用已注册的服务有三种常用方法.
#### Contructor Injection
#### 构造方法注入
This is the most common way of injecting a service into a class. For example:
这是将服务注入类的最常用方法.例如:
````C#
public class TaxAppService : ApplicationService
@ -182,18 +182,18 @@ public class TaxAppService : ApplicationService
public void DoSomething()
{
//...use _taxCalculator...
//...使用 _taxCalculator...
}
}
````
``TaxAppService`` gets ``ITaxCalculator`` in it's constructor. The dependency injection system automatically provides the requested service at runtime.
Constructor injection is preffered way of injecting dependencies to a class. In that way, the class can not be constructed unless all constructor-injected dependencies are provided. Thus, the class explicitly declares it's required services.
Property injection is not supported by Microsoft Dependency Injection library. However, ABP can integrate with 3rd-party DI providers ([Autofac](https://autofac.org/), for example) to make property injection possible. Example:
@ -207,24 +207,24 @@ public class MyService : ITransientDependency
public void DoSomething()
{
//...use Logger to write logs...
//...使用 Logger 写日志...
}
}
````
For a property-injection dependency, you declare a public property with public setter. This allows the DI framework to set it after creating your class.
对于属性注入依赖项,使用公开的setter声明公共属性.这允许DI框架在创建类之后设置它.
Property injected dependencies are generally considered as **optional** dependencies. That means the service can properly work without them. ``Logger`` is such a dependency, ``MyService`` can continue to work without logging.
To make the dependency properly optional, we generally set a default/fallback value to the dependency. In this sample, NullLogger is used as fallback. Thus, ``MyService`` can work but does not write logs if DI framework or you don't set Logger property after creating ``MyService``.
One restriction of property injection is that you cannot use the dependency in your constructor, since it's set after the object construction.
属性注入的一个限制是你不能在构造函数中使用依赖项,因为它是在对象构造之后设置的.
Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class.
You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below:
@ -244,17 +244,17 @@ public class MyService : ITransientDependency
}
````
#### Releasing/Disposing Services
#### 释放/处理(Releasing/Disposing)服务
If you used a constructor or property injection, you don't need to be concerned about releasing the service's resources. However, if you have resolved a service from ``IServiceProvider``, you might, in some cases, need to take care about releasing the service resources.
ASP.NET Core releases all services at the end of a current HTTP request, even if you directly resolved from ``IServiceProvider`` (assuming you injected IServiceProvider). But, there are several cases where you may want to release/dispose manually resolved services:
ASP.NET Core会在当前HTTP请求结束时释放所有服务,即使你直接从``IServiceProvider``解析了服务(假设你注入了IServiceProvider).但是,在某些情况下,你可能希望释放/处理手动解析的服务:
* Your code is executed outside of AspNet Core request and the executer hasn't handled the service scope.
* You only have a reference to the root service provider.
* You may want to immediately release & dispose services (for example, you may creating too many services with big memory usage and don't want to overuse memory).
This document explains how to integrate EF Core as an ORM provider to ABP based applications and how to configure it.
本文档介绍了如何将EF Core作为ORM提供程序集成到基于ABP的应用程序以及如何对其进行配置.
### Installation
### 安装
`Volo.Abp.EntityFrameworkCore`is the main nuget package for the EF Core integration. Install it to your project (for a layered application, to your data/infrastructure layer):
This will create a repository for each aggreate root entity (classes derived from AggregateRoot) by default. If you want to create repositories for other entities too, then set `includeAllEntities` to `true`:
ABP's localization system is seamlessly integrated to the `Microsoft.Extensions.Localization` package and compatible with the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization). It adds some useful features and enhancements to make it easier to use in real life application scenarios.
Then you can add **AbpLocalizationModule** dependency to your module:
然后,您可以将 **AbpLocalizationModule** 依赖项添加到您的模块:
```c#
using Volo.Abp.Modularity;
@ -28,9 +28,9 @@ namespace MyCompany.MyProject
}
```
#### Creating A Localization Resource
#### 创建本地化资源
A localization resource is used to group related localization strings together and separate them from other localization strings of the application. A module generally defines its own localization resource. Localization resource is just a plain class. Example:
Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example:
See the Getting Localized Test / Client Side section below.
请参阅下面的获取本地化资源Test中客户端部分.
##### Inherit From Other Resources
##### 继承其他资源
A resource can inherit from other resources which makes possible to re-use existing localization strings without referring the existing resource. Example:
资源可以从其他资源继承,这使得可以在不引用现有资源的情况下重用现有的本地化字符串. 例如:
````C#
[InheritResource(typeof(AbpValidationResource))]
@ -109,7 +109,7 @@ public class TestResource
}
````
Alternative inheritance by configuring the `AbpLocalizationOptions`:
* If the new resource defines the same localized string, it overrides the string.
* 资源可以从多个资源继承.
* 如果新的本地化资源定义了相同的本地化字符串, 那么它会覆盖该字符串
##### Extending Existing Resource
##### 扩展现有资源
Inheriting from a resource creates a new resource without modifying the existing one. In some cases, you may want to not create a new resource but directly extend an existing resource. Example:
Refer to the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) for details about using localization on the server side.
@ -32,16 +32,16 @@ public class BlogModule : AbpModule
}
````
You can register dependencies one by one as stated in Microsoft's [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection). But ABP has a **conventional dependency registration system** which automatically register all services in your assembly. See the [dependency Injection](Dependency-Injection.md) documentation for more about the dependency injection system.
``AbpModule`` class also defines ``PreConfigureServices`` and ``PostConfigureServices`` methods to override and write your code just before and just after ``ConfigureServices``. Notice that the code you have written into these methods will be executed before/after the ``ConfigureServices`` methods of all other modules.
Once all the services of all modules are configured, the application starts by initializing all modules. In this phase, you can resolve services from ``IServiceProvider`` since it's ready and available.
##### OnApplicationInitialization方法
##### OnApplicationInitialization Method
You can override ``OnApplicationInitialization`` method to execute code while application is being started. Example:
@ -100,19 +99,19 @@ public class AppModule : AbpModule
}
````
You can also perform startup logic if your module requires it
如果模块需要,你还可以执行启动逻辑
##### Pre & Post Application Initialization
##### 应用程序初始化前和后
``AbpModule`` class also defines ``OnPreApplicationInitialization`` and ``OnPostApplicationInitialization`` methods to override and write your code just before and just after ``OnApplicationInitialization``. Notice that the code you have written into these methods will be executed before/after the ``OnApplicationInitialization`` methods of all other modules.
In a modular application, it's not unusual for one module to depend upon another module(s). An Abp module must declare ``[DependsOn]`` attribute if it does have a dependcy upon another module, as shown below:
A depended module may depend on another module, but you only need to define your direct dependencies. ABP investigates the dependency graph for the application at startup and initializes/shutdowns modules in the correct order.
Repositories, in practice, are used to perform database operations for domain objects (see [Entities](Entities.md)). Generally, a separated repository is used for each **aggregate root** or entity.
ABP can provide a **default generic repository** for each aggregate root or entity. You can [inject](Dependency-Injection.md) `IRepository<TEntity, TKey>` into your service and perform standard **CRUD** operations. Example usage:
> The example above uses hand-made mapping between [entities](Entities.md) and [DTO](Data-Transfer-Objects.md)s. See [object to object mapping document](Object-To-Object-Mapping.md) for an automatic way of mapping.
If your entity does not have an Id primary key (it may have a composite primary key for instance) then you cannot use the `IRepository<TEntity, TKey>` defined above. In that case, you can inject and use`IRepository<TEntity>` for your entity.
> `IRepository<TEntity>`has a few missing methods those normally works with the `Id` property of an entity. Because of the entity has no `Id` property in that case, these methods are not available. One example is the `Get` method that gets an id and returns the entity with given id. However, you can still use `IQueryable<TEntity>` features to query entities by standard LINQ methods.
Standard `IRepository<TEntity, TKey>`interface extends standard `IQueryable<TEntity>` and you can freely query using standard LINQ methods. However, some ORM providers or database systems may not support standard `IQueryable` interface.
ABP provides `IBasicRepository<TEntity, TPrimaryKey>` and `IBasicRepository<TEntity>` interfaces to support such scenarios. You can extend these interfaces (and optionally derive from `BasicRepositoryBase`) to create custom repositories for your entities.
Depending on `IBasicRepository` but not depending on `IRepository` has an advantage to make possible to work with all data sources even if they don't support `IQueryable`. But major vendors, like Entity Framework, NHibernate or MongoDb already support`IQueryable`.
So, working with `IRepository` is the **suggested** way for typical applications. But reusable module developers may consider `IBasicRepository` to support a wider range of data sources.
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.
对于大多数情况, 默认通用仓储就足够了. 但是, 你可能会需要为实体创建自定义仓储类.
#### Custom Repository Example
#### 自定义仓储示例
ABP does not force you to implement any interface or inherit from any base class for a repository. It can be just a simple POCO class. However, it's suggested to inherit existing repository interface and classes to make your work easier and get the standard methods out of the box.
public class PersonRepository : EfCoreRepository<MyDbContext,Person,Guid>, IPersonRepository
@ -113,5 +113,4 @@ public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPe
}
````
You can directly access the data access provider (`DbContext` in this case) to perform operations. See [entity framework integration document](Entity-Framework-Core.md) for more about custom repositories based on EF Core.
The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image, cshtml...) files into assemblies and use them like physical files at runtime.
A file should be first marked as an embedded resource to embed the file into the assembly. The easiest way to do it is to select the file from the **Solution Explorer** and set **Build Action** to **Embedded Resource** from the **Properties** window. Example:
The `AddEmbedded` extension method takes a class, finds all embedded files from the assembly of the given class and registers them to the virtual file system. More concisely it could be written as follows:
After embedding a file into an assembly and registering it to the virtual file system, the `IVirtualFileProvider` interface can be used to get files or directory contents:
#### Dealing With Embedded Files During Development
#### 在开发过程中处理嵌入式文件
Embedding a file into a module assembly and being able to use it from another project just by referencing the assembly (or adding a nuget package) is invaluable for creating a re-usable module. However, it does make it a little bit harder to develop the module itself.
Let's assume that you're developing a module that contains an embedded JavaScript file. Whenever you change this file you must re-compile the project, re-start the application and refresh the browser page to take the change. Obviously, this is very time consuming and tedious.
What is needed is the ability for the application to directly use the physical file at development time and a have a browser refresh reflect any change made in the JavaScript file. The `ReplaceEmbeddedByPyhsical` method makes all this possible.
The example below shows an application that depends on a module (`MyModule`) that itself contains embedded files. The application can reach the source code of the module at development time.
@ -136,30 +137,30 @@ public class MyWebAppModule : AbpModule
}
````
The code above assumes that `MyWebAppModule` and `MyModule` are two different projects in a Visual Studio solution and `MyWebAppModule` depends on the `MyModule`.
The Virtual File System is well integrated to ASP.NET Core:
虚拟文件系统与 ASP.NET Core 无缝集成:
* Virtual files can be used just like physical (static) files in a web application.
* Razor Views, Razor Pages, js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files.
* An application (or another module) can override a virtual file of a module just like placing a file with the same name and extension into the same folder of the virtual file.
The Virtual Files Middleware is used to serve embedded (js, css, image...) files to clients/browsers just like physical files in the **wwwroot** folder. Add it just after the static file middleware as shown below:
Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file.
在静态文件中间件之后添加虚拟文件中间件, 可以通过放置在同一位置,使用物理文件来覆盖虚拟文件.
>The Virtual File Middleware only serves the virtual wwwroot folder contents - just like the other static files.
> 虚拟文件中间件只是像静态文件一样提供虚拟wwwroot文件夹内容.
#### Views & Pages
Embedded razor views/pages are available in the application without any configuration. Simply place them into the standard Views/Pages virtual folders of the module being developed.