pull/3079/head^2
Armağan Ünlü 5 years ago
commit 460432271a

@ -171,4 +171,7 @@ Background job system is extensible and you can change the default background jo
See pre-built job manager alternatives: See pre-built job manager alternatives:
* [Hangfire Background Job Manager](Background-Jobs-Hangfire.md) * [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)

@ -1,3 +1,140 @@
# Background Workers # 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)

@ -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. * `--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: * `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. * `--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. * `ef`: Entity Framework Core.
* `mongodb`: MongoDB. * `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). Additional options: * `module`: [Module template](Startup-Templates/Module.md). Additional options:

@ -21,7 +21,7 @@ This approach has the following benefits:
However, there is a drawback: 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. 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.

@ -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: 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) * [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)

@ -200,7 +200,7 @@ public async Task<BookDto> GetAsync(Guid id)
### CRUD应用服务 ### 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`声明以下方法: `ICrudAppService`声明以下方法:
@ -291,7 +293,52 @@ public class BookAppService :
`CrudAppService`实现了`ICrudAppService`接口中声明的所有方法. 然后,你可以添加自己的自定义方法或覆盖和自定义实现. `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)的,它们会自动注册到依赖注入系统.

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

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

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

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

@ -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) 学习如何更改依赖模块的权限定义.

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

@ -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)

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

@ -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", "text": "CLI",
"path": "CLI.md" "path": "CLI.md"
@ -249,16 +276,7 @@
}, },
{ {
"text": "Tag Helpers", "text": "Tag Helpers",
"items":[ "path": "UI/AspNetCore/Tag-Helpers/Index.md"
{
"text": "在线演示",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "按钮",
"path": "UI/AspNetCore/Tag-Helpers/Buttons.md"
}
]
}, },
{ {
"text": "仪表板和小部件(Widget)系统", "text": "仪表板和小部件(Widget)系统",

@ -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;
}
}
}

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

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

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

@ -30,19 +30,11 @@ namespace Volo.Abp.Account
(await UserManager.CreateAsync(user, input.Password)).CheckErrors(); (await UserManager.CreateAsync(user, input.Password)).CheckErrors();
await UserManager.SetEmailAsync(user,input.EmailAddress); await UserManager.SetEmailAsync(user,input.EmailAddress);
await UserManager.AddDefaultRolesAsync(user);
await SetDefaultRolesAsync(user);
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(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() protected virtual async Task CheckSelfRegistrationAsync()
{ {
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))

@ -212,6 +212,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
CheckIdentityErrors(await UserManager.CreateAsync(user)); CheckIdentityErrors(await UserManager.CreateAsync(user));
CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress)); CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
CheckIdentityErrors(await UserManager.AddLoginAsync(user, info)); CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));
return user; return user;
} }

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -15,12 +16,17 @@ namespace Volo.Abp.Identity
{ {
public class IdentityUserManager : UserManager<IdentityUser>, IDomainService public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
{ {
protected IIdentityRoleRepository RoleRepository { get; }
protected IIdentityUserRepository UserRepository { get; }
protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token;
private readonly ICancellationTokenProvider _cancellationTokenProvider; private readonly ICancellationTokenProvider _cancellationTokenProvider;
public IdentityUserManager( public IdentityUserManager(
IdentityUserStore store, IdentityUserStore store,
IIdentityRoleRepository roleRepository,
IIdentityUserRepository userRepository,
IOptions<IdentityOptions> optionsAccessor, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher, IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators, IEnumerable<IUserValidator<IdentityUser>> userValidators,
@ -41,6 +47,8 @@ namespace Volo.Abp.Identity
services, services,
logger) logger)
{ {
RoleRepository = roleRepository;
UserRepository = userRepository;
_cancellationTokenProvider = cancellationTokenProvider; _cancellationTokenProvider = cancellationTokenProvider;
} }
@ -76,5 +84,20 @@ namespace Volo.Abp.Identity
return IdentityResult.Success; return IdentityResult.Success;
} }
public virtual async Task<IdentityResult> AddDefaultRolesAsync([NotNull] IdentityUser user)
{
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, CancellationToken);
foreach (var role in await RoleRepository.GetDefaultOnesAsync(cancellationToken: CancellationToken))
{
if (!user.IsInRole(role.Id))
{
user.AddRole(role.Id);
}
}
return await UpdateUserAsync(user);
}
} }
} }

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Shouldly; using Shouldly;
using Volo.Abp.Castle.DynamicProxy;
using Volo.Abp.Uow; using Volo.Abp.Uow;
using Xunit; using Xunit;
@ -43,7 +45,9 @@ namespace Volo.Abp.Identity
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin())
{ {
var user = await _identityUserRepository.FindByNormalizedUserNameAsync( var user = await _identityUserRepository.FindByNormalizedUserNameAsync(
_lookupNormalizer.NormalizeName("david")); _lookupNormalizer.NormalizeName("david")
);
user.ShouldNotBeNull(); user.ShouldNotBeNull();
var identityResult = await _identityUserManager.SetRolesAsync(user, new List<string>() var identityResult = await _identityUserManager.SetRolesAsync(user, new List<string>()
@ -83,5 +87,88 @@ namespace Volo.Abp.Identity
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
} }
[Fact]
public async Task AddDefaultRolesAsync_In_Same_Uow()
{
await CreateRandomDefaultRoleAsync();
using (var uow = _unitOfWorkManager.Begin())
{
var user = CreateRandomUser();
(await _identityUserManager.CreateAsync(user)).CheckErrors();
user.Roles.Count.ShouldBe(0);
await _identityUserManager.AddDefaultRolesAsync(user);
user.Roles.Count.ShouldBeGreaterThan(0);
foreach (var roleId in user.Roles.Select(r => r.RoleId))
{
var role = await _identityRoleRepository.GetAsync(roleId);
role.IsDefault.ShouldBe(true);
}
await uow.CompleteAsync();
}
}
[Fact]
public async Task AddDefaultRolesAsync_In_Different_Uow()
{
await CreateRandomDefaultRoleAsync();
Guid userId;
using (var uow = _unitOfWorkManager.Begin())
{
var user = CreateRandomUser();
userId = user.Id;
(await _identityUserManager.CreateAsync(user)).CheckErrors();
user.Roles.Count.ShouldBe(0);
await uow.CompleteAsync();
}
using (var uow = _unitOfWorkManager.Begin())
{
var user = await _identityUserManager.GetByIdAsync(userId);
await _identityUserManager.AddDefaultRolesAsync(user);
user.Roles.Count.ShouldBeGreaterThan(0);
foreach (var roleId in user.Roles.Select(r => r.RoleId))
{
var role = await _identityRoleRepository.GetAsync(roleId);
role.IsDefault.ShouldBe(true);
}
await uow.CompleteAsync();
}
}
private async Task CreateRandomDefaultRoleAsync()
{
await _identityRoleRepository.InsertAsync(
new IdentityRole(
Guid.NewGuid(),
Guid.NewGuid().ToString()
)
{
IsDefault = true
}
);
}
private static IdentityUser CreateRandomUser()
{
return new IdentityUser(
Guid.NewGuid(),
Guid.NewGuid().ToString(),
Guid.NewGuid().ToString() + "@abp.io"
);
}
} }
} }

Loading…
Cancel
Save