mirror of https://github.com/abpframework/abp.git
172 changed files with 4510 additions and 233 deletions
@ -1,3 +0,0 @@ |
|||
## AutoMapper Integration |
|||
|
|||
TODO |
|||
@ -0,0 +1,267 @@ |
|||
# 对象扩展 |
|||
|
|||
ABP框架提供了 **实体扩展系统** 允许你 **添加额外属性** 到已存在的对象 **无需修改相关类**. 它允许你扩展[应用程序依赖模块](Modules/Index.md)实现的功能,尤其是当你要扩展[模块定义的实体](Customizing-Application-Modules-Extending-Entities.md)和[DTO](Customizing-Application-Modules-Overriding-Services.md)时. |
|||
|
|||
> 你自己的对象通常不需要对象扩展系统,因为你可以轻松的添加常规属性到你的类中. |
|||
|
|||
## IHasExtraProperties 接口 |
|||
|
|||
这是一个使类可扩展的接口. 它定义了 `Dictionary` 属性: |
|||
|
|||
````csharp |
|||
Dictionary<string, object> ExtraProperties { get; } |
|||
```` |
|||
|
|||
然后你可以使用此字典添加或获取其他属性. |
|||
|
|||
### 基类 |
|||
|
|||
默认以下基类实现了 `IHasExtraProperties` 接口: |
|||
|
|||
* 由 `AggregateRoot` 类实现 (参阅 [entities](Entities.md)). |
|||
* 由 `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... [DTO](Data-Transfer-Objects.md)基类实现. |
|||
* 由 `ExtensibleObject` 实现, 它是一个简单的基类,任何类型的对象都可以继承. |
|||
|
|||
如果你的类从这些类继承,那么你的类也是可扩展的,如果没有,你也可以随时手动继承. |
|||
|
|||
### 基本扩展方法 |
|||
|
|||
虽然可以直接使用类的 `ExtraProperties` 属性,但建议使用以下扩展方法使用额外属性. |
|||
|
|||
#### SetProperty |
|||
|
|||
用于设置额外属性值: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title"); |
|||
user.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
`SetProperty` 返回相同的对象, 你可以使用链式编程: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title") |
|||
.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
#### GetProperty |
|||
|
|||
用于读取额外属性的值: |
|||
|
|||
````csharp |
|||
var title = user.GetProperty<string>("Title"); |
|||
|
|||
if (user.GetProperty<bool>("IsSuperUser")) |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
* `GetProperty` 是一个泛型方法,对象类型做为泛型参数. |
|||
* 如果未设置给定的属性,则返回默认值 (`int` 的默认值为 `0` , `bool` 的默认值是 `false` ... 等). |
|||
|
|||
##### 非基本属性类型 |
|||
|
|||
如果您的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`. |
|||
|
|||
#### HasProperty |
|||
|
|||
用于检查对象之前是否设置了属性. |
|||
|
|||
#### RemoveProperty |
|||
|
|||
用于从对象中删除属性. 使用此方法代替为属性设置 `null` 值. |
|||
|
|||
### 一些最佳实践 |
|||
|
|||
为属性名称使用魔术字符串很危险,因为你很容易输入错误的属性名称-这并不安全; |
|||
|
|||
* 为你的额外属性名称定义一个常量. |
|||
* 使用扩展方法轻松设置你的属性. |
|||
|
|||
示例: |
|||
|
|||
````csharp |
|||
public static class IdentityUserExtensions |
|||
{ |
|||
private const string TitlePropertyName = "Title"; |
|||
|
|||
public static void SetTitle(this IdentityUser user, string title) |
|||
{ |
|||
user.SetProperty(TitlePropertyName, title); |
|||
} |
|||
|
|||
public static string GetTitle(this IdentityUser user) |
|||
{ |
|||
return user.GetProperty<string>(TitlePropertyName); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
然后, 你可以很容易地设置或获取 `Title` 属性: |
|||
|
|||
````csharp |
|||
user.SetTitle("My Title"); |
|||
var title = user.GetTitle(); |
|||
```` |
|||
|
|||
## Object Extension Manager |
|||
|
|||
你可以为可扩展对象(实现 `IHasExtraProperties`接口)设置任意属性, `ObjectExtensionManager` 用于显式定义可扩展类的其他属性. |
|||
|
|||
显式定义额外的属性有一些用例: |
|||
|
|||
* 允许控制如何在对象到对象的映射上处理额外的属性 (参阅下面的部分). |
|||
* 允许定义属性的元数据. 例如你可以在使用[EF Core](Entity-Framework-Core.md)时将额外的属性映射到数据库中的表字段. |
|||
|
|||
> `ObjectExtensionManager` 实现单例模式 (`ObjectExtensionManager.Instance`) ,你应该在应用程序启动之前定义对象扩展. [应用程序启动模板](Startup-Templates/Application.md) 有一些预定义的静态类,可以安全在内部定义对象扩展. |
|||
|
|||
### AddOrUpdate |
|||
|
|||
`AddOrUpdate` 是定义对象额外属性或更新对象额外属性的主要方法. |
|||
|
|||
示例: 为 `IdentityUser` 实体定义额外属性: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdate<IdentityUser>(options => |
|||
{ |
|||
options.AddOrUpdateProperty<string>("SocialSecurityNumber"); |
|||
options.AddOrUpdateProperty<bool>("IsSuperUser"); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
### AddOrUpdateProperty |
|||
|
|||
虽然可以如上所示使用 `AddOrUpdateProperty`, 但如果要定义单个额外的属性,也可以使用快捷的扩展方法: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber"); |
|||
```` |
|||
|
|||
有时将单个额外属性定义为多种类型是可行的. 你可以使用以下代码,而不是一个一个地定义: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<string>( |
|||
new[] |
|||
{ |
|||
typeof(IdentityUserDto), |
|||
typeof(IdentityUserCreateDto), |
|||
typeof(IdentityUserUpdateDto) |
|||
}, |
|||
"SocialSecurityNumber" |
|||
); |
|||
```` |
|||
|
|||
#### 属性配置 |
|||
|
|||
`AddOrUpdateProperty` 还可以为属性定义执行其他配置的操作. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
}); |
|||
```` |
|||
|
|||
> 参阅 "对象到对象映射" 部分了解 `CheckPairDefinitionOnMapping` 选项. |
|||
|
|||
`options` 有一个名为 `Configuration` 的字典,该字典存储对象扩展定义甚至可以扩展. EF Core使用它来将其他属性映射到数据库中的表字段. 请参阅[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md). |
|||
|
|||
## 对象到对象映射 |
|||
|
|||
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用. |
|||
|
|||
本节提供了一些**好的做法**,可以控制对象映射的额外属性。 |
|||
|
|||
### MapExtraPropertiesTo |
|||
|
|||
`MapExtraPropertiesTo` 是ABP框架提供的扩展方法,用于以受控方式将额外的属性从一个对象复制到另一个对象. 示例: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo(identityUserDto); |
|||
```` |
|||
|
|||
`MapExtraPropertiesTo` 需要在**两侧**(本例中是`IdentityUser` 和 `IdentityUserDto`)**定义属性**. 以将值复制到目标对象. 否则即使源对象(在此示例中为 `identityUser` )中确实存在该值,它也不会复制. 有一些重载此限制的方法. |
|||
|
|||
#### MappingPropertyDefinitionChecks |
|||
|
|||
`MapExtraPropertiesTo` 获取一个附加参数来控制单个映射操作的定义检查: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
MappingPropertyDefinitionChecks.None |
|||
); |
|||
```` |
|||
|
|||
> 要小心,因为 `MappingPropertyDefinitionChecks.None` 会复制所有的额外属性而不进行任何检查. `MappingPropertyDefinitionChecks` 枚举还有其他成员. |
|||
|
|||
如果要完全禁用属性的定义检查,可以在定义额外的属性(或更新现有定义)时进行,如下所示: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
}); |
|||
```` |
|||
|
|||
#### 忽略属性 |
|||
|
|||
你可能要在映射操作忽略某些属性: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
ignoredProperties: new[] {"MySensitiveProp"} |
|||
); |
|||
```` |
|||
|
|||
忽略的属性不会复制到目标对象. |
|||
|
|||
#### AutoMapper集成 |
|||
|
|||
如果您使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法. |
|||
|
|||
你可以在映射配置文件中使用 `MapExtraProperties()` 方法. |
|||
|
|||
````csharp |
|||
public class MyProfile : Profile |
|||
{ |
|||
public MyProfile() |
|||
{ |
|||
CreateMap<IdentityUser, IdentityUserDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
它与 `MapExtraPropertiesTo()` 方法具有相同的参数。 |
|||
|
|||
## Entity Framework Core 数据库映射 |
|||
|
|||
如果你使用的是EF Core,可以将额外的属性映射到数据库中的表字段. 例: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.MapEfCore(b => b.HasMaxLength(32)); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
参阅 [Entity Framework Core 集成文档](Entity-Framework-Core.md) 了解更多内容. |
|||
@ -0,0 +1,3 @@ |
|||
# ABP ASP.NET Core UI Datatables.Net 集成 |
|||
|
|||
TODO |
|||
File diff suppressed because it is too large
@ -0,0 +1,94 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.ApiExplorer; |
|||
using Microsoft.AspNetCore.Mvc.Formatters; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ApiExploring |
|||
{ |
|||
public class AbpRemoteServiceApiDescriptionProvider : IApiDescriptionProvider, ITransientDependency |
|||
{ |
|||
private readonly IModelMetadataProvider _modelMetadataProvider; |
|||
private readonly MvcOptions _mvcOptions; |
|||
private readonly AbpRemoteServiceApiDescriptionProviderOptions _options; |
|||
|
|||
public AbpRemoteServiceApiDescriptionProvider( |
|||
IModelMetadataProvider modelMetadataProvider, |
|||
IOptions<MvcOptions> mvcOptionsAccessor, |
|||
IOptions<AbpRemoteServiceApiDescriptionProviderOptions> optionsAccessor) |
|||
{ |
|||
_modelMetadataProvider = modelMetadataProvider; |
|||
_mvcOptions = mvcOptionsAccessor.Value; |
|||
_options = optionsAccessor.Value; |
|||
} |
|||
|
|||
public void OnProvidersExecuted(ApiDescriptionProviderContext context) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The order -999 ensures that this provider is executed right after the
|
|||
/// Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider.
|
|||
/// </summary>
|
|||
public int Order => -999; |
|||
|
|||
public void OnProvidersExecuting(ApiDescriptionProviderContext context) |
|||
{ |
|||
foreach (var apiResponseType in GetApiResponseTypes()) |
|||
{ |
|||
foreach (var result in context.Results.Where(x => IsRemoteService(x.ActionDescriptor))) |
|||
{ |
|||
var actionProducesResponseTypeAttributes = |
|||
ReflectionHelper.GetAttributesOfMemberOrDeclaringType<ProducesResponseTypeAttribute>( |
|||
result.ActionDescriptor.GetMethodInfo()); |
|||
if (actionProducesResponseTypeAttributes.Any(x => x.StatusCode == apiResponseType.StatusCode)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode, |
|||
() => apiResponseType); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual IEnumerable<ApiResponseType> GetApiResponseTypes() |
|||
{ |
|||
foreach (var apiResponse in _options.SupportedResponseTypes) |
|||
{ |
|||
apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(apiResponse.Type); |
|||
|
|||
foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>()) |
|||
{ |
|||
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(null, apiResponse.Type); |
|||
if (formatterSupportedContentTypes == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var formatterSupportedContentType in formatterSupportedContentTypes) |
|||
{ |
|||
apiResponse.ApiResponseFormats.Add(new ApiResponseFormat |
|||
{ |
|||
Formatter = (IOutputFormatter) responseTypeMetadataProvider, |
|||
MediaType = formatterSupportedContentType |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return _options.SupportedResponseTypes; |
|||
} |
|||
|
|||
protected virtual bool IsRemoteService(ActionDescriptor actionDescriptor) |
|||
{ |
|||
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(actionDescriptor.GetMethodInfo()); |
|||
return remoteServiceAttr != null && remoteServiceAttr.IsEnabled; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Mvc.ApiExplorer; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ApiExploring |
|||
{ |
|||
public class AbpRemoteServiceApiDescriptionProviderOptions |
|||
{ |
|||
public HashSet<ApiResponseType> SupportedResponseTypes { get; set; } = new HashSet<ApiResponseType>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Aspects; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AbpAuditPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
protected AbpAuditingOptions Options { get; } |
|||
private readonly IAuditingHelper _auditingHelper; |
|||
private readonly IAuditingManager _auditingManager; |
|||
|
|||
public AbpAuditPageFilter(IOptions<AbpAuditingOptions> options, IAuditingHelper auditingHelper, IAuditingManager auditingManager) |
|||
{ |
|||
Options = options.Value; |
|||
_auditingHelper = auditingHelper; |
|||
_auditingManager = auditingManager; |
|||
} |
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !ShouldSaveAudit(context, out var auditLog, out var auditLogAction)) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.Auditing)) |
|||
{ |
|||
var stopwatch = Stopwatch.StartNew(); |
|||
|
|||
try |
|||
{ |
|||
var result = await next(); |
|||
|
|||
if (result.Exception != null && !result.ExceptionHandled) |
|||
{ |
|||
auditLog.Exceptions.Add(result.Exception); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
auditLog.Exceptions.Add(ex); |
|||
throw; |
|||
} |
|||
finally |
|||
{ |
|||
stopwatch.Stop(); |
|||
auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); |
|||
auditLog.Actions.Add(auditLogAction); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool ShouldSaveAudit(PageHandlerExecutingContext context, out AuditLogInfo auditLog, out AuditLogActionInfo auditLogAction) |
|||
{ |
|||
auditLog = null; |
|||
auditLogAction = null; |
|||
|
|||
if (!Options.IsEnabled) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var auditLogScope = _auditingManager.Current; |
|||
if (auditLogScope == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!_auditingHelper.ShouldSaveAudit(context.HandlerMethod.MethodInfo, true)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
auditLog = auditLogScope.Log; |
|||
auditLogAction = _auditingHelper.CreateAuditLogAction( |
|||
auditLog, |
|||
context.HandlerMethod.GetType(), |
|||
context.HandlerMethod.MethodInfo, |
|||
context.HandlerArguments |
|||
); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.AspNetCore.ExceptionHandling; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class AbpExceptionPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
public ILogger<AbpExceptionPageFilter> Logger { get; set; } |
|||
|
|||
private readonly IExceptionToErrorInfoConverter _errorInfoConverter; |
|||
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; |
|||
private readonly IJsonSerializer _jsonSerializer; |
|||
|
|||
public AbpExceptionPageFilter( |
|||
IExceptionToErrorInfoConverter errorInfoConverter, |
|||
IHttpExceptionStatusCodeFinder statusCodeFinder, |
|||
IJsonSerializer jsonSerializer) |
|||
{ |
|||
_errorInfoConverter = errorInfoConverter; |
|||
_statusCodeFinder = statusCodeFinder; |
|||
_jsonSerializer = jsonSerializer; |
|||
|
|||
Logger = NullLogger<AbpExceptionPageFilter>.Instance; |
|||
} |
|||
|
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !ShouldHandleException(context)) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var pageHandlerExecutedContext = await next(); |
|||
if (pageHandlerExecutedContext.Exception == null) |
|||
{ |
|||
return;; |
|||
} |
|||
|
|||
await HandleAndWrapException(pageHandlerExecutedContext); |
|||
} |
|||
|
|||
protected virtual bool ShouldHandleException(PageHandlerExecutingContext context) |
|||
{ |
|||
//TODO: Create DontWrap attribute to control wrapping..?
|
|||
|
|||
if (context.ActionDescriptor.IsPageAction() && |
|||
ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (context.HttpContext.Request.IsAjax()) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
protected virtual async Task HandleAndWrapException(PageHandlerExecutedContext context) |
|||
{ |
|||
//TODO: Trigger an AbpExceptionHandled event or something like that.
|
|||
|
|||
context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); |
|||
context.HttpContext.Response.StatusCode = (int)_statusCodeFinder.GetStatusCode(context.HttpContext, context.Exception); |
|||
|
|||
var remoteServiceErrorInfo = _errorInfoConverter.Convert(context.Exception); |
|||
|
|||
context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo)); |
|||
|
|||
var logLevel = context.Exception.GetLogLevel(); |
|||
|
|||
Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); |
|||
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); |
|||
Logger.LogException(context.Exception, logLevel); |
|||
|
|||
await context.HttpContext |
|||
.RequestServices |
|||
.GetRequiredService<IExceptionNotifier>() |
|||
.NotifyAsync( |
|||
new ExceptionNotificationContext(context.Exception) |
|||
); |
|||
|
|||
context.Exception = null; //Handled!
|
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Volo.Abp.Aspects; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class AbpFeaturePageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; |
|||
|
|||
public AbpFeaturePageFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) |
|||
{ |
|||
_methodInvocationAuthorizationService = methodInvocationAuthorizationService; |
|||
} |
|||
|
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var methodInfo = context.HandlerMethod.MethodInfo; |
|||
|
|||
using (AbpCrossCuttingConcerns.Applying(context.HandlerInstance, AbpCrossCuttingConcerns.FeatureChecking)) |
|||
{ |
|||
await _methodInvocationAuthorizationService.CheckAsync( |
|||
new MethodInvocationFeatureCheckerContext(methodInfo) |
|||
); |
|||
|
|||
await next(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Uow; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class AbpUowPageFilter : IAsyncPageFilter, ITransientDependency |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly AbpUnitOfWorkDefaultOptions _defaultOptions; |
|||
|
|||
public AbpUowPageFilter(IUnitOfWorkManager unitOfWorkManager, IOptions<AbpUnitOfWorkDefaultOptions> options) |
|||
{ |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_defaultOptions = options.Value; |
|||
} |
|||
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) |
|||
{ |
|||
if (context.HandlerMethod == null || !context.ActionDescriptor.IsPageAction()) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var methodInfo = context.HandlerMethod.MethodInfo; |
|||
var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo); |
|||
|
|||
context.HttpContext.Items["_AbpActionInfo"] = new AbpActionInfoInHttpContext |
|||
{ |
|||
IsObjectResult = ActionResultHelper.IsObjectResult(context.HandlerMethod.MethodInfo.ReturnType) |
|||
}; |
|||
|
|||
if (unitOfWorkAttr?.IsDisabled == true) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var options = CreateOptions(context, unitOfWorkAttr); |
|||
|
|||
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
|
|||
if (_unitOfWorkManager.TryBeginReserved(AbpUnitOfWorkMiddleware.UnitOfWorkReservationName, options)) |
|||
{ |
|||
var result = await next(); |
|||
if (!Succeed(result)) |
|||
{ |
|||
await RollbackAsync(context); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
//Begin a new, independent unit of work
|
|||
using (var uow = _unitOfWorkManager.Begin(options)) |
|||
{ |
|||
var result = await next(); |
|||
if (Succeed(result)) |
|||
{ |
|||
await uow.CompleteAsync(context.HttpContext.RequestAborted); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private AbpUnitOfWorkOptions CreateOptions(PageHandlerExecutingContext context, UnitOfWorkAttribute unitOfWorkAttribute) |
|||
{ |
|||
var options = new AbpUnitOfWorkOptions(); |
|||
|
|||
unitOfWorkAttribute?.SetOptions(options); |
|||
|
|||
if (unitOfWorkAttribute?.IsTransactional == null) |
|||
{ |
|||
options.IsTransactional = _defaultOptions.CalculateIsTransactional( |
|||
autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase) |
|||
); |
|||
} |
|||
|
|||
return options; |
|||
} |
|||
|
|||
private async Task RollbackAsync(PageHandlerExecutingContext context) |
|||
{ |
|||
var currentUow = _unitOfWorkManager.Current; |
|||
if (currentUow != null) |
|||
{ |
|||
await currentUow.RollbackAsync(context.HttpContext.RequestAborted); |
|||
} |
|||
} |
|||
|
|||
private static bool Succeed(PageHandlerExecutedContext result) |
|||
{ |
|||
return result.Exception == null || result.ExceptionHandled; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.DynamicProxy |
|||
{ |
|||
/// <summary>
|
|||
/// Castle's dynamic proxy class feature will have performance issues for some components, such as the controller of Asp net core MVC.
|
|||
/// For related discussions, see: https://github.com/castleproject/Core/issues/486 https://github.com/abpframework/abp/issues/3180
|
|||
/// The Abp framework may enable interceptors for certain components (UOW, Auditing, Authorization, etc.), which requires dynamic proxy classes, but will cause application performance to decline.
|
|||
/// We need to use other methods for the controller to implement interception, such as middleware or MVC / Page filters.
|
|||
/// So we provide some ignored types to avoid enabling dynamic proxy classes.
|
|||
/// By default it is empty. When you use middleware or filters for these components in your application, you can add these types to the list.
|
|||
/// </summary>
|
|||
public static class DynamicProxyIgnoreTypes |
|||
{ |
|||
private static HashSet<Type> IgnoredTypes { get; } = new HashSet<Type>(); |
|||
|
|||
public static void Add<T>() |
|||
{ |
|||
lock (IgnoredTypes) |
|||
{ |
|||
IgnoredTypes.AddIfNotContains(typeof(T)); |
|||
} |
|||
} |
|||
|
|||
public static bool Contains(Type type, bool includeDerivedTypes = true) |
|||
{ |
|||
lock (IgnoredTypes) |
|||
{ |
|||
return includeDerivedTypes |
|||
? IgnoredTypes.Any(t => t.IsAssignableFrom(type)) |
|||
: IgnoredTypes.Contains(type); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,7 +1,12 @@ |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Minify |
|||
{ |
|||
public interface IMinifier |
|||
{ |
|||
string Minify(string source, string fileName = null); |
|||
string Minify( |
|||
string source, |
|||
[CanBeNull] string fileName = null, |
|||
[CanBeNull] string originalFileName = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,200 @@ |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DynamicProxy; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public static class ExtensibleObjectValidator |
|||
{ |
|||
[NotNull] |
|||
public static bool IsValid( |
|||
[NotNull] IHasExtraProperties extensibleObject, |
|||
[CanBeNull] ValidationContext objectValidationContext = null) |
|||
{ |
|||
return GetValidationErrors( |
|||
extensibleObject, |
|||
objectValidationContext |
|||
).Any(); |
|||
} |
|||
|
|||
[NotNull] |
|||
public static List<ValidationResult> GetValidationErrors( |
|||
[NotNull] IHasExtraProperties extensibleObject, |
|||
[CanBeNull] ValidationContext objectValidationContext = null) |
|||
{ |
|||
var validationErrors = new List<ValidationResult>(); |
|||
|
|||
AddValidationErrors( |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext |
|||
); |
|||
|
|||
return validationErrors; |
|||
} |
|||
|
|||
public static void AddValidationErrors( |
|||
[NotNull] IHasExtraProperties extensibleObject, |
|||
[NotNull] List<ValidationResult> validationErrors, |
|||
[CanBeNull] ValidationContext objectValidationContext = null) |
|||
{ |
|||
Check.NotNull(extensibleObject, nameof(extensibleObject)); |
|||
Check.NotNull(validationErrors, nameof(validationErrors)); |
|||
|
|||
if (objectValidationContext == null) |
|||
{ |
|||
objectValidationContext = new ValidationContext( |
|||
extensibleObject, |
|||
null, |
|||
new Dictionary<object, object>() |
|||
); |
|||
} |
|||
|
|||
var objectType = ProxyHelper.UnProxy(extensibleObject).GetType(); |
|||
|
|||
var objectExtensionInfo = ObjectExtensionManager.Instance |
|||
.GetOrNull(objectType); |
|||
|
|||
if (objectExtensionInfo == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
AddPropertyValidationErrors( |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext, |
|||
objectExtensionInfo |
|||
); |
|||
|
|||
ExecuteCustomObjectValidationActions( |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext, |
|||
objectExtensionInfo |
|||
); |
|||
} |
|||
|
|||
private static void AddPropertyValidationErrors( |
|||
IHasExtraProperties extensibleObject, |
|||
List<ValidationResult> validationErrors, |
|||
ValidationContext objectValidationContext, |
|||
ObjectExtensionInfo objectExtensionInfo) |
|||
{ |
|||
var properties = objectExtensionInfo.GetProperties(); |
|||
if (!properties.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var property in properties) |
|||
{ |
|||
AddPropertyValidationErrors(extensibleObject, validationErrors, objectValidationContext, property); |
|||
} |
|||
} |
|||
|
|||
private static void AddPropertyValidationErrors( |
|||
IHasExtraProperties extensibleObject, |
|||
List<ValidationResult> validationErrors, |
|||
ValidationContext objectValidationContext, |
|||
ObjectExtensionPropertyInfo property) |
|||
{ |
|||
AddPropertyValidationAttributeErrors( |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext, |
|||
property |
|||
); |
|||
|
|||
ExecuteCustomPropertyValidationActions( |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext, |
|||
property |
|||
); |
|||
} |
|||
|
|||
private static void AddPropertyValidationAttributeErrors( |
|||
IHasExtraProperties extensibleObject, |
|||
List<ValidationResult> validationErrors, |
|||
ValidationContext objectValidationContext, |
|||
ObjectExtensionPropertyInfo property) |
|||
{ |
|||
if (!property.ValidationAttributes.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null) |
|||
{ |
|||
DisplayName = property.Name, |
|||
MemberName = property.Name |
|||
}; |
|||
|
|||
foreach (var attribute in property.ValidationAttributes) |
|||
{ |
|||
var result = attribute.GetValidationResult( |
|||
extensibleObject.GetProperty(property.Name), |
|||
propertyValidationContext |
|||
); |
|||
|
|||
if (result != null) |
|||
{ |
|||
validationErrors.Add(result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void ExecuteCustomPropertyValidationActions( |
|||
IHasExtraProperties extensibleObject, |
|||
List<ValidationResult> validationErrors, |
|||
ValidationContext objectValidationContext, |
|||
ObjectExtensionPropertyInfo property) |
|||
{ |
|||
if (!property.Validators.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var context = new ObjectExtensionPropertyValidationContext( |
|||
property, |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext, |
|||
extensibleObject.GetProperty(property.Name) |
|||
); |
|||
|
|||
foreach (var validator in property.Validators) |
|||
{ |
|||
validator(context); |
|||
} |
|||
} |
|||
|
|||
private static void ExecuteCustomObjectValidationActions( |
|||
IHasExtraProperties extensibleObject, |
|||
List<ValidationResult> validationErrors, |
|||
ValidationContext objectValidationContext, |
|||
ObjectExtensionInfo objectExtensionInfo) |
|||
{ |
|||
if (!objectExtensionInfo.Validators.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var context = new ObjectExtensionValidationContext( |
|||
objectExtensionInfo, |
|||
extensibleObject, |
|||
validationErrors, |
|||
objectValidationContext |
|||
); |
|||
|
|||
foreach (var validator in objectExtensionInfo.Validators) |
|||
{ |
|||
validator(context); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public class ObjectExtensionPropertyValidationContext |
|||
{ |
|||
/// <summary>
|
|||
/// Related property extension information.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reference to the validating object.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public IHasExtraProperties ValidatingObject { get; } |
|||
|
|||
/// <summary>
|
|||
/// Add validation errors to this list.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public List<ValidationResult> ValidationErrors { get; } |
|||
|
|||
/// <summary>
|
|||
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public ValidationContext ValidationContext { get; } |
|||
|
|||
/// <summary>
|
|||
/// The value of the validating property of the <see cref="ValidatingObject"/>.
|
|||
/// </summary>
|
|||
[CanBeNull] |
|||
public object Value { get; } |
|||
|
|||
/// <summary>
|
|||
/// Can be used to resolve services from the dependency injection container.
|
|||
/// </summary>
|
|||
[CanBeNull] |
|||
public IServiceProvider ServiceProvider => ValidationContext; |
|||
|
|||
public ObjectExtensionPropertyValidationContext( |
|||
[NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo, |
|||
[NotNull] IHasExtraProperties validatingObject, |
|||
[NotNull] List<ValidationResult> validationErrors, |
|||
[NotNull] ValidationContext validationContext, |
|||
[CanBeNull] object value) |
|||
{ |
|||
ExtensionPropertyInfo = Check.NotNull(objectExtensionPropertyInfo, nameof(objectExtensionPropertyInfo)); |
|||
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject)); |
|||
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors)); |
|||
ValidationContext = Check.NotNull(validationContext, nameof(validationContext)); |
|||
Value = value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public class ObjectExtensionValidationContext |
|||
{ |
|||
/// <summary>
|
|||
/// Related object extension information.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public ObjectExtensionInfo ObjectExtensionInfo { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reference to the validating object.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public IHasExtraProperties ValidatingObject { get; } |
|||
|
|||
/// <summary>
|
|||
/// Add validation errors to this list.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public List<ValidationResult> ValidationErrors { get; } |
|||
|
|||
/// <summary>
|
|||
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public ValidationContext ValidationContext { get; } |
|||
|
|||
/// <summary>
|
|||
/// Can be used to resolve services from the dependency injection container.
|
|||
/// </summary>
|
|||
[CanBeNull] |
|||
public IServiceProvider ServiceProvider => ValidationContext; |
|||
|
|||
public ObjectExtensionValidationContext( |
|||
[NotNull] ObjectExtensionInfo objectExtensionInfo, |
|||
[NotNull] IHasExtraProperties validatingObject, |
|||
[NotNull] List<ValidationResult> validationErrors, |
|||
[NotNull] ValidationContext validationContext) |
|||
{ |
|||
ObjectExtensionInfo = Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); |
|||
ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject)); |
|||
ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors)); |
|||
ValidationContext = Check.NotNull(validationContext, nameof(validationContext)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,23 @@ |
|||
using System.Collections.Specialized; |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Auditing.AuditTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,33 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Auditing; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AuditTestPage : AbpPageModel |
|||
{ |
|||
private readonly AbpAuditingOptions _options; |
|||
|
|||
public AuditTestPage(IOptions<AbpAuditingOptions> options) |
|||
{ |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public IActionResult OnGetAuditSuccessForGetRequests() |
|||
{ |
|||
return new OkResult(); |
|||
} |
|||
|
|||
public IActionResult OnGetAuditFailForGetRequests() |
|||
{ |
|||
throw new UserFriendlyException("Exception occurred!"); |
|||
} |
|||
|
|||
public ObjectResult OnGetAuditFailForGetRequestsReturningObject() |
|||
{ |
|||
throw new UserFriendlyException("Exception occurred!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Extensions.Options; |
|||
using NSubstitute; |
|||
using Volo.Abp.Auditing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Auditing |
|||
{ |
|||
public class AuditTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly AbpAuditingOptions _options; |
|||
private IAuditingStore _auditingStore; |
|||
|
|||
public AuditTestPage_Tests() |
|||
{ |
|||
_options = ServiceProvider.GetRequiredService<IOptions<AbpAuditingOptions>>().Value; |
|||
_auditingStore = ServiceProvider.GetRequiredService<IAuditingStore>(); |
|||
} |
|||
|
|||
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) |
|||
{ |
|||
_auditingStore = Substitute.For<IAuditingStore>(); |
|||
services.Replace(ServiceDescriptor.Singleton(_auditingStore)); |
|||
base.ConfigureServices(context, services); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Success_For_GetRequests() |
|||
{ |
|||
_options.IsEnabledForGetRequests = true; |
|||
_options.AlwaysLogOnException = false; |
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditSuccessForGetRequests"); |
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_Always() |
|||
{ |
|||
_options.IsEnabled = true; |
|||
_options.AlwaysLogOnException = true; |
|||
|
|||
try |
|||
{ |
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequests", System.Net.HttpStatusCode.Forbidden); |
|||
} |
|||
catch { } |
|||
|
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() |
|||
{ |
|||
_options.IsEnabled = true; |
|||
_options.AlwaysLogOnException = true; |
|||
|
|||
await GetResponseAsync("/Auditing/AuditTestPage?handler=AuditFailForGetRequestsReturningObject", System.Net.HttpStatusCode.Forbidden); |
|||
|
|||
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Authorization.AuthTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Authorization |
|||
{ |
|||
[Authorize] |
|||
public class AuthTestPage : AbpPageModel |
|||
{ |
|||
public static Guid FakeUserId { get; } = Guid.NewGuid(); |
|||
|
|||
public ActionResult OnGet() |
|||
{ |
|||
return Content("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.MemoryDb; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Security.Claims; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Authorization |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreTestBaseModule), |
|||
typeof(AbpMemoryDbTestModule), |
|||
typeof(AbpAspNetCoreMvcModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AuthTestPage_Tests: AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly FakeUserClaims _fakeRequiredService; |
|||
|
|||
public AuthTestPage_Tests() |
|||
{ |
|||
_fakeRequiredService = GetRequiredService<FakeUserClaims>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Call_Simple_Authorized_Method_With_Authenticated_User() |
|||
{ |
|||
_fakeRequiredService.Claims.AddRange(new[] |
|||
{ |
|||
new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString()) |
|||
}); |
|||
|
|||
var result = await GetResponseAsStringAsync("/Authorization/AuthTestPage"); |
|||
result.ShouldBe("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.ExceptionHandling.ExceptionTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class ExceptionTestPage : AbpPageModel |
|||
{ |
|||
public void OnGetUserFriendlyException1() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public IActionResult OnGetUserFriendlyException2() |
|||
{ |
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Volo.Abp.ExceptionHandling; |
|||
using Volo.Abp.Http; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling |
|||
{ |
|||
public class ExceptionTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private IExceptionSubscriber _fakeExceptionSubscriber; |
|||
|
|||
protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) |
|||
{ |
|||
base.ConfigureServices(context, services); |
|||
|
|||
_fakeExceptionSubscriber = Substitute.For<IExceptionSubscriber>(); |
|||
|
|||
services.AddSingleton(_fakeExceptionSubscriber); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() |
|||
{ |
|||
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException1", HttpStatusCode.Forbidden); |
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe("This is a sample exception!"); |
|||
|
|||
#pragma warning disable 4014
|
|||
_fakeExceptionSubscriber |
|||
.Received() |
|||
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); |
|||
#pragma warning restore 4014
|
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values() |
|||
{ |
|||
await Assert.ThrowsAsync<UserFriendlyException>( |
|||
async () => await GetResponseAsObjectAsync<RemoteServiceErrorResponse>( |
|||
"/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2" |
|||
) |
|||
); |
|||
|
|||
#pragma warning disable 4014
|
|||
_fakeExceptionSubscriber |
|||
.DidNotReceive() |
|||
.HandleAsync(Arg.Any<ExceptionNotificationContext>()); |
|||
#pragma warning restore 4014
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Features.FeatureTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,27 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FeatureTestPage : AbpPageModel |
|||
{ |
|||
[RequiresFeature("AllowedFeature")] |
|||
public Task OnGetAllowedFeatureAsync() |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
[RequiresFeature("NotAllowedFeature")] |
|||
public void OnGetNotAllowedFeature() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ObjectResult OnGetNoFeature() |
|||
{ |
|||
return new ObjectResult(42); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FeatureTestPage_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Allow_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=AllowedFeature" |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Allow_Not_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=NotAllowedFeature", |
|||
HttpStatusCode.Unauthorized |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Allow_Actions_With_No_Feature() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/Features/FeatureTestPage?handler=NoFeature" |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Json; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class UnitOfWorkPageFilter_Exception_Rollback_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Rollback_Transaction_For_Handled_Exceptions() |
|||
{ |
|||
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/Uow/UnitOfWorkTestPage?handler=HandledException", HttpStatusCode.Forbidden); |
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe("This is a sample exception!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Gracefully_Handle_Exceptions_On_Complete() |
|||
{ |
|||
var response = await GetResponseAsync("/Uow/UnitOfWorkTestPage?handler=ExceptionOnComplete", HttpStatusCode.Forbidden); |
|||
|
|||
response.Headers.GetValues(AbpHttpConsts.AbpErrorFormat).FirstOrDefault().ShouldBe("true"); |
|||
|
|||
var resultAsString = await response.Content.ReadAsStringAsync(); |
|||
|
|||
var result = ServiceProvider.GetRequiredService<IJsonSerializer>().Deserialize<RemoteServiceErrorResponse>(resultAsString); |
|||
|
|||
result.Error.ShouldNotBeNull(); |
|||
result.Error.Message.ShouldBe(TestUnitOfWorkConfig.ExceptionOnCompleteMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
public class UnitOfWorkPageFilter_Tests: AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Get_Actions_Should_Not_Be_Transactional() |
|||
{ |
|||
await GetResponseAsStringAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Non_Get_Actions_Should_Be_Transactional() |
|||
{ |
|||
var result = await Client.PostAsync("/Uow/UnitOfWorkTestPage?handler=RequiresUow", null); |
|||
result.IsSuccessStatusCode.ShouldBeTrue(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
@page |
|||
@model Volo.Abp.AspNetCore.Mvc.Uow.UnitOfWorkTestPage |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,54 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Shouldly; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Uow |
|||
{ |
|||
[IgnoreAntiforgeryToken] |
|||
public class UnitOfWorkTestPage : AbpPageModel |
|||
{ |
|||
private readonly TestUnitOfWorkConfig _testUnitOfWorkConfig; |
|||
|
|||
public UnitOfWorkTestPage(TestUnitOfWorkConfig testUnitOfWorkConfig) |
|||
{ |
|||
_testUnitOfWorkConfig = testUnitOfWorkConfig; |
|||
} |
|||
|
|||
public IActionResult OnGetRequiresUow() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); |
|||
|
|||
return Content("OK"); |
|||
} |
|||
|
|||
public IActionResult OnPostRequiresUow() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); |
|||
|
|||
return Content("OK"); |
|||
} |
|||
|
|||
[UnitOfWork(isTransactional: true)] |
|||
public void OnGetHandledException() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); |
|||
|
|||
throw new UserFriendlyException("This is a sample exception!"); |
|||
} |
|||
|
|||
public ObjectResult OnGetExceptionOnComplete() |
|||
{ |
|||
CurrentUnitOfWork.ShouldNotBeNull(); |
|||
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); |
|||
|
|||
_testUnitOfWorkConfig.ThrowExceptionOnComplete = true; |
|||
|
|||
//Prevent rendering of pages.
|
|||
return new ObjectResult(""); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Authorization.TestServices |
|||
{ |
|||
public interface IMyAuthorizedServiceWithRole |
|||
{ |
|||
Task<int> ProtectedByRole(); |
|||
|
|||
Task<int> ProtectedByScheme(); |
|||
|
|||
Task<int> ProtectedByAnotherRole(); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Authorization.TestServices |
|||
{ |
|||
public interface IMySimpleAuthorizedService |
|||
{ |
|||
Task<int> ProtectedByClassAsync(); |
|||
|
|||
Task<int> AnonymousAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Authorization.TestServices |
|||
{ |
|||
[Authorize(Roles = "MyRole")] |
|||
public class MyAuthorizedServiceWithRole : IMyAuthorizedServiceWithRole, ITransientDependency |
|||
{ |
|||
public virtual Task<int> ProtectedByRole() |
|||
{ |
|||
return Task.FromResult(42); |
|||
} |
|||
|
|||
[Authorize(Roles = "MyAnotherRole")] |
|||
public virtual Task<int> ProtectedByAnotherRole() |
|||
{ |
|||
return Task.FromResult(42); |
|||
} |
|||
|
|||
[Authorize(AuthenticationSchemes = "Bearer")] |
|||
public virtual Task<int> ProtectedByScheme() |
|||
{ |
|||
return Task.FromResult(42); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Authorization.TestServices |
|||
{ |
|||
[Authorize] |
|||
public class MySimpleAuthorizedService : IMySimpleAuthorizedService, ITransientDependency |
|||
{ |
|||
public Task<int> ProtectedByClassAsync() |
|||
{ |
|||
return Task.FromResult(42); |
|||
} |
|||
|
|||
[AllowAnonymous] |
|||
public Task<int> AnonymousAsync() |
|||
{ |
|||
return Task.FromResult(42); |
|||
} |
|||
} |
|||
} |
|||
@ -1,7 +1,9 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB.DomainEvents |
|||
{ |
|||
[Collection(MongoTestCollection.Name)] |
|||
public class DomainEvents_Tests : DomainEvents_Tests<AbpMongoDbTestModule> |
|||
{ |
|||
} |
|||
|
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using Mongo2Go; |
|||
|
|||
namespace Volo.Abp.MongoDB |
|||
{ |
|||
public class MongoDbFixture : IDisposable |
|||
{ |
|||
private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); |
|||
public static readonly string ConnectionString = MongoDbRunner.ConnectionString; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
MongoDbRunner?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB |
|||
{ |
|||
[CollectionDefinition(Name)] |
|||
public class MongoTestCollection : ICollectionFixture<MongoDbFixture> |
|||
{ |
|||
public const string Name = "MongoDB Collection"; |
|||
} |
|||
} |
|||
@ -1,7 +1,9 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB.Repositories |
|||
{ |
|||
[Collection(MongoTestCollection.Name)] |
|||
public class Repository_Specifications_Tests : Repository_Specifications_Tests<AbpMongoDbTestModule> |
|||
{ |
|||
} |
|||
|
|||
@ -1,9 +1,10 @@ |
|||
using Volo.Abp.TestApp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB.Uow |
|||
{ |
|||
[Collection(MongoTestCollection.Name)] |
|||
public class Uow_Completed_Tests : Uow_Completed_Tests<AbpMongoDbTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Shouldly; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Threading; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public class ExtensibleObjectValidator_Tests |
|||
{ |
|||
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
|||
|
|||
static ExtensibleObjectValidator_Tests() |
|||
{ |
|||
OneTimeRunner.Run(() => |
|||
{ |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdate<ExtensiblePersonObject>(options => |
|||
{ |
|||
options.AddOrUpdateProperty<string>("Name", propertyInfo => |
|||
{ |
|||
propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); |
|||
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 }); |
|||
}); |
|||
|
|||
options.AddOrUpdateProperty<string>("Address", propertyInfo => |
|||
{ |
|||
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255)); |
|||
}); |
|||
|
|||
options.AddOrUpdateProperty<byte>("Age", propertyInfo => |
|||
{ |
|||
propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); |
|||
propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99)); |
|||
}); |
|||
|
|||
options.AddOrUpdateProperty<bool>("IsMarried", propertyInfo => |
|||
{ |
|||
|
|||
}); |
|||
|
|||
options.AddOrUpdateProperty<string>("Password", propertyInfo => |
|||
{ |
|||
}); |
|||
|
|||
options.AddOrUpdateProperty<string>("PasswordRepeat", propertyInfo => |
|||
{ |
|||
propertyInfo.Validators.Add(context => |
|||
{ |
|||
if (context.ValidatingObject.HasProperty("Password")) |
|||
{ |
|||
if (context.ValidatingObject.GetProperty<string>("Password") != |
|||
context.Value as string) |
|||
{ |
|||
context.ValidationErrors.Add( |
|||
new ValidationResult( |
|||
"If you specify a password, then please correctly repeat it!", |
|||
new[] {"Password", "PasswordRepeat"} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
options.Validators.Add(context => |
|||
{ |
|||
if (context.ValidatingObject.GetProperty<string>("Name") == "BadValue") |
|||
{ |
|||
context.ValidationErrors.Add( |
|||
new ValidationResult( |
|||
"Name can not be 'BadValue', sorry :(", |
|||
new[] { "Name" } |
|||
) |
|||
); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Validate_If_The_Properties_Are_Valid() |
|||
{ |
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Name", "John"}, |
|||
{"Age", "42"}, |
|||
} |
|||
} |
|||
).Count.ShouldBe(0); //All Valid
|
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Validate_If_The_Properties_Are_Not_Valid() |
|||
{ |
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject() |
|||
).Count.ShouldBe(2); // Name & Age
|
|||
|
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Address", new string('x', 256) } |
|||
} |
|||
} |
|||
).Count.ShouldBe(3); // Name, Age & Address
|
|||
|
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Age", "42" } |
|||
} |
|||
} |
|||
).Count.ShouldBe(1); // Name
|
|||
|
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Address", new string('x', 256) }, |
|||
{"Age", "100" } |
|||
} |
|||
} |
|||
).Count.ShouldBe(3); // Name, Age & Address
|
|||
|
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Name", "John"}, |
|||
{"Age", "42"}, |
|||
{"Password", "123"}, |
|||
{"PasswordRepeat", "1256"} |
|||
} |
|||
} |
|||
).Count.ShouldBe(1); // PasswordRepeat != Password
|
|||
|
|||
ExtensibleObjectValidator |
|||
.GetValidationErrors( |
|||
new ExtensiblePersonObject |
|||
{ |
|||
ExtraProperties = |
|||
{ |
|||
{"Name", "BadValue"}, |
|||
{"Age", "42"}, |
|||
} |
|||
} |
|||
).Count.ShouldBe(1); //Name is 'BadValue'!
|
|||
} |
|||
|
|||
private class ExtensiblePersonObject : ExtensibleObject |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue