Browse Source

Merge branch 'dev' into maliming/Microsoft

pull/3150/head
maliming 7 years ago
parent
commit
a045728071
  1. 52
      docs/en/Application-Services.md
  2. 5
      docs/en/Background-Jobs.md
  3. 139
      docs/en/Background-Workers.md
  4. 9
      docs/en/CLI.md
  5. 2
      docs/en/Customizing-Application-Modules-Guide.md
  6. 5
      docs/en/Customizing-Application-Modules-Overriding-User-Interface.md
  7. 16
      docs/en/Entity-Framework-Core-Other-DBMS.md
  8. 55
      docs/zh-Hans/Application-Services.md
  9. 3
      docs/zh-Hans/AspNet-Boilerplate-Migration-Guide.md
  10. 4
      docs/zh-Hans/Authorization.md
  11. 5
      docs/zh-Hans/CLI.md
  12. 3
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  13. 62
      docs/zh-Hans/Customizing-Application-Modules-Guide.md
  14. 3
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  15. 6
      docs/zh-Hans/Customizing-Application-Modules-Overriding-User-Interface.md
  16. 16
      docs/zh-Hans/Entity-Framework-Core-Other-DBMS.md
  17. 3
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  18. 38
      docs/zh-Hans/docs-nav.json
  19. 47
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs
  20. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs
  21. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs
  22. 38
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  23. 38
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs
  24. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  25. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs
  26. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs
  27. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs
  28. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs
  29. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  30. 53
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs
  31. 15
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  32. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs
  33. 1
      framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs
  34. 6
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs
  35. 341
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs
  36. 262
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs
  37. 6
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs
  38. 15
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  39. 14
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEfCoreDbContextInitializationContext.cs
  40. 7
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IAbpEfCoreDbContext.cs
  41. 11
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
  42. 30
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs
  43. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs
  44. 14
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs
  45. 22
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs
  46. 16
      modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs
  47. 8
      modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/AccountController.cs
  48. 14
      modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs
  49. 2
      modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs
  50. 2
      modules/account/src/Volo.Abp.Account.Web/AccountModuleToolbarContributor.cs
  51. 30
      modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs
  52. 2
      modules/account/src/Volo.Abp.Account.Web/Modules/Account/Components/Toolbar/UserLoginLink/UserLoginLinkViewComponent.cs
  53. 8
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs
  54. 13
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  55. 5
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs
  56. 13
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs
  57. 6
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs
  58. 6
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs
  59. 20
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs
  60. 8
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs
  61. 8
      modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs
  62. 2
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs
  63. 4
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs
  64. 4
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs
  65. 25
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  66. 88
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml
  67. 5
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml
  68. 3
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.scss
  69. 92
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml
  70. 23
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/_bootstrap-overwrite.scss
  71. 17
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/_home.scss
  72. 27
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.css
  73. 2
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.min.css
  74. 2
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.scss
  75. 4
      modules/blogging/src/Volo.Blogging.Web/compilerconfig.json
  76. 5
      modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200312050853_init.Designer.cs
  77. 1
      modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200312050853_init.cs
  78. 3
      modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs
  79. 11
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  80. 2
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs
  81. 2
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs
  82. 6
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs
  83. 3
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentSource.cs
  84. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs
  85. 81
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs
  86. 121
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubPatchAnalyzer.cs
  87. 10
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs
  88. 12
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubPatchAnalyzer.cs
  89. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs
  90. 6
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs
  91. 12
      modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs
  92. 3
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  93. 4
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js
  94. 23
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs
  95. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs
  96. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs
  97. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs
  98. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureStore.cs
  99. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs
  100. 12
      modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs

52
docs/en/Application-Services.md

@ -201,7 +201,7 @@ See the [authorization document](Authorization.md) for more.
## CRUD Application Services
If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from `CrudAppService`.
If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from the `CrudAppService`.
### Example
@ -219,7 +219,9 @@ public interface IBookAppService :
}
````
* `ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
`ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface).
> Creating interface for an application service is a good practice, but not required by the ABP Framework. You can skip the interface part.
`ICrudAppService` declares the following methods:
@ -292,6 +294,52 @@ public class BookAppService :
`CrudAppService` implements all methods declared in the `ICrudAppService` interface. You can then add your own custom methods or override and customize base methods.
> `CrudAppService` has different versions gets different number of generic arguments. Use the one suitable for you.
### AbstractKeyCrudAppService
`CrudAppService` requires to have an Id property as the primary key of your entity. If you are using composite keys then you can not utilize it.
`AbstractKeyCrudAppService` implements the same `ICrudAppService` interface, but this time without making assumption about your primary key.
#### Example
Assume that you have a `District` entity with `CityId` and `Name` as a composite primary key. Using `AbstractKeyCrudAppService` requires to implement `DeleteByIdAsync` and `GetEntityByIdAsync` methods yourself:
````csharp
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
````
This implementation requires you to create a class represents your composite key:
````csharp
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}
````
## Lifetime
Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system.

5
docs/en/Background-Jobs.md

@ -171,4 +171,7 @@ Background job system is extensible and you can change the default background jo
See pre-built job manager alternatives:
* [Hangfire Background Job Manager](Background-Jobs-Hangfire.md)
* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md)
* [RabbitMQ Background Job Manager](Background-Jobs-RabbitMq.md)
## See Also
* [Background Workers](Background-Workers.md)

139
docs/en/Background-Workers.md

@ -1,3 +1,140 @@
# Background Workers
TODO
## Introduction
Background workers are simple independent threads in the application running in the background. Generally, they run periodically to perform some tasks. Examples;
* A background worker can run periodically to **delete old logs**.
* A background worker can run periodically to **determine inactive users** and **send emails** to get users to return to your application.
## Create a Background Worker
A background worker should directly or indirectly implement the `IBackgroundWorker` interface.
> A background worker is inherently [singleton](Dependency-Injection.md). So, only a single instance of your worker class is instantiated and run.
### BackgroundWorkerBase
`BackgroundWorkerBase` is an easy way to create a background worker.
````csharp
public class MyWorker : BackgroundWorkerBase
{
public override Task StartAsync(CancellationToken cancellationToken = default)
{
//...
}
public override Task StopAsync(CancellationToken cancellationToken = default)
{
//...
}
}
````
Start your worker in the `StartAsync` (which is called when the application begins) and stop in the `StopAsync` (which is called when the application shuts down).
> You can directly implement the `IBackgroundWorker`, but `BackgroundWorkerBase` provides some useful properties like `Logger`.
### AsyncPeriodicBackgroundWorkerBase
Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below:
````csharp
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
{
public PassiveUserCheckerWorker(
AbpTimer timer,
IServiceScopeFactory serviceScopeFactory
) : base(
timer,
serviceScopeFactory)
{
Timer.Period = 600000; //10 minutes
}
protected override async Task DoWorkAsync(
PeriodicBackgroundWorkerContext workerContext)
{
Logger.LogInformation("Starting: Setting status of inactive users...");
//Resolve dependencies
var userRepository = workerContext
.ServiceProvider
.GetRequiredService<IUserRepository>();
//Do the work
await userRepository.UpdateInactiveUserStatusesAsync();
Logger.LogInformation("Completed: Setting status of inactive users...");
}
}
````
* `AsyncPeriodicBackgroundWorkerBase` uses the `AbpTimer` (a thread-safe timer) object to determine **the period**. We can set its `Period` property in the constructor.
* It required to implement the `DoWorkAsync` method to **execute** the periodic work.
* It is a good practice to **resolve dependencies** from the `PeriodicBackgroundWorkerContext` instead of constructor injection. Because `AsyncPeriodicBackgroundWorkerBase` uses a `IServiceScope` that is **disposed** when your work finishes.
* `AsyncPeriodicBackgroundWorkerBase` **catches and logs exceptions** thrown by the `DoWorkAsync` method.
## Register Background Worker
After creating a background worker class, you should to add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitialization` method of your module class:
````csharp
[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
public override void OnApplicationInitialization(
ApplicationInitializationContext context)
{
context.AddBackgroundWorker<PassiveUserCheckerWorker>();
}
}
````
`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below:
````
context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.Add(
context
.ServiceProvider
.GetRequiredService<PassiveUserCheckerWorker>()
);
````
So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`.
While we generally add workers in `OnApplicationInitialization`, there are no restrictions on that. You can inject `IBackgroundWorkerManager` anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down.
## Options
`AbpBackgroundWorkerOptions` class is used to [set options](Options.md) for the background workers. Currently, there is only one option:
* `IsEnabled` (default: true): Used to **enable/disable** the background worker system for your application.
> See the [Options](Options.md) document to learn how to set options.
## Making Your Application Always Run
Background workers only work if your application is running. If you host the background job execution in your web application (this is the default behavior), you should ensure that your web application is configured to always be running. Otherwise, background jobs only work while your application is in use.
## Running On a Cluster
Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example).
If that's a problem for your workers, you have two options;
* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them.
* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers.
## Quartz Integration
ABP Framework's background worker system is good to implement periodic tasks. However, you may want to use an advanced task scheduler like [Quartz](https://www.quartz-scheduler.net/). See the community contributed [quartz integration](Background-Workers-Quartz.md) for the background workers.
## See Also
* [Quartz Integration for the background workers](Background-Workers-Quartz.md)
* [Background Jobs](Background-Jobs.md)

9
docs/en/CLI.md

@ -48,7 +48,10 @@ abp new Acme.BookStore
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `none`: Without UI. There are some additional options for this template:
* `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks:
* `none`: no mobile application.
* `react-native`: React Native.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). Additional options:
@ -141,7 +144,7 @@ abp switch-to-preview [options]
````
#### Options
`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### switch-to-stable
@ -154,7 +157,7 @@ abp switch-to-stable [options]
````
#### Options
`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory.
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### login

2
docs/en/Customizing-Application-Modules-Guide.md

@ -21,7 +21,7 @@ This approach has the following benefits:
However, there is a drawback:
* You may not able to **customize** the module source code as it is in your own solution.
* You may not able to **customize** the module because the module source is not in your solution.
This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations.

5
docs/en/Customizing-Application-Modules-Overriding-User-Interface.md

@ -3,7 +3,4 @@
You may want to override a page, a component, a JavaScript, CSS or an image file of your depended module. Overriding the UI completely depends on the UI framework you're using. Select the UI framework to continue:
* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md)
* [Angular](UI/Angular/Customization-User-Interface.md)
* [Angular](UI/Angular/Customization-User-Interface.md)

16
docs/en/Entity-Framework-Core-Other-DBMS.md

@ -91,4 +91,18 @@ Run the `.DbMigrator` project to create the database and seed the initial data.
## Run the Application
It is ready. Just run the application and enjoy coding.
It is ready. Just run the application and enjoy coding.
## DBMS restrictions
Different DBMS may have some restrictions, such as the maximum length of field names, index length, etc.
The module may provide some built-in solutions. You can configure it via `ModelBuilder`. eg: `Identity Server` module.
```csharp
builder.ConfigureIdentityServer(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
```
Related discussions: https://github.com/abpframework/abp/issues/1920

55
docs/zh-Hans/Application-Services.md

@ -200,7 +200,7 @@ public async Task<BookDto> GetAsync(Guid id)
### CRUD应用服务
如果需要创建具有Create,Update,Delete和Get方法的简单CRUD应用服务,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService.
如果需要创建具有Create,Update,Delete和Get方法的简单**CRUD应用服务**,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService.
示例:
@ -218,7 +218,9 @@ public interface IBookAppService :
}
````
* ICrudAppService有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口).
`ICrudAppService` 有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口).
> 为应用程序服务创建一个接口是最佳做法,但是ABP框架并不强制你这么做,你可以跳过接口部分.
`ICrudAppService`声明以下方法:
@ -291,7 +293,52 @@ public class BookAppService :
`CrudAppService`实现了`ICrudAppService`接口中声明的所有方法. 然后,你可以添加自己的自定义方法或覆盖和自定义实现.
### 生命周期
> `CrudAppService` 有不同数量泛型参数的版本,你可以选择适合的使用.
### AbstractKeyCrudAppService
`CrudAppService` 要求你的实体拥有一个Id属性做为主键. 如果你使用的是复合主键,那么你无法使用它.
`AbstractKeyCrudAppService` 实现了相同的 `ICrudAppService` 接口,但它没有假设你的主键.
#### 示例
假设你有实体 `District`,它的`CityId` 和 `Name` 做为复合主键,使用 `AbstractKeyCrudAppService` 时需要你自己实现 `DeleteByIdAsync``GetEntityByIdAsync` 方法:
````csharp
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统.
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
````
这个实现需要你创建一个类做为复合键:
````csharp
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}
````
### 生命周期
应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统.

3
docs/zh-Hans/AspNet-Boilerplate-Migration-Guide.md

@ -0,0 +1,3 @@
# ASP.NET Boilerplate v5+ 迁移到 ABP Framework
TODO...

4
docs/zh-Hans/Authorization.md

@ -205,6 +205,10 @@ public class AuthorAppService : ApplicationService, IAuthorAppService
参阅 [基于策略的授权](https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/policies) 文档了解如何自定义策略.
### 更改依赖模块的权限定义
`PermissionDefinitionProvider` 派生的类(就像上面的示例一样) 可以获取现有的权限定义(由依赖[模块](Module-Development-Basics.md)定义)并更改其定义.
## IAuthorizationService
ASP.NET Core 提供了 `IAuthorizationService` 用于检查权限. 注入后使用它进行条件控制权限.

5
docs/zh-Hans/CLI.md

@ -48,6 +48,9 @@ abp new Acme.BookStore
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项:
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项:
* `none`: 不包含移动应用程序.
* `react-native`: React Native.
* `--database-provider` 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
@ -148,7 +151,7 @@ abp switch-to-stable [options]
#### Options
`--solution-path``-sp`: 指定解决方案(.sln)文件路径. 如果未指定,CLI试寻找当前目录中的.sln文件.
`--solution-directory``-sd`: 指定解决方案文件夹. 解决方案应该在指定文件夹或子文件夹中. 如果未指定,默认为当前目录.
### login

3
docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md

@ -0,0 +1,3 @@
# 自定义应用模块: 扩展实体
TODO...

62
docs/zh-Hans/Customizing-Application-Modules-Guide.md

@ -0,0 +1,62 @@
# 自定义现有模块
ABP框架提供的设计旨在支持构建完全[模块化的应用程序](Module-Development-Basics.md)和系统. 它还提供了一些可以在任何类型的应用程序中**使用**的[预构建应用模块](Modules/Index.md)
例如,你可以在你的应用程序中**重用**[身份管理模块](Modules/Identity.md)去添加用户,角色和权限管理. [应用程序启动模板](Startup-Templates/Application.md)已经**预装**了Identity和其他模块.
## 复用应用模块
你有两个选项去复用应用模块:
### 添加包引用
你可以添加相关模块的 **NuGet****NPM** 包引用到你的应用程序,并配置模块(根据它的文档)集成到你的应用程序中.
正如前面提到,[应用程序启动模板](Startup-Templates/Application.md)已经**预装了一些基本模块**,它引用模块的NuGet和NPM包.
这种方法具有以下优点:
* 你的解决方案会非常**干净**,只包含你**自己的应用程序代码**.
* 你可以**很简单的**升级模块到最新的可用模板. `abp update` [CLI](CLI.md) 命令会使更新变的更加简单. 通过这种方式, 你可以获得**最新功能和Bus修复**.
然而有一个缺点:
* 你可能无法**自定义**模块,因为模块源码没有在你的解决方案中.
本文档介绍了 **或者自定义或扩展** 依赖模块并且无需更改其源码,尽快与更改完整的源码比起是有限的,但仍有一些好的方法可以自定义.
如果你不认为自己会对预构建的模块进行重大更改,那么使用包引用的方法复用模块是推荐的方法.
### 包含源码
如果你想要在预构建的模块上进行**重大**更改或添加**主要功能**,但是可用的扩展点不够使用,那么可以考虑直接使用依赖模块的源码.
这种情况下,你通常**添加模块源码**到你的解决方案中,并将**包引用替换**为本地项目引用. **[ABP CLI](CLI.md)** 可以为你自动化这一过程.
#### 分离模块解决方案
你可能不希望将模块源代码**直接包含在解决方案**中. 每个模块都包含十多个项目文件,添加**多个模块**会使解决方案变的臃肿可能还会影响**开发时的加载速度**,另外你可能有不同的开发团队维护不同模块.
无论如何,你都可以为需要的模块创建**单独的解决方案**,将依赖模块做为解决方案中的项目引用. 比如在[abp仓库](https://github.com/abpframework/abp/),我们就是这样做的.
> 我们看到的一个问题是Visual Studio在这种方式下不能很好的工作(解决方案目录之外对本地项目的引用不能很好地支持). 如果在开发过程中出错(对于外部模块),请在Visual Studio打开应用程序的解决方案后,在命令行运行 `dotnet restore`命令.
#### 发布的自定义模块的包
一个备选方案是将重新打包模块的源代码(NuGet/NPM包),使用包引用. 你可以为公司使用本地私人的Nuget/NPM服务器.
## 模块自定义/扩展途径
如果你决定使用预构建模块的NuGet/NPM包引用方式. 下面的文档详细解释了如何自定义/扩展现有模块的方法:
* [扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [重写服务](Customizing-Application-Modules-Overriding-Services.md)
* [重写界面](Customizing-Application-Modules-Overriding-User-Interface.md)
### 另请参阅
另外,请参阅以下文档:
* 参阅 [本地化文档](Localization.md) 学习如何扩展已存在的本地化资源.
* 参阅 [设置文档](Settings.md) 学习如何更改依赖模块的设置定义.
* 参阅 [授权文档](Authorization.md) 学习如何更改依赖模块的权限定义.

3
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -0,0 +1,3 @@
# 自定义应用模块: 覆盖服务
TODO...

6
docs/zh-Hans/Customizing-Application-Modules-Overriding-User-Interface.md

@ -0,0 +1,6 @@
# 重写用户界面
你可以想要重写页面,组件,JavaScript,CSS或你依赖模块的图片文件. 重写UI取决于你使用的UI框架. 选择UI框架以继续:
* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md)
* [Angular](UI/Angular/Customization-User-Interface.md)

16
docs/zh-Hans/Entity-Framework-Core-Other-DBMS.md

@ -91,4 +91,18 @@ builder.ConfigureIdentityServer(options =>
## 运行应用程序
它已准备就绪, 只需要运行该应用程序与享受编码.
它已准备就绪, 只需要运行该应用程序与享受编码.
## DBMS限制
不同的DBMS可能存在一些限制, 如字段名称的最大长度, 索引长度等等.
模块可能提供了一些内置的解决方案. 你可以通过`ModelBuilder`来配置它 如: Identity Server模块
```csharp
builder.ConfigureIdentityServer(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
```
相关讨论: https://github.com/abpframework/abp/issues/1920

3
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -0,0 +1,3 @@
# ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南
TODO...

38
docs/zh-Hans/docs-nav.json

@ -42,6 +42,33 @@
}
]
},
{
"text": "指南",
"items": [
{
"text": "自定义应用模块",
"path": "Customizing-Application-Modules-Guide.md",
"items": [
{
"text": "扩展实体",
"path": "Customizing-Application-Modules-Extending-Entities.md"
},
{
"text": "重写服务",
"path": "Customizing-Application-Modules-Overriding-Services.md"
},
{
"text": "重写用户界面",
"path": "Customizing-Application-Modules-Overriding-User-Interface.md"
}
]
},
{
"text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md"
}
]
},
{
"text": "CLI",
"path": "CLI.md"
@ -249,16 +276,7 @@
},
{
"text": "Tag Helpers",
"items":[
{
"text": "在线演示",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "按钮",
"path": "UI/AspNetCore/Tag-Helpers/Buttons.md"
}
]
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "仪表板和小部件(Widget)系统",

47
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs

@ -39,6 +39,43 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling
AddWithDependencies(contributorType);
}
public void Replace<TSourceContributor, TDestContributorType>(bool includeDependencies = false)
where TSourceContributor : IBundleContributor, new()
where TDestContributorType : IBundleContributor, new()
{
Replace(typeof(TSourceContributor), typeof(TDestContributorType), includeDependencies);
}
public void Replace([NotNull] Type sourceContributorType, [NotNull] Type destContributorType, bool includeDependencies = false)
{
Check.NotNull(sourceContributorType, nameof(sourceContributorType));
Check.NotNull(destContributorType, nameof(destContributorType));
if (!includeDependencies)
{
_contributors.ReplaceOne(x => x.GetType() == sourceContributorType,
contributor => (IBundleContributor) Activator.CreateInstance(destContributorType));
}
else
{
RemoveWithDependencies(sourceContributorType);
Add(destContributorType);
}
}
public void Remove<TContributor>(bool includeDependencies = false)
where TContributor : IBundleContributor, new()
{
if (!includeDependencies)
{
_contributors.RemoveAll(x => x.GetType() == typeof(TContributor));
}
else
{
RemoveWithDependencies(typeof(TContributor));
}
}
public IReadOnlyList<IBundleContributor> GetAll()
{
return _contributors.ToImmutableList();
@ -58,6 +95,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling
AddInstanceToContributors(contributorType);
}
private void RemoveWithDependencies(Type contributorType)
{
foreach (var dependedType in GetDirectDependencies(contributorType))
{
RemoveWithDependencies(dependedType); //Recursive call
}
_contributors.RemoveAll(x => x.GetType() == contributorType);
}
private IEnumerable<Type> GetDirectDependencies(Type contributorType)
{

7
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs

@ -21,7 +21,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago
? "en"
: CultureInfo.CurrentUICulture.Name;
if (TryAddCultureFile(context, cultureName))
if (TryAddCultureFile(context, MapCultureName(cultureName)))
{
return;
}
@ -39,5 +39,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago
context.Files.AddIfNotContains(filePath);
return true;
}
protected virtual string MapCultureName(string cultureName)
{
return cultureName;
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs

@ -25,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation
var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Replace('-', '_');
if (TryAddCultureFile(context, cultureName))
if (TryAddCultureFile(context, MapCultureName(cultureName)))
{
return;
}
@ -35,7 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation
return;
}
TryAddCultureFile(context, cultureName.Substring(0, cultureName.IndexOf('_')));
TryAddCultureFile(context, MapCultureName(cultureName.Substring(0, cultureName.IndexOf('_'))));
}
protected virtual bool TryAddCultureFile(BundleConfigurationContext context, string cultureName)
@ -51,5 +51,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation
context.Files.AddIfNotContains(filePath);
return true;
}
protected virtual string MapCultureName(string cultureName)
{
return cultureName;
}
}
}

38
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -12,6 +12,7 @@ using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.Conventions
@ -81,7 +82,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
continue;
}
if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType))
if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true))
{
if (CanUseFormBodyBinding(action, prm))
{
@ -94,7 +95,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
{
if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
//We want to use "id" as path parameter, not body!
if (parameter.ParameterName == "id")
{
return false;
}
if (_options.ConventionalControllers
.FormBodyBindingIgnoredTypes
.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
{
return false;
}
@ -251,7 +260,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod}));
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
}
}
}
@ -295,9 +304,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}";
//Add {id} path if needed
if (action.Parameters.Any(p => p.ParameterName == "id"))
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameterModel != null)
{
url += "/{id}";
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
{
url += "/{id}";
}
else
{
var properties = idParameterModel
.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
url += "/{" + property.Name + "}";
}
}
}
//Add action name if needed
@ -341,7 +365,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
{
if(configuration?.UrlControllerNameNormalizer == null)
if (configuration?.UrlControllerNameNormalizer == null)
{
return controllerName;
}
@ -364,7 +388,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual bool IsEmptySelector(SelectorModel selector)
{
return selector.AttributeRouteModel == null
return selector.AttributeRouteModel == null
&& selector.ActionConstraints.IsNullOrEmpty()
&& selector.EndpointMetadata.IsNullOrEmpty();
}

38
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs

@ -0,0 +1,38 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.BackgroundWorkers
{
public static class BackgroundWorkersApplicationInitializationContextExtensions
{
public static ApplicationInitializationContext AddBackgroundWorker<TWorker>([NotNull] this ApplicationInitializationContext context)
where TWorker : IBackgroundWorker
{
Check.NotNull(context, nameof(context));
context.AddBackgroundWorker(typeof(TWorker));
return context;
}
public static ApplicationInitializationContext AddBackgroundWorker([NotNull] this ApplicationInitializationContext context, [NotNull] Type workerType)
{
Check.NotNull(context, nameof(context));
Check.NotNull(workerType, nameof(workerType));
if (!workerType.IsAssignableTo<IBackgroundWorker>())
{
throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!");
}
context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.Add(
(IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType)
);
return context;
}
}
}

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -162,6 +162,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("");
sb.AppendLine("-t|--template <template-name> (default: app)");
sb.AppendLine("-u|--ui <ui-framework> (if supported by the template)");
sb.AppendLine("-m|--mobile <mobile-framework> (if supported by the template)");
sb.AppendLine("-d|--database-provider <database-provider> (if supported by the template)");
sb.AppendLine("-o|--output-folder <output-folder> (default: current folder)");
sb.AppendLine("-v|--version <version> (default: latest version)");
@ -177,6 +178,8 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore --tiered");
sb.AppendLine(" abp new Acme.BookStore -u angular");
sb.AppendLine(" abp new Acme.BookStore -u angular -d mongodb");
sb.AppendLine(" abp new Acme.BookStore -m none");
sb.AppendLine(" abp new Acme.BookStore -m react-native");
sb.AppendLine(" abp new Acme.BookStore -d mongodb");
sb.AppendLine(" abp new Acme.BookStore -d mongodb -o d:\\my-project");
sb.AppendLine(" abp new Acme.BookStore -t module");

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs

@ -29,7 +29,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp switch-to-preview [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("-sp|--solution-path");
sb.AppendLine("-sd|--solution-directory");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs

@ -29,7 +29,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp switch-to-stable [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("-sp|--solution-path");
sb.AppendLine("-sd|--solution-directory");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs

@ -95,21 +95,19 @@ namespace Volo.Abp.Cli.NuGet
.OrResult(msg => !msg.IsSuccessStatusCode)
.WaitAndRetryAsync(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(7)
TimeSpan.FromSeconds(1)
},
(responseMessage, timeSpan, retryCount, context) =>
{
if (responseMessage.Exception != null)
{
Logger.LogWarning(
Logger.LogDebug(
$"{retryCount}. HTTP request attempt failed to {url} with an error: HTTP {(int)responseMessage.Result.StatusCode}-{responseMessage.Exception.Message}. " +
$"Waiting {timeSpan.TotalSeconds} secs for the next try...");
}
else if (responseMessage.Result != null)
{
Logger.LogWarning(
Logger.LogDebug(
$"{retryCount}. HTTP request attempt failed to {url} with an error: {(int)responseMessage.Result.StatusCode}-{responseMessage.Result.ReasonPhrase}. " +
$"Waiting {timeSpan.TotalSeconds} secs for the next try...");
}

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/MobileApp.cs

@ -17,9 +17,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
{
case MobileApp.ReactNative:
return "react-native";
default:
return null;
}
throw new Exception("Mobile app folder name is not set!");
}
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -77,7 +77,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
if (context.BuildArgs.MobileApp != MobileApp.ReactNative)
{
steps.Add(new RemoveFolderStep(MobileApp.ReactNative.GetFolderName()?.EnsureStartsWith('/')));
steps.Add(new RemoveFolderStep(MobileApp.ReactNative.GetFolderName().EnsureStartsWith('/')));
}
}

53
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs

@ -31,55 +31,74 @@ namespace Volo.Abp.Cli.ProjectModification
{
_packageSourceAdder.Add("ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json");
var solutionPath = GetSolutionPath(commandLineArgs);
var solutionFolder = GetSolutionFolder(commandLineArgs);
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(
GetSolutionPath(commandLineArgs),
solutionPath,
true);
await _npmPackagesUpdater.Update(
Path.GetFileName(GetSolutionPath(commandLineArgs)),
solutionFolder,
true);
}
public async Task SwitchToStable(CommandLineArgs commandLineArgs)
{
var solutionPath = GetSolutionPath(commandLineArgs);
var solutionFolder = GetSolutionFolder(commandLineArgs);
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(
GetSolutionPath(commandLineArgs),
solutionPath,
false,
true);
await _npmPackagesUpdater.Update(
Path.GetFileName(GetSolutionPath(commandLineArgs)),
false,
solutionFolder,
false,
true);
}
private string GetSolutionPath(CommandLineArgs commandLineArgs)
{
var solutionPath = commandLineArgs.Options.GetOrNull(Options.SolutionPath.Short, Options.SolutionPath.Long);
var directory = commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long)
?? Directory.GetCurrentDirectory();
var solutionPath = Directory.GetFiles(directory, "*.sln").FirstOrDefault();
if (solutionPath == null)
{
try
{
solutionPath = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.sln").Single();
}
catch (Exception)
var subDirectories = Directory.GetDirectories(directory);
foreach (var subDirectory in subDirectories)
{
Logger.LogError("There is no solution or more that one solution in current directory.");
throw;
var slnInSubDirectory = Directory.GetFiles(subDirectory, "*.sln").FirstOrDefault();
if (slnInSubDirectory != null)
{
return Path.Combine(subDirectory, slnInSubDirectory);
}
}
Logger.LogError("There is no solution or more that one solution in current directory.");
return null;
}
return solutionPath;
}
private string GetSolutionFolder(CommandLineArgs commandLineArgs)
{
return commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long)
?? Directory.GetCurrentDirectory();
}
public static class Options
{
public static class SolutionPath
public static class SolutionDirectory
{
public const string Short = "sp";
public const string Long = "solution-path";
public const string Short = "sd";
public const string Long = "solution-directory";
}
}
}

15
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -107,14 +107,18 @@ namespace Volo.Abp.Cli.ProjectModification
private async Task DownloadSourceCodesToSolutionFolder(ModuleWithMastersInfo module, string modulesFolderInSolution, string version = null)
{
var targetModuleFolder = Path.Combine(modulesFolderInSolution, module.Name);
await SourceCodeDownloadService.DownloadAsync(
module.Name,
Path.Combine(modulesFolderInSolution, module.Name),
targetModuleFolder,
version,
null,
null
);
await DeleteAppFolderAsync(targetModuleFolder);
if (module.MasterModuleInfos == null)
{
return;
@ -126,6 +130,15 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
private async Task DeleteAppFolderAsync(string targetModuleFolder)
{
var appFolder = Path.Combine(targetModuleFolder, "app");
if (Directory.Exists(appFolder))
{
Directory.Delete(appFolder, true);
}
}
private async Task AddNugetAndNpmReferences(ModuleWithMastersInfo module, string[] projectFiles)
{
foreach (var nugetPackage in module.NugetPackages)

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs

@ -106,7 +106,7 @@ namespace Volo.Abp.Cli.ProjectModification
}
else
{
Logger.LogDebug("Package: \"{0}-v{1}\" is up to date.", packageId, packageVersion);
Logger.LogInformation("Package: \"{0}-v{1}\" is up to date.", packageId, packageVersion);
}
}
}

1
framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs

@ -14,6 +14,7 @@ namespace Volo.Abp.Cli
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
#if DEBUG
.MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Debug)
#else

6
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs

@ -5,36 +5,30 @@ namespace Volo.Abp.Application.Services
{
public interface ICrudAppService<TEntityDto, in TKey>
: ICrudAppService<TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput>
: ICrudAppService<TEntityDto, TKey, TGetListInput, TEntityDto, TEntityDto>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput>
: ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: ICrudAppService<TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntityDto : IEntityDto<TKey>
{
}
public interface ICrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: IApplicationService
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
Task<TGetOutputDto> GetAsync(TKey id);

341
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs

@ -0,0 +1,341 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services
{
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto, TEntityDto>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: AbstractKeyCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity
{
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
: base(repository)
{
}
protected override TEntityDto MapToGetListOutputDto(TEntity entity)
{
return MapToGetOutputDto(entity);
}
}
public abstract class AbstractKeyCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: ApplicationService,
ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity
{
public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }
protected IRepository<TEntity> Repository { get; }
protected virtual string GetPolicyName { get; set; }
protected virtual string GetListPolicyName { get; set; }
protected virtual string CreatePolicyName { get; set; }
protected virtual string UpdatePolicyName { get; set; }
protected virtual string DeletePolicyName { get; set; }
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
{
Repository = repository;
AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance;
}
public virtual async Task<TGetOutputDto> GetAsync(TKey id)
{
await CheckGetPolicyAsync();
var entity = await GetEntityByIdAsync(id);
return MapToGetOutputDto(entity);
}
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
await CheckGetListPolicyAsync();
var query = CreateFilteredQuery(input);
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto<TGetListOutputDto>(
totalCount,
entities.Select(MapToGetListOutputDto).ToList()
);
}
public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync();
var entity = MapToEntity(input);
TryToSetTenantId(entity);
await Repository.InsertAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync(id);
//TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task DeleteAsync(TKey id)
{
await CheckDeletePolicyAsync();
await DeleteByIdAsync(id);
}
protected abstract Task DeleteByIdAsync(TKey id);
protected abstract Task<TEntity> GetEntityByIdAsync(TKey id);
protected virtual async Task CheckGetPolicyAsync()
{
await CheckPolicyAsync(GetPolicyName);
}
protected virtual async Task CheckGetListPolicyAsync()
{
await CheckPolicyAsync(GetListPolicyName);
}
protected virtual async Task CheckCreatePolicyAsync()
{
await CheckPolicyAsync(CreatePolicyName);
}
protected virtual async Task CheckUpdatePolicyAsync()
{
await CheckPolicyAsync(UpdatePolicyName);
}
protected virtual async Task CheckDeletePolicyAsync()
{
await CheckPolicyAsync(DeletePolicyName);
}
/// <summary>
/// Should apply sorting if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
return query.OrderBy(sortInput.Sorting);
}
}
//IQueryable.Task requires sorting, so we should sort if Take will be used.
if (input is ILimitedResultRequest)
{
return ApplyDefaultSorting(query);
}
//No sorting
return query;
}
/// <summary>
/// Applies sorting if no sorting specified but a limited result requested.
/// </summary>
/// <param name="query">The query.</param>
protected virtual IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
{
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())
{
return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime);
}
throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyCrudAppService!");
}
/// <summary>
/// Should apply paging if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListInput input)
{
//Try to use paging if available
if (input is IPagedResultRequest pagedInput)
{
return query.PageBy(pagedInput);
}
//Try to limit query result if available
if (input is ILimitedResultRequest limitedInput)
{
return query.Take(limitedInput.MaxResultCount);
}
//No paging
return query;
}
/// <summary>
/// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
/// It should filter query if needed, but should not do sorting or paging.
/// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
/// methods.
/// </summary>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetListInput input)
{
return Repository;
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetListOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TCreateInput"/> to <see cref="TEntity"/> to create a new entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TEntity MapToEntity(TCreateInput createInput)
{
var entity = ObjectMapper.Map<TCreateInput, TEntity>(createInput);
SetIdForGuids(entity);
return entity;
}
/// <summary>
/// Sets Id value for the entity if <see cref="TKey"/> is <see cref="Guid"/>.
/// It's used while creating a new entity.
/// </summary>
protected virtual void SetIdForGuids(TEntity entity)
{
var entityWithGuidId = entity as IEntity<Guid>;
if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty)
{
return;
}
EntityHelper.TrySetId(
entityWithGuidId,
() => GuidGenerator.Create(),
true
);
}
/// <summary>
/// Maps <see cref="TUpdateInput"/> to <see cref="TEntity"/> to update the entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity)
{
ObjectMapper.Map(updateInput, entity);
}
protected virtual void TryToSetTenantId(TEntity entity)
{
if (entity is IMultiTenant && HasTenantIdProperty(entity))
{
var tenantId = CurrentTenant.Id;
if (!tenantId.HasValue)
{
return;
}
var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId));
if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null)
{
return;
}
propertyInfo.SetValue(entity, tenantId);
}
}
protected virtual bool HasTenantIdProperty(TEntity entity)
{
return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null;
}
}
}

262
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs

@ -1,13 +1,10 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services
{
@ -65,274 +62,49 @@ namespace Volo.Abp.Application.Services
}
public abstract class CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: ApplicationService,
ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
: AbstractKeyCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }
protected IRepository<TEntity, TKey> Repository { get; }
protected virtual string GetPolicyName { get; set; }
protected virtual string GetListPolicyName { get; set; }
protected virtual string CreatePolicyName { get; set; }
protected virtual string UpdatePolicyName { get; set; }
protected virtual string DeletePolicyName { get; set; }
protected new IRepository<TEntity, TKey> Repository { get; }
protected CrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{
Repository = repository;
AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance;
}
public virtual async Task<TGetOutputDto> GetAsync(TKey id)
protected override async Task DeleteByIdAsync(TKey id)
{
await CheckGetPolicyAsync();
var entity = await GetEntityByIdAsync(id);
return MapToGetOutputDto(entity);
}
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
await CheckGetListPolicyAsync();
var query = CreateFilteredQuery(input);
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto<TGetListOutputDto>(
totalCount,
entities.Select(MapToGetListOutputDto).ToList()
);
}
public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
await CheckCreatePolicyAsync();
var entity = MapToEntity(input);
TryToSetTenantId(entity);
await Repository.InsertAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync(id);
//TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return MapToGetOutputDto(entity);
}
public virtual async Task DeleteAsync(TKey id)
{
await CheckDeletePolicyAsync();
await Repository.DeleteAsync(id);
}
protected virtual Task<TEntity> GetEntityByIdAsync(TKey id)
{
return Repository.GetAsync(id);
}
protected virtual async Task CheckGetPolicyAsync()
{
await CheckPolicyAsync(GetPolicyName);
}
protected virtual async Task CheckGetListPolicyAsync()
{
await CheckPolicyAsync(GetListPolicyName);
}
protected virtual async Task CheckCreatePolicyAsync()
{
await CheckPolicyAsync(CreatePolicyName);
}
protected virtual async Task CheckUpdatePolicyAsync()
{
await CheckPolicyAsync(UpdatePolicyName);
}
protected virtual async Task CheckDeletePolicyAsync()
protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
{
await CheckPolicyAsync(DeletePolicyName);
return await Repository.GetAsync(id);
}
/// <summary>
/// Should apply sorting if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
return query.OrderBy(sortInput.Sorting);
}
}
//IQueryable.Task requires sorting, so we should sort if Take will be used.
if (input is ILimitedResultRequest)
{
return query.OrderByDescending(e => e.Id);
}
//No sorting
return query;
}
/// <summary>
/// Should apply paging if needed.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListInput input)
{
//Try to use paging if available
if (input is IPagedResultRequest pagedInput)
{
return query.PageBy(pagedInput);
}
//Try to limit query result if available
if (input is ILimitedResultRequest limitedInput)
{
return query.Take(limitedInput.MaxResultCount);
}
//No paging
return query;
}
/// <summary>
/// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
/// It should filter query if needed, but should not do sorting or paging.
/// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
/// methods.
/// </summary>
/// <param name="input">The input.</param>
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetListInput input)
{
return Repository;
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TEntity"/> to <see cref="TGetListOutputDto"/>.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
{
return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
}
/// <summary>
/// Maps <see cref="TCreateInput"/> to <see cref="TEntity"/> to create a new entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual TEntity MapToEntity(TCreateInput createInput)
{
var entity = ObjectMapper.Map<TCreateInput, TEntity>(createInput);
SetIdForGuids(entity);
return entity;
}
/// <summary>
/// Sets Id value for the entity if <see cref="TKey"/> is <see cref="Guid"/>.
/// It's used while creating a new entity.
/// </summary>
protected virtual void SetIdForGuids(TEntity entity)
{
var entityWithGuidId = entity as IEntity<Guid>;
if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty)
{
return;
}
EntityHelper.TrySetId(
entityWithGuidId,
() => GuidGenerator.Create(),
true
);
}
/// <summary>
/// Maps <see cref="TUpdateInput"/> to <see cref="TEntity"/> to update the entity.
/// It uses <see cref="IObjectMapper"/> by default.
/// It can be overriden for custom mapping.
/// </summary>
protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity)
protected override void MapToEntity(TUpdateInput updateInput, TEntity entity)
{
if (updateInput is IEntityDto<TKey> entityDto)
{
entityDto.Id = entity.Id;
}
ObjectMapper.Map(updateInput, entity);
base.MapToEntity(updateInput, entity);
}
protected virtual void TryToSetTenantId(TEntity entity)
protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
{
if (entity is IMultiTenant && HasTenantIdProperty(entity))
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())
{
var tenantId = CurrentTenant.Id;
if (!tenantId.HasValue)
{
return;
}
var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId));
if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null)
{
return;
}
propertyInfo.SetValue(entity, tenantId);
return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime);
}
else
{
return query.OrderByDescending(e => e.Id);
}
}
protected virtual bool HasTenantIdProperty(TEntity entity)
{
return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null;
}
}
}

6
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs

@ -47,7 +47,11 @@ namespace Volo.Abp.Domain.Entities
/// Creates a new <see cref="EntityNotFoundException"/> object.
/// </summary>
public EntityNotFoundException(Type entityType, object id, Exception innerException)
: base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException)
: base(
id == null
? $"There is no such an entity given given id. Entity type: {entityType.FullName}"
: $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}",
innerException)
{
EntityType = entityType;
Id = id;

15
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -28,7 +28,7 @@ using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore
{
public abstract class AbpDbContext<TDbContext> : DbContext, IEfCoreDbContext, ITransientDependency
public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext, ITransientDependency
where TDbContext : DbContext
{
protected virtual Guid? CurrentTenantId => CurrentTenant?.Id;
@ -144,6 +144,19 @@ namespace Volo.Abp.EntityFrameworkCore
}
}
public virtual void Initialize(AbpEfCoreDbContextInitializationContext initializationContext)
{
if (initializationContext.UnitOfWork.Options.Timeout.HasValue &&
Database.IsRelational() &&
!Database.GetCommandTimeout().HasValue)
{
Database.SetCommandTimeout(initializationContext.UnitOfWork.Options.Timeout.Value.TotalSeconds.To<int>());
}
ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
}
protected virtual EntityChangeReport ApplyAbpConcepts()
{
var changeReport = new EntityChangeReport();

14
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEfCoreDbContextInitializationContext.cs

@ -0,0 +1,14 @@
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore
{
public class AbpEfCoreDbContextInitializationContext
{
public IUnitOfWork UnitOfWork { get; }
public AbpEfCoreDbContextInitializationContext(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
}
}

7
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IAbpEfCoreDbContext.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.EntityFrameworkCore
{
public interface IAbpEfCoreDbContext : IEfCoreDbContext
{
void Initialize(AbpEfCoreDbContextInitializationContext initializationContext);
}
}

11
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs

@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
@ -53,11 +54,13 @@ namespace Volo.Abp.Uow.EntityFrameworkCore
{
var dbContext = CreateDbContext(unitOfWork);
if (unitOfWork.Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
if (dbContext is IAbpEfCoreDbContext abpEfCoreDbContext)
{
dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To<int>());
abpEfCoreDbContext.Initialize(
new AbpEfCoreDbContextInitializationContext(
unitOfWork
)
);
}
return dbContext;

30
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs

@ -0,0 +1,30 @@
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Application.Dto;
namespace Volo.Abp.TestApp.Application
{
//This is especially used to test the AbstractKeyCrudAppService
public class DistrictAppService : AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}
}

11
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs

@ -0,0 +1,11 @@
using System;
namespace Volo.Abp.TestApp.Application
{
public class DistrictKey
{
public Guid CityId { get; set; }
public string Name { get; set; }
}
}

14
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.TestApp.Application.Dto
{
public class DistrictDto : EntityDto
{
public Guid CityId { get; set; }
public string Name { get; set; }
public int Population { get; set; }
}
}

22
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs

@ -60,5 +60,27 @@ namespace Volo.Abp.TestApp.Testing
douglas.Age.ShouldBe(42);
}
}
[Fact]
public async Task Cascading_Entities_Should_Not_Be_Deleted_When_Soft_Deleting_Entities()
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.Phones.ShouldNotBeEmpty();
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();
douglas.Phones.ShouldNotBeEmpty();
}
}
}
}

16
modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs

@ -10,14 +10,14 @@ namespace Volo.Abp.Account
{
public class AccountAppService : ApplicationService, IAccountAppService
{
private readonly IIdentityRoleRepository _roleRepository;
protected IIdentityRoleRepository RoleRepository { get; }
protected IdentityUserManager UserManager { get; }
public AccountAppService(
IdentityUserManager userManager,
IIdentityRoleRepository roleRepository)
{
_roleRepository = roleRepository;
RoleRepository = roleRepository;
UserManager = userManager;
}
@ -30,19 +30,11 @@ namespace Volo.Abp.Account
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
await UserManager.SetEmailAsync(user,input.EmailAddress);
await SetDefaultRolesAsync(user);
await UserManager.AddDefaultRolesAsync(user);
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
}
protected virtual async Task SetDefaultRolesAsync(IdentityUser user)
{
var defaultRoles = await _roleRepository.GetDefaultOnesAsync();
await UserManager.SetRolesAsync(user, defaultRoles.Select(r => r.Name));
}
protected virtual async Task CheckSelfRegistrationAsync()
{
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
@ -51,4 +43,4 @@ namespace Volo.Abp.Account
}
}
}
}
}

8
modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/AccountController.cs

@ -10,18 +10,18 @@ namespace Volo.Abp.Account
[Route("api/account")]
public class AccountController : AbpController, IAccountAppService
{
private readonly IAccountAppService _accountAppService;
protected IAccountAppService AccountAppService { get; }
public AccountController(IAccountAppService accountAppService)
{
_accountAppService = accountAppService;
AccountAppService = accountAppService;
}
[HttpPost]
[Route("register")]
public Task<IdentityUserDto> RegisterAsync(RegisterDto input)
public virtual Task<IdentityUserDto> RegisterAsync(RegisterDto input)
{
return _accountAppService.RegisterAsync(input);
return AccountAppService.RegisterAsync(input);
}
}
}

14
modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs

@ -68,10 +68,10 @@ namespace Volo.Abp.Account.Web.Pages.Account
return Page();
}
var schemes = await _schemeProvider.GetAllSchemesAsync();
var schemes = await SchemeProvider.GetAllSchemesAsync();
var providers = schemes
.Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
.Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
.Select(x => new ExternalProviderModel
{
DisplayName = x.DisplayName,
@ -174,7 +174,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
[UnitOfWork]
public override async Task<IActionResult> OnPostExternalLogin(string provider)
{
if (_accountOptions.WindowsAuthenticationSchemeName == provider)
if (AccountOptions.WindowsAuthenticationSchemeName == provider)
{
return await ProcessWindowsLoginAsync();
}
@ -184,10 +184,10 @@ namespace Volo.Abp.Account.Web.Pages.Account
private async Task<IActionResult> ProcessWindowsLoginAsync()
{
var result = await HttpContext.AuthenticateAsync(_accountOptions.WindowsAuthenticationSchemeName);
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (!(result?.Principal is WindowsPrincipal windowsPrincipal))
{
return Challenge(_accountOptions.WindowsAuthenticationSchemeName);
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
var props = new AuthenticationProperties
@ -195,11 +195,11 @@ namespace Volo.Abp.Account.Web.Pages.Account
RedirectUri = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }),
Items =
{
{"scheme", _accountOptions.WindowsAuthenticationSchemeName},
{"scheme", AccountOptions.WindowsAuthenticationSchemeName},
}
};
var identity = new ClaimsIdentity(_accountOptions.WindowsAuthenticationSchemeName);
var identity = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
identity.AddClaim(new Claim(JwtClaimTypes.Subject, windowsPrincipal.Identity.Name));
identity.AddClaim(new Claim(JwtClaimTypes.Name, windowsPrincipal.Identity.Name));

2
modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.Account.Web
{
public class AbpAccountUserMenuContributor : IMenuContributor
{
public Task ConfigureMenuAsync(MenuConfigurationContext context)
public virtual Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name != StandardMenus.User)
{

2
modules/account/src/Volo.Abp.Account.Web/AccountModuleToolbarContributor.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.Account.Web
{
public class AccountModuleToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
public virtual Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name != StandardToolbars.Main)
{

30
modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs

@ -22,17 +22,17 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
[Route("api/account")]
public class AccountController : AbpController
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IdentityUserManager _userManager;
private readonly ISettingProvider _settingProvider;
protected SignInManager<IdentityUser> SignInManager { get; }
protected IdentityUserManager UserManager { get; }
protected ISettingProvider SettingProvider { get; }
public AccountController(SignInManager<IdentityUser> signInManager, IdentityUserManager userManager, ISettingProvider settingProvider)
{
LocalizationResource = typeof(AccountResource);
_signInManager = signInManager;
_userManager = userManager;
_settingProvider = settingProvider;
SignInManager = signInManager;
UserManager = userManager;
SettingProvider = settingProvider;
}
[HttpPost]
@ -45,7 +45,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
await ReplaceEmailToUsernameOfInputIfNeeds(login);
return GetAbpLoginResult(await _signInManager.PasswordSignInAsync(
return GetAbpLoginResult(await SignInManager.PasswordSignInAsync(
login.UserNameOrEmailAddress,
login.Password,
login.RememberMe,
@ -57,7 +57,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
[Route("logout")]
public virtual async Task Logout()
{
await _signInManager.SignOutAsync();
await SignInManager.SignOutAsync();
}
[HttpPost]
@ -68,14 +68,14 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
await ReplaceEmailToUsernameOfInputIfNeeds(login);
var identityUser = await _userManager.FindByNameAsync(login.UserNameOrEmailAddress);
var identityUser = await UserManager.FindByNameAsync(login.UserNameOrEmailAddress);
if (identityUser == null)
{
return new AbpLoginResult(LoginResultType.InvalidUserNameOrPassword);
}
return GetAbpLoginResult(await _signInManager.CheckPasswordSignInAsync(identityUser, login.Password, true));
return GetAbpLoginResult(await SignInManager.CheckPasswordSignInAsync(identityUser, login.Password, true));
}
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(UserLoginInfo login)
@ -85,13 +85,13 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
return;
}
var userByUsername = await _userManager.FindByNameAsync(login.UserNameOrEmailAddress);
var userByUsername = await UserManager.FindByNameAsync(login.UserNameOrEmailAddress);
if (userByUsername != null)
{
return;
}
var userByEmail = await _userManager.FindByEmailAsync(login.UserNameOrEmailAddress);
var userByEmail = await UserManager.FindByEmailAsync(login.UserNameOrEmailAddress);
if (userByEmail == null)
{
return;
@ -125,7 +125,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
return new AbpLoginResult(LoginResultType.Success);
}
private void ValidateLoginInfo(UserLoginInfo login)
protected virtual void ValidateLoginInfo(UserLoginInfo login)
{
if (login == null)
{
@ -143,9 +143,9 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
}
}
private async Task CheckLocalLoginAsync()
protected virtual async Task CheckLocalLoginAsync()
{
if (!await _settingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
{
throw new UserFriendlyException(L["LocalLoginDisabledMessage"]);
}

2
modules/account/src/Volo.Abp.Account.Web/Modules/Account/Components/Toolbar/UserLoginLink/UserLoginLinkViewComponent.cs

@ -5,7 +5,7 @@ namespace Volo.Abp.Account.Web.Modules.Account.Components.Toolbar.UserLoginLink
{
public class UserLoginLinkViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
public virtual IViewComponentResult Invoke()
{
return View("~/Modules/Account/Components/Toolbar/UserLoginLink/Default.cshtml");
}

8
modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs

@ -21,12 +21,12 @@ namespace Volo.Abp.Account.Web.Pages.Account
ObjectMapperContext = typeof(AbpAccountWebModule);
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
protected virtual RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
protected void CheckIdentityErrors(IdentityResult identityResult)
protected virtual void CheckIdentityErrors(IdentityResult identityResult)
{
if (!identityResult.Succeeded)
{
@ -36,7 +36,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
//identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp
}
private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
@ -48,7 +48,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
protected virtual string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{

13
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -48,25 +48,25 @@ namespace Volo.Abp.Account.Web.Pages.Account
//public IClientStore ClientStore { get; set; }
//public IEventService IdentityServerEvents { get; set; }
protected IAuthenticationSchemeProvider _schemeProvider;
protected AbpAccountOptions _accountOptions;
protected IAuthenticationSchemeProvider SchemeProvider { get; }
protected AbpAccountOptions AccountOptions { get; }
public LoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions)
{
_schemeProvider = schemeProvider;
_accountOptions = accountOptions.Value;
SchemeProvider = schemeProvider;
AccountOptions = accountOptions.Value;
}
public virtual async Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
var schemes = await _schemeProvider.GetAllSchemesAsync();
var schemes = await SchemeProvider.GetAllSchemesAsync();
var providers = schemes
.Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
.Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
.Select(x => new ExternalProviderModel
{
DisplayName = x.DisplayName,
@ -212,6 +212,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
CheckIdentityErrors(await UserManager.CreateAsync(user));
CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));
return user;
}

5
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs

@ -23,5 +23,10 @@ namespace Volo.Abp.Account.Web.Pages.Account
return RedirectToPage("/Account/Login");
}
public virtual Task OnPostAsync()
{
return Task.CompletedTask;
}
}
}

13
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs

@ -10,19 +10,24 @@ namespace Volo.Abp.Account.Web.Pages.Account
public PersonalSettingsInfoModel PersonalSettingsInfoModel { get; set; }
private readonly IProfileAppService _profileAppService;
protected IProfileAppService ProfileAppService { get; }
public ManageModel(IProfileAppService profileAppService)
{
_profileAppService = profileAppService;
ProfileAppService = profileAppService;
}
public async Task OnGetAsync()
public virtual async Task OnGetAsync()
{
var user = await _profileAppService.GetAsync();
var user = await ProfileAppService.GetAsync();
PersonalSettingsInfoModel = ObjectMapper.Map<ProfileDto, PersonalSettingsInfoModel>(user);
}
public virtual Task OnPostAsync()
{
return Task.CompletedTask;
}
}
public class ChangePasswordInfoModel

6
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs

@ -15,7 +15,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
{
public class RegisterModel : AccountPageModel
{
private readonly IAccountAppService _accountAppService;
protected IAccountAppService AccountAppService { get; }
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }
@ -28,7 +28,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
public RegisterModel(IAccountAppService accountAppService)
{
_accountAppService = accountAppService;
AccountAppService = accountAppService;
}
public virtual async Task OnGetAsync()
@ -51,7 +51,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
UserName = Input.UserName
};
var userDto = await _accountAppService.RegisterAsync(registerDto);
var userDto = await AccountAppService.RegisterAsync(registerDto);
var user = await UserManager.GetByIdAsync(userDto.Id);
await UserManager.SetEmailAsync(user, Input.EmailAddress);

6
modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
{
public List<SelectListItem> Providers { get; set; }
public async Task<IActionResult> OnGetAsync()
public virtual async Task<IActionResult> OnGetAsync()
{
var user = await SignInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
@ -41,5 +41,9 @@ namespace Volo.Abp.Account.Web.Pages.Account
//);
}
public virtual Task OnPostAsync()
{
return Task.CompletedTask;
}
}
}

20
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs

@ -14,10 +14,10 @@ namespace Volo.Abp.AuditLogging
{
public ILogger<AuditingStore> Logger { get; set; }
private readonly IAuditLogRepository _auditLogRepository;
private readonly IGuidGenerator _guidGenerator;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly AbpAuditingOptions Options;
protected IAuditLogRepository AuditLogRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
public AuditingStore(
IAuditLogRepository auditLogRepository,
@ -25,15 +25,15 @@ namespace Volo.Abp.AuditLogging
IUnitOfWorkManager unitOfWorkManager,
IOptions<AbpAuditingOptions> options)
{
_auditLogRepository = auditLogRepository;
_guidGenerator = guidGenerator;
_unitOfWorkManager = unitOfWorkManager;
AuditLogRepository = auditLogRepository;
GuidGenerator = guidGenerator;
UnitOfWorkManager = unitOfWorkManager;
Options = options.Value;
Logger = NullLogger<AuditingStore>.Instance;
}
public async Task SaveAsync(AuditLogInfo auditInfo)
public virtual async Task SaveAsync(AuditLogInfo auditInfo)
{
if (!Options.HideErrors)
{
@ -54,9 +54,9 @@ namespace Volo.Abp.AuditLogging
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
{
using (var uow = _unitOfWorkManager.Begin(true))
using (var uow = UnitOfWorkManager.Begin(true))
{
await _auditLogRepository.InsertAsync(new AuditLog(_guidGenerator, auditInfo));
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, auditInfo));
await uow.SaveChangesAsync();
}
}

8
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs

@ -19,7 +19,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
}
public async Task<List<AuditLog>> GetListAsync(
public virtual async Task<List<AuditLog>> GetListAsync(
string sorting = null,
int maxResultCount = 50,
int skipCount = 0,
@ -59,7 +59,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
return auditLogs;
}
public async Task<long> GetCountAsync(
public virtual async Task<long> GetCountAsync(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
@ -92,7 +92,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
return totalCount;
}
private IQueryable<AuditLog> GetListQuery(
protected virtual IQueryable<AuditLog> GetListQuery(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
@ -123,7 +123,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
.WhereIf(minExecutionDuration != null && minExecutionDuration.Value > 0, auditLog => auditLog.ExecutionDuration >= minExecutionDuration);
}
public async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate)
public virtual async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate)
{
var result = await DbSet.AsNoTracking()
.Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate)

8
modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs

@ -20,7 +20,7 @@ namespace Volo.Abp.AuditLogging.MongoDB
}
public async Task<List<AuditLog>> GetListAsync(
public virtual async Task<List<AuditLog>> GetListAsync(
string sorting = null,
int maxResultCount = 50,
int skipCount = 0,
@ -58,7 +58,7 @@ namespace Volo.Abp.AuditLogging.MongoDB
.ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<long> GetCountAsync(
public virtual async Task<long> GetCountAsync(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
@ -92,7 +92,7 @@ namespace Volo.Abp.AuditLogging.MongoDB
return count;
}
private IQueryable<AuditLog> GetListQuery(
protected virtual IQueryable<AuditLog> GetListQuery(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
@ -122,7 +122,7 @@ namespace Volo.Abp.AuditLogging.MongoDB
}
public async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate)
public virtual async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate)
{
var result = await GetMongoQueryable()
.Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate)

2
modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs

@ -48,7 +48,7 @@ namespace Volo.Abp.BackgroundJobs
/// </summary>
public virtual BackgroundJobPriority Priority { get; set; }
private BackgroundJobRecord()
protected BackgroundJobRecord()
{
}

4
modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/EfCoreBackgroundJobRepository.cs

@ -21,13 +21,13 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore
Clock = clock;
}
public async Task<List<BackgroundJobRecord>> GetWaitingListAsync(int maxResultCount)
public virtual async Task<List<BackgroundJobRecord>> GetWaitingListAsync(int maxResultCount)
{
return await GetWaitingListQuery(maxResultCount)
.ToListAsync();
}
private IQueryable<BackgroundJobRecord> GetWaitingListQuery(int maxResultCount)
protected virtual IQueryable<BackgroundJobRecord> GetWaitingListQuery(int maxResultCount)
{
var now = Clock.Now;
return DbSet

4
modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs

@ -21,13 +21,13 @@ namespace Volo.Abp.BackgroundJobs.MongoDB
Clock = clock;
}
public async Task<List<BackgroundJobRecord>> GetWaitingListAsync(int maxResultCount)
public virtual async Task<List<BackgroundJobRecord>> GetWaitingListAsync(int maxResultCount)
{
return await GetWaitingListQuery(maxResultCount)
.ToListAsync();
}
private IMongoQueryable<BackgroundJobRecord> GetWaitingListQuery(int maxResultCount)
protected virtual IMongoQueryable<BackgroundJobRecord> GetWaitingListQuery(int maxResultCount)
{
var now = Clock.Now;
return GetMongoQueryable()

25
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml

@ -27,7 +27,7 @@
<abp-input asp-for="FocusCommentId" class="m-0" />
<div class="container">
<div class="row">
<div class="col-md-8 col-lg-8 mx-auto">
<div class="col-10 col-md-8 col-lg-7 mx-auto">
<section class="hero-section">
<div class="hero-articles">
<div class="hero-content">
@ -86,7 +86,7 @@
</div>
<div class="row">
<div class="col-md-8 col-lg-8 mx-auto">
<div class="col-10 col-md-8 col-lg-7 mx-auto">
<section class="post-content">
<p>
@Html.Raw(RenderMarkdownToHtml(Model.Post.Content))
@ -96,8 +96,8 @@
</div>
<div class="row">
<div class="col-md-8 col-lg-8 mx-auto">
<hr />
<div class="col-10 col-md-8 col-lg-7 mx-auto">
<hr />
<div class="mb-2 mt-1">
@(L["ShareOn"].Value + " :")
<a href="#" target="_blank" class="mr-2" id="TwitterShareLink" title="Twitter">
@ -121,7 +121,6 @@
</div>
}
@if (Model.CommentsWithReplies.Count > 0)
{
<abp-row v-align="Start">
@ -191,7 +190,7 @@
<input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
<textarea class="form-control" name="text" id="textBoxId" rows="4"></textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Comment"].Value" />
<abp-button button-type="Danger" class="btn-rounded float-right replyCancelButton" text="@L["Cancel"].Value" />
@ -208,7 +207,7 @@
<form class="editFormClass">
<input name="commentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@commentWithRepliesDto.Comment.Text</textarea>
<textarea class="form-control" name="text" id="textBoxId" rows="4">@commentWithRepliesDto.Comment.Text</textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
<abp-button button-type="Danger" class="btn-rounded float-right editCancelButton" text="@L["Cancel"].Value" />
@ -258,7 +257,7 @@
@if (hasCommentingPermission)
{
<div class="comment-form mt-4 replyForm">
<div class="clearfix bg-light p-4">
<div class="clearfix bg-light py-4">
<h3 class="mt-0">
@L["ReplyTo", commentWithRepliesDto.Comment.Writer == null ? "" : commentWithRepliesDto.Comment.Writer.UserName]
</h3>
@ -267,7 +266,7 @@
<input name="postId" value="@Model.Post.Id" hidden />
<input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
<textarea class="form-control" name="text" id="textBoxId" rows="4"></textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
<abp-button button-type="Danger" class="btn-rounded float-right replyCancelButton" text="@L["Cancel"].Value" />
@ -279,12 +278,12 @@
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{
<div class="comment-form mt-4 editForm">
<div class="clearfix bg-light p-4">
<div class="clearfix bg-light py-4">
<div>
<form class="editFormClass">
<input name="commentId" value="@reply.Id" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@reply.Text</textarea>
<textarea class="form-control" name="text" id="textBoxId" rows="4">@reply.Text</textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
<abp-button button-type="Danger" class="btn-rounded float-right editCancelButton" text="@L["Cancel"].Value" />
@ -308,13 +307,13 @@
<div class="vs-blog-title mb-0">
<h3>@L["LeaveComment"]</h3>
</div>
<div class="clearfix bg-light p-4">
<div class="clearfix bg-light py-4">
<div>
<form method="post">
<input name="postId" value="@Model.Post.Id" hidden />
<input name="repliedCommentId" id="repliedCommentId" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
<textarea class="form-control" name="text" id="textBoxId" rows="4"></textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
</form>

88
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml

@ -17,55 +17,63 @@
<abp-script type="@typeof(TuiEditorScriptContributor)" />
<abp-script src="/Pages/Blogs/Posts/edit.js" />
</abp-script-bundle>
}
}
<div id="edit-post-container">
<form method="post" id="edit-post-form">
<abp-input asp-for="Post.Title" auto-focus="true" />
<abp-input asp-for="Post.Url" />
<abp-input asp-for="Post.CoverImage" />
<div class="container py-5">
<div class="row">
<div class="col-10 col-md-8 col-lg-7 mx-auto">
<div class="card">
<div class="card-body">
<form method="post" id="edit-post-form">
<abp-input asp-for="Post.Title" auto-focus="true" />
<abp-input asp-for="Post.Url" />
<abp-input asp-for="Post.CoverImage" />
<abp-row>
<abp-column size-sm="_9">
<div class="form-group">
<label for="CoverImageFile">@L["CoverImage"]</label>
<input class="form-control" type="file" id="CoverImageFile" />
</div>
</abp-column>
<abp-column size-sm="_3">
<img id="CoverImage" src="@Model.Post.CoverImage" height="80" width="160" />
</abp-column>
</abp-row>
<abp-row>
<abp-column size-sm="_9">
<div class="form-group">
<label for="CoverImageFile">@L["CoverImage"]</label>
<input class="form-control" type="file" id="CoverImageFile" />
</div>
</abp-column>
<abp-column size-sm="_3">
<img id="CoverImage" src="@Model.Post.CoverImage" height="80" width="160" />
</abp-column>
</abp-row>
<abp-input asp-for="Post.Tags" />
<abp-input asp-for="Post.BlogId" />
<abp-input asp-for="Post.Id" />
<abp-input asp-for="Post.Content" />
<abp-input asp-for="Post.Tags" />
<abp-input asp-for="Post.BlogId" />
<abp-input asp-for="Post.Id" />
<abp-input asp-for="Post.Content" />
<div class="form-group">
<div class="edit-post-editor">
<div class="gradient-background-animation loading-cover"></div>
</div>
</div>
<div class="form-group">
<div class="edit-post-editor">
<div class="gradient-background-animation loading-cover"></div>
</div>
</div>
<div class="actions d-flex">
<div class="text-muted editor-info d-none d-lg-block mr-auto">
<div class="actions d-flex">
<div class="text-muted editor-info d-none d-lg-block mr-auto">
<div>
<svg class="markdown-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path>
</svg> <small>@L["MarkdownSupported"] </small>
</div>
<div>
<svg class="markdown-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path>
</svg> <small>@L["MarkdownSupported"] </small>
</div>
<div><small><i class="fa fa-copy"></i> @L["FileUploadInfo"].Value</small></div>
<div><small><i class="fa fa-copy"></i> @L["FileUploadInfo"].Value</small></div>
</div>
<div class="mt-3 d-flex flex-row-reverse">
<abp-button button-type="Primary" type="submit" form="edit-post-form" text="@L["Submit"].Value" icon="check" />
</div>
<div class="mt-3 d-flex flex-row-reverse">
<abp-button button-type="Primary" type="submit" form="edit-post-form" text="@L["Submit"].Value" icon="check" />
<a asp-page="/Blog/Posts/Detail" asp-route-postUrl="@Model.Post.Url" asp-route-blogShortName="@Model.BlogShortName" class="btn btn-default mr-2"><span>@L["Cancel"]</span></a>
<a asp-page="/Blog/Posts/Detail" asp-route-postUrl="@Model.Post.Url" asp-route-blogShortName="@Model.BlogShortName" class="btn btn-default mr-2"><span>@L["Cancel"]</span></a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
</div>

5
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml

@ -7,8 +7,7 @@
@inject IAuthorizationService Authorization
@model Volo.Blogging.Pages.Blog.Posts.IndexModel
@{
ViewBag.Title = "Read All Blog Posts";
ViewBag.Description = "Volosoft blogs about new features and technologies either used to build Volosoft's open-source repositories and products or general interest.";
ViewBag.Title = "Blog";
}
@section scripts {
<abp-script-bundle name="@typeof(IndexModel).FullName">
@ -44,6 +43,7 @@
</h2>
<p class="article-sum">
@Html.Raw(GetShortContent(post.Content))
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">Continue Reading</a>
</p>
</div>
@if (post.Writer != null)
@ -157,6 +157,7 @@
</h3>
<p>
@Html.Raw(GetShortContent(post.Content))
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">Continue Reading</a>
</p>
<div class="article-owner">
<div class="article-infos">

3
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.scss

@ -4,7 +4,6 @@
min-height: 480px;
background: center center no-repeat;
background-size: cover;
}
}
}
}

92
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml

@ -18,59 +18,63 @@
<abp-script src="/Pages/Blogs/Posts/new.js" />
</abp-script-bundle>
}
<main>
<section>
<div class="container">
<div id="qa-new-post-container">
<form method="post" id="new-post-form">
<abp-input asp-for="Post.BlogId" />
<abp-input asp-for="Post.Title" auto-focus="true" />
<abp-input asp-for="Post.Url" />
<abp-input asp-for="Post.CoverImage" />
<div id="qa-new-post-container">
<div class="container py-5">
<div class="row">
<div class="col-10 col-md-8 col-lg-7 mx-auto">
<div class="card">
<div class="card-body">
<form method="post" id="new-post-form">
<abp-input asp-for="Post.BlogId" />
<abp-input asp-for="Post.Title" auto-focus="true" />
<abp-input asp-for="Post.Url" />
<abp-input asp-for="Post.CoverImage" />
<abp-row>
<abp-column size-sm="_9">
<div class="form-group">
<label for="CoverImageFile">@L["CoverImage"]</label>
<input class="form-control" type="file" id="CoverImageFile" />
</div>
</abp-column>
<abp-column size-sm="_3">
<img id="CoverImage" src="@Model.Post.CoverImage" height="80" width="160" style="display:none" />
</abp-column>
</abp-row>
<abp-row>
<abp-column size-sm="_9">
<div class="form-group">
<label for="CoverImageFile">@L["CoverImage"]</label>
<input class="form-control" type="file" id="CoverImageFile" />
</div>
</abp-column>
<abp-column size-sm="_3">
<img id="CoverImage" src="@Model.Post.CoverImage" height="80" width="160" style="display:none" />
</abp-column>
</abp-row>
<abp-input asp-for="Post.Content" />
<abp-input asp-for="Post.Tags" />
<abp-input asp-for="Post.Content" />
<abp-input asp-for="Post.Tags" />
<div class="form-group">
<div class="new-post-editor">
<div class="gradient-background-animation loading-cover"></div>
</div>
</div>
<div class="form-group">
<div class="new-post-editor">
<div class="gradient-background-animation loading-cover"></div>
</div>
</div>
<div class="actions d-flex">
<div class="text-muted editor-info d-none d-lg-block mr-auto">
<div class="actions d-flex">
<div class="text-muted editor-info d-none d-lg-block mr-auto">
<div>
<svg class="markdown-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path>
</svg> <small>@L["MarkdownSupported"] </small>
</div>
<div>
<svg class="markdown-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path>
</svg> <small>@L["MarkdownSupported"] </small>
</div>
<div><small><i class="fa fa-copy"></i> @L["FileUploadInfo"].Value</small></div>
<div><small><i class="fa fa-copy"></i> @L["FileUploadInfo"].Value</small></div>
</div>
<div class="mt-3 d-flex flex-row-reverse">
<abp-button button-type="Primary" type="submit" form="new-post-form" text="@L["Submit"].Value" icon="check" />
</div>
<div class="mt-3 d-flex flex-row-reverse">
<abp-button button-type="Primary" type="submit" form="new-post-form" text="@L["Submit"].Value" icon="check" />
<a asp-page="./Index" class="btn btn-default mr-2"><span>@L["Cancel"]</span></a>
</div>
<a asp-page="./Index" class="btn btn-default mr-2"><span>@L["Cancel"]</span></a>
</div>
</div>
</form>
</div>
</form>
</div>
</div>
</div>
</section>
</main>
</div>
</div>

23
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/_bootstrap-overwrite.scss

@ -13,19 +13,20 @@ h1, h2, h3, h4, h5, h6, .tab-title {
font-weight: 700;
}
h1 {
font-size: 2.25em;
font-size: 2em;
margin: 1rem 0 1.5rem;
line-height: 1.25;
}
h2, .tab-title {
font-size: 1.5em;
margin: 1.5rem 0 .75rem;
}
h3 {
font-size: 1.5em;
font-size: 1.25em;
margin: 1.5rem 0 .75rem;
}
h4 {
font-size: 1.25em;
font-size: 1.125em;
margin: 1.5rem 0 .75rem;
}
h5 {
@ -40,9 +41,10 @@ h6 {
}
img {
max-width: 100%;
}
input, select, textarea, .form-control , .btn {
}
input, select, textarea, .form-control, .btn {
border-radius: 0;
border-width: 1px 1px 2px 1px;
}
.navbar-toggler {
background: aqua;
@ -58,7 +60,7 @@ input, select, textarea, .form-control , .btn {
.list-group-item {
position: relative;
display: block;
padding: 0 0 20px 0;
padding: 30px 0 30px;
background: none;
border-radius: 0;
border: 0;
@ -69,7 +71,7 @@ input, select, textarea, .form-control , .btn {
& + .list-group-item {
border-top: 1px solid #f5f5f5;
padding: 20px 0;
padding: 45px 0 30px;
}
h3 {
@ -79,8 +81,9 @@ input, select, textarea, .form-control , .btn {
&.small-list {
.list-group-item {
padding: 10px 0;
& + .list-group-item {
padding: 10px 0;
& + .list-group-item {
padding: 10px 0;
}
}
@ -100,6 +103,6 @@ input, select, textarea, .form-control , .btn {
font-size: 1em;
}
.font-125 {
font-size: 1.25em;
font-size: 1.125em;
}

17
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/_home.scss

@ -5,11 +5,12 @@
position: relative;
overflow: hidden;
.hero-content {
.hero-content {
h2 {
margin-top: .5rem;
font-size: 2em;
font-size: 1.75em;
font-weight: bold;
line-height: 1.25;
}
}
@ -25,9 +26,9 @@
}
}
.article-owner {
.article-owner {
.article-infos {
color: black;
color: black;
.seperator {
@ -35,17 +36,17 @@
color: rgba(255, 255, 255, 0.2);
}
img.article-avatar {
img.article-avatar {
display: inline-block;
border-radius: 50%;
}
}
}
.img-container {
.img-container {
img {
width: 100%;
}
}
}
&:hover {
@ -128,7 +129,7 @@
}
.article-owner {
font-size: .85em;
font-size: .72em;
}
.user-link-icons {

27
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.css

@ -3,7 +3,7 @@
background: white;
padding: 30px;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 14px; }
font-size: 16px; }
div.vs-blog a, div.vs-blog a:hover {
text-decoration: none;
color: #000000; }
@ -15,16 +15,17 @@
font-family: Helvetica, Arial, sans-serif;
font-weight: 700; }
div.vs-blog h1 {
font-size: 2.25em;
margin: 1rem 0 1.5rem; }
font-size: 2em;
margin: 1rem 0 1.5rem;
line-height: 1.25; }
div.vs-blog h2, div.vs-blog .tab-title {
font-size: 1.5em;
margin: 1.5rem 0 .75rem; }
div.vs-blog h3 {
font-size: 1.5em;
font-size: 1.25em;
margin: 1.5rem 0 .75rem; }
div.vs-blog h4 {
font-size: 1.25em;
font-size: 1.125em;
margin: 1.5rem 0 .75rem; }
div.vs-blog h5 {
font-size: 1em; }
@ -36,7 +37,8 @@
div.vs-blog img {
max-width: 100%; }
div.vs-blog input, div.vs-blog select, div.vs-blog textarea, div.vs-blog .form-control, div.vs-blog .btn {
border-radius: 0; }
border-radius: 0;
border-width: 1px 1px 2px 1px; }
div.vs-blog .navbar-toggler {
background: aqua; }
div.vs-blog .no-border {
@ -46,7 +48,7 @@
div.vs-blog .list-group .list-group-item {
position: relative;
display: block;
padding: 0 0 20px 0;
padding: 30px 0 30px;
background: none;
border-radius: 0;
border: 0; }
@ -54,7 +56,7 @@
background: none; }
div.vs-blog .list-group .list-group-item + .list-group-item {
border-top: 1px solid #f5f5f5;
padding: 20px 0; }
padding: 45px 0 30px; }
div.vs-blog .list-group .list-group-item h3 {
margin-top: 0; }
div.vs-blog .list-group.small-list .list-group-item {
@ -70,7 +72,7 @@
div.vs-blog .font-100 {
font-size: 1em; }
div.vs-blog .font-125 {
font-size: 1.25em; }
font-size: 1.125em; }
div.vs-blog .vs-blog-title {
padding-bottom: 15px;
margin-bottom: 25px;
@ -92,8 +94,9 @@
overflow: hidden; }
div.vs-blog .hero-section .hero-articles .hero-content h2 {
margin-top: .5rem;
font-size: 2em;
font-weight: bold; }
font-size: 1.75em;
font-weight: bold;
line-height: 1.25; }
div.vs-blog .hero-section .hero-articles .tags .tag {
background: rgba(208, 208, 208, 0.3);
color: #fff !important; }
@ -149,7 +152,7 @@
display: inline-block;
border-radius: 50%; }
div.vs-blog .article-owner {
font-size: .85em; }
font-size: .72em; }
div.vs-blog .user-link-icons {
position: absolute;
right: 18px;

2
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.min.css

File diff suppressed because one or more lines are too long

2
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Styles/blog.scss

@ -3,7 +3,7 @@ div.vs-blog {
background: white;
padding: 30px;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 14px;
font-size: 16px;
@import "bootstrap-overwrite.scss";
@import "header.scss";
@import "home.scss";

4
modules/blogging/src/Volo.Blogging.Web/compilerconfig.json

@ -10,5 +10,9 @@
{
"outputFile": "Pages/Blog/Posts/Index.css",
"inputFile": "Pages/Blog/Posts/Index.scss"
},
{
"outputFile": "Pages/Blogs/Shared/Styles/blog.css",
"inputFile": "Pages/Blogs/Shared/Styles/blog.scss"
}
]

5
modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.Designer.cs → modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200312050853_init.Designer.cs

@ -10,7 +10,7 @@ using VoloDocs.EntityFrameworkCore;
namespace VoloDocs.EntityFrameworkCore.Migrations
{
[DbContext(typeof(VoloDocsDbContext))]
[Migration("20200218014727_init")]
[Migration("20200312050853_init")]
partial class init
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -492,6 +492,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations
b.Property<DateTime>("LastCachedTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastSignificantUpdateTime")
.HasColumnType("datetime2");
b.Property<DateTime>("LastUpdatedTime")
.HasColumnType("datetime2");

1
modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200218014727_init.cs → modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/20200312050853_init.cs

@ -132,6 +132,7 @@ namespace VoloDocs.EntityFrameworkCore.Migrations
LocalDirectory = table.Column<string>(maxLength: 512, nullable: true),
CreationTime = table.Column<DateTime>(nullable: false),
LastUpdatedTime = table.Column<DateTime>(nullable: false),
LastSignificantUpdateTime = table.Column<DateTime>(nullable: true),
LastCachedTime = table.Column<DateTime>(nullable: false)
},
constraints: table =>

3
modules/docs/app/VoloDocs.EntityFrameworkCore/Migrations/VoloDocsDbContextModelSnapshot.cs

@ -490,6 +490,9 @@ namespace VoloDocs.EntityFrameworkCore.Migrations
b.Property<DateTime>("LastCachedTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastSignificantUpdateTime")
.HasColumnType("datetime2");
b.Property<DateTime>("LastUpdatedTime")
.HasColumnType("datetime2");

11
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -96,6 +96,7 @@ namespace Volo.Docs.Documents
{
leaf.CreationTime = documentUpdateInfo.CreationTime;
leaf.LastUpdatedTime = documentUpdateInfo.LastUpdatedTime;
leaf.LastSignificantUpdateTime = documentUpdateInfo.LastSignificantUpdateTime;
}
}
@ -189,12 +190,12 @@ namespace Volo.Docs.Documents
{
version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version;
async Task<DocumentWithDetailsDto> GetDocumentAsync()
async Task<DocumentWithDetailsDto> GetDocumentAsync(Document oldDocument = null)
{
Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the source...");
var source = _documentStoreFactory.Create(project.DocumentStoreType);
var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version);
var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version, oldDocument?.LastSignificantUpdateTime);
await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version);
await _documentRepository.InsertAsync(sourceDocument, true);
@ -206,7 +207,8 @@ namespace Volo.Docs.Documents
{
Name = sourceDocument.Name,
CreationTime = sourceDocument.CreationTime,
LastUpdatedTime = sourceDocument.LastUpdatedTime
LastUpdatedTime = sourceDocument.LastUpdatedTime,
LastSignificantUpdateTime = sourceDocument.LastSignificantUpdateTime
});
return CreateDocumentWithDetailsDto(project, sourceDocument);
@ -229,7 +231,7 @@ namespace Volo.Docs.Documents
//TODO: Configurable cache time?
document.LastCachedTime + TimeSpan.FromHours(2) < DateTime.Now)
{
return await GetDocumentAsync();
return await GetDocumentAsync(document);
}
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";
@ -238,6 +240,7 @@ namespace Volo.Docs.Documents
Name = document.Name,
CreationTime = document.CreationTime,
LastUpdatedTime = document.LastUpdatedTime,
LastSignificantUpdateTime = document.LastSignificantUpdateTime
});
return CreateDocumentWithDetailsDto(project, document);

2
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentUpdateInfo.cs

@ -10,5 +10,7 @@ namespace Volo.Docs.Documents
public virtual DateTime CreationTime { get; set; }
public virtual DateTime LastUpdatedTime { get; set; }
public DateTime? LastSignificantUpdateTime { get; set; }
}
}

2
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs

@ -26,6 +26,8 @@ namespace Volo.Docs.Documents
public virtual DateTime? LastUpdatedTime { get; set; }
public DateTime? LastSignificantUpdateTime { get; set; }
public bool IsSelected(string documentName)
{
if (documentName == null)

6
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/Document.cs

@ -34,6 +34,8 @@ namespace Volo.Docs.Documents
public virtual DateTime CreationTime { get; set; }
public virtual DateTime LastUpdatedTime { get; set; }
public virtual DateTime? LastSignificantUpdateTime { get; set; }
public virtual DateTime LastCachedTime { get; set; }
@ -60,7 +62,8 @@ namespace Volo.Docs.Documents
[NotNull] string localDirectory,
DateTime creationTime,
DateTime lastUpdatedTime,
DateTime lastCachedTime
DateTime lastCachedTime,
DateTime? lastSignificantUpdateTime = null
)
{
Id = id;
@ -80,6 +83,7 @@ namespace Volo.Docs.Documents
CreationTime = creationTime;
LastUpdatedTime = lastUpdatedTime;
LastCachedTime = lastCachedTime;
LastSignificantUpdateTime = lastSignificantUpdateTime;
Contributors = new List<DocumentContributor>();
ExtraProperties = new Dictionary<string, object>();

3
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentSource.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
@ -8,7 +9,7 @@ namespace Volo.Docs.Documents
{
public interface IDocumentSource : IDomainService
{
Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version);
Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version, DateTime? lastKnownSignificantUpdateTime = null);
Task<List<VersionInfo>> GetVersionsAsync(Project project);

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs

@ -16,7 +16,7 @@ namespace Volo.Docs.FileSystem.Documents
{
public const string Type = "FileSystem";
public async Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version)
public async Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version, DateTime? lastKnownSignificantUpdateTime = null)
{
var projectFolder = project.GetFileSystemPath();
var path = Path.Combine(projectFolder, languageCode, documentName);

81
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs

@ -21,13 +21,15 @@ namespace Volo.Docs.GitHub.Documents
public const string Type = "GitHub";
private readonly IGithubRepositoryManager _githubRepositoryManager;
private readonly IGithubPatchAnalyzer _githubPatchAnalyzer;
public GithubDocumentSource(IGithubRepositoryManager githubRepositoryManager)
public GithubDocumentSource(IGithubRepositoryManager githubRepositoryManager, IGithubPatchAnalyzer githubPatchAnalyzer)
{
_githubRepositoryManager = githubRepositoryManager;
_githubPatchAnalyzer = githubPatchAnalyzer;
}
public virtual async Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version)
public virtual async Task<Document> GetDocumentAsync(Project project, string documentName, string languageCode, string version, DateTime? lastKnownSignificantUpdateTime = null)
{
var token = project.GetGitHubAccessTokenOrNull();
var rootUrl = project.GetGitHubUrl(version);
@ -46,25 +48,38 @@ namespace Volo.Docs.GitHub.Documents
fileName = documentName.Substring(documentName.LastIndexOf('/') + 1);
}
var fileCommits = await GetFileCommitsAsync(project, version, $"docs/{languageCode}/{documentName}");
var fileCommits = await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName));
var documentCreationTime = fileCommits.LastOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue;
var lastSignificantUpdateTime = !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName ?
await GetLastSignificantUpdateTime(
fileCommits,
project,
project.GetGitHubInnerUrl(languageCode, documentName),
lastKnownSignificantUpdateTime,
documentCreationTime
) ?? lastKnownSignificantUpdateTime
: null;
var document= new Document(GuidGenerator.Create(),
project.Id,
documentName,
version,
var document = new Document(GuidGenerator.Create(),
project.Id,
documentName,
version,
languageCode,
fileName,
fileName,
await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent),
project.Format,
editLink,
project.Format,
editLink,
rootUrl,
rawRootUrl,
rawRootUrl,
localDirectory,
fileCommits.LastOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue,
documentCreationTime,
fileCommits.FirstOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue,
DateTime.Now);
DateTime.Now,
lastSignificantUpdateTime);
var authors = fileCommits
var authors = fileCommits
.Where(x => x.Author != null)
.Select(x => x.Author)
.GroupBy(x => x.Id)
@ -82,6 +97,42 @@ namespace Volo.Docs.GitHub.Documents
return document;
}
private async Task<DateTime?> GetLastSignificantUpdateTime(
IReadOnlyList<GitHubCommit> fileCommits,
Project project,
string fileName,
DateTime? lastKnownSignificantUpdateTime,
DateTime documentCreationTime)
{
if (!fileCommits.Any())
{
return null;
}
var fileCommitsAfterCreation = fileCommits.Take(fileCommits.Count - 1);
var commitsToEvaluate = (lastKnownSignificantUpdateTime != null
? fileCommitsAfterCreation.Where(c => c.Commit.Author.Date.DateTime > lastKnownSignificantUpdateTime)
: fileCommitsAfterCreation).Where(c => c.Commit.Author.Date.DateTime > DateTime.Now.AddDays(-14));
foreach (var gitHubCommit in commitsToEvaluate)
{
var fullCommit = await _githubRepositoryManager.GetSingleCommitsAsync(
GetOwnerNameFromUrl(project.GetGitHubUrl()),
GetRepositoryNameFromUrl(project.GetGitHubUrl()),
gitHubCommit.Sha,
project.GetGitHubAccessTokenOrNull());
if (_githubPatchAnalyzer.HasPatchSignificantChanges(fullCommit.Files.First(f => f.Filename == fileName).Patch))
{
return gitHubCommit.Commit.Author.Date.DateTime;
}
}
return null;
}
public async Task<List<VersionInfo>> GetVersionsAsync(Project project)
{
List<VersionInfo> versions;

121
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubPatchAnalyzer.cs

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Volo.Abp.Domain.Services;
namespace Volo.Docs.GitHub.Documents
{
public class GithubPatchAnalyzer : DomainService, IGithubPatchAnalyzer
{
public bool HasPatchSignificantChanges(string patch)
{
var changes = GetChanges(patch);
return IsChangesSignificant(changes);
}
private bool IsChangesSignificant(CommitChanges change)
{
if (CompareLineCount(change))
{
return true;
}
if (CompareWordCount(change))
{
return true;
}
if (CompareWords(change))
{
return true;
}
return false;
}
private static bool CompareLineCount(CommitChanges change)
{
return Math.Abs(change.NewLines.Count - change.OldLines.Count) >= 3;
}
private static bool CompareWordCount(CommitChanges change)
{
var wordCountInNewLines =
string.Join(" ", change.NewLines).Split(" ").Count(s => !string.IsNullOrWhiteSpace(s));
var wordCountInOldLines =
string.Join(" ", change.OldLines).Split(" ").Count(s => !string.IsNullOrWhiteSpace(s));
return Math.Abs(wordCountInNewLines - wordCountInOldLines) >= 15;
}
private bool CompareWords(CommitChanges change)
{
var wordsInNewLines = GetDistinctWordsFromLineList(change.NewLines);
var wordsInOldLines = GetDistinctWordsFromLineList(change.OldLines);
var differentWordsInNewLines = wordsInNewLines.Except(wordsInOldLines).Count();
var differentWordsInOldLines = wordsInOldLines.Except(wordsInNewLines).Count();
return differentWordsInNewLines + differentWordsInOldLines >= 10;
}
private CommitChanges GetChanges(string patch)
{
var changes = new List<CommitChanges>();
var pathSplited = patch.Split("@@");
var changeList = pathSplited.Where(s => !string.IsNullOrWhiteSpace(s)).Where((c, i) => i % 2 == 1).ToList();
foreach (var change in changeList)
{
var commitChange = new CommitChanges();
var lines = change.Split("\n");
commitChange.OldLines.AddRange(lines.Where(l => l.StartsWith("-")).Select(l => l.Substring(1)).Where(l => !string.IsNullOrWhiteSpace(l)));
commitChange.NewLines.AddRange(lines.Where(l => l.StartsWith("+")).Select(l => l.Substring(1)).Where(l => !string.IsNullOrWhiteSpace(l)));
changes.Add(commitChange);
}
return MergeChanges(changes);
}
private CommitChanges MergeChanges(List<CommitChanges> changes)
{
var mergedChanges = new CommitChanges();
foreach (var commitChanges in changes)
{
mergedChanges.NewLines.AddRange(commitChanges.NewLines);
mergedChanges.OldLines.AddRange(commitChanges.OldLines);
}
return mergedChanges;
}
private List<string> GetDistinctWordsFromLineList(List<string> lines)
{
return string.Join(" ", lines).Split(" ").Where(s => !string.IsNullOrWhiteSpace(s))
.Select(TrimAndRemovePunctuation).Distinct().ToList();
}
private string TrimAndRemovePunctuation(string str)
{
return new string(str.Trim().ToCharArray().Where(c => !char.IsPunctuation(c)).ToArray());
}
private class CommitChanges
{
public List<string> OldLines { get; set; }
public List<string> NewLines { get; set; }
public CommitChanges()
{
OldLines = new List<string>();
NewLines = new List<string>();
}
}
}
}

10
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs

@ -75,5 +75,15 @@ namespace Volo.Docs.GitHub.Documents
var request = new CommitRequest { Path = filename, Sha = version };
return await client.Repository.Commit.GetAll(repo.Id, request);
}
public async Task<GitHubCommit> GetSingleCommitsAsync(string name, string repositoryName, string sha, string token)
{
var client = token.IsNullOrWhiteSpace()
? new GitHubClient(new ProductHeaderValue(name))
: new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token)));
var repo = await client.Repository.Get(name, repositoryName);
return await client.Repository.Commit.Get(repo.Id, sha);
}
}
}

12
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubPatchAnalyzer.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Domain.Services;
namespace Volo.Docs.GitHub.Documents
{
public interface IGithubPatchAnalyzer : IDomainService
{
bool HasPatchSignificantChanges(string patch);
}
}

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/IGithubRepositoryManager.cs

@ -16,5 +16,7 @@ namespace Volo.Docs.GitHub.Documents
Task<IReadOnlyList<Release>> GetReleasesAsync(string name, string repositoryName, string token);
Task<IReadOnlyList<GitHubCommit>> GetFileCommitsAsync(string name, string repositoryName, string version, string filename, string token);
Task<GitHubCommit> GetSingleCommitsAsync(string name, string repositoryName, string sha, string token);
}
}

6
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Projects/ProjectGithubExtensions.cs

@ -14,6 +14,12 @@ namespace Volo.Docs.GitHub.Projects
return project.ExtraProperties["GitHubRootUrl"] as string;
}
public static string GetGitHubInnerUrl([NotNull] this Project project, string languageCode, string documentName)
{
return project
.GetGitHubUrl().Split("{version}")[1].EnsureEndsWith('/').TrimStart('/') + languageCode + '/' + documentName;
}
public static string GetGitHubUrl([NotNull] this Project project, string version)
{
return project

12
modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs

@ -127,16 +127,16 @@ namespace Volo.Docs.Areas.Documents.TagHelpers
if (!node.Path.IsNullOrWhiteSpace() && node.CreationTime.HasValue && node.LastUpdatedTime.HasValue)
{
var newBadge = "<span class='badge badge-primary ml-2' title=\"" + _localizer["NewExplanation"] + "\">" + _localizer["New"] + "</span>";
var updBadge = "<span class='badge badge-light ml-2' title=\"" + _localizer["UpdatedExplanation"] + "\">" + _localizer["Upd"] + "</span>";
if(node.CreationTime + TimeSpan.FromDays(14) > DateTime.Now)
{
badge = newBadge;
var newBadge = "<span class='badge badge-primary ml-2' title=\"" + _localizer["NewExplanation"] + "\">" + _localizer["New"] + "</span>";
badge += newBadge;
}
else if (node.LastUpdatedTime + TimeSpan.FromDays(14) > DateTime.Now)
if (node.LastSignificantUpdateTime != null && node.LastSignificantUpdateTime + TimeSpan.FromDays(14) > DateTime.Now)
{
badge = updBadge;
var updBadge = "<span class='badge badge-light ml-2' title=\"" + _localizer["UpdatedExplanation"] + "\">" + _localizer["Upd"] + "</span>";
badge += updBadge;
}
}

3
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -18,8 +18,7 @@
@{
ViewBag.FluidLayout = true;
Layout = ThemeManager.CurrentTheme.GetEmptyLayout();
@* PageLayout.Content.Title = Model.DocumentName?.Replace("-", " ");*@
ViewBag.Title = "Documentation Center";
PageLayout.Content.Title = Model.DocumentName?.Replace("-", " ");
}
@section styles {
<abp-style-bundle name="@typeof(IndexModel).FullName">

4
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js

@ -47,7 +47,7 @@
return;
}
var $targetElement = $(hash);
var $targetElement = $(decodeURIComponent(hash));
$targetElement = $targetElement.length ? $targetElement : $('[name=' + this.hash.slice(1) + ']');
@ -77,7 +77,7 @@
event.preventDefault();
var hash = this.hash;
$('html, body').animate({
scrollTop: $(hash).offset().top
scrollTop: $(decodeURIComponent(hash)).offset().top
}, 500, function () {
window.location.hash = hash;
});

23
modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs

@ -17,18 +17,15 @@ namespace Volo.Abp.FeatureManagement
{
protected FeatureManagementOptions Options { get; }
private readonly IFeatureManager _featureManager;
private readonly IFeatureDefinitionManager _featureDefinitionManager;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
protected IFeatureManager FeatureManager { get; }
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
public FeatureAppService(IFeatureManager featureManager,
IFeatureDefinitionManager featureDefinitionManager,
IStringLocalizerFactory stringLocalizerFactory,
IOptions<FeatureManagementOptions> options)
{
_featureManager = featureManager;
_featureDefinitionManager = featureDefinitionManager;
_stringLocalizerFactory = stringLocalizerFactory;
FeatureManager = featureManager;
FeatureDefinitionManager = featureDefinitionManager;
Options = options.Value;
}
@ -36,7 +33,7 @@ namespace Volo.Abp.FeatureManagement
{
await CheckProviderPolicy(providerName);
var featureDefinitions = _featureDefinitionManager.GetAll();
var featureDefinitions = FeatureDefinitionManager.GetAll();
var features = new List<FeatureDto>();
foreach (var featureDefinition in featureDefinitions)
@ -44,11 +41,11 @@ namespace Volo.Abp.FeatureManagement
features.Add(new FeatureDto
{
Name = featureDefinition.Name,
DisplayName = featureDefinition.DisplayName?.Localize(_stringLocalizerFactory),
DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory),
ValueType = featureDefinition.ValueType,
Description = featureDefinition.Description?.Localize(_stringLocalizerFactory),
Description = featureDefinition.Description?.Localize(StringLocalizerFactory),
ParentName = featureDefinition.Parent?.Name,
Value = await _featureManager.GetOrNullAsync(featureDefinition.Name, providerName, providerKey)
Value = await FeatureManager.GetOrNullAsync(featureDefinition.Name, providerName, providerKey)
});
}
@ -63,11 +60,11 @@ namespace Volo.Abp.FeatureManagement
foreach (var feature in input.Features)
{
await _featureManager.SetAsync(feature.Name, feature.Value, providerName, providerKey);
await FeatureManager.SetAsync(feature.Name, feature.Value, providerName, providerKey);
}
}
private void SetFeatureDepth(List<FeatureDto> features, string providerName, string providerKey,
protected virtual void SetFeatureDepth(List<FeatureDto> features, string providerName, string providerKey,
FeatureDto parentFeature = null, int depth = 0)
{
foreach (var feature in features)

6
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/DefaultValueFeatureManagementProvider.cs

@ -8,17 +8,17 @@ namespace Volo.Abp.FeatureManagement
{
public string Name => DefaultValueFeatureValueProvider.ProviderName;
public Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey)
public virtual Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey)
{
return Task.FromResult(feature.DefaultValue);
}
public Task SetAsync(FeatureDefinition feature, string value, string providerKey)
public virtual Task SetAsync(FeatureDefinition feature, string value, string providerKey)
{
throw new AbpException($"Can not set default value of a feature. It is only possible while defining the feature in a {typeof(IFeatureDefinitionProvider)} implementation.");
}
public Task ClearAsync(FeatureDefinition feature, string providerKey)
public virtual Task ClearAsync(FeatureDefinition feature, string providerKey)
{
throw new AbpException($"Can not clear default value of a feature. It is only possible while defining the feature in a {typeof(IFeatureDefinitionProvider)} implementation.");
}

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementProvider.cs

@ -14,7 +14,7 @@ namespace Volo.Abp.FeatureManagement
Store = store;
}
public async Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey)
public virtual async Task<string> GetOrNullAsync(FeatureDefinition feature, string providerKey)
{
return await Store.GetOrNullAsync(feature.Name, Name, NormalizeProviderKey(providerKey));
}

6
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManagementStore.cs

@ -21,13 +21,13 @@ namespace Volo.Abp.FeatureManagement
Cache = cache;
}
public async Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
public virtual async Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
{
var cacheItem = await GetCacheItemAsync(name, providerName, providerKey);
return cacheItem.Value;
}
public async Task SetAsync(string name, string value, string providerName, string providerKey)
public virtual async Task SetAsync(string name, string value, string providerName, string providerKey)
{
var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey);
if (featureValue == null)
@ -42,7 +42,7 @@ namespace Volo.Abp.FeatureManagement
}
}
public async Task DeleteAsync(string name, string providerName, string providerKey)
public virtual async Task DeleteAsync(string name, string providerName, string providerKey)
{
var featureValue = await FeatureValueRepository.FindAsync(name, providerName, providerKey);
if (featureValue != null)

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureStore.cs

@ -13,7 +13,7 @@ namespace Volo.Abp.FeatureManagement
FeatureManagementStore = featureManagementStore;
}
public Task<string> GetOrNullAsync(
public virtual Task<string> GetOrNullAsync(
string name,
string providerName,
string providerKey)

4
modules/feature-management/src/Volo.Abp.FeatureManagement.EntityFrameworkCore/Volo/Abp/FeatureManagement/EntityFrameworkCore/EfCoreFeatureValueRepository.cs

@ -15,7 +15,7 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore
{
}
public async Task<FeatureValue> FindAsync(string name, string providerName, string providerKey)
public virtual async Task<FeatureValue> FindAsync(string name, string providerName, string providerKey)
{
return await DbSet
.FirstOrDefaultAsync(
@ -23,7 +23,7 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore
);
}
public async Task<List<FeatureValue>> GetListAsync(string providerName, string providerKey)
public virtual async Task<List<FeatureValue>> GetListAsync(string providerName, string providerKey)
{
return await DbSet
.Where(

12
modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs

@ -8,21 +8,21 @@ namespace Volo.Abp.FeatureManagement
[Area("abp")]
public class FeaturesController : AbpController, IFeatureAppService
{
private readonly IFeatureAppService _featureAppService;
protected IFeatureAppService FeatureAppService { get; }
public FeaturesController(IFeatureAppService featureAppService)
{
_featureAppService = featureAppService;
FeatureAppService = featureAppService;
}
public Task<FeatureListDto> GetAsync(string providerName, string providerKey)
public virtual Task<FeatureListDto> GetAsync(string providerName, string providerKey)
{
return _featureAppService.GetAsync(providerName, providerKey);
return FeatureAppService.GetAsync(providerName, providerKey);
}
public Task UpdateAsync(string providerName, string providerKey, UpdateFeaturesDto input)
public virtual Task UpdateAsync(string providerName, string providerKey, UpdateFeaturesDto input)
{
return _featureAppService.UpdateAsync(providerName, providerKey, input);
return FeatureAppService.UpdateAsync(providerName, providerKey, input);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save