mirror of https://github.com/abpframework/abp.git
committed by
GitHub
302 changed files with 147114 additions and 439 deletions
@ -0,0 +1,455 @@ |
|||
# 文本模板 |
|||
|
|||
## 介绍 |
|||
|
|||
ABP框架提供了一个简单有效的文本模板系统,文本模板用于动态渲染基于模板和模型(数据对象)内容: |
|||
|
|||
***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** |
|||
|
|||
它非常类似于 ASP.NET Core Razor View (或 Page): |
|||
|
|||
*RAZOR VIEW (or PAGE) + MODEL ==render==> HTML CONTENT* |
|||
|
|||
你可以将渲染的输出用于任何目的,例如发送电子邮件或准备一些报告. |
|||
|
|||
### 示例 |
|||
|
|||
Here, a simple template: |
|||
|
|||
```` |
|||
Hello {%{{{model.name}}}%} :) |
|||
```` |
|||
|
|||
你可以定义一个含有 `Name` 属性的类来渲染这个模板: |
|||
|
|||
````csharp |
|||
public class HelloModel |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
```` |
|||
|
|||
如果你使用 `Name` 为 `John` 的 `HelloModel` 渲染模板,输出为: |
|||
|
|||
```` |
|||
Hello John :) |
|||
```` |
|||
|
|||
模板渲染引擎非常强大; |
|||
|
|||
* 它基于 [Scriban](https://github.com/lunet-io/scriban) 库, 所以它支持 **条件逻辑**, **循环** 等. |
|||
* 模板内容 **可以本地化**. |
|||
* 你可以定义 **布局模板** 在渲染其他模板中用做布局. |
|||
* 对于高级场景,你可以传递任何对象到模板上下文. |
|||
|
|||
### 源码 |
|||
|
|||
这里是本文开发和引用的[示例应用程序源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo). |
|||
|
|||
## 安装 |
|||
|
|||
推荐使用 [ABP CLI](CLI.md) 安装包. |
|||
|
|||
### 使用 ABP CLI |
|||
|
|||
在项目目录(.csproj file)打开命令行窗口运行以下命令: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.TextTemplating |
|||
```` |
|||
|
|||
### 手动安装 |
|||
|
|||
如果你想要手动安装; |
|||
|
|||
1. 添加 [Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) NuGet包到你的项目: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.TextTemplating |
|||
```` |
|||
|
|||
2. 添加 `AbpTextTemplatingModule` 到你的模块依赖列表: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
//...other dependencies |
|||
typeof(AbpTextTemplatingModule) //Add the new module dependency |
|||
)] |
|||
public class YourModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
## 定义模板 |
|||
|
|||
在渲染模板之前,需要定义它. 创建一个继承自 `TemplateDefinitionProvider` 的类: |
|||
|
|||
````csharp |
|||
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider |
|||
{ |
|||
public override void Define(ITemplateDefinitionContext context) |
|||
{ |
|||
context.Add( |
|||
new TemplateDefinition("Hello") //template name: "Hello" |
|||
.WithVirtualFilePath( |
|||
"/Demos/Hello/Hello.tpl", //template content path |
|||
isInlineLocalized: true |
|||
) |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `context` 对象用于添加新模板或获取依赖模块定义的模板. 使用 `context.Add(...)` 定义新模板. |
|||
* `TemplateDefinition` 是代表模板的类,每个模板必须有唯一的名称(在渲染模板时使用). |
|||
* `/Demos/Hello/Hello.tpl` 是模板文件的路径. |
|||
* `isInlineLocalized` 声明针对所有语言使用一个模板(`true` 还是针对每种语言使用不同的模板(`false`). 更多内容参阅下面的本地化部分. |
|||
|
|||
### 模板内容 |
|||
|
|||
`WithVirtualFilePath` 表示我们使用[虚拟文件系统](Virtual-File-System.md)存储模板内容. 在项目内创建一个 `Hello.tpl` 文件,并在属性窗口中将其标记为"**嵌入式资源**": |
|||
|
|||
 |
|||
|
|||
示例 `Hello.tpl` 内容如下所示: |
|||
|
|||
```` |
|||
Hello {%{{{model.name}}}%} :) |
|||
```` |
|||
|
|||
[虚拟文件系统](Virtual-File-System.md) 需要在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法添加你的文件: |
|||
|
|||
````csharp |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo"); |
|||
}); |
|||
```` |
|||
|
|||
* `TextTemplateDemoModule`是模块类. |
|||
* `TextTemplateDemo` 是你的项目的根命名空间. |
|||
|
|||
## 渲染模板 |
|||
|
|||
`ITemplateRenderer` 服务用于渲染模板内容. |
|||
|
|||
### 示例: 渲染一个简单的模板 |
|||
|
|||
````csharp |
|||
public class HelloDemo : ITransientDependency |
|||
{ |
|||
private readonly ITemplateRenderer _templateRenderer; |
|||
|
|||
public HelloDemo(ITemplateRenderer templateRenderer) |
|||
{ |
|||
_templateRenderer = templateRenderer; |
|||
} |
|||
|
|||
public async Task RunAsync() |
|||
{ |
|||
var result = await _templateRenderer.RenderAsync( |
|||
"Hello", //the template name |
|||
new HelloModel |
|||
{ |
|||
Name = "John" |
|||
} |
|||
); |
|||
|
|||
Console.WriteLine(result); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `HelloDemo` 是一个简单的类,在构造函数注入了 `ITemplateRenderer` 并在 `RunAsync` 方法中使用它. |
|||
* `RenderAsync` 有两个基本参数: |
|||
* `templateName`: 要渲染的模板名称 (本示例中是 `Hello`). |
|||
* `model`: 在模板内部用做 `model` 的对象 (本示例中是 `HelloModel` 对象). |
|||
|
|||
示例会返回以下结果: |
|||
|
|||
````csharp |
|||
Hello John :) |
|||
```` |
|||
|
|||
### 匿名模型 |
|||
|
|||
虽然建议为模板创建模型类,但在简单情况下使用匿名对象也是可行的: |
|||
|
|||
````csharp |
|||
var result = await _templateRenderer.RenderAsync( |
|||
"Hello", |
|||
new |
|||
{ |
|||
Name = "John" |
|||
} |
|||
); |
|||
```` |
|||
|
|||
示例中我们并没有创建模型类,但是创建了一个匿名对象模型. |
|||
|
|||
### 大驼峰 与 小驼峰 |
|||
|
|||
PascalCase 属性名(如 `UserName`) 在模板中用做小驼峰(如 `userName`). |
|||
|
|||
## 本地化 |
|||
|
|||
可以基于当前文化对模板内容进行本地化. 以下部分描述了两种类型的本地化选项. |
|||
|
|||
### 内联本地化 |
|||
|
|||
内联本地化使用[本地化系统](Localization.md)本地化模板内的文本. |
|||
|
|||
#### 示例: 重置密码链接 |
|||
|
|||
假设你需要向用户发送电子邮件重置密码. 模板内容: |
|||
|
|||
```` |
|||
<a href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword"}}}%}</a> |
|||
```` |
|||
|
|||
`L` 函数用于根据当前用户的文化来定位给定的Key,你需要在本地化文件中定义 `ResetMyPassword` 键: |
|||
|
|||
````json |
|||
"ResetMyPassword": "Click here to reset your password" |
|||
```` |
|||
|
|||
你还需要在模板定义提供程序类中声明要与此模板一起使用的本地化资源: |
|||
|
|||
````csharp |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
"PasswordReset", //Template name |
|||
typeof(DemoResource) //LOCALIZATION RESOURCE |
|||
).WithVirtualFilePath( |
|||
"/Demos/PasswordReset/PasswordReset.tpl", //template content path |
|||
isInlineLocalized: true |
|||
) |
|||
); |
|||
```` |
|||
|
|||
当你这样渲染模板时: |
|||
|
|||
````csharp |
|||
var result = await _templateRenderer.RenderAsync( |
|||
"PasswordReset", //the template name |
|||
new PasswordResetModel |
|||
{ |
|||
Link = "https://abp.io/example-link?userId=123&token=ABC" |
|||
} |
|||
); |
|||
```` |
|||
|
|||
你可以看到以下本地化结果: |
|||
|
|||
````csharp |
|||
<a href="https://abp.io/example-link?userId=123&token=ABC">Click here to reset your password</a> |
|||
```` |
|||
|
|||
> 如果你为应用程序定义了 [默认本地化资源](Localization.md), 则无需声明模板定义的资源类型. |
|||
|
|||
### 多个内容本地化 |
|||
|
|||
你可能希望为每种语言创建不同的模板文件,而不是使用本地化系统本地化单个模板. 如果模板对于特定的文化(而不是简单的文本本地化)应该是完全不同的,则可能需要使用它. |
|||
|
|||
#### 示例: 欢迎电子邮件模板 |
|||
|
|||
假设你要发送电子邮件欢迎用户,但要定义基于用户的文化完全不同的模板. |
|||
|
|||
首先创建一个文件夹,将模板放在里面,像 `en.tpl`, `tr.tpl` 每一个你支持的文化: |
|||
|
|||
 |
|||
|
|||
然后在模板定义提供程序类中添加模板定义: |
|||
|
|||
````csharp |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
name: "WelcomeEmail", |
|||
defaultCultureName: "en" |
|||
) |
|||
.WithVirtualFilePath( |
|||
"/Demos/WelcomeEmail/Templates", //template content folder |
|||
isInlineLocalized: false |
|||
) |
|||
); |
|||
```` |
|||
|
|||
* 设置 **默认文化名称**, 当没有所需的文化模板,回退到缺省文化. |
|||
* 指定 **模板文件夹** 而不是单个模板文件. |
|||
* 设置 `isInlineLocalized` 为 `false`. |
|||
|
|||
就这些,你可以渲染当前文化的模板: |
|||
|
|||
````csharp |
|||
var result = await _templateRenderer.RenderAsync("WelcomeEmail"); |
|||
```` |
|||
|
|||
> 为了简单我们跳过了模型,但是你可以使用前面所述的模型. |
|||
|
|||
### 指定文化 |
|||
|
|||
`ITemplateRenderer` 服务如果没有指定则使用当前文化 (`CultureInfo.CurrentUICulture`). 如果你需要你可以使用 `cultureName` 参数指定文化. |
|||
|
|||
````csharp |
|||
var result = await _templateRenderer.RenderAsync( |
|||
"WelcomeEmail", |
|||
cultureName: "en" |
|||
); |
|||
```` |
|||
|
|||
## 布局模板 |
|||
|
|||
布局模板用于在其他模板之间创建共享布局. 它类似于ASP.NET Core MVC / Razor Pages中的布局系统. |
|||
|
|||
### 示例: 邮件HTML布局模板 |
|||
|
|||
例如,你想为所有电子邮件模板创建一个布局. |
|||
|
|||
首先像之前一样创建一个模板文件: |
|||
|
|||
````xml |
|||
<!DOCTYPE html> |
|||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
</head> |
|||
<body> |
|||
{%{{{content}}}%} |
|||
</body> |
|||
</html> |
|||
```` |
|||
|
|||
* 布局模板必须具有 **{%{{{content}}}%}** 部分作为渲染的子内容的占位符. |
|||
|
|||
在模板定义提供程序中注册模板: |
|||
|
|||
````csharp |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
"EmailLayout", |
|||
isLayout: true //SET isLayout! |
|||
).WithVirtualFilePath( |
|||
"/Demos/EmailLayout/EmailLayout.tpl", |
|||
isInlineLocalized: true |
|||
) |
|||
); |
|||
```` |
|||
|
|||
现在你可以将此模板用作任何其他模板的布局: |
|||
|
|||
````csharp |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
name: "WelcomeEmail", |
|||
defaultCultureName: "en", |
|||
layout: "EmailLayout" //Set the LAYOUT |
|||
).WithVirtualFilePath( |
|||
"/Demos/WelcomeEmail/Templates", |
|||
isInlineLocalized: false |
|||
) |
|||
); |
|||
```` |
|||
|
|||
## 全局上下文 |
|||
|
|||
ABP传递 `model`,可用于访问模板内的模型. 如果需要,可以传递更多的全局变量. |
|||
|
|||
示例模板内容: |
|||
|
|||
```` |
|||
A global object value: {%{{{myGlobalObject}}}%} |
|||
```` |
|||
|
|||
模板假定它渲染上下文中的 `myGlobalObject` 对象. 你可以如下所示提供它: |
|||
|
|||
````csharp |
|||
var result = await _templateRenderer.RenderAsync( |
|||
"GlobalContextUsage", |
|||
globalContext: new Dictionary<string, object> |
|||
{ |
|||
{"myGlobalObject", "TEST VALUE"} |
|||
} |
|||
); |
|||
```` |
|||
|
|||
渲染的结果将是: |
|||
|
|||
```` |
|||
A global object value: TEST VALUE |
|||
```` |
|||
|
|||
## 高级功能 |
|||
|
|||
本节介绍文本模板系统的一些内部知识和高级用法. |
|||
|
|||
### 模板内容Provider |
|||
|
|||
`TemplateRenderer` 用于渲染模板,这是大多数情况下所需的模板. 但是你可以使用 `ITemplateContentProvider` 获取原始(未渲染的)模板内容. |
|||
|
|||
> `ITemplateRenderer` 内部使用 `ITemplateContentProvider` 获取原始模板内容. |
|||
|
|||
示例: |
|||
|
|||
````csharp |
|||
public class TemplateContentDemo : ITransientDependency |
|||
{ |
|||
private readonly ITemplateContentProvider _templateContentProvider; |
|||
|
|||
public TemplateContentDemo(ITemplateContentProvider templateContentProvider) |
|||
{ |
|||
_templateContentProvider = templateContentProvider; |
|||
} |
|||
|
|||
public async Task RunAsync() |
|||
{ |
|||
var result = await _templateContentProvider |
|||
.GetContentOrNullAsync("Hello"); |
|||
|
|||
Console.WriteLine(result); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
结果是原始模板内容: |
|||
|
|||
```` |
|||
Hello {%{{{model.name}}}%} :) |
|||
```` |
|||
|
|||
* `GetContentOrNullAsync` 如果没有为请求的模板定义任何内容,则返回 `null`. |
|||
* 它可以获取 `cultureName` 参数,如果模板针对不同的文化具有不同的文件,则可以使用该参数(请参见上面的"多内容本地化"部分). |
|||
|
|||
### 模板内容贡献者 |
|||
|
|||
`ITemplateContentProvider` 服务使用 `ITemplateContentContributor` 实现来查找模板内容. 有一个预实现的内容贡献者 `VirtualFileTemplateContentContributor`,它从上面描述的虚拟文件系统中获取模板内容. |
|||
|
|||
你可以实现 `ITemplateContentContributor` 从另一个源读取原始模板内容. |
|||
|
|||
示例: |
|||
|
|||
````csharp |
|||
public class MyTemplateContentProvider |
|||
: ITemplateContentContributor, ITransientDependency |
|||
{ |
|||
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context) |
|||
{ |
|||
var templateName = context.TemplateDefinition.Name; |
|||
|
|||
//TODO: Try to find content from another source |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
如果源无法找到内容, 则返回 `null`, `ITemplateContentProvider` 将回退到下一个贡献者. |
|||
|
|||
### Template Definition Manager |
|||
|
|||
`ITemplateDefinitionManager` 服务可用于获取模板定义(由模板定义提供程序创建). |
|||
|
|||
## 另请参阅 |
|||
|
|||
* 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo). |
|||
* [本地化系统](Localization.md). |
|||
* [虚拟文件系统](Virtual-File-System.md). |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"GivenTenantIsNotAvailable": "الجهة المحددة غير متاحة: {0}", |
|||
"Tenant": "الجهة", |
|||
"Switch": "تغيير", |
|||
"Name": "اسم", |
|||
"SwitchTenantHint": "اترك حقل الاسم فارغًا للتبديل إلى المضيف.", |
|||
"SwitchTenant": "تغيير الجهة", |
|||
"NotSelected": "غير محدد" |
|||
} |
|||
} |
|||
@ -1,21 +1,28 @@ |
|||
using Microsoft.AspNetCore.SignalR; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Security.Claims; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.Abp.AspNetCore.SignalR |
|||
{ |
|||
public class AbpSignalRUserIdProvider : IUserIdProvider, ITransientDependency |
|||
{ |
|||
public ICurrentUser CurrentUser { get; } |
|||
|
|||
public AbpSignalRUserIdProvider(ICurrentUser currentUser) |
|||
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; |
|||
|
|||
private readonly ICurrentUser _currentUser; |
|||
|
|||
public AbpSignalRUserIdProvider(ICurrentPrincipalAccessor currentPrincipalAccessor, ICurrentUser currentUser) |
|||
{ |
|||
CurrentUser = currentUser; |
|||
_currentPrincipalAccessor = currentPrincipalAccessor; |
|||
_currentUser = currentUser; |
|||
} |
|||
|
|||
public virtual string GetUserId(HubConnectionContext connection) |
|||
{ |
|||
return CurrentUser.Id?.ToString(); |
|||
using (_currentPrincipalAccessor.Change(connection.User)) |
|||
{ |
|||
return _currentUser.Id?.ToString(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,46 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Security.Claims |
|||
{ |
|||
public class AbpClaimsMapMiddleware : IMiddleware, ITransientDependency |
|||
{ |
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
var currentPrincipalAccessor = context.RequestServices |
|||
.GetRequiredService<ICurrentPrincipalAccessor>(); |
|||
|
|||
var mapOptions = context.RequestServices |
|||
.GetRequiredService<IOptions<AbpClaimsMapOptions>>().Value; |
|||
|
|||
var mapClaims = currentPrincipalAccessor |
|||
.Principal |
|||
.Claims |
|||
.Where(claim => mapOptions.Maps.Keys.Contains(claim.Type)); |
|||
|
|||
currentPrincipalAccessor |
|||
.Principal |
|||
.AddIdentity( |
|||
new ClaimsIdentity( |
|||
mapClaims |
|||
.Select( |
|||
claim => new Claim( |
|||
mapOptions.Maps[claim.Type](), |
|||
claim.Value, |
|||
claim.ValueType, |
|||
claim.Issuer |
|||
) |
|||
) |
|||
) |
|||
); |
|||
|
|||
await next(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Security.Claims |
|||
{ |
|||
public class AbpClaimsMapOptions |
|||
{ |
|||
public Dictionary<string, Func<string>> Maps { get; } |
|||
|
|||
public AbpClaimsMapOptions() |
|||
{ |
|||
Maps = new Dictionary<string, Func<string>>() |
|||
{ |
|||
{ "sub", () => AbpClaimTypes.UserId }, |
|||
{ "role", () => AbpClaimTypes.Role }, |
|||
{ "email", () => AbpClaimTypes.Email }, |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Quartz; |
|||
|
|||
namespace Volo.Abp.BackgroundJobs.Quartz |
|||
{ |
|||
public class AbpBackgroundJobQuartzOptions |
|||
{ |
|||
public int RetryCount { get; set; } |
|||
|
|||
public int RetryIntervalMillisecond { get; set; } |
|||
|
|||
|
|||
[NotNull] |
|||
public Func<int, IJobExecutionContext, JobExecutionException,Task> RetryStrategy |
|||
{ |
|||
get => _retryStrategy; |
|||
set => _retryStrategy = Check.NotNull(value, nameof(value)); |
|||
} |
|||
private Func<int, IJobExecutionContext, JobExecutionException,Task> _retryStrategy; |
|||
|
|||
public AbpBackgroundJobQuartzOptions() |
|||
{ |
|||
RetryCount = 3; |
|||
RetryIntervalMillisecond = 3000; |
|||
_retryStrategy = DefaultRetryStrategy; |
|||
} |
|||
|
|||
private async Task DefaultRetryStrategy(int retryIndex, IJobExecutionContext executionContext, JobExecutionException exception) |
|||
{ |
|||
exception.RefireImmediately = true; |
|||
|
|||
var retryCount = executionContext.JobDetail.JobDataMap.GetIntValue(QuartzBackgroundJobManager.JobDataPrefix+ nameof(RetryCount)); |
|||
if (retryIndex > retryCount) |
|||
{ |
|||
exception.RefireImmediately = false; |
|||
exception.UnscheduleAllTriggers = true; |
|||
return; |
|||
} |
|||
|
|||
var retryInterval = executionContext.JobDetail.JobDataMap.GetIntValue(QuartzBackgroundJobManager.JobDataPrefix+ nameof(RetryIntervalMillisecond)); |
|||
await Task.Delay(retryInterval); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Quartz; |
|||
|
|||
namespace Volo.Abp.BackgroundJobs.Quartz |
|||
{ |
|||
public static class QuartzBackgroundJobManageExtensions |
|||
{ |
|||
public static async Task<string> EnqueueAsync<TArgs>(this IBackgroundJobManager backgroundJobManager, |
|||
TArgs args, int retryCount, int retryIntervalMillisecond, |
|||
BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) |
|||
{ |
|||
if (backgroundJobManager is QuartzBackgroundJobManager quartzBackgroundJobManager) |
|||
{ |
|||
return await quartzBackgroundJobManager.ReEnqueueAsync(args, retryCount, retryIntervalMillisecond, |
|||
priority, delay); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Volo.Abp.BackgroundWorkers.Quartz |
|||
{ |
|||
public class AbpBackgroundWorkerQuartzOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Default : true.
|
|||
/// </summary>
|
|||
public bool IsAutoRegisterEnabled { get; set; } = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
namespace Volo.Abp.Cli.ProjectBuilding.Templates.Console |
|||
{ |
|||
public class ConsoleTemplate : ConsoleTemplateBase |
|||
{ |
|||
/// <summary>
|
|||
/// "console".
|
|||
/// </summary>
|
|||
public const string TemplateName = "console"; |
|||
|
|||
public ConsoleTemplate() |
|||
: base(TemplateName) |
|||
{ |
|||
DocumentUrl = CliConsts.DocsLink + "/en/abp/latest/Getting-Started-Console-Application"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding.Templates.Console |
|||
{ |
|||
public abstract class ConsoleTemplateBase : TemplateInfo |
|||
{ |
|||
protected ConsoleTemplateBase([NotNull] string name) : |
|||
base(name) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"MaxResultCountExceededExceptionMessage": "لا يمكن أن يكون {0} أكثر من {1}! قم بزيادة{2}. {3} على الخادم للسماح بمزيد من النتائج." |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"DisplayName:Abp.Mailing.DefaultFromAddress": "العنوان الإفتراضي", |
|||
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "اسم المرسل الإفتراضي", |
|||
"DisplayName:Abp.Mailing.Smtp.Host": "المضيف", |
|||
"DisplayName:Abp.Mailing.Smtp.Port": "المنفذ", |
|||
"DisplayName:Abp.Mailing.Smtp.UserName": "اسم المستخدم", |
|||
"DisplayName:Abp.Mailing.Smtp.Password": "كلمة المرور", |
|||
"DisplayName:Abp.Mailing.Smtp.Domain": "المجال", |
|||
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "تمكين SSL", |
|||
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "استخدام الصلاحيات الإفتراضية", |
|||
"Description:Abp.Mailing.DefaultFromAddress": "وصف العنوان الافتراضي", |
|||
"Description:Abp.Mailing.DefaultFromDisplayName": "وصف اسم المرسل الإفتراضي", |
|||
"Description:Abp.Mailing.Smtp.Host": "اسم أو عنوان IP للمضيف المستخدم في معاملات SMTP.", |
|||
"Description:Abp.Mailing.Smtp.Port": "المنفذ المستخدم في معاملات.", |
|||
"Description:Abp.Mailing.Smtp.UserName": "اسم المستخدم المرتبط ببيانات الاعتماد.", |
|||
"Description:Abp.Mailing.Smtp.Password": "كلمة المرور لاسم المستخدم المرتبط ببيانات الاعتماد.", |
|||
"Description:Abp.Mailing.Smtp.Domain": "اسم المجال أو الكمبيوتر الذي يتحقق من بيانات الاعتماد.", |
|||
"Description:Abp.Mailing.Smtp.EnableSsl": "ما إذا كان SmtpClient يستخدم (SSL) لتشفير الاتصال.", |
|||
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب." |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"DisplayName:Abp.Localization.DefaultLanguage": "اللغة الافتراضية", |
|||
"Description:Abp.Localization.DefaultLanguage": "اللغة الافتراضية للتطبيق." |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System; |
|||
using System.Collections.Specialized; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Quartz; |
|||
|
|||
namespace Volo.Abp.Quartz |
|||
{ |
|||
public class AbpQuartzOptions |
|||
{ |
|||
/// <summary>
|
|||
/// The quartz configuration. Available properties can be found within Quartz.Impl.StdSchedulerFactory.
|
|||
/// </summary>
|
|||
public NameValueCollection Properties { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// How long Quartz should wait before starting. Default: 0.
|
|||
/// </summary>
|
|||
public TimeSpan StartDelay { get; set; } |
|||
|
|||
[NotNull] |
|||
public Func<IScheduler, Task> StartSchedulerFactory |
|||
{ |
|||
get => _startSchedulerFactory; |
|||
set => _startSchedulerFactory = Check.NotNull(value, nameof(value)); |
|||
} |
|||
private Func<IScheduler, Task> _startSchedulerFactory; |
|||
|
|||
public AbpQuartzOptions() |
|||
{ |
|||
Properties = new NameValueCollection(); |
|||
StartDelay = new TimeSpan(0); |
|||
_startSchedulerFactory = StartSchedulerAsync; |
|||
} |
|||
|
|||
private async Task StartSchedulerAsync(IScheduler scheduler) |
|||
{ |
|||
if (StartDelay.Ticks > 0) |
|||
{ |
|||
await scheduler.StartDelayed(StartDelay); |
|||
} |
|||
else |
|||
{ |
|||
await scheduler.Start(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Specialized; |
|||
|
|||
namespace Volo.Abp.Quartz |
|||
{ |
|||
public class AbpQuartzPreOptions |
|||
{ |
|||
/// <summary>
|
|||
/// The quartz configuration. Available properties can be found within Quartz.Impl.StdSchedulerFactory.
|
|||
/// </summary>
|
|||
public NameValueCollection Properties { get; set; } |
|||
/// <summary>
|
|||
/// How long Quartz should wait before starting. Default: 0.
|
|||
/// </summary>
|
|||
public TimeSpan StartDelay { get; set; } |
|||
|
|||
public AbpQuartzPreOptions() |
|||
{ |
|||
Properties = new NameValueCollection(); |
|||
StartDelay = new TimeSpan(0); |
|||
} |
|||
} |
|||
} |
|||
@ -1,9 +1,12 @@ |
|||
using System.Security.Claims; |
|||
using System; |
|||
using System.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Security.Claims |
|||
{ |
|||
public interface ICurrentPrincipalAccessor |
|||
{ |
|||
ClaimsPrincipal Principal { get; } |
|||
|
|||
IDisposable Change(ClaimsPrincipal principal); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Localization; |
|||
using Scriban; |
|||
using Scriban.Runtime; |
|||
using Scriban.Syntax; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateLocalizer : IScriptCustomFunction |
|||
{ |
|||
private readonly IStringLocalizer _localizer; |
|||
|
|||
public TemplateLocalizer(IStringLocalizer localizer) |
|||
{ |
|||
_localizer = localizer; |
|||
} |
|||
|
|||
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, |
|||
ScriptBlockStatement blockStatement) |
|||
{ |
|||
return GetString(arguments); |
|||
} |
|||
|
|||
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, |
|||
ScriptBlockStatement blockStatement) |
|||
{ |
|||
return new ValueTask<object>(GetString(arguments)); |
|||
} |
|||
|
|||
private string GetString(ScriptArray arguments) |
|||
{ |
|||
if (arguments.IsNullOrEmpty()) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
var name = arguments[0]; |
|||
if (name == null || name.ToString().IsNullOrWhiteSpace()) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
var args = arguments.Skip(1).Where(x => x != null && !x.ToString().IsNullOrWhiteSpace()).ToArray(); |
|||
return args.Any() ? _localizer[name.ToString(), args] : _localizer[name.ToString()]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"Menu:Administration": "الإدارة" |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"'{0}' and '{1}' do not match.": "'{0}' and '{1}' do not match.", |
|||
"The {0} field is not a valid credit card number.": "The {0} field is not a valid credit card number.", |
|||
"{0} is not valid.": "{0} is not valid.", |
|||
"The {0} field is not a valid e-mail address.": "The {0} field is not a valid e-mail address.", |
|||
"The {0} field only accepts files with the following extensions: {1}": "The {0} field only accepts files with the following extensions: {1}", |
|||
"The field {0} must be a string or array type with a maximum length of '{1}'.": "The field {0} must be a string or array type with a maximum length of '{1}'.", |
|||
"The field {0} must be a string or array type with a minimum length of '{1}'.": "The field {0} must be a string or array type with a minimum length of '{1}'.", |
|||
"The {0} field is not a valid phone number.": "The {0} field is not a valid phone number.", |
|||
"The field {0} must be between {1} and {2}.": "The field {0} must be between {1} and {2}.", |
|||
"The field {0} must match the regular expression '{1}'.": "The field {0} must match the regular expression '{1}'.", |
|||
"The {0} field is required.": "The {0} field is required.", |
|||
"The field {0} must be a string with a maximum length of {1}.": "The field {0} must be a string with a maximum length of {1}.", |
|||
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", |
|||
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "The {0} field is not a valid fully-qualified http, https, or ftp URL.", |
|||
"The field {0} is invalid.": "The field {0} is invalid.", |
|||
"ThisFieldIsNotAValidCreditCardNumber.": "This field is not a valid credit card number.", |
|||
"ThisFieldIsNotValid.": "This field is not valid.", |
|||
"ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", |
|||
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}", |
|||
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", |
|||
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.", |
|||
"ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.", |
|||
"ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.", |
|||
"ThisFieldMustMatchTheRegularExpression{0}": "This field must match the regular expression '{0}'.", |
|||
"ThisFieldIsRequired.": "This field is required.", |
|||
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "This field must be a string with a maximum length of {0}.", |
|||
"ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "This field must be a string with a minimum length of {1} and a maximum length of {0}.", |
|||
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "This field is not a valid fully-qualified http, https, or ftp URL.", |
|||
"ThisFieldIsInvalid.": "This field is invalid." |
|||
} |
|||
} |
|||
@ -1,18 +1,35 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class ExceptionTestPage : AbpPageModel |
|||
{ |
|||
public void OnGetUserFriendlyException1() |
|||
public void OnGetUserFriendlyException_void() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public IActionResult OnGetUserFriendlyException2() |
|||
|
|||
public Task OnGetUserFriendlyException_Task() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public IActionResult OnGetUserFriendlyException_ActionResult() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public JsonResult OnGetUserFriendlyException_JsonResult() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public Task<JsonResult> OnGetUserFriendlyException_Task_JsonResult() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"BirthDate": "تاريخ الميلاد", |
|||
"Value1": "القيمة الأولى" |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Security.Claims; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Shouldly; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Security.Claims |
|||
{ |
|||
public class ClaimsMapTestController : AbpController |
|||
{ |
|||
public ActionResult ClaimsMapTest() |
|||
{ |
|||
var serialNumber = CurrentUser.FindClaim(ClaimTypes.SerialNumber); |
|||
serialNumber.ShouldNotBeNull(); |
|||
serialNumber.Value.ShouldBe("123456"); |
|||
|
|||
var dateOfBirth = CurrentUser.FindClaim(ClaimTypes.DateOfBirth); |
|||
dateOfBirth.ShouldNotBeNull(); |
|||
dateOfBirth.Value.ShouldBe("2020"); |
|||
|
|||
return Content("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.Mvc.Authorization; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.MemoryDb; |
|||
using Volo.Abp.Modularity; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Security.Claims |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpMemoryDbTestModule), |
|||
typeof(AbpAspNetCoreMvcModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class ClaimsMapTestController_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly FakeUserClaims _fakeRequiredService; |
|||
|
|||
public ClaimsMapTestController_Tests() |
|||
{ |
|||
_fakeRequiredService = GetRequiredService<FakeUserClaims>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Claims_Should_Be_Mapped() |
|||
{ |
|||
_fakeRequiredService.Claims.AddRange(new[] |
|||
{ |
|||
new Claim("SerialNumber", "123456"), |
|||
new Claim("DateOfBirth", "2020") |
|||
}); |
|||
|
|||
var result = await GetResponseAsStringAsync("/ClaimsMapTest/ClaimsMapTest"); |
|||
result.ShouldBe("OK"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static void Main(string[] args) |
|||
{ |
|||
CreateHostBuilder(args).Build().Run(); |
|||
} |
|||
|
|||
public static IHostBuilder CreateHostBuilder(string[] args) => |
|||
Host.CreateDefaultBuilder(args) |
|||
.ConfigureWebHostDefaults(webBuilder => |
|||
{ |
|||
webBuilder.UseStartup<Startup>(); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:56574", |
|||
"sslPort": 44374 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.TestBase\Volo.Abp.AspNetCore.TestBase.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Tests\Volo.Abp.AspNetCore.Tests.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared |
|||
{ |
|||
public class AbpAspNetCoreMvcUiThemeSharedTestBase : AbpAspNetCoreTestBase<Startup> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpAspNetCoreMvcUiThemeSharedModule) |
|||
)] |
|||
public class AbpAspNetCoreMvcUiThemeSharedTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Shared.Components.AbpPageToolbar.Button; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.PageToolbars; |
|||
using Volo.Abp.Localization; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.PageToolbars |
|||
{ |
|||
public class PageToolbar_Tests : AbpAspNetCoreMvcUiThemeSharedTestBase |
|||
{ |
|||
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) |
|||
{ |
|||
services.Configure<AbpPageToolbarOptions>(options => |
|||
{ |
|||
options.Configure("TestPage1", toolbar => |
|||
{ |
|||
toolbar.Contributors.Add(new MyToolbarContributor()); |
|||
toolbar.Contributors.Add(new MyToolbarContributor2()); |
|||
toolbar.AddComponent<MyPageComponent5>(); |
|||
toolbar.AddButton(new FixedLocalizableString("My button"), order: -1); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AbpPageToolbarOptions_Should_Contain_Contributors() |
|||
{ |
|||
var options = GetRequiredService<IOptions<AbpPageToolbarOptions>>().Value; |
|||
options.Toolbars.Count.ShouldBe(1); |
|||
options.Toolbars.ShouldContainKey("TestPage1"); |
|||
options.Toolbars["TestPage1"].Contributors.Count.ShouldBe(4); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task PageToolbarManager_Should_Return_ToolbarItems() |
|||
{ |
|||
var pageToolbarManager = GetRequiredService<IPageToolbarManager>(); |
|||
var items = await pageToolbarManager.GetItemsAsync("TestPage1"); |
|||
items.Length.ShouldBe(5); |
|||
items[0].ComponentType.ShouldBe(typeof(AbpPageToolbarButtonViewComponent)); |
|||
items[1].ComponentType.ShouldBe(typeof(MyPageComponent2)); |
|||
items[2].ComponentType.ShouldBe(typeof(MyPageComponent3)); |
|||
items[3].ComponentType.ShouldBe(typeof(MyPageComponent4)); |
|||
items[4].ComponentType.ShouldBe(typeof(MyPageComponent5)); |
|||
} |
|||
|
|||
public class MyToolbarContributor : IPageToolbarContributor |
|||
{ |
|||
public Task ContributeAsync(PageToolbarContributionContext context) |
|||
{ |
|||
context.Items.Add(new PageToolbarItem(typeof(MyPageComponent1))); |
|||
context.Items.Add(new PageToolbarItem(typeof(MyPageComponent2))); |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
public class MyToolbarContributor2 : IPageToolbarContributor |
|||
{ |
|||
public Task ContributeAsync(PageToolbarContributionContext context) |
|||
{ |
|||
context.Items.RemoveAll(i => i.ComponentType == typeof(MyPageComponent1)); |
|||
context.Items.Add(new PageToolbarItem(typeof(MyPageComponent3))); |
|||
context.Items.Add(new PageToolbarItem(typeof(MyPageComponent4))); |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
public class MyPageComponent1 : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult InvokeAsync() |
|||
{ |
|||
return Content("MyPageComponent1"); |
|||
} |
|||
} |
|||
|
|||
public class MyPageComponent2 : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult InvokeAsync() |
|||
{ |
|||
return Content("MyPageComponent2"); |
|||
} |
|||
} |
|||
|
|||
public class MyPageComponent3 : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult InvokeAsync() |
|||
{ |
|||
return Content("MyPageComponent3"); |
|||
} |
|||
} |
|||
|
|||
public class MyPageComponent4 : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult InvokeAsync() |
|||
{ |
|||
return Content("MyPageComponent4"); |
|||
} |
|||
} |
|||
|
|||
public class MyPageComponent5 : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult InvokeAsync() |
|||
{ |
|||
return Content("MyPageComponent4"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared |
|||
{ |
|||
public class Startup |
|||
{ |
|||
public void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddApplication<AbpAspNetCoreMvcUiThemeSharedTestModule>(); |
|||
} |
|||
|
|||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) |
|||
{ |
|||
app.InitializeApplication(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"hello": "مرحبا" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"USA": "الولايات المتحدة الامريكية", |
|||
"Brazil": "البرازيل" |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"ThisFieldIsRequired": "الحقل مطلوب", |
|||
"MaxLenghtErrorMessage": "اقصى طول للحقل '{0}' حرف" |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"Hello <b>{0}</b>.": "مرحباً <b>{0}</b>.", |
|||
"Car": "سيارة", |
|||
"CarPlural": "سيارات", |
|||
"MaxLenghtErrorMessage": "اقصى طول للحقل '{0}' حرف", |
|||
"Universe": "عالم", |
|||
"FortyTwo": "اثنان وأربعون" |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"SeeYou": "الى لقاء" |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System.Collections.Generic; |
|||
using System.Security.Claims; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims |
|||
{ |
|||
public class CurrentPrincipalAccessor_Test : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; |
|||
|
|||
public CurrentPrincipalAccessor_Test() |
|||
{ |
|||
_currentPrincipalAccessor = GetRequiredService<ICurrentPrincipalAccessor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Get_Changed_Principal_If() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> |
|||
{ |
|||
new Claim(ClaimTypes.Name,"bob"), |
|||
new Claim(ClaimTypes.NameIdentifier,"123456") |
|||
})); |
|||
|
|||
var claimsPrincipal2 = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> |
|||
{ |
|||
new Claim(ClaimTypes.Name,"lee"), |
|||
new Claim(ClaimTypes.NameIdentifier,"654321") |
|||
})); |
|||
|
|||
|
|||
_currentPrincipalAccessor.Principal.ShouldBe(null); |
|||
|
|||
using (_currentPrincipalAccessor.Change(claimsPrincipal)) |
|||
{ |
|||
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal); |
|||
|
|||
using (_currentPrincipalAccessor.Change(claimsPrincipal2)) |
|||
{ |
|||
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal2); |
|||
} |
|||
|
|||
_currentPrincipalAccessor.Principal.ShouldBe(claimsPrincipal); |
|||
} |
|||
_currentPrincipalAccessor.Principal.ShouldBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,6 +1,7 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"HelloText": "Hello" |
|||
} |
|||
} |
|||
"HelloText": "Hello {0}", |
|||
"HowAreYou": "how are you?" |
|||
} |
|||
} |
|||
|
|||
@ -1,6 +1,7 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"HelloText": "Merhaba" |
|||
} |
|||
} |
|||
"HelloText": "Merhaba {0}", |
|||
"HowAreYou": "nasılsın?" |
|||
} |
|||
} |
|||
|
|||
@ -1 +1 @@ |
|||
{{L "HelloText"}}. Please click to the following link to get an email to reset your password! |
|||
{{L "HelloText" model.name}}, {{L "HowAreYou" }}. Please click to the following link to get an email to reset your password! |
|||
@ -0,0 +1,45 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"UserName": "اسم المستخدم", |
|||
"EmailAddress": "البريد الإلكتروني", |
|||
"UserNameOrEmailAddress": "اسم المستخدم أو البريد الإلكتروني", |
|||
"Password": "كلمة المرور", |
|||
"RememberMe": "تذكرني", |
|||
"UseAnotherServiceToLogin": "استخدم خدمة أخرى لتسجيل الدخول", |
|||
"UserLockedOutMessage": "تم قفل حساب المستخدم بسبب محاولات تسجيل الدخول غير الصالحة. يرجى الانتظار بعض الوقت والمحاولة مرة أخرى.", |
|||
"InvalidUserNameOrPassword": "اسم مستخدم أو كلمة مرور غير صالحة!", |
|||
"LoginIsNotAllowed": "غير مسموح لك بتسجيل الدخول! أنت بحاجة إلى تأكيد بريدك الإلكتروني / رقم هاتفك.", |
|||
"SelfRegistrationDisabledMessage": "تم تعطيل التسجيل الذاتي لهذا التطبيق. يرجى الاتصال بمسؤول التطبيق لتسجيل مستخدم جديد.", |
|||
"LocalLoginDisabledMessage": "تسجيل الدخول المحلي معطّل لهذا التطبيق.", |
|||
"Login": "دخول", |
|||
"Cancel": "إلغاء", |
|||
"Register": "تسجيل", |
|||
"AreYouANewUser": "هل أنت مستخدم جديد?", |
|||
"AlreadyRegistered": "مسجل بالفعل?", |
|||
"InvalidLoginRequest": "طلب تسجيل دخول غير صالح", |
|||
"ThereAreNoLoginSchemesConfiguredForThisClient": "لم يتم تكوين أنظمة تسجيل دخول لهذا العميل.", |
|||
"LogInUsingYourProviderAccount": "قم بتسجيل الدخول باستخدام حسابك في {0}", |
|||
"DisplayName:CurrentPassword": "كلمة المرور الحالية", |
|||
"DisplayName:NewPassword": "كلمة مرور جديدة", |
|||
"DisplayName:NewPasswordConfirm": "تأكيد كلمة المرور الجديدة", |
|||
"PasswordChangedMessage": "تم تغيير كلمة مرورك بنجاح.", |
|||
"DisplayName:UserName": "اسم المستخدم", |
|||
"DisplayName:Email": "البريد الإلكتروني", |
|||
"DisplayName:Name": "الاسم", |
|||
"DisplayName:Surname": "اللقب", |
|||
"DisplayName:Password": "كلمة المرور", |
|||
"DisplayName:EmailAddress": "البريد الإلكتروني", |
|||
"DisplayName:PhoneNumber": "رقم الهاتف", |
|||
"PersonalSettings": "الإعدادات الشخصية", |
|||
"PersonalSettingsSaved": "تم حفظ الإعدادات الشخصية", |
|||
"PasswordChanged": "تم تغيير كلمة المرور", |
|||
"NewPasswordConfirmFailed": "يرجى تأكيد كلمة المرور الجديدة.", |
|||
"Manage": "إدارة", |
|||
"ManageYourProfile": "إدارة ملف التعريف الخاص بك", |
|||
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "هل تم تمكين التسجيل الذاتي", |
|||
"Description:Abp.Account.IsSelfRegistrationEnabled": "ما إذا كان يمكن للمستخدم تسجيل الحساب بنفسه أم لا.", |
|||
"DisplayName:Abp.Account.EnableLocalLogin": "المصادقة باستخدام حساب محلي", |
|||
"Description:Abp.Account.EnableLocalLogin": "يشير إلى ما إذا كان الخادم سيسمح للمستخدمين بالمصادقة باستخدام حساب محلي." |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue