Browse Source

Merge branch 'dev' into pr/2807

pull/2844/head
Halil İbrahim Kalkan 7 years ago
parent
commit
8d76491209
  1. 2
      README.md
  2. 734
      docs/en/AspNet-Boilerplate-Migration-Guide.md
  3. 8
      docs/en/Background-Jobs.md
  4. 4
      docs/en/Blog-Posts/2019-02-22/Post.md
  5. 3
      docs/en/Clock.md
  6. 3
      docs/en/Features.md
  7. 3
      docs/en/Logging.md
  8. 3
      docs/en/Modules/Feature-Management.md
  9. 1
      docs/en/Modules/Index.md
  10. 1
      docs/en/Repositories.md
  11. 12
      docs/en/docs-nav.json
  12. 44
      docs/zh-Hans/Background-Jobs-Hangfire.md
  13. 8
      docs/zh-Hans/Background-Jobs.md
  14. 12
      docs/zh-Hans/Blog-Posts/2019-02-22/Post.md
  15. 16
      docs/zh-Hans/Connection-Strings.md
  16. 29
      docs/zh-Hans/Contribution/Localization-Text-Files.md
  17. 2
      docs/zh-Hans/Dependency-Injection.md
  18. 2
      docs/zh-Hans/Entity-Framework-Core.md
  19. 2
      docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md
  20. 144
      docs/zh-Hans/Modules/Docs.md
  21. 2
      docs/zh-Hans/Modules/Index.md
  22. 16
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  23. 22
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md
  24. BIN
      docs/zh-Hans/images/auditlog-object-diagram.png
  25. BIN
      docs/zh-Hans/images/docs-section-ui.png
  26. 26
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
  27. 7
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs
  28. 8
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
  29. 28
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs
  30. 20
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs
  31. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs
  32. 1
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs
  33. 30
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs
  34. 55
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs
  35. 3
      npm/get-version.js
  36. 2
      npm/ng-packs/package.json
  37. 2
      npm/ng-packs/packages/account/src/lib/account.module.ts
  38. 8
      npm/ng-packs/packages/core/src/lib/actions/config.actions.ts
  39. 12
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  40. 14
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  41. 7
      npm/ng-packs/packages/core/src/lib/utils/route-utils.ts
  42. 2
      npm/ng-packs/packages/identity/src/lib/identity.module.ts
  43. 2
      npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts
  44. 7
      npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts
  45. 8
      npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts
  46. 4
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  47. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts
  48. 1
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts
  49. 5
      npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts
  50. 12
      npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts
  51. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts
  52. 5
      npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts
  53. 15
      npm/ng-packs/scripts/package-lock.json
  54. 3
      npm/ng-packs/scripts/package.json
  55. 27
      npm/ng-packs/scripts/publish.ts
  56. 20
      npm/ng-packs/scripts/yarn.lock
  57. 5
      npm/package.json
  58. 30
      npm/preview-publish.ps1
  59. 13
      npm/publish.ps1
  60. 7
      npm/yarn.lock
  61. 2
      samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json
  62. 2
      samples/BookStore-Modular/application/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json
  63. 2
      samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json
  64. 2
      samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/zh-Hans.json
  65. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json
  66. 50
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Data/MyProjectNameDbMigrationService.cs
  67. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/EntityFrameworkCoreMyProjectNameDbSchemaMigrator.cs

2
README.md

@ -3,7 +3,7 @@
[![Build Status](http://vjenkins.dynu.net:5480/job/abp/badge/icon)](http://ci.volosoft.com:5480/blue/organizations/jenkins/abp/activity) [![Build Status](http://vjenkins.dynu.net:5480/job/abp/badge/icon)](http://ci.volosoft.com:5480/blue/organizations/jenkins/abp/activity)
[![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) [![NuGet](https://img.shields.io/nuget/v/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core) [![NuGet Download](https://img.shields.io/nuget/dt/Volo.Abp.Core.svg?style=flat-square)](https://www.nuget.org/packages/Volo.Abp.Core)
[![MyGet (with prereleases)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.Core.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds) [![MyGet (with prereleases)](https://img.shields.io/myget/abp-nightly/vpre/Volo.Abp.svg?style=flat-square)](https://docs.abp.io/en/abp/latest/Nightly-Builds)
This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework. See [the announcement](https://blog.abp.io/abp/Abp-vNext-Announcement). This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework. See [the announcement](https://blog.abp.io/abp/Abp-vNext-Announcement).

734
docs/en/AspNet-Boilerplate-Migration-Guide.md

@ -0,0 +1,734 @@
# ASP.NET Boilerplate v5+ to ABP Framework Migration
ABP Framework is **the successor** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. This guide aims to help you to **migrate your existing solutions** (you developed with the ASP.NET Boilerplate framework) to the ABP Framework.
## Introduction
**ASP.NET Boilerplate** is being **actively developed** [since 2013](https://github.com/aspnetboilerplate/aspnetboilerplate/graphs/contributors). It is loved, used and contributed by the community. It started as a side project of [a developer](http://halilibrahimkalkan.com/), but now it is officially maintained and improved by the company [Volosoft](https://volosoft.com/) in addition to the great community support.
ABP Framework has the same goal of the ASP.NET Boilerplate framework: **Don't Repeat Yourself**! It provides infrastructure, tools and startup templates to make a developer's life easier while developing enterprise software solutions.
See [the introduction blog post](https://blog.abp.io/abp/Abp-vNext-Announcement) if you wonder why we needed to re-write the ASP.NET Boilerplate framework.
### Should I Migrate?
No, you don't have to!
* ASP.NET Boilerplate is still in active development and maintenance.
* It also works on the latest ASP.NET Core and related libraries and tools. It is up to date.
However, if you want to take the advantage of the new ABP Framework [features](https://abp.io/features) and the new architecture opportunities (like support for NoSQL databases, microservice compatibility, advanced modularity), you can use this document as a guide.
### What About the ASP.NET Zero?
[ASP.NET Zero](https://aspnetzero.com/) is a commercial product developed by the core ASP.NET Boilerplate team, on top of the ASP.NET Boilerplate framework. It provides pre-built application [features](https://aspnetzero.com/Features), code generation tooling and a nice looking modern UI. It is trusted and used by thousands of companies from all around the World.
We have created the [ABP Commercial](https://commercial.abp.io/) as an alternative to the ASP.NET Zero. ABP Commercial is more modular and upgradeable compared to the ASP.NET Zero. It currently has less features compared to ASP.NET Zero, but the gap will be closed by the time (it also has some features don't exist in the ASP.NET Zero).
We think ASP.NET Zero is still a good choice while starting a new application. It is production ready and mature solution delivered as a full source code. It is being actively developed and we are constantly adding new features.
We don't suggest to migrate your ASP.NET Zero based solution to the ABP Commercial if;
* Your ASP.NET Zero solution is mature and it is in maintenance rather than a rapid development.
* You don't have enough development time to perform the migration.
* A monolithic solution fits in your business.
* You've customized existing ASP.NET Zero features too much based on your requirements.
We also suggest you to compare the features of two products based on your needs.
If you have an ASP.NET Zero based solution and want to migrate to the ABP Commercial, this guide will also help you.
### ASP.NET MVC 5.x Projects
The ABP Framework doesn't support ASP.NET MVC 5.x, it only works with ASP.NET Core. So, if you migrate your ASP.NET MVC 5.x based projects, you will also deal with the .NET Core migration.
## The Migration Progress
We've designed the ABP Framework by **getting the best parts** of the ASP.NET Boilerplate framework, so it will be familiar to you if you've developed ASP.NET Boilerplate based applications.
In the ASP.NET Boilerplate, we have not worked much on the UI side, but used some free themes (we've used [metronic theme](https://keenthemes.com/metronic/) for ASP.NET Zero on the other side). In the ABP Framework, we worked a lot on the UI side (especially for the MVC / Razor Pages UI, because Angular already has a good modular system of its own). So, the **most challenging part** of the migration will be the **User Interface** of your solution.
ABP Framework is (and ASP.NET Boilerplate was) designed based on the [Domain Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) patterns & principles and the startup templates are layered based on the DDD layers. So, this guide respects to that layering model and explains the migration layer by layer.
## Creating the Solution
First step of the migration is to create a new solution. We suggest you to create a fresh new project using [the startup templates](https://abp.io/get-started) (see [this document](https://docs.abp.io/en/commercial/latest/getting-started) for the ABP Commercial).
After creating the project and running the application, you can copy your code from your existing solution to the new solution step by step, layer by layer.
### About Pre-Built Modules
The startup projects for the ABP Framework use the [pre-built modules](https://docs.abp.io/en/abp/latest/Modules/Index) (not all of them, but the essentials) and themes as NuGet/NPM packages. So, you don't see the source code of the modules/themes in your solution. This has an advantage that you can easily update these packages when a new version is released. However, you can not easily customize them as their source code in your hands.
We suggest to continue to use these modules as package references, in this way you can get new features easily (see [abp update command](https://docs.abp.io/en/abp/latest/CLI#update)). In this case, you have a few options to customize or extend the functionality of the used modules;
* You can create your own entity and share the same database table with an entity in a used module. An example of this is the `AppUser` entity comes in the startup template.
* You can [replace](https://docs.abp.io/en/abp/latest/Dependency-Injection#replace-a-service) a domain service, application service, controller, page model or other types of services with your own implementation. We suggest you to inherit from the existing implementation and override the method you need.
* You can replace a `.cshtml` view, page, view component, partial view... with your own one using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System).
* You can override javascript, css, image or any other type of static files using the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System).
More extend/customization options will be developed and documented by the time. However, if you need to fully change the module implementation, it is best to add the [source code](https://github.com/abpframework/abp/tree/dev/modules) of the related module into your own solution and remove the package dependencies.
The source code of the modules and the themes are [MIT](https://opensource.org/licenses/MIT) licensed, you can fully own and customize it without any limitation (for the ABP Commercial, you can download the source code of a [module](https://commercial.abp.io/modules)/[theme](https://commercial.abp.io/themes) if you have a [license](https://commercial.abp.io/pricing) type that includes the source code).
## The Domain Layer
Most of your domain layer code will remain same, while you need to perform some minor changes in your domain objects.
### Aggregate Roots & Entities
The ABP Framework and the ASP.NET Boilerplate both have the `IEntity` and `IEntity<T>` interfaces and `Entity` and `Entity<T>` base classes to define entities but they have some differences.
If you have an entity in the ASP.NET Boilerplate application like that:
````csharp
public class Person : Entity //Default PK is int for the ASP.NET Boilerplate
{
...
}
````
Then your primary key (the `Id` property in the base class) is `int` which is the **default primary key** (PK) type for the ASP.NET Boilerplate. If you want to set another type of PK, you need to explicitly declare it:
````csharp
public class Person : Entity<Guid> //Set explicit PK in the ASP.NET Boilerplate
{
...
}
````
ABP Framework behaves differently and expects to **always explicitly set** the PK type:
````csharp
public class Person : Entity<Guid> //Set explicit PK in the ASP.NET Boilerplate
{
...
}
````
`Id` property (and the corresponding PK in the database) will be `Guid` in this case.
#### Composite Primary Keys
ABP Framework also has a non-generic `Entity` base class, but this time it has no `Id` property. Its purpose is to allow you to create entities with composite PKs. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the composite PKs.
#### Aggregate Root
It is best practice now to use the `AggregateRoot` base class instead of `Entity` for aggregate root entities. See [the documentation](https://docs.abp.io/en/abp/latest/Entities#aggregateroot-class) to learn more about the aggregate roots.
In opposite to the ASP.NET Boilerplate, the ABP Framework creates default repositories (`IRepository<T>`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`.
If you still want to create default repositories for all entity types, find the *YourProjectName*EntityFrameworkCoreModule class in your solution and change `options.AddDefaultRepositories()` to `options.AddDefaultRepositories(includeAllEntities: true)` (it may be already like that for the application startup template).
#### Migrating the Existing Entities
We suggest & use the GUID as the PK type for all the ABP Framework modules. However, you can continue to use your existing PK types to migrate your database tables easier.
The challenging part will be the primary keys of the ASP.NET Boilerplate related entities, like Users, Roles, Tenants, Settings... etc. Our suggestion is to copy data from existing database to the new database tables using a tool or in a manual way (be careful about the foreign key values).
#### Documentation
See the documentation for details on the entities:
* [ASP.NET Boilerplate - Entity documentation](https://aspnetboilerplate.com/Pages/Documents/Entities)
* [ABP Framework - Entity documentation](https://docs.abp.io/en/abp/latest/Entities)
### Repositories
> ABP Framework creates default repositories (`IRepository<T>`) **only for the aggregate roots**. It doesn't create for other types derived from the `Entity`. See the "Aggregate Root" section above for more information.
The ABP Framework and the ASP.NET Boilerplate both have the default generic repository system, but has some differences.
#### Injecting the Repositories
In the ASP.NET Boilerplate, there are two default repository interfaces you can directly inject and use:
* `IRepository<TEntity>` (e.g. `IRepository<Person>`) is used for entities with `int` primary key (PK) which is the default PK type.
* `IRepository<TEntity, TKey>` (e.g. `IRepository<Person, Guid>`) is used for entities with other types of PKs.
ABP Framework doesn't have a default PK type, so you need to **explicitly declare the PK type** of your entity, like `IRepository<Person, int>` or `IRepository<Person, Guid>`.
ABP Framework also has the `IRepository<TEntity>` (without PK), but it is mostly used when your entity has a composite PK (because this repository has no methods work with the `Id` property). See [the documentation](https://docs.abp.io/en/abp/latest/Entities#entities-with-composite-keys) to learn more about the **composite PKs**.
#### Restricted Repositories
ABP Framework additionally provides a few repository interfaces:
* `IBasicRepository<TEntity, TKey>` has the same methods with the `IRepository` except it doesn't have `IQueryable` support. It can be useful if you don't want to expose complex querying code to the application layer. In this case, you typically want to create custom repositories to encapsulate the querying logic. It is also useful for database providers those don't support `IQueryable`.
* `IReadOnlyRepository<TEntity,TKey>` has the methods get data from the database, but doesn't contain any method change the database.
* `IReadOnlyBasicRepository<TEntity, TKey>` is similar to the read only repository but also doesn't support `IQueryable`.
All the interfaces also have versions without `TKey` (like ``IReadOnlyRepository<TEntity>`) those can be used for composite PKs just like explained above.
#### GetAll() vs IQueryable
ASP.NET Boilerplate's repository has a `GetAll()` method that is used to obtain an `IQueryable` object to execute LINQ on it. An example application service calls the `GetAll()` method:
````csharp
public class PersonAppService : ApplicationService, IPersonAppService
{
private readonly IRepository<Person, Guid> _personRepository;
public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
public async Task DoIt()
{
var people = await _personRepository
.GetAll() //GetAll() returns IQueryable
.Where(p => p.BirthYear > 2000) //Use LINQ extension methods
.ToListAsync();
}
}
````
ABP Framework's repository doesn't have this method. Instead, it implements the `IQueryable` itself. So, you can directly use LINQ on the repository:
````csharp
public class PersonAppService : ApplicationService, IPersonAppService
{
private readonly IRepository<Person, Guid> _personRepository;
public PersonAppService(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
public async Task DoIt()
{
var people = await _personRepository
.Where(p => p.BirthYear > 2000) //Use LINQ extension methods
.ToListAsync();
}
}
````
> Note that in order to use the async LINQ extension methods (like `ToListAsync` here), you may need to depend on the database provider (like EF Core) since these methods are defined in the database provider package, they are not standard LINQ methods.
#### FirstOrDefault(predicate), Single()... Methods
ABP Framework repository has not such methods get predicate (expression) since the repository itself is `IQueryable` and all these methods are already standard LINQ extension methods those can be directly used.
However, it provides the following methods those can be used to query a single entity by its Id:
* `FindAsync(id)` returns the entity or null if not found.
* `GetAsync(id)` method returns the entity or throws an `EntityNotFoundException` (which causes HTTP 404 status code) if not found.
#### Sync vs Async
ABP Framework repository has no sync methods (like `Insert`). All the methods are async (like `InsertAsync`). So, if your application has sync repository method usages, convert them to async versions.
In general, ABP Framework forces you to completely use async everywhere, because mixing async & sync methods is not a recommended approach.
#### Documentation
See the documentation for details on the repositories:
* [ASP.NET Boilerplate - Repository documentation](https://aspnetboilerplate.com/Pages/Documents/Repositories)
* [ABP Framework - Repository documentation](https://docs.abp.io/en/abp/latest/Repositories)
### Domain Services
Your domain service logic mostly remains same on the migration. ABP Framework also defines the base `DomainService` class and the `IDomainService` interface just works like the ASP.NET Boilerplate.
## The Application Layer
Your application service logic remains similar on the migration. ABP Framework also defines the base `ApplicationService` class and the `IApplicationService` interface just works like the ASP.NET Boilerplate, but there are some differences in details.
### Declarative Authorization
ASP.NET Boilerplate has `AbpAuthorize` and `AbpMvcAuthorize` attributes for declarative authorization. Example usage:
````csharp
[AbpAuthorize("MyUserDeletionPermissionName")]
public async Task DeleteUserAsync(...)
{
...
}
````
ABP Framework doesn't has such a custom attribute. It uses the standard `Authorize` attribute in all layers.
````csharp
[Authorize("MyUserDeletionPermissionName")]
public async Task DeleteUserAsync(...)
{
...
}
````
This is possible with the better integration to the Microsoft Authorization Extensions libraries. See the Authorization section below for more information about the authorization system.
### CrudAppService and AsyncCrudAppService Classes
ASP.NET Boilerplate has `CrudAppService` (with sync service methods) and `AsyncCrudAppService` (with async service methods) classes.
ABP Framework only has the `CrudAppService` which actually has only the async methods (instead of sync methods).
ABP Framework's `CrudAppService` method signatures are slightly different than the old one. For example, old update method signature was ` Task<TEntityDto> UpdateAsync(TUpdateInput input) ` while the new one is ` Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input) `. The main difference is that it gets the Id of the updating entity as a separate parameter instead of including in the input DTO.
### Data Transfer Objects (DTOs)
There are similar base DTO classes (like `EntityDto`) in the ABP Framework too. So, you can find the corresponding DTO base class if you need.
#### Validation
You can continue to use the data annotation attributes to validate your DTOs just like in the ASP.NET Boilerplate.
ABP Framework doesn't include the ` ICustomValidate ` that does exists in the ASP.NET Boilerplate. Instead, you should implement the standard `IValidatableObject` interface for your custom validation logic.
## The Infrastructure Layer
### Namespaces
ASP.NET Boilerplate uses the `Abp.*` namespaces while the ABP Framework uses the `Volo.Abp.*` namespaces for the framework and pre-built fundamental modules.
In addition, there are also some pre-built application modules (like docs and blog modules) those are using the `Volo.*` namespaces (like `Volo.Blogging.*` and `Volo.Docs.*`). We consider these modules as standalone open source products developed by Volosoft rather than add-ons or generic modules completing the ABP Framework and used in the applications. We've developed them as a module to make them re-usable as a part of a bigger solution.
### Module System
Both of the ASP.NET Boilerplate and the ABP Framework have the `AbpModule` while they are a bit different.
ASP.NET Boilerplate's `AbpModule` class has `PreInitialize`, `Initialize` and `PostInitialize` methods you can override and configure the framework and the depended modules. You can also register and resolve dependencies in these methods.
ABP Framework's `AbpModule` class has the `ConfigureServices` and `OnApplicationInitialization` methods (and their Pre and Post versions). It is similar to ASP.NET Core's Startup class. You configure other services and register dependencies in the `ConfigureServices`. However, you can now resolve dependencies in that point. You can resolve dependencies and configure the ASP.NET Core pipeline in the `OnApplicationInitialization` method while you can not register dependencies here. So, the new module classes separate dependency registration phase from dependency resolution phase since it follows the ASP.NET Core's approach.
### Dependency Injection
#### The DI Framework
ASP.NET Boilerplate is using the [Castle Windsor](http://www.castleproject.org/projects/windsor/) as the dependency injection framework. This is a fundamental dependency of the ASP.NET Boilerplate framework. We've got a lot of feedback to make the ASP.NET Boilerplate DI framework agnostic, but it was not so easy because of the design.
ABP Framework is dependency injection framework independent since it uses Microsoft's [Dependency Injection Extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection) library as an abstraction. None of the ABP Framework or module packages depends on any specific library.
However, ABP Framework doesn't use the Microsoft's base DI library because it has some missing features ABP Framework needs to: Property Injection and Interception. All the startup templates and the samples are using the [Autofac](https://autofac.org/) as the DI library and it is the only [officially integrated](Autofac-Integration.md) library to the ABP Framework. We suggest you to use the Autofac with the ABP Framework if you have not a good reason. If you have a good reason, please create an [issue](https://github.com/abpframework/abp/issues/new) on GitHub to request it or just implement it and send a pull request :)
#### Registering the Dependencies
Registering the dependencies are similar and mostly handled by the framework conventionally (like repositories, application services, controllers... etc). Implement the same `ITransientDependency`, `ISingletonDependency` and `IScopedDependency` interfaces for the services not registered by conventions.
When you need to manually register dependencies, use the `context.Services` in the `ConfigureServices` method of your module. Example:
````csharp
public class BlogModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//Register an instance as singleton
context.Services.AddSingleton<TaxCalculator>(new TaxCalculator(taxRatio: 0.18));
//Register a factory method that resolves from IServiceProvider
context.Services.AddScoped<ITaxCalculator>(
sp => sp.GetRequiredService<TaxCalculator>()
);
}
}
````
See the ABP Framework [dependency injection document](https://docs.abp.io/en/abp/latest/Dependency-Injection) for details.
### Configuration vs Options System
ASP.NET Boilerplate has its own configuration system to configure the framework and the modules. For example, you could disable the audit logging in the `Initialize` method of your [module](https://aspnetboilerplate.com/Pages/Documents/Module-System):
````csharp
public override void Initialize()
{
Configuration.Auditing.IsEnabled = false;
}
````
ABP Framework uses [the options pattern](Options.md) to configure the framework and the modules. You typically configure the options in the `ConfigureServices` method of your [module](Module-Development-Basics.md):
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAuditingOptions>(options =>
{
options.IsEnabled = false;
});
}
````
Instead of a central configuration object, there are separated option classes for every module and feature those are defined in the related documents.
### IAbpSession vs ICurrentUser and ICurrentTenant
ASP.NET Boilerplate's `IAbpSession` service is used to obtain the current user and tenant information, like ` UserId ` and `TenantId`.
ABP Framework doesn't have the same service. Instead, use `ICurrentUser` and `ICurrentTenant` services. These services are defined as base properties in some common classes (like `ApplicationService` and `AbpController`), so you generally don't need to manually inject them. They also have much properties compared to the `IAbpSession`.
### Authorization
ABP Framework extends the [ASP.NET Core Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing the authorization system to be usable in the [application services](Application-Services.md) too.
#### AbpAutorize vs Autorize
Use the standard `[Autorize]` and `[AllowAnonymous]` attributes instead of ASP.NET Boilerplate's custom `[AbpAutorize]` and `[AbpAllowAnonymous]` attributes.
#### IPermissionChecker vs IAuthorizationService
Use the standard `IAuthorizationService` to check permissions instead of the ASP.NET Boilerplate's `IPermissionChecker` service. While `IPermissionChecker` also exists in the ABP Framework, it is used to explicitly use the permissions. Using `IAuthorizationService` is the recommended way since it covers other type of policy checks too.
#### AuthorizationProvider vs PermissionDefinitionProvider
You inherit from the `AuthorizationProvider` in the ASP.NET Boilerplate to define your permissions. ABP Framework replaces it by the `PermissionDefinitionProvider` base class. So, define your permissions by inheriting from the `PermissionDefinitionProvider` class.
### Unit of Work
Unit of work system has been designed to work seamlessly. For most of the cases, you don't need to change anything.
`UnitOfWork` attribute of the ABP Framework doesn't have the `ScopeOption` (type of `TransactionScopeOption`) property. Instead, use `IUnitOfWorkManager.Begin()` method with `requiresNew = true` to create an independent inner transaction in a transaction scope.
#### Data Filters
ASP.NET Boilerplate implements the data filtering system as a part of the unit of work. ABP Framework has a separate `IDataFilter` service.
See the [data filtering document](Data-Filtering.md) to learn how to enable/disable a filter.
See [the UOW documentation](Unit-Of-Work.md) for more about the UOW system.
### Multi-Tenancy
#### IMustHaveTenant & IMayHaveTenant vs IMultiTenant
ASP.NET Boilerplate defines `IMustHaveTenant` and `IMayHaveTenant` interfaces to implement them for your entities. In this way, your entities are automatically filtered according to the current tenant. Because of the design, there was a problem: You had to create a "Default" tenant in the database with "1" as the Id if you want to create a non multi-tenant application (this "Default" tenant was used as the single tenant).
ABP Framework has a single interface for multi-tenant entities: `IMultiTenant` which defines a nullable `TenantId` property of type `Guid`. If your application is not multi-tenant, then your entities will have null TenantId (instead of a default one).
On the migration, you need to change the TenantId field type and replace these interfaces with the `IMultiTenant`
#### Switch Between Tenants
In some cases you might need to switch to a tenant for a code scope and work with the tenant's data in this scope.
In ASP.NET Boilerplate, it is done using the `IUnitOfWorkManager` service:
````csharp
public async Task<List<Product>> GetProducts(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
return await _productRepository.GetAllListAsync();
}
}
````
In the ABP Framework it is done with the `ICurrentTenant` service:
````csharp
public async Task<List<Product>> GetProducts(Guid tenantId)
{
using (_currentTenant.Change(tenantId))
{
return await _productRepository.GetListAsync();
}
}
````
Pass `null` to the `Change` method to switch to the host side.
### Caching
ASP.NET Boilerplate has its [own distributed caching abstraction](https://aspnetboilerplate.com/Pages/Documents/Caching) which has in-memory and Redis implementations. You typically inject the `ICacheManager` service and use its `GetCache(...)` method to obtain a cache, then get and set objects in the cache.
ABP Framework uses and extends ASP.NET Core's [distributed caching abstraction](Caching.md). It defines the `IDistributedCache<T>` services to inject a cache and get/set objects.
### Logging
ASP.NET Boilerplate uses Castle Windsor's [logging facility](http://docs.castleproject.org/Windsor.Logging-Facility.ashx) as an abstraction and supports multiple logging providers including Log4Net (the default one comes with the startup projects) and Serilog. You typically property-inject the logger:
````csharp
using Castle.Core.Logging; //1: Import Logging namespace
public class TaskAppService : ITaskAppService
{
//2: Getting a logger using property injection
public ILogger Logger { get; set; }
public TaskAppService()
{
//3: Do not write logs if no Logger supplied.
Logger = NullLogger.Instance;
}
public void CreateTask(CreateTaskInput input)
{
//4: Write logs
Logger.Info("Creating a new task with description: " + input.Description);
//...
}
}
````
ABP Framework depends on Microsoft's [logging extensions](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) library which is also an abstraction and there are many providers implement it. Startup templates are using the Serilog as the pre-configured logging libary while it is easy to change in your project. The usage pattern is similar:
````csharp
//1: Import the Logging namespaces
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
public class TaskAppService : ITaskAppService
{
//2: Getting a logger using property injection
public ILogger<TaskAppService> Logger { get; set; }
public TaskAppService()
{
//3: Do not write logs if no Logger supplied.
Logger = NullLogger<TaskAppService>.Instance;
}
public void CreateTask(CreateTaskInput input)
{
//4: Write logs
Logger.Info("Creating a new task with description: " + input.Description);
//...
}
}
````
You inject the `ILogger<T>` instead of the `ILogger`.
### Object to Object Mapping
#### IObjectMapper Service
ASP.NET Boilerplate defines an `IObjectMapper` service ([see](https://aspnetboilerplate.com/Pages/Documents/Object-To-Object-Mapping)) and has an integration to the [AutoMapper](https://automapper.org/) library.
Example usage: Create a `User` object with the given `CreateUserInput` object:
````csharp
public void CreateUser(CreateUserInput input)
{
var user = ObjectMapper.Map<User>(input);
...
}
````
Example: Update an existing `User` properties with the given `UpdateUserInput` object:
````csharp
public async Task UpdateUserAsync(Guid id, UpdateUserInput input)
{
var user = await _userRepository.GetAsync(id);
ObjectMapper.Map(input, user);
}
````
ABP Framework has the same `IObjectMapper` service ([see](Object-To-Object-Mapping.md)) and the AutoMapper integration with a slightly different mapping methods.
Example usage: Create a `User` object with the given `CreateUserInput` object:
````csharp
public void CreateUser(CreateUserInput input)
{
var user = ObjectMapper.Map<CreateUserInput, User>(input);
}
````
This time you need to explicitly declare the source type and target type (while ASP.NET Boilerplate was requiring only the target type).
Example: Update an existing `User` properties with the given `UpdateUserInput` object:
````csharp
public async Task UpdateUserAsync(Guid id, UpdateUserInput input)
{
var user = await _userRepository.GetAsync(id);
ObjectMapper.Map<UpdateUserInput, User>(input, user);
}
````
Again, ABP Framework expects to explicitly set the source and target types.
#### AutoMapper Integration
##### Auto Mapping Attributes
ASP.NET Boilerplate has `AutoMapTo`, `AutoMapFrom` and `AutoMap` attributes to automatically create mappings for the declared types. Example:
````csharp
[AutoMapTo(typeof(User))]
public class CreateUserInput
{
public string Name { get; set; }
public string Surname { get; set; }
...
}
````
ABP Framework has no such attributes, because AutoMapper as a [similar attribute](https://automapper.readthedocs.io/en/latest/Attribute-mapping.html) now. You need to switch to AutoMapper's attribute.
##### Mapping Definitions
ABP Framework follows AutoMapper principles closely. You can define classes derived from the `Profile` class to define your mappings.
##### Configuration Validation
Configuration validation is a best practice for the AutoMapper to maintain your mapping configuration in a safe way.
See [the documentation](Object-To-Object-Mapping.md) for more information related to the object mapping.
### Setting Management
#### Defining the Settings
In an ASP.NET Boilerplate based application, you create a class deriving from the `SettingProvider` class, implement the `GetSettingDefinitions` method and add your class to the `Configuration.Settings.Providers` list.
In the ABP Framework, you need to derive your class from the `SettingDefinitionProvider` and implement the `Define` method. You don't need to register your class since the ABP Framework automatically discovers it.
#### Getting the Setting Values
ASP.NET Boilerplate provides the `ISettingManager` to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side.
ABP Framework has the `ISettingProvider` service to read the setting values in the server side and `abp.setting.get(...)` method in the JavaScript side.
#### Setting the Setting Values
For ASP.NET Boilerplate, you use the same `ISettingManager` service to change the setting values.
ABP Framework separates it and provides the setting management module (pre-added to the startup projects) which has the ` ISettingManager ` to change the setting values. This separation was introduced to support tiered deployment scenarios (where `ISettingProvider` can also work in the client application while `ISettingManager ` can also work in the server (API) side).
### Clock
ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time.
ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it.
### Event Bus
ASP.NET Boilerplate has an in-process event bus system. You typically inject the `IEventBus` (or use the static instance `EventBus.Default`) to trigger an event. It automatically triggers events for entity changes (like `EntityCreatingEventData` and `EntityUpdatedEventData`). You create a class by implementing the `IEventHandler<T>` interface.
ABP Framework separates the event bus into two services: `ILocalEventBus` and `IDistributedEventBus`.
The local event bus is similar to the event bus of the ASP.NET Boilerplate while the distributed event bus is new feature introduced in the ABP Framework.
So, to migrate your code;
* Use the `ILocalEventBus` instead of the `IEventBus`.
* Implement the `ILocalEventHandler` instead of the `IEventHandler`.
> Note that ABP Framework has also an `IEventBus` interface, but it does exists to be a common interface for the local and distributed event bus. It is not injected and directly used.
### Feature Management
Feature system is used in multi-tenant applications to define features of your application check if given feature is available for the current tenant.
#### Defining Features
In the ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Feature-Management)), you create a class inheriting from the `FeatureProvider`, override the `SetFeatures` method and add your class to the `Configuration.Features.Providers` list.
In the ABP Framework ([see](Features.md)), you derive your class from the `FeatureDefinitionProvider` and override the `Define` method. No need to add your class to the configuration, it is automatically discovered by the framework.
#### Checking Features
You can continue to use the `RequiresFeature` attribute and `IFeatureChecker` service to check if a feature is enabled for the current tenant.
#### Changing the Feature Values
In the ABP Framework you use the `IFeatureManager` to change a feature value for a tenant.
### Audit Logging
The ASP.NET Boilerplate ([see](https://aspnetboilerplate.com/Pages/Documents/Audit-Logging)) and the ABP Framework ([see](Audit-Logging.md)) has similar audit logging systems. ABP Framework requires to add `UseAuditing()` middleware to the ASP.NET Core pipeline, which is already added in the startup templates. So, most of the times it will be work out of the box.
### Localization
ASP.NET Boilerplate supports XML and JSON files to define the localization key-values for the UI ([see](https://aspnetboilerplate.com/Pages/Documents/Localization)). ABP Framework only supports the JSON formatter localization files ([see](Localization.md)). So, you need to convert your XML file to JSON.
The ASP.NET Boilerplate has its own the `ILocalizationManager` service to be injected and used for the localization in the server side.
The ABP Framework uses [Microsoft localization extension](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) library, so it is completely integrated to ASP.NET Core. You use the `IStringLocalizer<T>` service to get a localized text. Example:
````csharp
public class MyService
{
private readonly IStringLocalizer<TestResource> _localizer;
public MyService(IStringLocalizer<TestResource> localizer)
{
_localizer = localizer;
}
public void Foo()
{
var str = _localizer["HelloWorld"]; //Get a localized text
}
}
````
So, you need to replace `ILocalizationManager` usage by the `IStringLocalizer`.
It also provides API used in the client side:
````js
var testResource = abp.localization.getResource('Test');
var str = testResource('HelloWorld');
````
It was like `abp.localization.localize(...)` in the ASP.NET Boilerplate.
### Navigation vs Menu
In ASP.NET you create a class deriving from the `NavigationProvider` to define your menu elements. Menu items has `requiredPermissionName` attributes to restrict access to a menu element. Menu items were static and your class is executed only one time.
Int the ABP Framework you need to create a class implements the `IMenuContributor` interface. Your class is executed whenever the menu needs to be rendered. So, you can conditionally add menu items.
As an example, this is the menu contributor of the tenant management module:
````csharp
public class AbpTenantManagementWebMainMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
//Add items only to the main menu
if (context.Menu.Name != StandardMenus.Main)
{
return;
}
//Get the standard administration menu item
var administrationMenu = context.Menu.GetAdministration();
//Resolve some needed services from the DI container
var authorizationService = context.ServiceProvider
.GetRequiredService<IAuthorizationService>();
var l = context.ServiceProvider
.GetRequiredService<IStringLocalizer<AbpTenantManagementResource>>();
var tenantManagementMenuItem = new ApplicationMenuItem(
TenantManagementMenuNames.GroupName,
l["Menu:TenantManagement"],
icon: "fa fa-users");
administrationMenu.AddItem(tenantManagementMenuItem);
//Conditionally add the "Tenants" menu item based on the permission
if (await authorizationService
.IsGrantedAsync(TenantManagementPermissions.Tenants.Default))
{
tenantManagementMenuItem.AddItem(
new ApplicationMenuItem(
TenantManagementMenuNames.Tenants,
l["Tenants"],
url: "/TenantManagement/Tenants"));
}
}
}
````
So, you need to check permission using the `IAuthorizationService` if you want to show a menu item only when the user has the related permission.
> Navigation/Menu system is only for ASP.NET Core MVC / Razor Pages applications. Angular applications has a different system implemented in the startup templates.
## Missing Features
The following features are not present for the ABP Framework. Here, a list of some major missing features (and the related issue for that feature waiting on the ABP Framework GitHub repository):
* [Multi-Lingual Entities](https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities) ([#1754](https://github.com/abpframework/abp/issues/1754))
* [Real time notification system](https://aspnetboilerplate.com/Pages/Documents/Notification-System) ([#633](https://github.com/abpframework/abp/issues/633))
* [NHibernate Integration](https://aspnetboilerplate.com/Pages/Documents/NHibernate-Integration) ([#339](https://github.com/abpframework/abp/issues/339)) - We don't intent to work on this, but any community contribution welcome.
Some of these features will eventually be implemented. However, you can implement them yourself if they are important for you. If you want, you can [contribute](Contribution/Index.md) to the framework, it is appreciated.

8
docs/en/Background-Jobs.md

@ -108,7 +108,7 @@ Enqueue method gets some optional arguments to control the background job:
You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process. You may want to disable background job execution for your application. This is generally needed if you want to execute background jobs in another process and disable it for the current process.
Use `BackgroundJobOptions` to configure the job execution: Use `AbpBackgroundJobOptions` to configure the job execution:
````csharp ````csharp
[DependsOn(typeof(AbpBackgroundJobsModule))] [DependsOn(typeof(AbpBackgroundJobsModule))]
@ -116,7 +116,7 @@ public class MyModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BackgroundJobOptions>(options => Configure<AbpBackgroundJobOptions>(options =>
{ {
options.IsJobExecutionEnabled = false; //Disables job execution options.IsJobExecutionEnabled = false; //Disables job execution
}); });
@ -140,7 +140,7 @@ ABP framework includes a simple `IBackgroundJobManager` implementation that;
### Configuration ### Configuration
Use `BackgroundJobWorkerOptions` in your [module class](Module-Development-Basics.md) to configure the default background job manager. The example below changes the timeout duration for background jobs: Use `AbpBackgroundJobWorkerOptions` in your [module class](Module-Development-Basics.md) to configure the default background job manager. The example below changes the timeout duration for background jobs:
````csharp ````csharp
[DependsOn(typeof(AbpBackgroundJobsModule))] [DependsOn(typeof(AbpBackgroundJobsModule))]
@ -148,7 +148,7 @@ public class MyModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BackgroundJobWorkerOptions>(options => Configure<AbpBackgroundJobWorkerOptions>(options =>
{ {
options.DefaultTimeout = 864000; //10 days (as seconds) options.DefaultTimeout = 864000; //10 days (as seconds)
}); });

4
docs/en/Blog-Posts/2019-02-22/Post.md

@ -1,6 +1,6 @@
# Microservice Demo, Projects Status and Road Map # Microservice Demo, Projects Status and Road Map
After [the first announcement](https://abp.io/blog/abp/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project. After [the first announcement](https://blog.abp.io/Abp-vNext-Announcement) on the ABP vNext, we have a lot of improvements on the codebase (1100+ commits on the [GitHub repository](https://github.com/abpframework/abp)). We've created features, samples, documentation and much more. In this post, I want to inform you about some news and the status of the project.
## Microservice Demo Solution ## Microservice Demo Solution
@ -38,7 +38,7 @@ First release may not include a SPA template. However, we want to prepare a simp
## Chinese Web Site ## Chinese Web Site
There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://cn.abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming). There is a big ABP community in China. They have created a Chinese version of the abp.io web site: https://abp.io/ They are keeping it up to date. Thanks to the Chinese developers and especially to [Liming Ma](https://github.com/maliming).
## NDC {London} 2019 ## NDC {London} 2019

3
docs/en/Clock.md

@ -0,0 +1,3 @@
# Clock
TODO

3
docs/en/Features.md

@ -0,0 +1,3 @@
# Features
TODO

3
docs/en/Logging.md

@ -2,4 +2,5 @@
ABP Framework doesn't implement any logging infrastructure. It uses the [ASP.NET Core's logging system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging). ABP Framework doesn't implement any logging infrastructure. It uses the [ASP.NET Core's logging system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging).
> .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application. > .NET Core's logging system is actually independent from the ASP.NET Core. It is usable in any type of application.

3
docs/en/Modules/Feature-Management.md

@ -0,0 +1,3 @@
# Feature Management Module
TODO

1
docs/en/Modules/Index.md

@ -16,6 +16,7 @@ There are some **free and open source** application modules developed and mainta
* **Background Jobs**: Persist background jobs when using the default background job manager. * **Background Jobs**: Persist background jobs when using the default background job manager.
* **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module. * **Blogging**: Used to create fancy blogs. ABP's [own blog](https://blog.abp.io/) already using this module.
* [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module. * [**Docs**](Docs.md): Used to create technical documentation pages. ABP's [own documentation](https://docs.abp.io) already using this module.
* **Feature Management**: Used to persist and manage the [features](../Features.md).
* **Identity**: Manages roles, users and their permissions, based on the Microsoft Identity library. * **Identity**: Manages roles, users and their permissions, based on the Microsoft Identity library.
* **IdentityServer**: Integrates to IdentityServer4. * **IdentityServer**: Integrates to IdentityServer4.
* **Permission Management**: Used to persist permissions. * **Permission Management**: Used to persist permissions.

1
docs/en/Repositories.md

@ -52,7 +52,6 @@ Generic Repositories provides some standard CRUD features out of the box:
* Provides `Update` and `Delete` methods to update or delete an entity by entity object or it's id. * Provides `Update` and `Delete` methods to update or delete an entity by entity object or it's id.
* Provides `Delete` method to delete multiple entities by a filter. * Provides `Delete` method to delete multiple entities by a filter.
* Implements `IQueryable<TEntity>`, so you can use LINQ and extension methods like `FirstOrDefault`, `Where`, `OrderBy`, `ToList` and so on... * Implements `IQueryable<TEntity>`, so you can use LINQ and extension methods like `FirstOrDefault`, `Where`, `OrderBy`, `ToList` and so on...
* Have **sync** and **async** versions for all methods.
### Basic Repositories ### Basic Repositories

12
docs/en/docs-nav.json

@ -49,6 +49,10 @@
} }
] ]
}, },
{
"text": "ASP.NET Boilerplate Migration Guide",
"path": "AspNet-Boilerplate-Migration-Guide.md"
},
{ {
"text": "CLI", "text": "CLI",
"path": "CLI.md" "path": "CLI.md"
@ -104,6 +108,10 @@
"text": "Caching", "text": "Caching",
"path": "Caching.md" "path": "Caching.md"
}, },
{
"text": "Logging",
"path": "Logging.md"
},
{ {
"text": "Audit Logging", "text": "Audit Logging",
"path": "Audit-Logging.md" "path": "Audit-Logging.md"
@ -111,6 +119,10 @@
{ {
"text": "Settings", "text": "Settings",
"path": "Settings.md" "path": "Settings.md"
},
{
"text": "Data Filtering",
"path": "Data-Filtering.md"
} }
] ]
}, },

44
docs/zh-Hans/Background-Jobs-Hangfire.md

@ -1,3 +1,43 @@
# Hangfire Background Job Manager # Hangfire后台作业管理
待添加 [Hangfire](https://www.hangfire.io/)是一个高级的后台作业管理. 你可以用ABP框架集成Hangfire代替[默认后台作业管理](Background-Jobs.md). 通过这种方式你可以使用相同的后台作业API,将你的代码独立于Hangfire. 如果你喜欢也可以直接使用Hangfire的API.
> 参阅[后台作业文档](Background-Jobs.md),学习如何使用后台作业系统. 本文只介绍了如何安装和配置Hangfire集成.
## 安装
建议使用[ABP CLI](CLI.md)安装包.
### 使用ABP CLI
在项目的文件夹(.csproj文件)中打开命令行窗口输入以下命令:
````bash
abp add-package Volo.Abp.BackgroundJobs.HangFire
````
### 手动安装
如果你想手动安装;
1. 添加 [Volo.Abp.BackgroundJobs.HangFire](https://www.nuget.org/packages/Volo.Abp.BackgroundJobs.HangFire) NuGet包添加到你的项目:
````
Install-Package Volo.Abp.BackgroundJobs.HangFire
````
2. 添加 `AbpBackgroundJobsHangfireModule` 到你的模块的依赖列表:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsHangfireModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## 配置
TODO...

8
docs/zh-Hans/Background-Jobs.md

@ -110,7 +110,7 @@ Enqueue方法接收一些可选参数用于控制后台作业:
你可能希望在你的应用程序中禁用后台作业执行. 如果你希望在另一个进程中执行后台作业并在当前进程中禁用它,通常可以使用以下命令. 你可能希望在你的应用程序中禁用后台作业执行. 如果你希望在另一个进程中执行后台作业并在当前进程中禁用它,通常可以使用以下命令.
使用 `BackgroundJobOptions` 配置作业执行: 使用 `AbpBackgroundJobOptions` 配置作业执行:
````csharp ````csharp
[DependsOn(typeof(AbpBackgroundJobsModule))] [DependsOn(typeof(AbpBackgroundJobsModule))]
@ -118,7 +118,7 @@ public class MyModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BackgroundJobOptions>(options => Configure<AbpBackgroundJobOptions>(options =>
{ {
options.IsJobExecutionEnabled = false; //禁用作业执行 options.IsJobExecutionEnabled = false; //禁用作业执行
}); });
@ -142,7 +142,7 @@ ABP framework 包含一个简单的 `IBackgroundJobManager` 实现;
### 配置 ### 配置
在你的[模块类](Module-Development-Basics.md)中使用 `BackgroundJobWorkerOptions` 配置默认作业管理器. 在你的[模块类](Module-Development-Basics.md)中使用 `AbpBackgroundJobWorkerOptions` 配置默认作业管理器.
示例中更改后台作业的的超时时间: 示例中更改后台作业的的超时时间:
````csharp ````csharp
@ -151,7 +151,7 @@ public class MyModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BackgroundJobWorkerOptions>(options => Configure<AbpBackgroundJobWorkerOptions>(options =>
{ {
options.DefaultTimeout = 864000; //10 days (as seconds) options.DefaultTimeout = 864000; //10 days (as seconds)
}); });

12
docs/zh-Hans/Blog-Posts/2019-02-22/Post.md

@ -1,12 +1,12 @@
# 微服务演示,项目状态和路线图 # 微服务演示,项目状态和路线图
在ABP vNext上的[第一个公告](https://cn.abp.io/blog/abp/Abp-vNext-Announcement)之后,我们对代码库进行了很多改进([GitHub存储库](https://github.com/abpframework/abp)上的1100多次提交).我们已经创建了功能,示例,文档等等.在这篇文章中,我想告诉你一些新闻和项目的状态. 在ABP vNext上的[第一个公告](https://abp.io/blog/abp/Abp-vNext-Announcement)之后,我们对代码库进行了很多改进([GitHub存储库](https://github.com/abpframework/abp)上的1100多次提交).我们已经创建了功能,示例,文档等等.在这篇文章中,我想告诉你一些新闻和项目的状态.
## 微服务演示解决方案 ## 微服务演示解决方案
ABP框架的主要目标之一是提供[创建微服务解决方案的便利基础设施](https://cn.abp.io/documents/abp/latest/Microservice-Architecture). ABP框架的主要目标之一是提供[创建微服务解决方案的便利基础设施](https://abp.io/documents/abp/latest/Microservice-Architecture).
我们一直在努力开发微服务解决方案演示.初始版本已完成并[文档化](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo).该示例解决方案旨在演示一个简单而完整的微服务解决方案; 我们一直在努力开发微服务解决方案演示.初始版本已完成并[文档化](https://abp.io/documents/abp/latest/Samples/Microservice-Demo).该示例解决方案旨在演示一个简单而完整的微服务解决方案;
- 具有多个独立的,可自我部署的**微服务**. - 具有多个独立的,可自我部署的**微服务**.
- 多个**Web应用程序**,每个都使用不同的API网关. - 多个**Web应用程序**,每个都使用不同的API网关.
@ -20,7 +20,7 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
- 使用[Docker](https://www.docker.com/)和[Kubernates](https://kubernetes.io/)**部署**并运行所有服务和应用程序. - 使用[Docker](https://www.docker.com/)和[Kubernates](https://kubernetes.io/)**部署**并运行所有服务和应用程序.
- 使用[Elasticsearch](https://www.elastic.co/products/elasticsearch)和[Kibana](https://www.elastic.co/products/kibana)存储和可视化日志(使用[Serilog](https://serilog.net/)编写). - 使用[Elasticsearch](https://www.elastic.co/products/elasticsearch)和[Kibana](https://www.elastic.co/products/kibana)存储和可视化日志(使用[Serilog](https://serilog.net/)编写).
有关解决方案的详细说明,请参阅[其文档](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo). 有关解决方案的详细说明,请参阅[其文档](https://abp.io/documents/abp/latest/Samples/Microservice-Demo).
## 改进/功能 ## 改进/功能
@ -32,13 +32,13 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋. 根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋.
我们还将完善[文档](https://cn.abp.io/documents/abp/latest),因为它现在还远未完成. 我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将您的想法写为对此帖的评论. 第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将您的想法写为对此帖的评论.
## 中文网 ## 中文网
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://cn.abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming). 中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
## NDC {London} 2019 ## NDC {London} 2019

16
docs/zh-Hans/Connection-Strings.md

@ -35,6 +35,22 @@ ABP框架的设计是[模块化](Module-Development-Basics.md), [微服务兼容
[预构建的应用程序模块](Modules/Index.md) 为连接字符串名称定义常量. 例如IdentityServer模块在 `AbpIdentityServerDbProperties` 类(位于 `Volo.Abp.IdentityServer` 命名空间)定义了 `ConnectionStringName` 常量 . 其他的模块类似的定义常量,你可以查看连接字符串的名称. [预构建的应用程序模块](Modules/Index.md) 为连接字符串名称定义常量. 例如IdentityServer模块在 `AbpIdentityServerDbProperties` 类(位于 `Volo.Abp.IdentityServer` 命名空间)定义了 `ConnectionStringName` 常量 . 其他的模块类似的定义常量,你可以查看连接字符串的名称.
### AbpDbConnectionOptions
ABP实际上使用 `AbpDbConnectionOptions` 获取连接字符串. 如果如上所述设置了连接字符串, `AbpDbConnectionOptions` 会被自动填充. 但是你也可以使用[选项模式](Options.md)设置或覆盖连接字符串. 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置`AbpDbConnectionOptions`).
如下所示:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "...";
options.ConnectionStrings["AbpPermissionManagement"] = "...";
});
}
````
## 设置连接字符串名称 ## 设置连接字符串名称
模块通常使用 `ConnectionStringName` attribute 为 `DbContext` 类关联一个唯一的连接字符串名称. 示例: 模块通常使用 `ConnectionStringName` attribute 为 `DbContext` 类关联一个唯一的连接字符串名称. 示例:

29
docs/zh-Hans/Contribution/Localization-Text-Files.md

@ -3,7 +3,7 @@
这是一个来自框架的本地化文本文件列表, 任何人都可以做出贡献. 我们会将此列表保持最新: 这是一个来自框架的本地化文本文件列表, 任何人都可以做出贡献. 我们会将此列表保持最新:
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpValidation/en.json * https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json * https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json
@ -12,29 +12,20 @@
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json * https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json
* https://github.com/abpframework/abp/tree/master/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json * https://github.com/abpframework/abp/blob/master/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json * https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Web/Localization/Resources/Docs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/en.json * https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Localization/Resources/AbpPermissionManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json * https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json * https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Application.Contracts/Localization/MyProjectName/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/DomainShared/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Web/Localization/MyProjectName/Web/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json

2
docs/zh-Hans/Dependency-Injection.md

@ -319,4 +319,4 @@ public class AppModule : AbpModule
### 请参阅 ### 请参阅
* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://cn.abp.io/blog/Abp/asp-net-core-dependency-injection-best-practices-tips-tricks) * [ASP.NET Core依赖注入最佳实践,提示和技巧](https://blog.abp.io/asp-net-core-dependency-injection-best-practices-tips-tricks)

2
docs/zh-Hans/Entity-Framework-Core.md

@ -181,7 +181,7 @@ public interface IBookRepository : IRepository<Book, Guid>
} }
```` ````
你通常希望从IRepository派生以继承标准存储库方法. 然而,你没有必要这样做. 仓储接口在分层应用程序的领域层中定义,它在数据访问/基础设施层([启动模板](https://cn.abp.io/Templates)中的`EntityFrameworkCore`项目)中实现 你通常希望从IRepository派生以继承标准存储库方法. 然而,你没有必要这样做. 仓储接口在分层应用程序的领域层中定义,它在数据访问/基础设施层([启动模板](https://abp.io/Templates)中的`EntityFrameworkCore`项目)中实现
IBookRepository接口的实现示例: IBookRepository接口的实现示例:

2
docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md

@ -2,7 +2,7 @@
### 创建新项目 ### 创建新项目
本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://cn.abp.io/get-started)页面. 本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面.
如果你之前未安装,请使用命令行安装ABP CLI: 如果你之前未安装,请使用命令行安装ABP CLI:

144
docs/zh-Hans/Modules/Docs.md

@ -14,21 +14,19 @@
### 版本 ### 版本
当你使用GitHub存储文档时,文档模块支持多版本. 如果你的文档具有多个版本, UI上有一个组合框,用于切换版本. 如果你选择使用文件系统存储文档, 那么它不支持多版本. 当你使用GitHub存储文档时,文档模块支持多版本. 如果你的文档具有多个版本, UI上有一个组合框,用于切换版本. 如果你选择使用文件系统存储文档, 那么它不支持多版本.
ABP框架的[文档](https://abp.io/documents/)也是使用的此模块. ABP框架的[文档](docs.abp.io)也是使用的此模块.
> 文档模块遵循 [模块化架构最佳实践](../Best-Practices/Module-Architecture.md) 指南. > 文档模块遵循 [模块化架构最佳实践](../Best-Practices/Module-Architecture.md) 指南.
## 安装 ## 安装
### 1- 下载 ### 1- 下载
如果你没有现有的ABP项目, 这个步骤向你展示如何在[abp.io](https://cn.abp.io)创建一个新项目并添加文档模块. 如果你本地已经有了一个ABP项目, 那么你可以跳过这一步. 如果你没有现有的ABP项目, 这个步骤向你展示如何在[abp.io](https://abp.io)创建一个新项目并添加文档模块. 如果你本地已经有了一个ABP项目, 那么你可以跳过这一步.
打开 https://cn.abp.io/Templates. 输入项目名称为 `Acme.MyProject`, 选择 `ASP.NET Core Mvc Application` 和选择 `Entity Framework Core` 做为数据库提供者. 打开 https://abp.io/Templates. 输入项目名称为 `Acme.MyProject`, 选择 `ASP.NET Core Mvc Application` 和选择 `Entity Framework Core` 做为数据库提供者.
请注意,本文档包含了 `Entity Framework Core` 提供者 不过你也可以选择 `MongoDB` 做为数据库提供者. 请注意,本文档包含了 `Entity Framework Core` 提供者 不过你也可以选择 `MongoDB` 做为数据库提供者.
@ -36,7 +34,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
### 2- 运行这个空项目 ### 2- 运行这个空项目
下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Domain `, `EntityFrameworkCore` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**. 下载项目后, 解压压缩文档并且打开 `Acme.MyProject.sln`. 你可以看到这个解决方案包含了 `Application`, `Domain`, `EntityFrameworkCore` 和 `Web` 项目. 右键选择 `Acme.MyProject.Web` 项目**设置为启动项目**.
![创建新项目](../images/docs-module_solution-explorer.png) ![创建新项目](../images/docs-module_solution-explorer.png)
@ -67,31 +65,32 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
```csharp ```csharp
<PackageReference Include="Volo.Docs.Domain" Version="0.9.0" /> <PackageReference Include="Volo.Docs.Domain" Version="0.9.0" />
``` ```
* [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) 需要安装到 `Acme.MyProject.EntityFrameworkCore` 项目. * [Volo.Docs.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Docs.EntityFrameworkCore/) 需要安装到 `Acme.MyProject.EntityFrameworkCore` 项目.
- 修改 `Acme.MyProject.EntityFrameworkCore.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. * 修改 `Acme.MyProject.EntityFrameworkCore.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本.
```csharp ```csharp
<PackageReference Include="Volo.Docs.EntityFrameworkCore" Version="0.9.0" /> <PackageReference Include="Volo.Docs.EntityFrameworkCore" Version="0.9.0" />
``` ```
* [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) 需要安装到 `Acme.MyProject.Application` 项目. * [Volo.Docs.Application](https://www.nuget.org/packages/Volo.Docs.Application/) 需要安装到 `Acme.MyProject.Application` 项目.
* 修改 `Acme.MyProject.Application.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. * 修改 `Acme.MyProject.Application.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本.
```csharp ```csharp
<PackageReference Include="Volo.Docs.Application" Version="0.9.0" /> <PackageReference Include="Volo.Docs.Application" Version="0.9.0" />
``` ```
* [Volo.Docs.Web ](https://www.nuget.org/packages/Volo.Docs.Web/) 需要安装到 `Acme.MyProject.Web` 项目.
- 修改 `Acme.MyProject.Web.csproj`文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本. * [Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Web/) 需要安装到 `Acme.MyProject.Web` 项目.
* 修改 `Acme.MyProject.Web.csproj` 文件并且添加以下行. 需要注意它要设置(v0.9.0)为Latest版本.
```csharp ```csharp
<PackageReference Include="Volo.Docs.Web" Version="0.9.0" /> <PackageReference Include="Volo.Docs.Web" Version="0.9.0" />
``` ```
### 3- 添加模块依赖
### 3- 添加模块添加
一个ABP模块必须声明 `[DependsOn]` attribute 如果它依赖于另一个模块. 每个模块都必须在相关的项目的`[DependsOn]`Attribute 中添加. 一个ABP模块必须声明 `[DependsOn]` attribute 如果它依赖于另一个模块. 每个模块都必须在相关的项目的`[DependsOn]`Attribute 中添加.
@ -155,7 +154,6 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
} }
``` ```
* 打开 `MyProjectWebModule.cs`并且添加 `typeof(DocsWebModule)` 如下所示; * 打开 `MyProjectWebModule.cs`并且添加 `typeof(DocsWebModule)` 如下所示;
```csharp ```csharp
@ -174,15 +172,13 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
} }
``` ```
### 4- 数据库集成 ### 4- 数据库集成
#### 4.1- Entity Framework 集成 #### 4.1- Entity Framework 集成
如果你选择了Entity Framework 做为数据库供应者,你需要在DbContext中配置文档模块. 做以下操作; 如果你选择了Entity Framework 做为数据库供应者,你需要在DbContext中配置文档模块. 做以下操作;
- 打开 `MyProjectDbContext.cs` 并且添加 `modelBuilder.ConfigureDocs()` 到 `OnModelCreating()` 方法中 * 打开 `MyProjectDbContext.cs` 并且添加 `modelBuilder.ConfigureDocs()` 到 `OnModelCreating()` 方法中
```csharp ```csharp
[ConnectionStringName("Default")] [ConnectionStringName("Default")]
@ -195,7 +191,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
//... //...
modelBuilder.ConfigureDocs(); modelBuilder.ConfigureDocs();
} }
@ -218,7 +214,6 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
最后你可以查看数据库中创建的新表,例如你可以看到 `DocsProjects` 表已经添加到数据库中. 最后你可以查看数据库中创建的新表,例如你可以看到 `DocsProjects` 表已经添加到数据库中.
### 5- 链接文档模块 ### 5- 链接文档模块
文档模块的默认路由是; 文档模块的默认路由是;
@ -262,7 +257,7 @@ ABP框架的[文档](https://abp.io/documents/)也是使用的此模块.
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io.", "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io.",
"Menu:Docs": "文档" "Menu:Docs": "文档"
} }
} }
@ -352,10 +347,10 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume
- ExtraProperties: - ExtraProperties:
```json ```json
{"Path":"C:\\Github\\abp\\docs\\zh-Hans"} {"Path":"C:\\Github\\abp\\docs"}
``` ```
请注意 `Path` 必须使用本地docs目录替换. 你可以从https://github.com/abpframework/abp/tree/master/docs/zh-hans获取ABP Framework的文档并且复制到该目录 `C:\\Github\\abp\\docs\\zh-Hans` 使其正常工作. 请注意 `Path` 必须使用本地docs目录替换. 你可以从https://github.com/abpframework/abp/tree/master/docs获取ABP Framework的文档并且复制到该目录 `C:\\Github\\abp\\docs` 使其正常工作.
- MainWebsiteUrl: `/` - MainWebsiteUrl: `/`
@ -364,7 +359,7 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume
对于 `SQL` 数据库,你可以使用下面的 `T-SQL` 命令将指定的示例插入到 `DocsProjects` 表中: 对于 `SQL` 数据库,你可以使用下面的 `T-SQL` 命令将指定的示例插入到 `DocsProjects` 表中:
```mssql ```mssql
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs\\zh-Hans"}', N'/', NULL) INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL)
``` ```
添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接,点击菜单链接打开文档页面. 添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接,点击菜单链接打开文档页面.
@ -408,6 +403,101 @@ public class Person
[https://github.com/abpframework/abp/blob/master/docs/zh-Hans/](https://github.com/abpframework/abp/blob/master/docs/zh-Hans/) [https://github.com/abpframework/abp/blob/master/docs/zh-Hans/](https://github.com/abpframework/abp/blob/master/docs/zh-Hans/)
#### 有条件的部分功能(使用Scriban)
文档模块使用[Scriban](<https://github.com/lunet-io/scriban/tree/master/doc>)有条件的显示或隐藏文档的某些部分. 使用该功能你需要为每一种语言创建一个JSON文件做为**参数文档**. 它包含所有键值以及它们的显示名称.
例如 [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json):
```json
{
"parameters": [{
"name": "UI",
"displayName": "UI",
"values": {
"MVC": "MVC / Razor Pages",
"NG": "Angular"
}
},
{
"name": "DB",
"displayName": "Database",
"values": {
"EF": "Entity Framework Core",
"Mongo": "MongoDB"
}
},
{
"name": "Tiered",
"displayName": "Tiered",
"values": {
"No": "Not Tiered",
"Yes": "Tiered"
}
}]
}
```
因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段,在文档的任何地方都可以使用JSON块.
例如 [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md):
```
.....
​````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
​````
........
```
这个部分会在渲染时自动删除.前提是这些键值必须与**参数文档**中的键值匹配.
![Interface](../images/docs-section-ui.png)
现在你可以使用 **Scriban** 语法在文档中创建章节.
示例 :
````
{{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case.
{{ end }}
{{ if DB == "Mongo" }}
* `-d` argument specifies the database provider, `mongodb` in this case.
{{ end }}
{{ if Tiered == "Yes" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ end }}
````
还可以在文本中使用变量,在其键中添加 **_Value** 后缀:
````
This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider.
````
如果你想要得到的当前文档的语言或版本,可以使用预定义的 **Document_Language_Code** 和 **DOCUMENT_VERSION** 键(这对于创建重定向到另一个地区中另一个文档系统的链接很有用).
------
**重要提示**: Scriban 的语法是 "{{" and "}}". 如果要在文档(如Angular文档)中使用转义,则必须使用转义块. 参阅 [Scriban文档](<https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block> ) 了解更多信息.
### 8- 创建文档导航 ### 8- 创建文档导航
导航文档是文档页面的主菜单. 它位于页面的左侧,是一个`JSON` 文件. 请查看以下示例导航文档以了解结构. 导航文档是文档页面的主菜单. 它位于页面的左侧,是一个`JSON` 文件. 请查看以下示例导航文档以了解结构.
@ -464,4 +554,8 @@ public class Person
![Navigation menu](../images/docs-module_download-sample-navigation-menu.png) ![Navigation menu](../images/docs-module_download-sample-navigation-menu.png)
最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供. 最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供.
## 下一步
文档模块也可以做为独立的应用程序. 查看 [VoloDocs](../Apps/VoloDocs).

2
docs/zh-Hans/Modules/Index.md

@ -14,7 +14,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages**
* **Account**: 用于用户登录/注册应用程序. * **Account**: 用于用户登录/注册应用程序.
* **Audit Logging**: 用于将审计日志持久化到数据库. * **Audit Logging**: 用于将审计日志持久化到数据库.
* **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业. * **Background Jobs**: 用于在使用默认后台作业管理器时保存后台作业.
* **Blogging**: 用于创建精美的博客. ABP的[博客](https://abp.io/blog/abp/) 就使用了此模块. * **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块.
* [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块.
* **Identity**: 用于管理角色,用户和他们的权限. * **Identity**: 用于管理角色,用户和他们的权限.
* **Identity Server**: 集成了IdentityServer4. * **Identity Server**: 集成了IdentityServer4.

16
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md

@ -50,6 +50,18 @@ namespace Acme.BookStore
public DateTime PublishDate { get; set; } public DateTime PublishDate { get; set; }
public float Price { get; set; } public float Price { get; set; }
protected Book()
{
}
public Book(Guid id, string name, BookType type, DateTime publishDate, float price)
:base(id)
{
Name = name;
Type = type;
PublishDate = publishDate;
Price = price;
}
} }
} }
```` ````
@ -349,7 +361,7 @@ successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7
```` ````
* 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. * 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法.
* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. * 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它.
#### 将Books页面添加到主菜单 #### 将Books页面添加到主菜单
@ -426,7 +438,7 @@ context.Menu.AddItem(
```` ````
* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../../AspNetCore/Bundling-Minification.md)获取更多信息. * `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../../AspNetCore/Bundling-Minification.md)获取更多信息.
* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers.md). * `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers/Index.md).
* 你可以像上面本地化菜单一样 **本地化** 列名. * 你可以像上面本地化菜单一样 **本地化** 列名.
#### 添加脚本文件 #### 添加脚本文件

22
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md

@ -56,26 +56,12 @@ namespace Acme.BookStore
public async Task SeedAsync(DataSeedContext context) public async Task SeedAsync(DataSeedContext context)
{ {
await _bookRepository.InsertAsync( await _bookRepository.InsertAsync(
new Book new Book(_guidGenerator.Create(), "Test book 1",
{ BookType.Fantastic, new DateTime(2015, 05, 24), 21));
Id = _guidGenerator.Create(),
Name = "Test book 1",
Type = BookType.Fantastic,
PublishDate = new DateTime(2015, 05, 24),
Price = 21
}
);
await _bookRepository.InsertAsync( await _bookRepository.InsertAsync(
new Book new Book(_guidGenerator.Create(), "Test book 2",
{ BookType.Science, new DateTime(2014, 02, 11), 15));
Id = _guidGenerator.Create(),
Name = "Test book 2",
Type = BookType.Science,
PublishDate = new DateTime(2014, 02, 11),
Price = 15
}
);
} }
} }
} }

BIN
docs/zh-Hans/images/auditlog-object-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/zh-Hans/images/docs-section-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

26
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs

@ -28,38 +28,46 @@ namespace Volo.Abp.AspNetCore.Auditing
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
if (!ShouldWriteAuditLog(context)) bool hasError = false;
{
await next(context);
return;
}
using (var scope = _auditingManager.BeginScope()) using (var scope = _auditingManager.BeginScope())
{ {
try try
{ {
await next(context); await next(context);
} }
catch (Exception)
{
hasError = true;
throw;
}
finally finally
{ {
await scope.SaveAsync(); if (ShouldWriteAuditLog(context, hasError))
{
await scope.SaveAsync();
}
} }
} }
} }
private bool ShouldWriteAuditLog(HttpContext httpContext) private bool ShouldWriteAuditLog(HttpContext httpContext, bool hasError = false)
{ {
if (!Options.IsEnabled) if (!Options.IsEnabled)
{ {
return false; return false;
} }
if (Options.AlwaysLogOnException && hasError)
{
return true;
}
if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated)
{ {
return false; return false;
} }
if (!Options.IsEnabledForGetRequests && if (!Options.IsEnabledForGetRequests &&
string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase)) string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
{ {
return false; return false;

7
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs

@ -30,6 +30,12 @@ namespace Volo.Abp.Auditing
/// Default: true. /// Default: true.
/// </summary> /// </summary>
public bool IsEnabledForAnonymousUsers { get; set; } public bool IsEnabledForAnonymousUsers { get; set; }
/// <summary>
/// Audit log on exceptions.
/// Default: true.
/// </summary>
public bool AlwaysLogOnException { get; set; }
public List<AuditLogContributor> Contributors { get; } public List<AuditLogContributor> Contributors { get; }
@ -48,6 +54,7 @@ namespace Volo.Abp.Auditing
IsEnabled = true; IsEnabled = true;
IsEnabledForAnonymousUsers = true; IsEnabledForAnonymousUsers = true;
HideErrors = true; HideErrors = true;
AlwaysLogOnException = true;
Contributors = new List<AuditLogContributor>(); Contributors = new List<AuditLogContributor>();

8
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs

@ -23,8 +23,8 @@ namespace Volo.Abp.Auditing
private readonly IAuditingStore _auditingStore; private readonly IAuditingStore _auditingStore;
public AuditingManager( public AuditingManager(
IAmbientScopeProvider<IAuditLogScope> ambientScopeProvider, IAmbientScopeProvider<IAuditLogScope> ambientScopeProvider,
IAuditingHelper auditingHelper, IAuditingHelper auditingHelper,
IAuditingStore auditingStore, IAuditingStore auditingStore,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IOptions<AbpAuditingOptions> options) IOptions<AbpAuditingOptions> options)
@ -84,7 +84,7 @@ namespace Volo.Abp.Auditing
{ {
var changeGroups = auditLog.EntityChanges var changeGroups = auditLog.EntityChanges
.Where(e => e.ChangeType == EntityChangeType.Updated) .Where(e => e.ChangeType == EntityChangeType.Updated)
.GroupBy(e => new {e.EntityTypeFullName, e.EntityId}) .GroupBy(e => new { e.EntityTypeFullName, e.EntityId })
.ToList(); .ToList();
foreach (var changeGroup in changeGroups) foreach (var changeGroup in changeGroups)
@ -141,7 +141,7 @@ namespace Volo.Abp.Auditing
public DisposableSaveHandle( public DisposableSaveHandle(
AuditingManager auditingManager, AuditingManager auditingManager,
IDisposable scope, IDisposable scope,
AuditLogInfo auditLog, AuditLogInfo auditLog,
Stopwatch stopWatch) Stopwatch stopWatch)
{ {
_auditingManager = auditingManager; _auditingManager = auditingManager;

28
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs

@ -33,15 +33,19 @@ namespace Volo.Abp.Cli.Commands
); );
} }
Console.Write("Password: "); var password = commandLineArgs.Options.GetOrNull(Options.Password.Short, Options.Password.Long);
var password = ConsoleHelper.ReadSecret(); if (password == null)
if (password.IsNullOrWhiteSpace())
{ {
throw new CliUsageException( Console.Write("Password: ");
"Password is missing!" + password = ConsoleHelper.ReadSecret();
Environment.NewLine + Environment.NewLine + if (password.IsNullOrWhiteSpace())
GetUsageInfo() {
); throw new CliUsageException(
"Password is missing!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
} }
await AuthService.LoginAsync( await AuthService.LoginAsync(
@ -60,10 +64,12 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine("Usage:"); sb.AppendLine("Usage:");
sb.AppendLine(" abp login <username>"); sb.AppendLine(" abp login <username>");
sb.AppendLine(" abp login <username> -p <password>");
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine("Example:"); sb.AppendLine("Example:");
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine(" abp login john"); sb.AppendLine(" abp login john");
sb.AppendLine(" abp login john -p 1234");
sb.AppendLine(""); sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
@ -82,6 +88,12 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "o"; public const string Short = "o";
public const string Long = "organization"; public const string Long = "organization";
} }
public static class Password
{
public const string Short = "p";
public const string Long = "password";
}
} }
} }
} }

20
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs

@ -54,7 +54,7 @@ namespace Volo.Abp.Cli.Commands
private async Task InstallSuiteAsync() private async Task InstallSuiteAsync()
{ {
var nugetIndexUrl = await GetNuGetIndexUrlAsync(); var nugetIndexUrl = await GetNuGetIndexUrlAsync();
if (nugetIndexUrl == null) if (nugetIndexUrl == null)
{ {
return; return;
@ -108,13 +108,21 @@ namespace Volo.Abp.Cli.Commands
{ {
var apiKeyResult = await _apiKeyService.GetApiKeyOrNullAsync(); var apiKeyResult = await _apiKeyService.GetApiKeyOrNullAsync();
if (apiKeyResult == null || string.IsNullOrEmpty(apiKeyResult.ApiKey)) if (apiKeyResult == null)
{
Logger.LogWarning("You are not signed in! Use the CLI command \"abp login <username>\" to sign in, then try again.");
return null;
}
if (!string.IsNullOrWhiteSpace(apiKeyResult.ErrorMessage))
{ {
Logger.LogError("Couldn't retrieve your NuGet API key!"); Logger.LogWarning(apiKeyResult.ErrorMessage);
Logger.LogWarning(File.Exists(CliPaths.AccessToken) return null;
? "Make sure you have an active session and license on commercial.abp.io. To re-sign in you can use the CLI command \"abp login <username>\"." }
: "You are not signed in to commercial.abp.io. Use the CLI command \"abp login <username>\" to sign in.");
if (string.IsNullOrEmpty(apiKeyResult.ApiKey))
{
Logger.LogError("Couldn't retrieve your NuGet API key! You can re-sign in with the CLI command \"abp login <username>\".");
return null; return null;
} }

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs

@ -10,5 +10,15 @@ namespace Volo.Abp.Cli.Licensing
public DateTime? LicenseEndTime { get; set; } public DateTime? LicenseEndTime { get; set; }
public bool CanDownloadSourceCode { get; set; } public bool CanDownloadSourceCode { get; set; }
public string LicenseCode { get; set; } public string LicenseCode { get; set; }
public string ErrorMessage { get; set; }
public LicenseErrorType? ErrorType { get; set; }
public enum LicenseErrorType
{
NotAuthenticated = 1,
NotMemberOfAnOrganization = 2,
NoActiveLicense = 3,
NotDeveloperOfTheOrganization = 4
}
} }
} }

1
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/ILocalEventHandler.cs

@ -2,6 +2,7 @@ using System.Threading.Tasks;
namespace Volo.Abp.EventBus namespace Volo.Abp.EventBus
{ {
//TODO: Move to the right namespace in v3.0
public interface ILocalEventHandler<in TEvent> : IEventHandler public interface ILocalEventHandler<in TEvent> : IEventHandler
{ {
/// <summary> /// <summary>

30
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController.cs

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
namespace Volo.Abp.AspNetCore.Mvc.Auditing
{
[Route("api/audit-test")]
[Audited]
public class AuditTestController : AbpController
{
private readonly AbpAuditingOptions _options;
public AuditTestController(IOptions<AbpAuditingOptions> options)
{
_options = options.Value;
}
[Route("audit-success")]
public IActionResult AuditSuccessForGetRequests()
{
return Ok();
}
[Route("audit-fail")]
public IActionResult AuditFailForGetRequests()
{
throw new UserFriendlyException("Exception occurred!");
}
}
}

55
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs

@ -0,0 +1,55 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NSubstitute;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Auditing
{
public class AuditTestController_Tests : AspNetCoreMvcTestBase
{
private readonly AbpAuditingOptions _options;
private IAuditingStore _auditingStore;
public AuditTestController_Tests()
{
_options = ServiceProvider.GetRequiredService<IOptions<AbpAuditingOptions>>().Value;
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>();
}
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
_auditingStore = Substitute.For<IAuditingStore>();
services.Replace(ServiceDescriptor.Singleton(_auditingStore));
base.ConfigureServices(context, services);
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests()
{
_options.IsEnabledForGetRequests = true;
_options.AlwaysLogOnException = false;
await GetResponseAsync("api/audit-test/audit-success");
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); //Won't work, save happens out of scope
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always()
{
_options.IsEnabled = true;
_options.AlwaysLogOnException = true;
try
{
await GetResponseAsync("api/audit-test/audit-fail", System.Net.HttpStatusCode.Forbidden);
}
catch { }
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); //Won't work, save happens out of scope
}
}
}

3
npm/get-version.js

@ -0,0 +1,3 @@
const fse = require("fs-extra");
console.log(fse.readJSONSync("package.json").version);

2
npm/ng-packs/package.json

@ -82,7 +82,7 @@
"protractor": "~5.4.0", "protractor": "~5.4.0",
"rxjs": "~6.4.0", "rxjs": "~6.4.0",
"snq": "^1.0.3", "snq": "^1.0.3",
"symlink-manager": "^1.4.1", "symlink-manager": "^1.4.2",
"ts-node": "~7.0.0", "ts-node": "~7.0.0",
"tsickle": "^0.37.0", "tsickle": "^0.37.0",
"tslint": "~5.20.0", "tslint": "~5.20.0",

2
npm/ng-packs/packages/account/src/lib/account.module.ts

@ -3,7 +3,6 @@ import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule, Provider } from '@angular/core'; import { NgModule, Provider } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core'; import { NgxValidateCoreModule } from '@ngx-validate/core';
import { TableModule } from 'primeng/table';
import { AccountRoutingModule } from './account-routing.module'; import { AccountRoutingModule } from './account-routing.module';
import { ChangePasswordComponent } from './components/change-password/change-password.component'; import { ChangePasswordComponent } from './components/change-password/change-password.component';
import { LoginComponent } from './components/login/login.component'; import { LoginComponent } from './components/login/login.component';
@ -29,7 +28,6 @@ import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.com
CoreModule, CoreModule,
AccountRoutingModule, AccountRoutingModule,
ThemeSharedModule, ThemeSharedModule,
TableModule,
NgbDropdownModule, NgbDropdownModule,
NgxValidateCoreModule, NgxValidateCoreModule,
], ],

8
npm/ng-packs/packages/core/src/lib/actions/config.actions.ts

@ -1,4 +1,5 @@
import { ABP } from '../models'; import { ABP } from '../models/common';
import { Config } from '../models/config';
export class PatchRouteByName { export class PatchRouteByName {
static readonly type = '[Config] Patch Route By Name'; static readonly type = '[Config] Patch Route By Name';
@ -16,3 +17,8 @@ export class AddRoute {
static readonly type = '[Config] Add Route'; static readonly type = '[Config] Add Route';
constructor(public payload: Omit<ABP.Route, 'children'>) {} constructor(public payload: Omit<ABP.Route, 'children'>) {}
} }
export class SetEnvironment {
static readonly type = '[Config] Set Environment';
constructor(public environment: Config.Environment) {}
}

12
npm/ng-packs/packages/core/src/lib/services/config-state.service.ts

@ -1,8 +1,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import {
AddRoute,
GetAppConfiguration,
PatchRouteByName,
SetEnvironment,
} from '../actions/config.actions';
import { ConfigState } from '../states'; import { ConfigState } from '../states';
import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions';
import { ABP } from '../models';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -61,4 +65,8 @@ export class ConfigStateService {
dispatchAddRoute(...args: ConstructorParameters<typeof AddRoute>) { dispatchAddRoute(...args: ConstructorParameters<typeof AddRoute>) {
return this.store.dispatch(new AddRoute(...args)); return this.store.dispatch(new AddRoute(...args));
} }
dispatchSetEnvironment(...args: ConstructorParameters<typeof SetEnvironment>) {
return this.store.dispatch(new SetEnvironment(...args));
}
} }

14
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -2,7 +2,12 @@ import { Action, createSelector, Selector, State, StateContext, Store } from '@n
import { of } from 'rxjs'; import { of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators'; import { switchMap, tap } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; import {
GetAppConfiguration,
PatchRouteByName,
AddRoute,
SetEnvironment,
} from '../actions/config.actions';
import { SetLanguage } from '../actions/session.actions'; import { SetLanguage } from '../actions/session.actions';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { Config } from '../models/config'; import { Config } from '../models/config';
@ -291,6 +296,13 @@ export class ConfigState {
flattedRoutes, flattedRoutes,
}); });
} }
@Action(SetEnvironment)
setEnvironment({ patchState }: StateContext<Config.State>, environment: Config.Environment) {
return patchState({
environment,
});
}
} }
function patchRouteDeep( function patchRouteDeep(

7
npm/ng-packs/packages/core/src/lib/utils/route-utils.ts

@ -1,4 +1,4 @@
import { ABP } from '../models'; import { ABP } from '../models/common';
export function organizeRoutes( export function organizeRoutes(
routes: ABP.FullRoute[], routes: ABP.FullRoute[],
@ -33,7 +33,10 @@ export function organizeRoutes(
return filteredRoutes; return filteredRoutes;
} }
export function setChildRoute(routes: ABP.FullRoute[], parentNameArr: ABP.FullRoute[]): ABP.FullRoute[] { export function setChildRoute(
routes: ABP.FullRoute[],
parentNameArr: ABP.FullRoute[],
): ABP.FullRoute[] {
return routes.map(route => { return routes.map(route => {
if (route.children && route.children.length) { if (route.children && route.children.length) {
route.children = setChildRoute(route.children, parentNameArr); route.children = setChildRoute(route.children, parentNameArr);

2
npm/ng-packs/packages/identity/src/lib/identity.module.ts

@ -8,7 +8,6 @@ import { NgbTabsetModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { ThemeSharedModule } from '@abp/ng.theme.shared'; import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { UsersComponent } from './components/users/users.component'; import { UsersComponent } from './components/users/users.component';
import { PermissionManagementModule } from '@abp/ng.permission-management'; import { PermissionManagementModule } from '@abp/ng.permission-management';
import { TableModule } from 'primeng/table';
import { NgxValidateCoreModule } from '@ngx-validate/core'; import { NgxValidateCoreModule } from '@ngx-validate/core';
@NgModule({ @NgModule({
@ -19,7 +18,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
IdentityRoutingModule, IdentityRoutingModule,
NgbTabsetModule, NgbTabsetModule,
ThemeSharedModule, ThemeSharedModule,
TableModule,
NgbDropdownModule, NgbDropdownModule,
PermissionManagementModule, PermissionManagementModule,
NgxValidateCoreModule, NgxValidateCoreModule,

2
npm/ng-packs/packages/tenant-management/src/lib/tenant-management.module.ts

@ -3,7 +3,6 @@ import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule, Provider } from '@angular/core'; import { NgModule, Provider } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { TableModule } from 'primeng/table';
import { TenantsComponent } from './components/tenants/tenants.component'; import { TenantsComponent } from './components/tenants/tenants.component';
import { TenantManagementState } from './states/tenant-management.state'; import { TenantManagementState } from './states/tenant-management.state';
import { TenantManagementRoutingModule } from './tenant-management-routing.module'; import { TenantManagementRoutingModule } from './tenant-management-routing.module';
@ -17,7 +16,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
NgxsModule.forFeature([TenantManagementState]), NgxsModule.forFeature([TenantManagementState]),
NgxValidateCoreModule, NgxValidateCoreModule,
CoreModule, CoreModule,
TableModule,
ThemeSharedModule, ThemeSharedModule,
NgbDropdownModule, NgbDropdownModule,
FeatureManagementModule, FeatureManagementModule,

7
npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts

@ -1,10 +1,10 @@
import { import {
ABP, ABP,
ApplicationConfiguration, ApplicationConfiguration,
AuthService,
Config, Config,
ConfigState, ConfigState,
eLayoutType, eLayoutType,
GetAppConfiguration,
SessionState, SessionState,
SetLanguage, SetLanguage,
takeUntilDestroy, takeUntilDestroy,
@ -14,15 +14,11 @@ import {
AfterViewInit, AfterViewInit,
Component, Component,
OnDestroy, OnDestroy,
QueryList,
Renderer2, Renderer2,
TemplateRef, TemplateRef,
TrackByFunction, TrackByFunction,
ViewChild, ViewChild,
ViewChildren,
ElementRef,
} from '@angular/core'; } from '@angular/core';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { Navigate, RouterState } from '@ngxs/router-plugin'; import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import compare from 'just-compare'; import compare from 'just-compare';
@ -32,7 +28,6 @@ import snq from 'snq';
import { AddNavigationElement } from '../../actions'; import { AddNavigationElement } from '../../actions';
import { Layout } from '../../models/layout'; import { Layout } from '../../models/layout';
import { LayoutState } from '../../states'; import { LayoutState } from '../../states';
import { AuthService } from 'packages/core/src/lib/services/auth.service';
@Component({ @Component({
selector: 'abp-layout-application', selector: 'abp-layout-application',

8
npm/ng-packs/packages/theme-basic/src/lib/theme-basic.module.ts

@ -4,7 +4,6 @@ import { NgModule } from '@angular/core';
import { NgbCollapseModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbCollapseModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core'; import { NgxValidateCoreModule } from '@ngx-validate/core';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { ToastModule } from 'primeng/toast';
import { AccountLayoutComponent } from './components/account-layout/account-layout.component'; import { AccountLayoutComponent } from './components/account-layout/account-layout.component';
import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component'; import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component';
import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component'; import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component';
@ -21,7 +20,6 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
ThemeSharedModule, ThemeSharedModule,
NgbCollapseModule, NgbCollapseModule,
NgbDropdownModule, NgbDropdownModule,
ToastModule,
NgxValidateCoreModule, NgxValidateCoreModule,
NgxsModule.forFeature([LayoutState]), NgxsModule.forFeature([LayoutState]),
NgxValidateCoreModule.forRoot({ NgxValidateCoreModule.forRoot({
@ -29,9 +27,11 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
blueprints: { blueprints: {
email: 'AbpAccount::ThisFieldIsNotAValidEmailAddress.', email: 'AbpAccount::ThisFieldIsNotAValidEmailAddress.',
max: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', max: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
maxlength: 'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}[{{ requiredLength }}]', maxlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}[{{ requiredLength }}]',
min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]', min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
minlength: 'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]', minlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]',
required: 'AbpAccount::ThisFieldIsRequired.', required: 'AbpAccount::ThisFieldIsRequired.',
passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed', passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed',
}, },

4
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -190,8 +190,8 @@ export class ErrorHandler {
if (body.details) { if (body.details) {
message = body.details; message = body.details;
title = body.message; title = body.message;
} else if (body.code) { } else if (body.message) {
title = body.code; title = DEFAULT_ERROR_MESSAGES.defaultError.title;
message = body.message; message = body.message;
} else { } else {
message = body.message || DEFAULT_ERROR_MESSAGES.defaultError.title; message = body.message || DEFAULT_ERROR_MESSAGES.defaultError.title;

2
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -3,7 +3,6 @@ import { Component } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { MessageService } from 'primeng/components/common/messageservice';
import { ConfirmationService } from '../services/confirmation.service'; import { ConfirmationService } from '../services/confirmation.service';
import { ThemeSharedModule } from '../theme-shared.module'; import { ThemeSharedModule } from '../theme-shared.module';
import { OAuthModule, OAuthService } from 'angular-oauth2-oidc'; import { OAuthModule, OAuthService } from 'angular-oauth2-oidc';
@ -24,7 +23,6 @@ describe('ConfirmationService', () => {
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: DummyComponent, component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule], imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
providers: [MessageService],
mocks: [OAuthService], mocks: [OAuthService],
}); });

1
npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts

@ -6,7 +6,6 @@ import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { NgxsModule, Store } from '@ngxs/store'; import { NgxsModule, Store } from '@ngxs/store';
import { DEFAULT_ERROR_MESSAGES, ErrorHandler } from '../handlers'; import { DEFAULT_ERROR_MESSAGES, ErrorHandler } from '../handlers';
import { ThemeSharedModule } from '../theme-shared.module'; import { ThemeSharedModule } from '../theme-shared.module';
import { MessageService } from 'primeng/components/common/messageservice';
import { RouterError, RouterDataResolved } from '@ngxs/router-plugin'; import { RouterError, RouterDataResolved } from '@ngxs/router-plugin';
import { NavigationError, ResolveEnd } from '@angular/router'; import { NavigationError, ResolveEnd } from '@angular/router';
import { OAuthModule, OAuthService } from 'angular-oauth2-oidc'; import { OAuthModule, OAuthService } from 'angular-oauth2-oidc';

5
npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts

@ -1,8 +1,6 @@
import { LocalizationPipe } from '@abp/ng.core'; import { LocalizationPipe } from '@abp/ng.core';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { MessageService } from 'primeng/components/common/messageservice';
import { ToastModule } from 'primeng/toast';
import { timer } from 'rxjs'; import { timer } from 'rxjs';
import { ButtonComponent, ConfirmationComponent, ModalComponent } from '../components'; import { ButtonComponent, ConfirmationComponent, ModalComponent } from '../components';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@ -16,9 +14,8 @@ describe('ModalComponent', () => {
let disappearFn; let disappearFn;
const createHost = createHostFactory({ const createHost = createHostFactory({
component: ModalComponent, component: ModalComponent,
imports: [ToastModule, RouterTestingModule], imports: [RouterTestingModule],
declarations: [ConfirmationComponent, LocalizationPipe, ButtonComponent], declarations: [ConfirmationComponent, LocalizationPipe, ButtonComponent],
providers: [MessageService],
mocks: [Store], mocks: [Store],
}); });

12
npm/ng-packs/packages/theme-shared/src/lib/tests/table-sort.directive.spec.ts

@ -1,17 +1,21 @@
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest';
import { TableModule, Table } from 'primeng/table';
import { TableSortDirective } from '../directives/table-sort.directive'; import { TableSortDirective } from '../directives/table-sort.directive';
import { TableComponent } from '../components/table/table.component';
import { DummyLocalizationPipe } from './table.component.spec';
import { PaginationComponent } from '../components';
describe('TableSortDirective', () => { describe('TableSortDirective', () => {
let spectator: SpectatorDirective<TableSortDirective>; let spectator: SpectatorDirective<TableSortDirective>;
let directive: TableSortDirective; let directive: TableSortDirective;
const createDirective = createDirectiveFactory({ const createDirective = createDirectiveFactory({
directive: TableSortDirective, directive: TableSortDirective,
imports: [TableModule], declarations: [TableComponent, DummyLocalizationPipe, PaginationComponent],
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective(`<p-table [value]="[1,4,2]" [abpTableSort]="{ order: 'asc' }"></p-table>`); spectator = createDirective(
`<abp-table [value]="[1,4,2]" [abpTableSort]="{ order: 'asc' }"></abp-table>`,
);
directive = spectator.directive; directive = spectator.directive;
}); });
@ -21,7 +25,7 @@ describe('TableSortDirective', () => {
test('should change table value', () => { test('should change table value', () => {
expect(directive.value).toEqual([1, 4, 2]); expect(directive.value).toEqual([1, 4, 2]);
const table = spectator.query(Table); const table = spectator.query(TableComponent);
expect(table.value).toEqual([1, 2, 4]); expect(table.value).toEqual([1, 2, 4]);
}); });
}); });

2
npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts

@ -3,7 +3,6 @@ import { Component } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { MessageService } from 'primeng/components/common/messageservice';
import { ToasterService } from '../services/toaster.service'; import { ToasterService } from '../services/toaster.service';
import { ThemeSharedModule } from '../theme-shared.module'; import { ThemeSharedModule } from '../theme-shared.module';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
@ -24,7 +23,6 @@ describe('ToasterService', () => {
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: DummyComponent, component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule], imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
providers: [MessageService],
mocks: [OAuthService], mocks: [OAuthService],
}); });

5
npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts

@ -3,8 +3,6 @@ import { DatePipe } from '@angular/common';
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core'; import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap'; import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core'; import { NgxValidateCoreModule } from '@ngx-validate/core';
import { MessageService } from 'primeng/components/common/messageservice';
import { ToastModule } from 'primeng/toast';
import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component'; import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component';
import { ButtonComponent } from './components/button/button.component'; import { ButtonComponent } from './components/button/button.component';
import { ChartComponent } from './components/chart/chart.component'; import { ChartComponent } from './components/chart/chart.component';
@ -40,7 +38,7 @@ export function appendScript(injector: Injector) {
} }
@NgModule({ @NgModule({
imports: [CoreModule, ToastModule, NgxValidateCoreModule], imports: [CoreModule, NgxValidateCoreModule],
declarations: [ declarations: [
BreadcrumbComponent, BreadcrumbComponent,
ButtonComponent, ButtonComponent,
@ -92,7 +90,6 @@ export class ThemeSharedModule {
deps: [Injector], deps: [Injector],
useFactory: appendScript, useFactory: appendScript,
}, },
{ provide: MessageService, useClass: MessageService },
{ provide: HTTP_ERROR_CONFIG, useValue: options.httpErrorConfig }, { provide: HTTP_ERROR_CONFIG, useValue: options.httpErrorConfig },
{ {
provide: 'HTTP_ERROR_CONFIG', provide: 'HTTP_ERROR_CONFIG',

15
npm/ng-packs/scripts/package-lock.json

@ -4,6 +4,15 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@types/fs-extra": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.1.tgz",
"integrity": "sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/json5": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -463,9 +472,9 @@
} }
}, },
"commander": { "commander": {
"version": "4.0.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==" "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="
}, },
"component-emitter": { "component-emitter": {
"version": "1.3.0", "version": "1.3.0",

3
npm/ng-packs/scripts/package.json

@ -13,12 +13,13 @@
"author": "", "author": "",
"dependencies": { "dependencies": {
"babel-preset-node6": "^11.0.0", "babel-preset-node6": "^11.0.0",
"commander": "^4.0.1", "commander": "^4.1.1",
"execa": "^2.0.3", "execa": "^2.0.3",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"prompt-confirm": "^2.0.4" "prompt-confirm": "^2.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^8.0.1",
"@types/node": "^13.1.2", "@types/node": "^13.1.2",
"esm": "^3.2.25", "esm": "^3.2.25",
"ts-node": "^8.5.4", "ts-node": "^8.5.4",

27
npm/ng-packs/scripts/publish.ts

@ -1,9 +1,23 @@
import execa from 'execa'; import execa from 'execa';
import fse from 'fs-extra'; import fse from 'fs-extra';
import program from 'commander';
program
.option(
'-v, --nextVersion <version>',
'next semantic version. Available versions: ["major", "minor", "patch", "premajor", "preminor", "prepatch", "prerelease", "or type a custom version"]',
)
.option('-p, --preview', 'publish with preview tag');
program.parse(process.argv);
const publish = async () => { const publish = async () => {
const versions = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease']; const versions = ['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'];
let nextSemanticVersion = (process.argv[2] || '').toLowerCase();
if (!program.nextVersion) {
console.error('Please provide a version with --nextVersion attribute');
process.exit(1);
}
try { try {
await execa('yarn', ['install-new-dependencies'], { stdout: 'inherit' }); await execa('yarn', ['install-new-dependencies'], { stdout: 'inherit' });
@ -12,7 +26,7 @@ const publish = async () => {
await execa( await execa(
'yarn', 'yarn',
['lerna', 'version', nextSemanticVersion, '--yes', '--no-commit-hooks', '--skip-git'], ['lerna', 'version', program.nextVersion, '--yes', '--no-commit-hooks', '--skip-git'],
{ stdout: 'inherit', cwd: '../' }, { stdout: 'inherit', cwd: '../' },
); );
@ -26,7 +40,14 @@ const publish = async () => {
await execa( await execa(
'yarn', 'yarn',
['lerna', 'exec', '--', '"npm publish --registry https://registry.npmjs.org"'], [
'lerna',
'exec',
'--',
`"npm publish --registry https://registry.npmjs.org${
program.preview ? ' --tag preview' : ''
}"`,
],
{ {
stdout: 'inherit', stdout: 'inherit',
cwd: '../', cwd: '../',

20
npm/ng-packs/scripts/yarn.lock

@ -2,11 +2,23 @@
# yarn lockfile v1 # yarn lockfile v1
"@types/fs-extra@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.1.tgz#a2378d6e7e8afea1564e44aafa2e207dadf77686"
integrity sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw==
dependencies:
"@types/node" "*"
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/node@*":
version "13.7.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d"
integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==
"@types/node@^13.1.2": "@types/node@^13.1.2":
version "13.1.2" version "13.1.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385"
@ -412,10 +424,10 @@ collection-visit@^1.0.0:
map-visit "^1.0.0" map-visit "^1.0.0"
object-visit "^1.0.0" object-visit "^1.0.0"
commander@^4.0.1: commander@^4.1.1:
version "4.0.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA== integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
component-emitter@^1.2.1: component-emitter@^1.2.1:
version "1.3.0" version "1.3.0"

5
npm/package.json

@ -1,4 +1,5 @@
{ {
"version": "2.1.0",
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",
"gulp:app": "node run-gulp-script.js ../templates/app/aspnet-core", "gulp:app": "node run-gulp-script.js ../templates/app/aspnet-core",
@ -7,11 +8,13 @@
"update:templates": "node abp-package-update-script.js ../templates" "update:templates": "node abp-package-update-script.js ../templates"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^8.0.1",
"glob": "^7.1.5", "glob": "^7.1.5",
"lerna": "^3.18.4", "lerna": "^3.18.4",
"npm-check-updates": "^3.1.25" "npm-check-updates": "^3.1.25"
}, },
"dependencies": { "dependencies": {
"execa": "^3.4.0" "execa": "^3.4.0",
"fs-extra": "^8.1.0"
} }
} }

30
npm/preview-publish.ps1

@ -0,0 +1,30 @@
param(
[string]$Version
)
npm install
$NextVersion = $(node get-version.js) + '-preview' + (Get-Date).tostring(“yyyyMMdd”) + '-1'
$rootFolder = (Get-Item -Path "./" -Verbose).FullName
if(-Not $Version) {
$Version = $NextVersion;
}
$commands = (
"cd ng-packs\scripts",
"npm install",
"npm run publish-packages -- --nextVersion $Version --preview",
"cd ../../",
"yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish --dist-tag preview"
)
foreach ($command in $commands) {
Write-Host $command
Invoke-Expression $command
if($LASTEXITCODE -ne '0' -And $command -notlike '*cd *'){
Write-Host ("Process failed! " + $command)
Set-Location $rootFolder
exit $LASTEXITCODE
}
}

13
npm/publish.ps1

@ -2,19 +2,20 @@ param(
[string]$Version [string]$Version
) )
if(-Not $Version) { npm install
echo 'Please pass a semantic version like this: ./publish.ps1 -Version patch'
exit
}
$NextVersion = $(node get-version.js)
$rootFolder = (Get-Item -Path "./" -Verbose).FullName $rootFolder = (Get-Item -Path "./" -Verbose).FullName
if(-Not $Version) {
$Version = $NextVersion;
}
$commands = ( $commands = (
"cd ng-packs\scripts", "cd ng-packs\scripts",
"npm install", "npm install",
"npm run publish-packages -- $Version", "npm run publish-packages -- --nextVersion $Version",
"cd ../../", "cd ../../",
"yarn",
"yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish", "yarn lerna publish $Version --no-push --yes --no-git-reset --no-commit-hooks --no-git-tag-version --force-publish",
"yarn update:templates", "yarn update:templates",
"yarn gulp:app", "yarn gulp:app",

7
npm/yarn.lock

@ -832,6 +832,13 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/fs-extra@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.1.tgz#a2378d6e7e8afea1564e44aafa2e207dadf77686"
integrity sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw==
dependencies:
"@types/node" "*"
"@types/glob@^7.1.1": "@types/glob@^7.1.1":
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"

2
samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json

@ -3,6 +3,6 @@
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io." "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io."
} }
} }

2
samples/BookStore-Modular/application/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json

@ -3,6 +3,6 @@
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io." "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io."
} }
} }

2
samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/zh-Hans.json

@ -3,7 +3,7 @@
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io.", "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io.",
"Menu:BookStore": "图书商店", "Menu:BookStore": "图书商店",
"Menu:Books": "图书", "Menu:Books": "图书",
"Name": "名称", "Name": "名称",

2
samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/zh-Hans.json

@ -3,6 +3,6 @@
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io." "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io."
} }
} }

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json

@ -3,6 +3,6 @@
"texts": { "texts": {
"Menu:Home": "首页", "Menu:Home": "首页",
"Welcome": "欢迎", "Welcome": "欢迎",
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 cn.abp.io." "LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io."
} }
} }

50
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Data/MyProjectNameDbMigrationService.cs

@ -1,8 +1,12 @@
using System.Threading.Tasks; using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement;
namespace MyCompanyName.MyProjectName.Data namespace MyCompanyName.MyProjectName.Data
{ {
@ -12,13 +16,19 @@ namespace MyCompanyName.MyProjectName.Data
private readonly IDataSeeder _dataSeeder; private readonly IDataSeeder _dataSeeder;
private readonly IMyProjectNameDbSchemaMigrator _dbSchemaMigrator; private readonly IMyProjectNameDbSchemaMigrator _dbSchemaMigrator;
private readonly ITenantRepository _tenantRepository;
private readonly ICurrentTenant _currentTenant;
public MyProjectNameDbMigrationService( public MyProjectNameDbMigrationService(
IDataSeeder dataSeeder, IDataSeeder dataSeeder,
IMyProjectNameDbSchemaMigrator dbSchemaMigrator) IMyProjectNameDbSchemaMigrator dbSchemaMigrator,
ITenantRepository tenantRepository,
ICurrentTenant currentTenant)
{ {
_dataSeeder = dataSeeder; _dataSeeder = dataSeeder;
_dbSchemaMigrator = dbSchemaMigrator; _dbSchemaMigrator = dbSchemaMigrator;
_tenantRepository = tenantRepository;
_currentTenant = currentTenant;
Logger = NullLogger<MyProjectNameDbMigrationService>.Instance; Logger = NullLogger<MyProjectNameDbMigrationService>.Instance;
} }
@ -27,13 +37,43 @@ namespace MyCompanyName.MyProjectName.Data
{ {
Logger.LogInformation("Started database migrations..."); Logger.LogInformation("Started database migrations...");
Logger.LogInformation("Migrating database schema..."); await MigrateHostDatabaseAsync();
var i = 0;
var tenants = await _tenantRepository.GetListAsync();
foreach (var tenant in tenants)
{
i++;
using (_currentTenant.Change(tenant.Id))
{
Logger.LogInformation($"Migrating {tenant.Name} database schema... ({i} of {tenants.Count})");
await MigrateTenantDatabasesAsync(tenant);
Logger.LogInformation($"Successfully completed {tenant.Name} database migrations.");
}
}
Logger.LogInformation("Successfully completed database migrations.");
}
private async Task MigrateHostDatabaseAsync()
{
Logger.LogInformation("Migrating host database schema...");
await _dbSchemaMigrator.MigrateAsync(); await _dbSchemaMigrator.MigrateAsync();
Logger.LogInformation("Executing database seed..."); Logger.LogInformation("Executing host database seed...");
await _dataSeeder.SeedAsync(); await _dataSeeder.SeedAsync();
Logger.LogInformation("Successfully completed database migrations."); Logger.LogInformation("Successfully completed host database migrations.");
}
private async Task MigrateTenantDatabasesAsync(Tenant tenant)
{
Logger.LogInformation($"Migrating schema for {tenant.Name} database...");
await _dbSchemaMigrator.MigrateAsync();
Logger.LogInformation($"Executing {tenant.Name} tenant database seed...");
await _dataSeeder.SeedAsync(tenant.Id);
} }
} }
} }

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/EntityFrameworkCoreMyProjectNameDbSchemaMigrator.cs

@ -1,24 +1,36 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace MyCompanyName.MyProjectName.EntityFrameworkCore namespace MyCompanyName.MyProjectName.EntityFrameworkCore
{ {
[Dependency(ReplaceServices = true)] [Dependency(ReplaceServices = true)]
public class EntityFrameworkCoreMyProjectNameDbSchemaMigrator public class EntityFrameworkCoreMyProjectNameDbSchemaMigrator
: IMyProjectNameDbSchemaMigrator, ITransientDependency : IMyProjectNameDbSchemaMigrator, ITransientDependency
{ {
private readonly MyProjectNameMigrationsDbContext _dbContext; private readonly IServiceProvider _serviceProvider;
public EntityFrameworkCoreMyProjectNameDbSchemaMigrator(MyProjectNameMigrationsDbContext dbContext) public EntityFrameworkCoreMyProjectNameDbSchemaMigrator(
IServiceProvider serviceProvider)
{ {
_dbContext = dbContext; _serviceProvider = serviceProvider;
} }
public async Task MigrateAsync() public async Task MigrateAsync()
{ {
await _dbContext.Database.MigrateAsync(); /* We intentionally resolving the MyProjectNameMigrationsDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.
*/
await _serviceProvider
.GetRequiredService<MyProjectNameMigrationsDbContext>()
.Database
.MigrateAsync();
} }
} }
} }
Loading…
Cancel
Save