mirror of https://github.com/abpframework/abp.git
181 changed files with 4607 additions and 1255 deletions
@ -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. |
|||
@ -0,0 +1,3 @@ |
|||
# Clock |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
# Features |
|||
|
|||
TODO |
|||
@ -1,6 +1,6 @@ |
|||
# Audit Logging Module |
|||
|
|||
The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. |
|||
The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. |
|||
|
|||
> Audit Logging module is already installed and configured for [the startup templates](../Startup-Templates/Index.md). So, most of the times you don't need to manually add this module to your application. |
|||
|
|||
|
|||
@ -0,0 +1,3 @@ |
|||
# Feature Management Module |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
## Ambient Context Pattern |
|||
|
|||
TODO |
|||
@ -0,0 +1,375 @@ |
|||
# 审计日志 |
|||
|
|||
[维基百科](https://en.wikipedia.org/wiki/Audit_trail): "*审计跟踪(也称为**审计日志**)是一种安全相关的按时间顺序记录,记录集或记录目的和来源. 这种记录提供了在任何特定时间的操作,过程或事件产生影响活动顺序的文件证据* ". |
|||
|
|||
ABP框架提供一个可扩展的**审计日志系统**,自动化的根据**约定**记录审计日志,并提供**配置**控制审计日志的级别. |
|||
|
|||
一个**审计日志对象**(参见下面的审计日志对象部分)通常是针对每个web请求创建和保存的.包括; |
|||
|
|||
* **请求和响应的细节** (如URL,HTTP方法,浏览器信息,HTTP状态代码...等). |
|||
* **执行的动作** (控制器操作和应用服务方法调用及其参数). |
|||
* **实体的变化** (在Web请求中). |
|||
* **异常信息** (如果在执行请求发生操作). |
|||
* **请求时长** (测量应用程序的性能). |
|||
|
|||
> [启动模板](Startup-Templates/Index.md)已经将审计日志系统配置为适用于大多数应用程序. 本文档介绍了对审计日志系统更精细的控制. |
|||
|
|||
## 数据库提供程序支持 |
|||
|
|||
* [Entity Framework Core](Entity-Framework-Core.md)提供程序完全支持. |
|||
* [MongoDB](MongoDB.md)提供程序不支持实体更改审计记录. 其他功能按预期工作. |
|||
|
|||
## UseAuditing() |
|||
|
|||
`UseAuditing()` 中间件应该被添加到ASP.NET Core请求管道,用于创建和保存审计日志. 如果你使用[启动模板](Startup-Templates/Index.md)创建的应用程序,它已经默认添加. |
|||
|
|||
## AbpAuditingOptions |
|||
|
|||
`AbpAuditingOptions` 是配置审计日志系统的主要[options对象](Options.md). 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中进行配置: |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.IsEnabled = false; //Disables the auditing system |
|||
}); |
|||
```` |
|||
|
|||
这里是你可以配置的选项列表: |
|||
|
|||
* `IsEnabled` (默认值: `true`): 启用或禁用审计系统的总开关. 如果值为 `false`,则不使用其他选项. |
|||
* `HideErrors` (默认值: `true`): 在保存审计日志对象时如果发生任何错误,审计日志系统会将错误隐藏并写入常规[日志](Logging.md). 如果保存审计日志对系统非常重要那么将其设置为 `false` 以便在隐藏错误时抛出异常. |
|||
* `IsEnabledForAnonymousUsers` (默认值: `true`): 如果只想为经过身份验证的用户记录审计日志,请设置为 `false`.如果为匿名用户保存审计日志,你将看到这些用户的 `UserId` 值为 `null`. |
|||
* `AlwaysLogOnException`(默认值: `true`): 如果设置为 `true`,将始终在异常/错误情况下保存审计日志,不检查其他选项(`IsEnabled` 除外,它完全禁用了审计日志). |
|||
* `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统. |
|||
* `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志. |
|||
* `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表. |
|||
* `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分. |
|||
* `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分. |
|||
|
|||
### 实体历史选择器 |
|||
|
|||
保存您的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**. |
|||
|
|||
要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法. |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.EntityHistorySelectors.AddAllEntities(); |
|||
}); |
|||
```` |
|||
|
|||
`options.EntityHistorySelectors` 实际上是一个类型谓词的列表,你可以写一个lambda表达式定义过滤器. |
|||
|
|||
下面的示例中与使用 `AddAllEntities()` 扩展方法效果相同: |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.EntityHistorySelectors.Add( |
|||
new NamedTypeSelector( |
|||
"MySelectorName", |
|||
type => |
|||
{ |
|||
if (typeof(IEntity).IsAssignableFrom(type)) |
|||
{ |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
) |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
条件 `typeof(IEntity).IsAssignableFrom(type)` 对于任何实现 `IEntity` 接口的类(从技术上来这些都是你应用程序中的实体) 结果都为 `true` . 你可以根据自己的逻辑编写条件并返回 `true` 或 `false`. |
|||
|
|||
`options.EntityHistorySelectors` 是一种灵活动态的选择实体进行审计日志记录的方法. 另一种方法是为每个实体使用 `Audited` 和 `DisableAuditing` attribute. |
|||
|
|||
## 启用/禁用审计日志服务 |
|||
|
|||
### 启用/禁用 Controllers & Actions |
|||
|
|||
默认所有的控制器动作都会被记录下来(有关GET请求,请参阅上面的 `IsEnabledForGetRequests` ). |
|||
|
|||
你可以使用 `[DisableAuditing]` 来禁用特定的控制器: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class HomeController : AbpController |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
使用 `[DisableAuditing]` 在action级别控制: |
|||
|
|||
````csharp |
|||
public class HomeController : AbpController |
|||
{ |
|||
[DisableAuditing] |
|||
public async Task<ActionResult> Home() |
|||
{ |
|||
//... |
|||
} |
|||
|
|||
public async Task<ActionResult> OtherActionLogged() |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### 启用/禁用 应用服务&方法 |
|||
|
|||
[应用服务](Application-Services.md)也默认包含在审计日志中. 你可在服务或方法级别使用 `[DisableAuditing]`. |
|||
|
|||
#### 启用/禁用 其他服务 |
|||
|
|||
可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用. |
|||
|
|||
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,您的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录. |
|||
|
|||
### 启用/禁用 实体 & 属性 |
|||
|
|||
以下情况下实体在实体更改审计日志记录中忽略实体; |
|||
|
|||
* 如果将实体类型添加到 `AbpAuditingOptions.IgnoredTypes`(如前所述),它在审计日志系统中被完全忽略. |
|||
* 如果对象不是[实体](Entities.md)(没有直接或固有的实现 `IEntity` - 所有实体默认实现这个接口). |
|||
* 如果实体访问级别不是public的. |
|||
|
|||
你可以使用 `Audited` 来启用实体更改审计日志: |
|||
|
|||
````csharp |
|||
[Audited] |
|||
public class MyEntity : Entity<Guid> |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
或者禁用实体: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class MyEntity : Entity<Guid> |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
只有前面提到的 `AbpAuditingOptions.EntityHistorySelector` 选择实体时才有必要禁用审计日志记录. |
|||
|
|||
你可以仅禁用实体的某些属性的审计,以审计日志记录进行精细控制: |
|||
|
|||
````csharp |
|||
[Audited] |
|||
public class MyUser : Entity<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
[DisableAuditing] //Ignore the Passoword on audit logging |
|||
public string Password { get; set; } |
|||
} |
|||
```` |
|||
|
|||
审计日志系统保存 `MyUser` 实体的更改,出于安全的目的忽略 `Password` 属性. |
|||
|
|||
在某些情况下你可能要保存一些属性,但忽略所有其他属性. 为忽略的属性编写 `[DisableAuditing]` 将很乏味. 这种情况下将 `[Audited]` 用于所需的属性,使用 `[DisableAuditing]` 属性标记该实体: |
|||
|
|||
````csharp |
|||
[DisableAuditing] |
|||
public class MyUser : Entity<Guid> |
|||
{ |
|||
[Audited] //Only log the Name change |
|||
public string Name { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
public string Password { get; set; } |
|||
} |
|||
```` |
|||
|
|||
## IAuditingStore |
|||
|
|||
`IAuditingStore` 是一个接口,用于保存ABP框架的审计日志对象(下面说明). 如果需要将审计日志对象保存到自定义数据存储中,可以在自己的应用程序中实现 `IAuditingStore` 并在[依赖注入系统](Dependency-Injection.md)替换. |
|||
|
|||
如果没有注册审计存储,则使用 `SimpleLogAuditingStore`. 它只是将审计对象写入标准[日志系统](Logging.md). |
|||
|
|||
[审计日志模块](Modules/Audit-Logging.md)已在[启动模板](Startup-Templates/Index.md)中配置,它将审计日志对象保存到数据库中(支持多个数据库提供程序). 所以大多数时候你并不需要关心 `IAuditingStore` 是如何实现和使用的. |
|||
|
|||
## 审计日志对象 |
|||
|
|||
默认为每个**web请求**创建一个**审计日志对象**,审计日志对象可以由以下关系图表示: |
|||
|
|||
 |
|||
|
|||
* **AuditLogInfo**: 具有以下属性: |
|||
* `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序. |
|||
* `UserId`:当前用户的Id,用户未登录为 `null`. |
|||
* `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找). |
|||
* `TenantId`: 当前租户的Id,对于多租户应用. |
|||
* `TenantName`: 当前租户的名称,对于多租户应用. |
|||
* `ExecutionTime`: 审计日志对象创建的时间. |
|||
* `ExecutionDuration`: 请求的总执行时间,以毫秒为单位. 可以用来观察应用程序的性能. |
|||
* `ClientId`: 当前客户端的Id,如果客户端已经通过认证.客户端通常是使用HTTP API的第三方应用程序. |
|||
* `ClientName`: 当前客户端的名称,如果有的话. |
|||
* `ClientIpAddress`: 客户端/用户设备的IP地址. |
|||
* `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志. |
|||
* `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话. |
|||
* `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等). |
|||
* `HttpStatusCode`: HTTP响应状态码. |
|||
* `Url`: 请求的URL. |
|||
* **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性: |
|||
* `ServiceName`:执行的控制器/服务的名称. |
|||
* `MethodName`:控制器/服务执行的方法的名称. |
|||
* `Parameters`:传递给方法的参数的JSON格文本. |
|||
* `ExecutionTime`: 执行的时间. |
|||
* `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能. |
|||
* **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性: |
|||
* `ChangeTime`: 当实体被改变的时间. |
|||
* `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2). |
|||
* `EntityId`: 更改实体的Id. |
|||
* `EntityTenantId`:实体所属的租户Id. |
|||
* `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*. |
|||
* **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改: |
|||
* `NewValue`: 属性的新值. 如果实体已被删除为 `null`. |
|||
* `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`. |
|||
* `PropertyName`: 实体类的属性名称. |
|||
* `PropertyTypeFullName`:属性类型的完整命名空间名称. |
|||
* **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息. |
|||
* **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释. |
|||
|
|||
除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo` 和 `EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性. |
|||
|
|||
## 审计日志贡献者 |
|||
|
|||
你可以创建类继承 `AuditLogContributor`类 来扩展审计系统,该类定义了 `PreContribute` 和 `PostContribute` 方法. |
|||
|
|||
唯一预构建的贡献者是 `AspNetCoreAuditLogContributor` 类,它设置HTTP请求的相关属性. |
|||
|
|||
贡献者可以设置 `AuditLogInfo` 类的属性和集合来添加更多信息. |
|||
|
|||
例: |
|||
|
|||
````csharp |
|||
public class MyAuditLogContributor : AuditLogContributor |
|||
{ |
|||
public override void PreContribute(AuditLogContributionContext context) |
|||
{ |
|||
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>(); |
|||
context.AuditInfo.SetProperty( |
|||
"MyCustomClaimValue", |
|||
currentUser.FindClaimValue("MyCustomClaim") |
|||
); |
|||
} |
|||
|
|||
public override void PostContribute(AuditLogContributionContext context) |
|||
{ |
|||
context.AuditInfo.Comments.Add("Some comment..."); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `context.ServiceProvider` 可以从[依赖注入系统](Dependency-Injection.md)中解析服务. |
|||
* `context.AuditInfo` 可以用来访问当前审计日志的对象并进行操作. |
|||
|
|||
创建贡献者后,需要将其添加到 `AbpAuditingOptions.Contributors` 列表中: |
|||
|
|||
````csharp |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.Contributors.Add(new MyAuditLogContributor()); |
|||
}); |
|||
```` |
|||
|
|||
## IAuditLogScope & IAuditingManager |
|||
|
|||
本节介绍用于高级用例的 `IAuditLogScope` 和 `IAuditingManager` 服务. |
|||
|
|||
**审计日志范围**是**构建**和**保存**审计日志对象的[环境范围](Ambient-Context-Pattern.md)(前面解释过). 默认审计日志中间件会为Web请求创建审计日志范围(请参阅上面的 `UseAuditing()` 部分). |
|||
|
|||
### 获取当前审计日志范围 |
|||
|
|||
上面提到,审计日志贡献者是操作审计日志对象的全局方法. 你可从服务中获得值. |
|||
|
|||
如果需要在应用程序的任意位置上操作审计日志对象,可以访问当前审计日志范围并获取当前审计日志对象(与范围的管理方式无关). |
|||
例: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public MyService(IAuditingManager auditingManager) |
|||
{ |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public async Task DoItAsync() |
|||
{ |
|||
var currentAuditLogScope = _auditingManager.Current; |
|||
if (currentAuditLogScope != null) |
|||
{ |
|||
currentAuditLogScope.Log.Comments.Add( |
|||
"Executed the MyService.DoItAsync method :)" |
|||
); |
|||
|
|||
currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
总是检查 `_auditingManager.Current` 是否为空,因为它是在外部范围中控制的,在调用方法之前你不知道是否创建了审计日志范围. |
|||
|
|||
### 手动创建审计日志范围 |
|||
|
|||
你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围. |
|||
例: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public MyService(IAuditingManager auditingManager) |
|||
{ |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public async Task DoItAsync() |
|||
{ |
|||
using (var auditingScope = _auditingManager.BeginScope()) |
|||
{ |
|||
try |
|||
{ |
|||
//Call other services... |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
//Add exceptions |
|||
_auditingManager.Current.Log.Exceptions.Add(ex); |
|||
} |
|||
finally |
|||
{ |
|||
//Always save the log |
|||
await auditingScope.SaveAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
您可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象. |
|||
|
|||
## 审计日志模块 |
|||
|
|||
审计日志模块基本上实现了 `IAuditingStore`, 将审计日志对象保存到数据库中并支持多个数据库提供程序. 默认此模块已添加到启动模板中. |
|||
|
|||
参见[审计日志模块文档](Modules/Audit-Logging.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... |
|||
@ -0,0 +1,7 @@ |
|||
# 审计日志模块 |
|||
|
|||
审计日志模块实现了 `IAuditingStore` 将审计日志对象保存到数据库中. |
|||
|
|||
> [启动模板](../Startup-Templates/Index.md)已经安装并配置了审计日志模块,所以你不需要手动安装到你的应用程序. |
|||
|
|||
参阅[审计日志系统](../Audit-Logging.md)文档了解更多关于审计日志的内容. |
|||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Domain.Repositories |
|||
{ |
|||
public static class UnitOfWorkItemNames |
|||
{ |
|||
public const string HardDeletedEntities = "AbpHardDeletedEntities"; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Uow |
|||
{ |
|||
public interface IUnitOfWorkManagerAccessor |
|||
{ |
|||
IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
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!"); |
|||
} |
|||
[Route("audit-fail-object")] |
|||
public object AuditFailForGetRequestsReturningObject() |
|||
{ |
|||
throw new UserFriendlyException("Exception occurred!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
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>()); |
|||
} |
|||
|
|||
[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>()); |
|||
} |
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() |
|||
{ |
|||
_options.IsEnabled = true; |
|||
_options.AlwaysLogOnException = true; |
|||
|
|||
await GetResponseAsync("api/audit-test/audit-fail-object", System.Net.HttpStatusCode.Forbidden); |
|||
|
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.DataFiltering |
|||
{ |
|||
public class HardDelete_Tests : HardDelete_Tests<AbpEntityFrameworkCoreTestModule> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Volo.Abp.TestApp.Testing; |
|||
|
|||
namespace Volo.Abp.MongoDB.DataFiltering |
|||
{ |
|||
public class HardDelete_Tests : HardDelete_Tests<AbpMongoDbTestModule> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
using Shouldly; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TestApp.Testing |
|||
{ |
|||
public abstract class HardDelete_Tests<TStartupModule> : TestAppTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
protected readonly IRepository<Person, Guid> PersonRepository; |
|||
protected readonly IDataFilter DataFilter; |
|||
protected readonly IUnitOfWorkManager UnitOfWorkManager; |
|||
|
|||
protected HardDelete_Tests() |
|||
{ |
|||
PersonRepository = GetRequiredService<IRepository<Person, Guid>>(); |
|||
DataFilter = GetRequiredService<IDataFilter>(); |
|||
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_HardDelete_Entities() |
|||
{ |
|||
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); |
|||
await PersonRepository.HardDeleteAsync(douglas); |
|||
|
|||
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); |
|||
douglas.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_HardDelete_Soft_Deleted_Entities() |
|||
{ |
|||
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); |
|||
await PersonRepository.DeleteAsync(douglas); |
|||
|
|||
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); |
|||
douglas.ShouldBeNull(); |
|||
|
|||
using (DataFilter.Disable<ISoftDelete>()) |
|||
{ |
|||
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); |
|||
douglas.ShouldNotBeNull(); |
|||
douglas.IsDeleted.ShouldBeTrue(); |
|||
douglas.DeletionTime.ShouldNotBeNull(); |
|||
} |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
using (DataFilter.Disable<ISoftDelete>()) |
|||
{ |
|||
douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); |
|||
} |
|||
|
|||
await PersonRepository.HardDeleteAsync(douglas); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId); |
|||
douglas.ShouldBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,465 +0,0 @@ |
|||
// <auto-generated />
|
|||
using System; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Metadata; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
|||
using VoloDocs.EntityFrameworkCore; |
|||
|
|||
namespace VoloDocs.EntityFrameworkCore.Migrations |
|||
{ |
|||
[DbContext(typeof(VoloDocsDbContext))] |
|||
[Migration("20181225134002_Initial20181225")] |
|||
partial class Initial20181225 |
|||
{ |
|||
protected override void BuildTargetModel(ModelBuilder modelBuilder) |
|||
{ |
|||
#pragma warning disable 612, 618
|
|||
modelBuilder |
|||
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846") |
|||
.HasAnnotation("Relational:MaxIdentifierLength", 128) |
|||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.IsRequired() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Description") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties"); |
|||
|
|||
b.Property<bool>("IsStatic"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Regex") |
|||
.HasMaxLength(512); |
|||
|
|||
b.Property<string>("RegexDescription") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<bool>("Required"); |
|||
|
|||
b.Property<int>("ValueType"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.ToTable("AbpClaimTypes"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.IsRequired() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties"); |
|||
|
|||
b.Property<bool>("IsDefault") |
|||
.HasColumnName("IsDefault"); |
|||
|
|||
b.Property<bool>("IsPublic") |
|||
.HasColumnName("IsPublic"); |
|||
|
|||
b.Property<bool>("IsStatic") |
|||
.HasColumnName("IsStatic"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("NormalizedName") |
|||
.IsRequired() |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("NormalizedName"); |
|||
|
|||
b.ToTable("AbpRoles"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("ClaimType") |
|||
.IsRequired() |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ClaimValue") |
|||
.HasMaxLength(1024); |
|||
|
|||
b.Property<Guid>("RoleId"); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("RoleId"); |
|||
|
|||
b.ToTable("AbpRoleClaims"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<int>("AccessFailedCount") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("AccessFailedCount") |
|||
.HasDefaultValue(0); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.HasColumnName("ConcurrencyStamp"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnName("CreatorId"); |
|||
|
|||
b.Property<Guid?>("DeleterId") |
|||
.HasColumnName("DeleterId"); |
|||
|
|||
b.Property<DateTime?>("DeletionTime") |
|||
.HasColumnName("DeletionTime"); |
|||
|
|||
b.Property<string>("Email") |
|||
.HasColumnName("Email") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<bool>("EmailConfirmed") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("EmailConfirmed") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties"); |
|||
|
|||
b.Property<bool>("IsDeleted") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("IsDeleted") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<DateTime?>("LastModificationTime") |
|||
.HasColumnName("LastModificationTime"); |
|||
|
|||
b.Property<Guid?>("LastModifierId") |
|||
.HasColumnName("LastModifierId"); |
|||
|
|||
b.Property<bool>("LockoutEnabled") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("LockoutEnabled") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<DateTimeOffset?>("LockoutEnd"); |
|||
|
|||
b.Property<string>("Name") |
|||
.HasColumnName("Name") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("NormalizedEmail") |
|||
.HasColumnName("NormalizedEmail") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("NormalizedUserName") |
|||
.IsRequired() |
|||
.HasColumnName("NormalizedUserName") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("PasswordHash") |
|||
.HasColumnName("PasswordHash") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("PhoneNumber") |
|||
.HasColumnName("PhoneNumber") |
|||
.HasMaxLength(16); |
|||
|
|||
b.Property<bool>("PhoneNumberConfirmed") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("PhoneNumberConfirmed") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("SecurityStamp") |
|||
.IsRequired() |
|||
.HasColumnName("SecurityStamp") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Surname") |
|||
.HasColumnName("Surname") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId"); |
|||
|
|||
b.Property<bool>("TwoFactorEnabled") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("TwoFactorEnabled") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("UserName") |
|||
.IsRequired() |
|||
.HasColumnName("UserName") |
|||
.HasMaxLength(256); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Email"); |
|||
|
|||
b.HasIndex("NormalizedEmail"); |
|||
|
|||
b.HasIndex("NormalizedUserName"); |
|||
|
|||
b.HasIndex("UserName"); |
|||
|
|||
b.ToTable("AbpUsers"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("ClaimType") |
|||
.IsRequired() |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ClaimValue") |
|||
.HasMaxLength(1024); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.Property<Guid>("UserId"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("UserId"); |
|||
|
|||
b.ToTable("AbpUserClaims"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => |
|||
{ |
|||
b.Property<Guid>("UserId"); |
|||
|
|||
b.Property<string>("LoginProvider") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderDisplayName") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.IsRequired() |
|||
.HasMaxLength(196); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.HasKey("UserId", "LoginProvider"); |
|||
|
|||
b.HasIndex("LoginProvider", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpUserLogins"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => |
|||
{ |
|||
b.Property<Guid>("UserId"); |
|||
|
|||
b.Property<Guid>("RoleId"); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.HasKey("UserId", "RoleId"); |
|||
|
|||
b.HasIndex("RoleId", "UserId"); |
|||
|
|||
b.ToTable("AbpUserRoles"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => |
|||
{ |
|||
b.Property<Guid>("UserId"); |
|||
|
|||
b.Property<string>("LoginProvider") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("Name") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.Property<string>("Value"); |
|||
|
|||
b.HasKey("UserId", "LoginProvider", "Name"); |
|||
|
|||
b.ToTable("AbpUserTokens"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.IsRequired() |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderName") |
|||
.IsRequired() |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Name", "ProviderName", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpPermissionGrants"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderName") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("Value") |
|||
.IsRequired() |
|||
.HasMaxLength(2048); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Name", "ProviderName", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpSettings"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Docs.Projects.Project", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd(); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.HasColumnName("ConcurrencyStamp"); |
|||
|
|||
b.Property<string>("DefaultDocumentName") |
|||
.IsRequired() |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("DocumentStoreType"); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties"); |
|||
|
|||
b.Property<string>("Format"); |
|||
|
|||
b.Property<string>("LatestVersionBranchName") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("MainWebsiteUrl"); |
|||
|
|||
b.Property<string>("MinimumVersion"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("NavigationDocumentName") |
|||
.IsRequired() |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ShortName") |
|||
.IsRequired() |
|||
.HasMaxLength(32); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.ToTable("DocsProjects"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityRole") |
|||
.WithMany("Claims") |
|||
.HasForeignKey("RoleId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser") |
|||
.WithMany("Claims") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser") |
|||
.WithMany("Logins") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityRole") |
|||
.WithMany() |
|||
.HasForeignKey("RoleId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
|
|||
b.HasOne("Volo.Abp.Identity.IdentityUser") |
|||
.WithMany("Roles") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser") |
|||
.WithMany("Tokens") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade); |
|||
}); |
|||
#pragma warning restore 612, 618
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,670 @@ |
|||
// <auto-generated />
|
|||
using System; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Metadata; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
|||
using VoloDocs.EntityFrameworkCore; |
|||
|
|||
namespace VoloDocs.EntityFrameworkCore.Migrations |
|||
{ |
|||
[DbContext(typeof(VoloDocsDbContext))] |
|||
[Migration("20200218014727_init")] |
|||
partial class init |
|||
{ |
|||
protected override void BuildTargetModel(ModelBuilder modelBuilder) |
|||
{ |
|||
#pragma warning disable 612, 618
|
|||
modelBuilder |
|||
.HasAnnotation("ProductVersion", "3.1.1") |
|||
.HasAnnotation("Relational:MaxIdentifierLength", 128) |
|||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.IsRequired() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Description") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<bool>("IsStatic") |
|||
.HasColumnType("bit"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Regex") |
|||
.HasColumnType("nvarchar(512)") |
|||
.HasMaxLength(512); |
|||
|
|||
b.Property<string>("RegexDescription") |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<bool>("Required") |
|||
.HasColumnType("bit"); |
|||
|
|||
b.Property<int>("ValueType") |
|||
.HasColumnType("int"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.ToTable("AbpClaimTypes"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.IsRequired() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<bool>("IsDefault") |
|||
.HasColumnName("IsDefault") |
|||
.HasColumnType("bit"); |
|||
|
|||
b.Property<bool>("IsPublic") |
|||
.HasColumnName("IsPublic") |
|||
.HasColumnType("bit"); |
|||
|
|||
b.Property<bool>("IsStatic") |
|||
.HasColumnName("IsStatic") |
|||
.HasColumnType("bit"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("NormalizedName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("NormalizedName"); |
|||
|
|||
b.ToTable("AbpRoles"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ClaimType") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ClaimValue") |
|||
.HasColumnType("nvarchar(1024)") |
|||
.HasMaxLength(1024); |
|||
|
|||
b.Property<Guid>("RoleId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("RoleId"); |
|||
|
|||
b.ToTable("AbpRoleClaims"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<int>("AccessFailedCount") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("AccessFailedCount") |
|||
.HasColumnType("int") |
|||
.HasDefaultValue(0); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnName("CreatorId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<Guid?>("DeleterId") |
|||
.HasColumnName("DeleterId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<DateTime?>("DeletionTime") |
|||
.HasColumnName("DeletionTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<string>("Email") |
|||
.IsRequired() |
|||
.HasColumnName("Email") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<bool>("EmailConfirmed") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("EmailConfirmed") |
|||
.HasColumnType("bit") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<bool>("IsDeleted") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("IsDeleted") |
|||
.HasColumnType("bit") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<DateTime?>("LastModificationTime") |
|||
.HasColumnName("LastModificationTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<Guid?>("LastModifierId") |
|||
.HasColumnName("LastModifierId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<bool>("LockoutEnabled") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("LockoutEnabled") |
|||
.HasColumnType("bit") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<DateTimeOffset?>("LockoutEnd") |
|||
.HasColumnType("datetimeoffset"); |
|||
|
|||
b.Property<string>("Name") |
|||
.HasColumnName("Name") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("NormalizedEmail") |
|||
.IsRequired() |
|||
.HasColumnName("NormalizedEmail") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("NormalizedUserName") |
|||
.IsRequired() |
|||
.HasColumnName("NormalizedUserName") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("PasswordHash") |
|||
.HasColumnName("PasswordHash") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("PhoneNumber") |
|||
.HasColumnName("PhoneNumber") |
|||
.HasColumnType("nvarchar(16)") |
|||
.HasMaxLength(16); |
|||
|
|||
b.Property<bool>("PhoneNumberConfirmed") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("PhoneNumberConfirmed") |
|||
.HasColumnType("bit") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("SecurityStamp") |
|||
.IsRequired() |
|||
.HasColumnName("SecurityStamp") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("Surname") |
|||
.HasColumnName("Surname") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<bool>("TwoFactorEnabled") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnName("TwoFactorEnabled") |
|||
.HasColumnType("bit") |
|||
.HasDefaultValue(false); |
|||
|
|||
b.Property<string>("UserName") |
|||
.IsRequired() |
|||
.HasColumnName("UserName") |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Email"); |
|||
|
|||
b.HasIndex("NormalizedEmail"); |
|||
|
|||
b.HasIndex("NormalizedUserName"); |
|||
|
|||
b.HasIndex("UserName"); |
|||
|
|||
b.ToTable("AbpUsers"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ClaimType") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(256)") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<string>("ClaimValue") |
|||
.HasColumnType("nvarchar(1024)") |
|||
.HasMaxLength(1024); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("UserId"); |
|||
|
|||
b.ToTable("AbpUserClaims"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => |
|||
{ |
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("LoginProvider") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderDisplayName") |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(196)") |
|||
.HasMaxLength(196); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("UserId", "LoginProvider"); |
|||
|
|||
b.HasIndex("LoginProvider", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpUserLogins"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => |
|||
{ |
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<Guid>("RoleId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("UserId", "RoleId"); |
|||
|
|||
b.HasIndex("RoleId", "UserId"); |
|||
|
|||
b.ToTable("AbpUserRoles"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => |
|||
{ |
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("LoginProvider") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("Name") |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("Value") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.HasKey("UserId", "LoginProvider", "Name"); |
|||
|
|||
b.ToTable("AbpUserTokens"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Name", "ProviderName", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpPermissionGrants"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ProviderKey") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("ProviderName") |
|||
.HasColumnType("nvarchar(64)") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("Value") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(2048)") |
|||
.HasMaxLength(2048); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("Name", "ProviderName", "ProviderKey"); |
|||
|
|||
b.ToTable("AbpSettings"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Docs.Documents.Document", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("Content") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<string>("EditLink") |
|||
.HasColumnType("nvarchar(2048)") |
|||
.HasMaxLength(2048); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("FileName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("Format") |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("LanguageCode") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<DateTime>("LastCachedTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<DateTime>("LastUpdatedTime") |
|||
.HasColumnType("datetime2"); |
|||
|
|||
b.Property<string>("LocalDirectory") |
|||
.HasColumnType("nvarchar(512)") |
|||
.HasMaxLength(512); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(255)") |
|||
.HasMaxLength(255); |
|||
|
|||
b.Property<Guid>("ProjectId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("RawRootUrl") |
|||
.HasColumnType("nvarchar(2048)") |
|||
.HasMaxLength(2048); |
|||
|
|||
b.Property<string>("RootUrl") |
|||
.HasColumnType("nvarchar(2048)") |
|||
.HasMaxLength(2048); |
|||
|
|||
b.Property<string>("Version") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.ToTable("DocsDocuments"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Docs.Documents.DocumentContributor", b => |
|||
{ |
|||
b.Property<Guid>("DocumentId") |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("Username") |
|||
.HasColumnType("nvarchar(450)"); |
|||
|
|||
b.Property<string>("AvatarUrl") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("UserProfileUrl") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.HasKey("DocumentId", "Username"); |
|||
|
|||
b.ToTable("DocsDocumentContributors"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Docs.Projects.Project", b => |
|||
{ |
|||
b.Property<Guid>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("uniqueidentifier"); |
|||
|
|||
b.Property<string>("ConcurrencyStamp") |
|||
.IsConcurrencyToken() |
|||
.HasColumnName("ConcurrencyStamp") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("DefaultDocumentName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("DocumentStoreType") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("ExtraProperties") |
|||
.HasColumnName("ExtraProperties") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("Format") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("LatestVersionBranchName") |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("MainWebsiteUrl") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("MinimumVersion") |
|||
.HasColumnType("nvarchar(max)"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("NavigationDocumentName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ParametersDocumentName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(128)") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<string>("ShortName") |
|||
.IsRequired() |
|||
.HasColumnType("nvarchar(32)") |
|||
.HasMaxLength(32); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.ToTable("DocsProjects"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityRole", null) |
|||
.WithMany("Claims") |
|||
.HasForeignKey("RoleId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser", null) |
|||
.WithMany("Claims") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser", null) |
|||
.WithMany("Logins") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityRole", null) |
|||
.WithMany() |
|||
.HasForeignKey("RoleId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
|
|||
b.HasOne("Volo.Abp.Identity.IdentityUser", null) |
|||
.WithMany("Roles") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => |
|||
{ |
|||
b.HasOne("Volo.Abp.Identity.IdentityUser", null) |
|||
.WithMany("Tokens") |
|||
.HasForeignKey("UserId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
|
|||
modelBuilder.Entity("Volo.Docs.Documents.DocumentContributor", b => |
|||
{ |
|||
b.HasOne("Volo.Docs.Documents.Document", null) |
|||
.WithMany("Contributors") |
|||
.HasForeignKey("DocumentId") |
|||
.OnDelete(DeleteBehavior.Cascade) |
|||
.IsRequired(); |
|||
}); |
|||
#pragma warning restore 612, 618
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.Docs.Admin.Documents |
|||
{ |
|||
public interface IDocumentAdminAppService : IApplicationService |
|||
{ |
|||
Task PullAllAsync(PullAllDocumentInput input); |
|||
|
|||
Task PullAsync(PullDocumentInput input); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Docs.Documents; |
|||
|
|||
namespace Volo.Docs.Admin.Documents |
|||
{ |
|||
public class PullAllDocumentInput |
|||
{ |
|||
public Guid ProjectId { get; set; } |
|||
|
|||
[StringLength(DocumentConsts.MaxLanguageCodeNameLength)] |
|||
public string LanguageCode { get; set; } |
|||
|
|||
[StringLength(DocumentConsts.MaxVersionNameLength)] |
|||
public string Version { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Docs.Documents; |
|||
|
|||
namespace Volo.Docs.Admin.Documents |
|||
{ |
|||
public class PullDocumentInput |
|||
{ |
|||
public Guid ProjectId { get; set; } |
|||
|
|||
[StringLength(DocumentConsts.MaxNameLength)] |
|||
public string Name { get; set; } |
|||
|
|||
[StringLength(DocumentConsts.MaxLanguageCodeNameLength)] |
|||
public string LanguageCode { get; set; } |
|||
|
|||
[StringLength(DocumentConsts.MaxVersionNameLength)] |
|||
public string Version { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Newtonsoft.Json; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Docs.Documents; |
|||
using Volo.Docs.Projects; |
|||
|
|||
namespace Volo.Docs.Admin.Documents |
|||
{ |
|||
[Authorize(DocsAdminPermissions.Documents.Default)] |
|||
public class DocumentAdminAppService : ApplicationService, IDocumentAdminAppService |
|||
{ |
|||
private readonly IProjectRepository _projectRepository; |
|||
private readonly IDocumentRepository _documentRepository; |
|||
private readonly IDocumentSourceFactory _documentStoreFactory; |
|||
private readonly IDistributedCache<DocumentUpdateInfo> _documentUpdateCache; |
|||
|
|||
public DocumentAdminAppService(IProjectRepository projectRepository, |
|||
IDocumentRepository documentRepository, |
|||
IDocumentSourceFactory documentStoreFactory, |
|||
IDistributedCache<DocumentUpdateInfo> documentUpdateCache) |
|||
{ |
|||
_projectRepository = projectRepository; |
|||
_documentRepository = documentRepository; |
|||
_documentStoreFactory = documentStoreFactory; |
|||
_documentUpdateCache = documentUpdateCache; |
|||
} |
|||
|
|||
public async Task PullAllAsync(PullAllDocumentInput input) |
|||
{ |
|||
var project = await _projectRepository.GetAsync(input.ProjectId); |
|||
|
|||
var navigationFile = await GetDocumentAsync( |
|||
project, |
|||
project.NavigationDocumentName, |
|||
input.LanguageCode, |
|||
input.Version |
|||
); |
|||
|
|||
var nav = JsonConvert.DeserializeObject<NavigationNode>(navigationFile.Content); |
|||
var leafs = nav.Items.GetAllNodes(x => x.Items) |
|||
.Where(x => x.IsLeaf && !x.Path.IsNullOrWhiteSpace()) |
|||
.ToList(); |
|||
|
|||
var source = _documentStoreFactory.Create(project.DocumentStoreType); |
|||
|
|||
var documents = new List<Document>(); |
|||
foreach (var leaf in leafs) |
|||
{ |
|||
var sourceDocument = |
|||
await source.GetDocumentAsync(project, leaf.Path, input.LanguageCode, input.Version); |
|||
documents.Add(sourceDocument); |
|||
} |
|||
|
|||
foreach (var document in documents) |
|||
{ |
|||
await _documentRepository.DeleteAsync(document.ProjectId, document.Name, |
|||
document.LanguageCode, |
|||
document.Version); |
|||
|
|||
await _documentRepository.InsertAsync(document, true); |
|||
await UpdateDocumentUpdateInfoCache(document); |
|||
} |
|||
} |
|||
|
|||
public async Task PullAsync(PullDocumentInput input) |
|||
{ |
|||
var project = await _projectRepository.GetAsync(input.ProjectId); |
|||
|
|||
var source = _documentStoreFactory.Create(project.DocumentStoreType); |
|||
var sourceDocument = await source.GetDocumentAsync(project, input.Name, input.LanguageCode, input.Version); |
|||
|
|||
await _documentRepository.DeleteAsync(sourceDocument.ProjectId, sourceDocument.Name, |
|||
sourceDocument.LanguageCode, sourceDocument.Version); |
|||
await _documentRepository.InsertAsync(sourceDocument, true); |
|||
await UpdateDocumentUpdateInfoCache(sourceDocument); |
|||
} |
|||
|
|||
private async Task UpdateDocumentUpdateInfoCache(Document document) |
|||
{ |
|||
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}"; |
|||
await _documentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo |
|||
{ |
|||
Name = document.Name, |
|||
CreationTime = document.CreationTime, |
|||
LastUpdatedTime = document.LastUpdatedTime |
|||
}); |
|||
} |
|||
|
|||
private async Task<Document> GetDocumentAsync( |
|||
Project project, |
|||
string documentName, |
|||
string languageCode, |
|||
string version) |
|||
{ |
|||
version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version; |
|||
var source = _documentStoreFactory.Create(project.DocumentStoreType); |
|||
var document = await source.GetDocumentAsync(project, documentName, languageCode, version); |
|||
return document; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Docs.Admin.Documents; |
|||
|
|||
namespace Volo.Docs.Admin |
|||
{ |
|||
[RemoteService] |
|||
[Area("docs")] |
|||
[ControllerName("DocumentsAdmin")] |
|||
[Route("api/docs/admin/documents")] |
|||
public class DocumentsAdminController : AbpController, IDocumentAdminAppService |
|||
{ |
|||
private readonly IDocumentAdminAppService _documentAdminAppService; |
|||
|
|||
public DocumentsAdminController(IDocumentAdminAppService documentAdminAppService) |
|||
{ |
|||
_documentAdminAppService = documentAdminAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("PullAll")] |
|||
public Task PullAllAsync(PullAllDocumentInput input) |
|||
{ |
|||
return _documentAdminAppService.PullAllAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("Pull")] |
|||
public Task PullAsync(PullDocumentInput input) |
|||
{ |
|||
return _documentAdminAppService.PullAsync(input); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
@page |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal |
|||
@using Volo.Docs.Admin.Pages.Docs.Admin.Projects |
|||
@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage |
|||
@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.PullModel |
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
@if (Model.PullDocument != null) |
|||
{ |
|||
<abp-dynamic-form submit-button="false" abp-model="@Model.PullDocument" asp-page="/Docs/Admin/Projects/Pull"> |
|||
<abp-modal size="@(AbpModalSize.Default)"> |
|||
<abp-modal-header title="@L["Pull"].Value"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-form-content /> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"> |
|||
</abp-modal-footer> |
|||
</abp-modal> |
|||
</abp-dynamic-form> |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Docs.Admin.Documents; |
|||
using Volo.Docs.Admin.Projects; |
|||
using Volo.Docs.Documents; |
|||
|
|||
namespace Volo.Docs.Admin.Pages.Docs.Admin.Projects |
|||
{ |
|||
public class PullModel : DocsAdminPageModel |
|||
{ |
|||
[BindProperty] |
|||
public PullDocumentViewModel PullDocument { get; set; } |
|||
|
|||
private readonly IProjectAdminAppService _projectAppService; |
|||
private readonly IDocumentAdminAppService _documentAppService; |
|||
|
|||
public PullModel(IProjectAdminAppService projectAppService, |
|||
IDocumentAdminAppService documentAppService) |
|||
{ |
|||
_projectAppService = projectAppService; |
|||
_documentAppService = documentAppService; |
|||
} |
|||
|
|||
public async Task<ActionResult> OnGetAsync(Guid id) |
|||
{ |
|||
var project = await _projectAppService.GetAsync(id); |
|||
|
|||
PullDocument = new PullDocumentViewModel() |
|||
{ |
|||
ProjectId = project.Id, |
|||
All = false |
|||
}; |
|||
|
|||
return Page(); |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
if (PullDocument.All) |
|||
{ |
|||
await _documentAppService.PullAllAsync( |
|||
ObjectMapper.Map<PullDocumentViewModel, PullAllDocumentInput>(PullDocument)); |
|||
} |
|||
else |
|||
{ |
|||
await _documentAppService.PullAsync( |
|||
ObjectMapper.Map<PullDocumentViewModel, PullDocumentInput>(PullDocument)); |
|||
} |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
public class PullDocumentViewModel |
|||
{ |
|||
[HiddenInput] |
|||
public Guid ProjectId { get; set; } |
|||
|
|||
public bool All { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(DocumentConsts.MaxNameLength)] |
|||
public string Name { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(DocumentConsts.MaxLanguageCodeNameLength)] |
|||
public string LanguageCode { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(DocumentConsts.MaxVersionNameLength)] |
|||
public string Version { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
var abp = abp || {}; |
|||
|
|||
$(function () { |
|||
abp.modals.projectPull = function () { |
|||
var initModal = function (publicApi, args) { |
|||
var $form = publicApi.getForm(); |
|||
var fg = $form.find("#PullDocument_Name").parent(); |
|||
var nameInput = fg.html(); |
|||
|
|||
$form.find("input:checkbox").change(function() { |
|||
if ($(this).prop("checked")) { |
|||
fg.html(""); |
|||
} else { |
|||
fg.html(nameInput); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
return { |
|||
initModal: initModal |
|||
}; |
|||
}; |
|||
}); |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
[Serializable] |
|||
public class DocumentUpdateInfo |
|||
{ |
|||
public virtual string Name { get; set; } |
|||
|
|||
public virtual DateTime CreationTime { get; set; } |
|||
|
|||
public virtual DateTime LastUpdatedTime { get; set; } |
|||
} |
|||
} |
|||
@ -1,27 +1,110 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public class Document |
|||
public class Document : AggregateRoot<Guid> |
|||
{ |
|||
public string Title { get; set; } |
|||
public virtual Guid ProjectId { get; protected set; } |
|||
|
|||
public string Content { get; set; } |
|||
public virtual string Name { get; protected set; } |
|||
|
|||
public string Format { get; set; } |
|||
public virtual string Version { get; protected set; } |
|||
|
|||
public string EditLink { get; set; } |
|||
public virtual string LanguageCode { get; protected set; } |
|||
|
|||
public string RootUrl { get; set; } |
|||
public virtual string FileName { get; set; } |
|||
|
|||
public string RawRootUrl { get; set; } |
|||
public virtual string Content { get; set; } |
|||
|
|||
public string Version { get; set; } |
|||
public virtual string Format { get; set; } |
|||
|
|||
public string LocalDirectory { get; set; } |
|||
public virtual string EditLink { get; set; } |
|||
|
|||
public string FileName { get; set; } |
|||
public virtual string RootUrl { get; set; } |
|||
|
|||
public List<DocumentContributor> Contributors { get; set; } |
|||
public virtual string RawRootUrl { get; set; } |
|||
|
|||
public virtual string LocalDirectory { get; set; } |
|||
|
|||
public virtual DateTime CreationTime { get; set; } |
|||
|
|||
public virtual DateTime LastUpdatedTime { get; set; } |
|||
|
|||
public virtual DateTime LastCachedTime { get; set; } |
|||
|
|||
public virtual List<DocumentContributor> Contributors { get; set; } |
|||
|
|||
protected Document() |
|||
{ |
|||
Contributors = new List<DocumentContributor>(); |
|||
ExtraProperties = new Dictionary<string, object>(); |
|||
} |
|||
|
|||
public Document( |
|||
Guid id, |
|||
Guid projectId, |
|||
[NotNull] string name, |
|||
[NotNull] string version, |
|||
[NotNull] string languageCode, |
|||
[NotNull] string fileName, |
|||
[NotNull] string content, |
|||
[NotNull] string format, |
|||
[NotNull] string editLink, |
|||
[NotNull] string rootUrl, |
|||
[NotNull] string rawRootUrl, |
|||
[NotNull] string localDirectory, |
|||
DateTime creationTime, |
|||
DateTime lastUpdatedTime, |
|||
DateTime lastCachedTime |
|||
) |
|||
{ |
|||
Id = id; |
|||
ProjectId = projectId; |
|||
|
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
Version = Check.NotNullOrWhiteSpace(version, nameof(version)); |
|||
LanguageCode = Check.NotNullOrWhiteSpace(languageCode, nameof(languageCode)); |
|||
FileName = Check.NotNullOrWhiteSpace(fileName, nameof(fileName)); |
|||
Content = Check.NotNullOrWhiteSpace(content, nameof(content)); |
|||
Format = Check.NotNullOrWhiteSpace(format, nameof(format)); |
|||
EditLink = Check.NotNullOrWhiteSpace(editLink, nameof(editLink)); |
|||
RootUrl = Check.NotNullOrWhiteSpace(rootUrl, nameof(rootUrl)); |
|||
RawRootUrl = Check.NotNullOrWhiteSpace(rawRootUrl, nameof(rawRootUrl)); |
|||
LocalDirectory = Check.NotNull(localDirectory, nameof(localDirectory)); |
|||
|
|||
CreationTime = creationTime; |
|||
LastUpdatedTime = lastUpdatedTime; |
|||
LastCachedTime = lastCachedTime; |
|||
|
|||
Contributors = new List<DocumentContributor>(); |
|||
ExtraProperties = new Dictionary<string, object>(); |
|||
} |
|||
|
|||
public virtual void AddContributor(string username, string userProfileUrl, string avatarUrl) |
|||
{ |
|||
Contributors.AddIfNotContains(new DocumentContributor(Id, username, userProfileUrl, avatarUrl)); |
|||
} |
|||
|
|||
public virtual void RemoveAllContributors() |
|||
{ |
|||
Contributors.Clear(); |
|||
} |
|||
|
|||
public virtual void RemoveContributor(string username, string userProfileUrl, string avatarUrl) |
|||
{ |
|||
Contributors.RemoveAll(r => |
|||
r.Username == username && r.UserProfileUrl == userProfileUrl && r.AvatarUrl == avatarUrl); |
|||
} |
|||
|
|||
public virtual DocumentContributor FindContributor(string username, string userProfileUrl, string avatarUrl) |
|||
{ |
|||
return Contributors.FirstOrDefault(r => |
|||
r.Username == username && r.UserProfileUrl == userProfileUrl && r.AvatarUrl == avatarUrl); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,39 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public class DocumentContributor |
|||
public class DocumentContributor : Entity |
|||
{ |
|||
public Guid DocumentId { get; set; } |
|||
|
|||
public string Username { get; set; } |
|||
|
|||
public string UserProfileUrl { get; set; } |
|||
|
|||
public string AvatarUrl { get; set; } |
|||
|
|||
protected DocumentContributor() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public virtual bool Equals(Guid documentId, string username) |
|||
{ |
|||
return DocumentId == documentId && Username == username; |
|||
} |
|||
|
|||
public DocumentContributor(Guid documentId, string username, string userProfileUrl, string avatarUrl) |
|||
{ |
|||
DocumentId = documentId; |
|||
Username = username; |
|||
UserProfileUrl = userProfileUrl; |
|||
AvatarUrl = avatarUrl; |
|||
} |
|||
|
|||
public override object[] GetKeys() |
|||
{ |
|||
return new object[] { DocumentId, Username }; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public class DocumentSourceOptions |
|||
{ |
|||
public Dictionary<string, Type> Sources { get; set; } |
|||
|
|||
public DocumentSourceOptions() |
|||
{ |
|||
Sources = new Dictionary<string, Type>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public class DocumentStoreOptions |
|||
{ |
|||
public Dictionary<string, Type> Stores { get; set; } |
|||
|
|||
public DocumentStoreOptions() |
|||
{ |
|||
Stores = new Dictionary<string, Type>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public interface IDocumentRepository : IBasicRepository<Document> |
|||
{ |
|||
Task<Document> FindAsync(Guid projectId, string name, string languageCode, string version, |
|||
bool includeDetails = true, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task DeleteAsync(Guid projectId, string name, string languageCode, string version, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public interface IDocumentSourceFactory |
|||
{ |
|||
IDocumentSource Create(string sourceType); |
|||
} |
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
namespace Volo.Docs.Documents |
|||
{ |
|||
public interface IDocumentStoreFactory |
|||
{ |
|||
IDocumentStore Create(string storeType); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue