mirror of https://github.com/abpframework/abp.git
230 changed files with 5461 additions and 415 deletions
@ -1,3 +0,0 @@ |
|||
## AutoMapper Integration |
|||
|
|||
TODO |
|||
@ -1,3 +1,365 @@ |
|||
# Object Extensions |
|||
|
|||
TODO |
|||
ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module. |
|||
|
|||
> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes. |
|||
|
|||
## IHasExtraProperties Interface |
|||
|
|||
This is the interface to make a class extensible. It simply defines a `Dictionary` property: |
|||
|
|||
````csharp |
|||
Dictionary<string, object> ExtraProperties { get; } |
|||
```` |
|||
|
|||
Then you can add or get extra properties using this dictionary. |
|||
|
|||
### Base Classes |
|||
|
|||
`IHasExtraProperties` interface is implemented by several base classes by default: |
|||
|
|||
* Implemented by the `AggregateRoot` class (see [entities](Entities.md)). |
|||
* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes. |
|||
* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object. |
|||
|
|||
So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually. |
|||
|
|||
### Fundamental Extension Methods |
|||
|
|||
While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties. |
|||
|
|||
#### SetProperty |
|||
|
|||
Used to set the value of an extra property: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title"); |
|||
user.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
`SetProperty` returns the same object, so you can chain it: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title") |
|||
.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
#### GetProperty |
|||
|
|||
Used to read the value of an extra property: |
|||
|
|||
````csharp |
|||
var title = user.GetProperty<string>("Title"); |
|||
|
|||
if (user.GetProperty<bool>("IsSuperUser")) |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
* `GetProperty` is a generic method and takes the object type as the generic parameter. |
|||
* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc). |
|||
|
|||
##### Non Primitive Property Types |
|||
|
|||
If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`. |
|||
|
|||
#### HasProperty |
|||
|
|||
Used to check if the object has a property set before. |
|||
|
|||
#### RemoveProperty |
|||
|
|||
Used to remove a property from the object. Use this methods instead of setting a `null` value for the property. |
|||
|
|||
### Some Best Practices |
|||
|
|||
Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead; |
|||
|
|||
* Define a constant for your extra property names |
|||
* Create extension methods to easily set your extra properties. |
|||
|
|||
Example: |
|||
|
|||
````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); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can easily set or get the `Title` property: |
|||
|
|||
````csharp |
|||
user.SetTitle("My Title"); |
|||
var title = user.GetTitle(); |
|||
```` |
|||
|
|||
## Object Extension Manager |
|||
|
|||
While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes. |
|||
|
|||
Explicitly defining an extra property has some use cases: |
|||
|
|||
* Allows to control how the extra property is handled on object to object mapping (see the section below). |
|||
* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md). |
|||
|
|||
> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside. |
|||
|
|||
### AddOrUpdate |
|||
|
|||
`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object. |
|||
|
|||
Example: Define extra properties for the `IdentityUser` entity: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdate<IdentityUser>(options => |
|||
{ |
|||
options.AddOrUpdateProperty<string>("SocialSecurityNumber"); |
|||
options.AddOrUpdateProperty<bool>("IsSuperUser"); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
### AddOrUpdateProperty |
|||
|
|||
While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber"); |
|||
```` |
|||
|
|||
Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<string>( |
|||
new[] |
|||
{ |
|||
typeof(IdentityUserDto), |
|||
typeof(IdentityUserCreateDto), |
|||
typeof(IdentityUserUpdateDto) |
|||
}, |
|||
"SocialSecurityNumber" |
|||
); |
|||
```` |
|||
|
|||
### Property Configuration |
|||
|
|||
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
//Configure options... |
|||
}); |
|||
```` |
|||
|
|||
> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. |
|||
|
|||
The following sections explain the fundamental property configuration options. |
|||
|
|||
#### CheckPairDefinitionOnMapping |
|||
|
|||
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better. |
|||
|
|||
## Validation |
|||
|
|||
You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation: |
|||
|
|||
1. You can add **data annotation attributes** for a property. |
|||
2. You can write an action (code block) to perform a **custom validation**. |
|||
|
|||
Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated. |
|||
|
|||
### Data Annotation Attributes |
|||
|
|||
All of the standard data annotation attributes are valid for extra properties. Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUserCreateDto, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.ValidationAttributes.Add(new RequiredAttribute()); |
|||
options.ValidationAttributes.Add( |
|||
new StringLengthAttribute(32) { |
|||
MinimumLength = 6 |
|||
} |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided. |
|||
|
|||
### Custom Validation |
|||
|
|||
If you need, you can add a custom action that is executed to validate the extra properties. Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUserCreateDto, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.Validators.Add(context => |
|||
{ |
|||
var socialSecurityNumber = context.Value as string; |
|||
|
|||
if (socialSecurityNumber == null || |
|||
socialSecurityNumber.StartsWith("X")) |
|||
{ |
|||
context.ValidationErrors.Add( |
|||
new ValidationResult( |
|||
"Invalid social security number: " + socialSecurityNumber, |
|||
new[] { "SocialSecurityNumber" } |
|||
) |
|||
); |
|||
} |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios. |
|||
|
|||
In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdate<IdentityUserCreateDto>(objConfig => |
|||
{ |
|||
//Define two properties with their own validation rules |
|||
|
|||
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig => |
|||
{ |
|||
propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); |
|||
}); |
|||
|
|||
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig => |
|||
{ |
|||
propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); |
|||
}); |
|||
|
|||
//Write a common validation logic works on multiple properties |
|||
|
|||
objConfig.Validators.Add(context => |
|||
{ |
|||
if (context.ValidatingObject.GetProperty<string>("Password") != |
|||
context.ValidatingObject.GetProperty<string>("PasswordRepeat")) |
|||
{ |
|||
context.ValidationErrors.Add( |
|||
new ValidationResult( |
|||
"Please repeat the same password!", |
|||
new[] { "Password", "PasswordRepeat" } |
|||
) |
|||
); |
|||
} |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
## Object to Object Mapping |
|||
|
|||
Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients. |
|||
|
|||
This section offers some **good practices** to control your extra properties on object mapping. |
|||
|
|||
### MapExtraPropertiesTo |
|||
|
|||
`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo(identityUserDto); |
|||
```` |
|||
|
|||
`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction. |
|||
|
|||
#### MappingPropertyDefinitionChecks |
|||
|
|||
`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
MappingPropertyDefinitionChecks.None |
|||
); |
|||
```` |
|||
|
|||
> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too. |
|||
|
|||
If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
}); |
|||
```` |
|||
|
|||
#### Ignored Properties |
|||
|
|||
You may want to ignore some properties on a specific mapping operation: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
ignoredProperties: new[] {"MySensitiveProp"} |
|||
); |
|||
```` |
|||
|
|||
Ignored properties are not copied to the target object. |
|||
|
|||
#### AutoMapper Integration |
|||
|
|||
If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above. |
|||
|
|||
You can use the `MapExtraProperties()` method inside your mapping profile. |
|||
|
|||
````csharp |
|||
public class MyProfile : Profile |
|||
{ |
|||
public MyProfile() |
|||
{ |
|||
CreateMap<IdentityUser, IdentityUserDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It has the same parameters with the `MapExtraPropertiesTo` method. |
|||
|
|||
## Entity Framework Core Database Mapping |
|||
|
|||
If you're using the EF Core, you can map an extra property to a table field in the database. Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.MapEfCore(b => b.HasMaxLength(32)); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. |
|||
@ -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,63 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"InternalServerErrorMessage": "Zgodila se je napaka na strežniku!", |
|||
"ValidationErrorMessage": "Vaš zahtevek ni veljaven!", |
|||
"ValidationNarrativeErrorMessageTitle": "Pri preverjanju so bile zaznane sledeče napake.", |
|||
"DefaultErrorMessage": "Zgodila se je napaka!", |
|||
"DefaultErrorMessageDetail": "Strežnik ni poslal podrobnosti o napaki.", |
|||
"DefaultErrorMessage401": "Niste prijavljeni!", |
|||
"DefaultErrorMessage401Detail": "Za izvedbo te operacije se morate prijaviti.", |
|||
"DefaultErrorMessage403": "Nimate pravic!", |
|||
"DefaultErrorMessage403Detail": "Nimate dovoljenja za izvedbo te operacije!", |
|||
"DefaultErrorMessage404": "Vir ni bil najden!", |
|||
"DefaultErrorMessage404Detail": "Zahtevanega vira ni bilo mogoče najti na strežniku!", |
|||
"EntityNotFoundErrorMessage": "Ni entitete {0} z id-jem = {1}!", |
|||
"Languages": "Jeziki", |
|||
"Error": "Napaka", |
|||
"AreYouSure": "Ali ste prepričani?", |
|||
"Cancel": "Prekliči", |
|||
"Yes": "Da", |
|||
"No": "Ne", |
|||
"Ok": "V redu", |
|||
"Close": "Zapri", |
|||
"Save": "Shrani", |
|||
"SavingWithThreeDot": "Shranjujem...", |
|||
"Actions": "Dejanja", |
|||
"Delete": "Izbriši", |
|||
"Edit": "Uredi", |
|||
"Refresh": "Osveži", |
|||
"Language": "Jezik", |
|||
"LoadMore": "Naloži več", |
|||
"ProcessingWithThreeDot": "Procesiram...", |
|||
"LoadingWithThreeDot": "Nalagam...", |
|||
"Welcome": "Dobrodošli", |
|||
"Login": "Prijava", |
|||
"Register": "Registracija", |
|||
"Logout": "Odjava", |
|||
"Submit": "Pošlji", |
|||
"Back": "Nazaj", |
|||
"PagerSearch": "Iskanje", |
|||
"PagerNext": "Naslednja", |
|||
"PagerPrevious": "Prejšnja", |
|||
"PagerFirst": "Prva", |
|||
"PagerLast": "Zadnja", |
|||
"PagerInfo": "Prikazanih _START_ do _END_ od _TOTAL_ zapisov", |
|||
"PagerInfo{0}{1}{2}": "Prikazanih {0} do {1} od {2} zapisov", |
|||
"PagerInfoEmpty": "Prikazanih 0 do 0 od 0 zapisov", |
|||
"PagerInfoFiltered": "(filtrirano od vseh _MAX_ zapisov)", |
|||
"NoDataAvailableInDatatable": "V tabeli ni na voljo podatkov", |
|||
"PagerShowMenuEntries": "Prikaži _MENU_ zapise", |
|||
"DatatableActionDropdownDefaultText": "Dejanja", |
|||
"ChangePassword": "Zamenjaj geslo", |
|||
"PersonalInfo": "Moj profil", |
|||
"AreYouSureYouWantToCancelEditingWarningMessage": "Imate neshranjene spremembe.", |
|||
"UnhandledException": "Neobravnavana napaka!", |
|||
"401Message": "Nepooblaščeno", |
|||
"403Message": "Prepovedano", |
|||
"404Message": "Strani ni mogoče najti", |
|||
"500Message": "Napaka na strani strežnika", |
|||
"GoHomePage": "Pojdi na osnovno stran", |
|||
"GoBack": "Nazaj" |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue