Merge remote-tracking branch 'abpframework/dev' into docs

pull/3604/head
liangshiwei 6 years ago
commit 50575289b8

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

@ -17,17 +17,18 @@
##### Basic DTO
**Do** define a **basic** DTO for an entity.
**Do** define a **basic** DTO for an aggregate root.
- Include all the **primitive properties** directly on the entity.
- Exception: Can **exclude** properties for **security** reasons (like User.Password).
- Include all the **primitive properties** directly on the aggregate root.
- Exception: Can **exclude** properties for **security** reasons (like `User.Password`).
- Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**.
- Inherit from one of the **extensible entity DTO** classes for aggregate roots (and entities implement the `IHasExtraProperties`).
Example:
```c#
[Serializable]
public class IssueDto : FullAuditedEntityDto<Guid>
public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
@ -57,7 +58,7 @@ Example:
````C#
[Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
@ -66,14 +67,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
}
[Serializable]
public class MilestoneDto : EntityDto<Guid>
public class MilestoneDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public bool IsClosed { get; set; }
}
[Serializable]
public class LabelDto : EntityDto<Guid>
public class LabelDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public string Color { get; set; }
@ -120,6 +121,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **Do** use the `CreateAsync` **method name**.
* **Do** get a **specialized input** DTO to create the entity.
* **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
* **Do** use **data annotations** for input validation.
* Share constants between domain wherever possible (via constants defined in the **domain shared** package).
* **Do** return **the detailed** DTO for new created entity.
@ -135,10 +137,11 @@ The related **DTO**:
````C#
[Serializable]
public class CreateQuestionDto
public class CreateQuestionDto : ExtensibleObject
{
[Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
[StringLength(QuestionConsts.MaxTitleLength,
MinimumLength = QuestionConsts.MinTitleLength)]
public string Title { get; set; }
[StringLength(QuestionConsts.MaxTextLength)]
@ -152,6 +155,7 @@ public class CreateQuestionDto
- **Do** use the `UpdateAsync` **method name**.
- **Do** get a **specialized input** DTO to update the entity.
- **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
- **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO.
- **Do** use **data annotations** for input validation.
- Share constants between domain wherever possible (via constants defined in the **domain shared** package).
@ -200,6 +204,10 @@ This method votes a question and returns the current score of the question.
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source.
#### Extra Properties
* **Do** use either `MapExtraPropertiesTo` extension method ([see](Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services.
#### Manipulating / Deleting Entities
* **Do** always get all the related entities from repositories to perform the operations on them.

@ -2,6 +2,7 @@
* **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on).
* **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto<TKey>`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way.
* **Do** define DTO members with **public getter and setter**.
* **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service.
* **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary.

@ -24,3 +24,18 @@ Now, you can install preview / nightly packages to your project from Nuget Brows
3. Search a package. You will see prereleases of the package formatted as `(VERSION)-preview(DATE)` (like *v0.16.0-preview20190401* in this sample).
4. You can click to the `Install` button to add package to your project.
## Install & Uninstall Preview NPM Packages
The latest version of preview NPM packages can be installed by the running below command in the root folder of application:
```bash
abp switch-to-preview
```
If you're using the ABP Framework preview packages, you can switch back to stable version using this command:
```bash
abp switch-to-stable
```
See the [ABP CLI documentation](./CLI.md) for more information.

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

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate.
### Mapping the Object Extensions
[Object extension system](Object-Extensions.md) allows to define extra properties for existing classes. ABP Framework provides a mapping definition extension to properly map extra properties of two objects.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more.
## Advanced Topics
### IObjectMapper<TContext> Interface

@ -688,7 +688,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`.
Open `book-list.component.` file in `books\book-list` folder and replace the content as below:
Open `book-list.component.ts` file in `books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';

@ -35,17 +35,19 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
const scriptElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>`.
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>` and `scriptElement` will be an `HTMLScriptElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Insert Styles
If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `<style>` element with given `content` and place it in the designated DOM position.
@ -60,27 +62,68 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
const styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>`.
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>` and `styleElement` will be an `HTMLStyleElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Remove Inserted Scripts & Styles
If you pass the inserted `HTMLScriptElement` or `HTMLStyleElement` element as the first parameter of `removeContent` method, the `DomInsertionService` will remove the given element.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
private styleElement: HTMLStyleElement;
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
ngOnDestroy() {
this.domInsertionService.removeContent(this.styleElement);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element **will be removed** from `<head>` when the component is destroyed.
## API
### insertContent
```js
insertContent(contentStrategy: ContentStrategy): void
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T
```
- `contentStrategy` parameter is the primary focus here and is explained above.
- returns `HTMLScriptElement` or `HTMLStyleElement` based on given strategy.
### removeContent
```js
removeContent(element: HTMLScriptElement | HTMLStyleElement): void
```
- `element` parameter is the inserted `HTMLScriptElement` or `HTMLStyleElement` element, which was returned by `insertContent` method.
## What's Next?

@ -1381,7 +1381,6 @@ list.forEach((node, index) => console.log(node.value + index));
```
#### \*\[Symbol.iterator\]\(\)
A linked list is iterable. In other words, you may use methods like `for...of` on it.
@ -1482,7 +1481,4 @@ const str = list.toString(value => value.x);
/*
str === '1 <-> 2 <-> 3 <-> 4 <-> 5'
*/
```
```

@ -155,6 +155,10 @@
{
"text": "Data Filtering",
"path": "Data-Filtering.md"
},
{
"text": "Object Extensions",
"path": "Object-Extensions.md"
}
]
},

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

@ -17,7 +17,7 @@
##### 基础DTO
**推荐** 为实体定义一个**基础**DTO.
**推荐** 为聚合根定义一个**基础**DTO.
- 直接包含实体中所有的**原始属性**.
- 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
@ -27,7 +27,7 @@
```c#
[Serializable]
public class IssueDto : FullAuditedEntityDto<Guid>
public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
@ -57,7 +57,7 @@ public class IssueLabelDto
````C#
[Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Text { get; set; }
@ -66,14 +66,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
}
[Serializable]
public class MilestoneDto : EntityDto<Guid>
public class MilestoneDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public bool IsClosed { get; set; }
}
[Serializable]
public class LabelDto : EntityDto<Guid>
public class LabelDto : ExtensibleEntityDto<Guid>
{
public string Name { get; set; }
public string Color { get; set; }
@ -120,6 +120,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **推荐** 使用 `CreateAsync` 做为**方法名**.
* **推荐** 使用**专门的输入DTO**来创建实体.
* **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
* **推荐** 使用 **data annotations** 进行输入验证.
* 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
* **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性.
@ -134,7 +135,7 @@ Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
````C#
[Serializable]
public class CreateQuestionDto
public class CreateQuestionDto : ExtensibleObject
{
[Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
@ -151,6 +152,7 @@ public class CreateQuestionDto
- **推荐** 使用 `UpdateAsync` 做为**方法名**.
- **推荐** 使用**专门的输入DTO**来更新实体.
- **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
- **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO.
- **推荐** 使用 **data annotations** 进行输入验证.
- 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
@ -199,6 +201,10 @@ Task<int> VoteAsync(Guid id, VoteType type);
* **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
#### 额外的属性
* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务.
#### 操作/删除 实体
* **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作.

@ -2,6 +2,7 @@
* **推荐** 在 **application.contracts** 层中定义DTO.
* **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等).
* **推荐** 从**聚合根**的**扩展DTO**继承(如 `ExtensibleAuditedEntityDto<TKey>`), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO.
* **推荐** 定义 **public getter 和 setter** 的DTO成员 .
* **推荐** 使用 **data annotations** **验证** service输入DTO的属性.
* **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口.

@ -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) 了解更多内容.

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`.
### 映射对象扩展
[对象扩展系统](Object-Extensions.md) 允许为已存在的类定义额外属性. ABP 框架提供了一个映射定义扩展可以正确的映射两个对象的额外属性.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
如果两个类都是可扩展对象(实现了 `IHasExtraProperties` 接口),建议使用 `MapExtraProperties` 方法. 更多信息请参阅[对象扩展文档](Object-Extensions.md).
## 高级主题
### IObjectMapper<TContext> 接口

@ -0,0 +1,3 @@
# ABP ASP.NET Core UI Datatables.Net 集成
TODO

File diff suppressed because it is too large Load Diff

@ -140,6 +140,14 @@
{
"text": "设置管理",
"path": "Settings.md"
},
{
"text": "数据过滤",
"path": "Data-Filtering.md"
},
{
"text": "对象扩展",
"path": "Object-Extensions.md"
}
]
},
@ -313,6 +321,15 @@
"path": "UI/Angular/Component-Replacement.md"
}
]
},
{
"text": "通用",
"items": [
{
"text": "链表 (双向)",
"path": "UI/Common/Utils/Linked-List.md"
}
]
}
]
},

@ -155,7 +155,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource));
var pagerInfo = (TagHelper.ShowInfo ?? false) ?
" <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n"
" <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n"
: "";
return

@ -46,38 +46,54 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling
private void AddFileToBundle(IBundlerContext context, StringBuilder bundleContentBuilder, string fileName)
{
string fileContent = null;
var fileContent = GetFileContentConsideringMinification(context, fileName);
fileContent = ProcessBeforeAddingToTheBundle(context, fileName, fileContent);
bundleContentBuilder.Append(fileContent);
}
if (context.IsMinificationEnabled && !IsMinFile(fileName))
private string GetFileContentConsideringMinification(IBundlerContext context, string fileName)
{
var isMinFile = IsMinFile(fileName);
if (!context.IsMinificationEnabled || isMinFile)
{
var minFileInfo = GetMinFileInfoOrNull(fileName);
if (minFileInfo != null)
var fileContent = GetFileInfo(context, fileName).ReadAsString();
Logger.LogDebug($"- {fileName} ({fileContent.Length} bytes)");
if (context.IsMinificationEnabled && isMinFile)
{
Logger.LogDebug($"- {fileName} ({minFileInfo.Length} bytes) - already minified");
fileContent = minFileInfo.ReadAsString();
Logger.LogDebug(" > Already minified");
}
return fileContent;
}
if (fileContent == null)
var minFileInfo = GetMinFileInfoOrNull(fileName);
if (minFileInfo != null)
{
fileContent = GetFileContent(context, fileName);
Logger.LogDebug($"- {fileName} ({fileContent.Length} bytes) - non minified");
if (context.IsMinificationEnabled)
{
var nonMinifiedSize = fileContent.Length;
fileContent = Minifier.Minify(fileContent, context.BundleRelativePath);
Logger.LogInformation($" > Minified {fileName} ({nonMinifiedSize} bytes -> {fileContent.Length} bytes)");
}
var fileContent = minFileInfo.ReadAsString();
Logger.LogDebug($"- {fileName}");
Logger.LogDebug($" > Using the pre-minified file: {minFileInfo.Name} ({fileContent.Length} bytes)");
return fileContent;
}
fileContent = ProcessBeforeAddingToTheBundle(context, fileName, fileContent);
bundleContentBuilder.Append(fileContent);
return GetAndMinifyFileContent(context, fileName);
}
protected virtual string GetFileContent(IBundlerContext context, string file)
private string GetAndMinifyFileContent(IBundlerContext context, string fileName)
{
return GetFileInfo(context, file).ReadAsString();
var fileContent = GetFileInfo(context, fileName).ReadAsString();
var nonMinifiedSize = fileContent.Length;
Logger.LogDebug($"- {fileName} ({nonMinifiedSize} bytes) - non minified, minifying...");
fileContent = Minifier.Minify(
fileContent,
context.BundleRelativePath,
fileName
);
Logger.LogInformation($" > Minified {fileName} ({nonMinifiedSize} bytes -> {fileContent.Length} bytes)");
return fileContent;
}
protected virtual IFileInfo GetFileInfo(IBundlerContext context, string file)

@ -1,6 +1,7 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
@ -37,5 +38,10 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
{
return actionDescriptor is ControllerActionDescriptor;
}
public static bool IsPageAction(this ActionDescriptor actionDescriptor)
{
return actionDescriptor is PageActionDescriptor;
}
}
}

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
@ -11,15 +11,23 @@ using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Hosting;
using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Json;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
@ -38,6 +46,9 @@ namespace Volo.Abp.AspNetCore.Mvc
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
DynamicProxyIgnoreTypes.Add<ControllerBase>();
DynamicProxyIgnoreTypes.Add<PageModel>();
context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar());
}
@ -50,6 +61,25 @@ namespace Volo.Abp.AspNetCore.Mvc
options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter));
});
Configure<AbpRemoteServiceApiDescriptionProviderOptions>(options =>
{
var statusCodes = new List<int>
{
(int) HttpStatusCode.Forbidden,
(int) HttpStatusCode.Unauthorized,
(int) HttpStatusCode.BadRequest,
(int) HttpStatusCode.NotFound,
(int) HttpStatusCode.NotImplemented,
(int) HttpStatusCode.InternalServerError
};
options.SupportedResponseTypes.AddIfNotContains(statusCodes.Select(statusCode => new ApiResponseType
{
Type = typeof(RemoteServiceErrorResponse),
StatusCode = statusCode
}));
});
context.Services.PostConfigure<AbpAspNetCoreMvcOptions>(options =>
{
if (options.MinifyGeneratedScript == null)
@ -108,6 +138,9 @@ namespace Volo.Abp.AspNetCore.Mvc
//Use DI to create view components
context.Services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
//Use DI to create razor page
context.Services.Replace(ServiceDescriptor.Singleton<IPageModelActivatorProvider, ServiceBasedPageModelActivatorProvider>());
//Add feature providers
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
var application = context.Services.GetSingletonInstance<IAbpApplication>();

@ -16,7 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc
public static void AddAbp(this MvcOptions options, IServiceCollection services)
{
AddConventions(options, services);
AddFilters(options);
AddActionFilters(options);
AddPageFilters(options);
AddModelBinders(options);
AddMetadataProviders(options, services);
}
@ -26,7 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Conventions.Add(new AbpServiceConventionWrapper(services));
}
private static void AddFilters(MvcOptions options)
private static void AddActionFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter));
@ -36,6 +37,14 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Filters.AddService(typeof(AbpExceptionFilter));
}
private static void AddPageFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpExceptionPageFilter));
options.Filters.AddService(typeof(AbpAuditPageFilter));
options.Filters.AddService(typeof(AbpFeaturePageFilter));
options.Filters.AddService(typeof(AbpUowPageFilter));
}
private static void AddModelBinders(MvcOptions options)
{
options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());

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

@ -1,6 +1,7 @@
using System;
using System.Linq;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Auditing
{
@ -16,6 +17,11 @@ namespace Volo.Abp.Auditing
private static bool ShouldIntercept(Type type)
{
if (DynamicProxyIgnoreTypes.Contains(type))
{
return false;
}
if (ShouldAuditTypeByDefault(type))
{
return true;

@ -3,6 +3,7 @@ using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Authorization
{
@ -18,8 +19,8 @@ namespace Volo.Abp.Authorization
private static bool ShouldIntercept(Type type)
{
return type.IsDefined(typeof(AuthorizeAttribute), true) ||
AnyMethodHasAuthorizeAttribute(type);
return !DynamicProxyIgnoreTypes.Contains(type) &&
(type.IsDefined(typeof(AuthorizeAttribute), true) || AnyMethodHasAuthorizeAttribute(type));
}
private static bool AnyMethodHasAuthorizeAttribute(Type implementationType)

@ -3,26 +3,21 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Volo.Abp.Authorization
{
public class MethodInvocationAuthorizationService : IMethodInvocationAuthorizationService, ITransientDependency
{
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentUser _currentUser;
private readonly ICurrentClient _currentClient;
private readonly IAbpAuthorizationPolicyProvider _abpAuthorizationPolicyProvider;
private readonly IAbpAuthorizationService _abpAuthorizationService;
public MethodInvocationAuthorizationService(
IAuthorizationService authorizationService,
ICurrentUser currentUser,
ICurrentClient currentClient)
IAbpAuthorizationPolicyProvider abpAuthorizationPolicyProvider,
IAbpAuthorizationService abpAuthorizationService)
{
_authorizationService = authorizationService;
_currentUser = currentUser;
_currentClient = currentClient;
_abpAuthorizationPolicyProvider = abpAuthorizationPolicyProvider;
_abpAuthorizationService = abpAuthorizationService;
}
public async Task CheckAsync(MethodInvocationAuthorizationContext context)
@ -32,10 +27,17 @@ namespace Volo.Abp.Authorization
return;
}
foreach (var authorizationAttribute in GetAuthorizationDataAttributes(context.Method))
var authorizationPolicy = await AuthorizationPolicy.CombineAsync(
_abpAuthorizationPolicyProvider,
GetAuthorizationDataAttributes(context.Method)
);
if (authorizationPolicy == null)
{
await CheckAsync(authorizationAttribute);
return;
}
await _abpAuthorizationService.CheckAsync(authorizationPolicy);
}
protected virtual bool AllowAnonymous(MethodInvocationAuthorizationContext context)
@ -49,7 +51,7 @@ namespace Volo.Abp.Authorization
.GetCustomAttributes(true)
.OfType<IAuthorizeData>();
if (methodInfo.IsPublic)
if (methodInfo.IsPublic && methodInfo.DeclaringType != null)
{
attributes = attributes
.Union(
@ -61,23 +63,5 @@ namespace Volo.Abp.Authorization
return attributes;
}
protected async Task CheckAsync(IAuthorizeData authorizationAttribute)
{
if (authorizationAttribute.Policy == null)
{
//TODO: Can we find a better, unified, way of checking if current request has been authenticated
if (!_currentUser.IsAuthenticated && !_currentClient.IsAuthenticated)
{
throw new AbpAuthorizationException("Authorization failed! User has not logged in.");
}
}
else
{
await _authorizationService.CheckAsync(authorizationAttribute.Policy);
}
//TODO: What about roles and other props?
}
}
}

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -52,10 +53,15 @@ namespace Volo.Abp.Cli.ProjectBuilding
string version = null,
string templateSource = null)
{
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
var latestVersion = await GetLatestSourceCodeVersionAsync(name, type);
string latestVersion;
#if DEBUG
latestVersion = GetCurrentVersionFromAssembly();
#else
latestVersion = await GetLatestSourceCodeVersionAsync(name, type);
#endif
if (version == null)
{
if (latestVersion == null)
@ -64,7 +70,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
Logger.LogWarning(string.Empty);
Logger.LogWarning("Find the following template in your cache directory: ");
Logger.LogWarning("\t Template Name\tVersion");
var templateList = GetLocalTemplates();
foreach (var cacheFile in templateList)
{
@ -74,12 +80,18 @@ namespace Volo.Abp.Cli.ProjectBuilding
Logger.LogWarning(string.Empty);
throw new CliUsageException("Use command: abp new Acme.BookStore -v version");
}
version = latestVersion;
}
var nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version;
string nugetVersion;
#if DEBUG
nugetVersion = version;
#else
nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version;
#endif
if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource))
{
Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version);
@ -87,6 +99,14 @@ namespace Volo.Abp.Cli.ProjectBuilding
}
var localCacheFile = Path.Combine(CliPaths.TemplateCache, name + "-" + version + ".zip");
#if DEBUG
if (File.Exists(localCacheFile))
{
return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion, nugetVersion);
}
#endif
if (Options.CacheTemplates && File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())
{
Logger.LogInformation("Using cached " + type + ": " + name + ", version: " + version);
@ -111,7 +131,13 @@ namespace Volo.Abp.Cli.ProjectBuilding
}
return new TemplateFile(fileContent, version, latestVersion, nugetVersion);
}
private static string GetCurrentVersionFromAssembly()
{
var assembly = Assembly.GetExecutingAssembly();
var fullVersion = assembly.GetName().Version.ToString(); //eg: 2.6.0.0
return fullVersion.Substring(0, fullVersion.LastIndexOf('.')); //eg: 2.6.0
}
private async Task<string> GetLatestSourceCodeVersionAsync(string name, string type)
@ -224,15 +250,15 @@ namespace Volo.Abp.Cli.ProjectBuilding
private List<(string TemplateName, string Version)> GetLocalTemplates()
{
var templateList = new List<(string TemplateName, string Version)>();
var templateList = new List<(string TemplateName, string Version)>();
var stringBuilder = new StringBuilder();
foreach (var cacheFile in Directory.GetFiles(CliPaths.TemplateCache))
{
stringBuilder.AppendLine(cacheFile);
}
var matches = Regex.Matches(stringBuilder.ToString(),$"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName})-(.+).zip");
var matches = Regex.Matches(stringBuilder.ToString(), $"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName})-(.+).zip");
foreach (Match match in matches)
{
templateList.Add((match.Groups[1].Value, match.Groups[2].Value));

@ -110,7 +110,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
var oldNodeIncludeValue = oldNode.Attributes["Include"].Value;
// ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method
if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith(Path.GetFileName(oldNodeIncludeValue))))
if (oldNodeIncludeValue.Contains(_projectName) && _entries.Any(e=>e.Name.EndsWith(GetProjectNameWithExtensionFromProjectReference(oldNodeIncludeValue))))
{
continue;
}
@ -125,6 +125,16 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
return doc.OuterXml;
}
private string GetProjectNameWithExtensionFromProjectReference(string oldNodeIncludeValue)
{
if (string.IsNullOrWhiteSpace(oldNodeIncludeValue))
{
return oldNodeIncludeValue;
}
return oldNodeIncludeValue.Split('\\', '/').Last();
}
protected abstract XmlElement GetNewReferenceNode(XmlDocument doc, string oldNodeIncludeValue);

@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Volo.Abp.Cli.Commands;
using Volo.Abp.Cli.Licensing;
using Volo.Abp.Cli.ProjectBuilding.Analyticses;
@ -25,12 +26,15 @@ namespace Volo.Abp.Cli.ProjectBuilding
protected IJsonSerializer JsonSerializer { get; }
protected IApiKeyService ApiKeyService { get; }
private readonly IConfiguration _configuration;
public TemplateProjectBuilder(ISourceCodeStore sourceCodeStore,
ITemplateInfoProvider templateInfoProvider,
ICliAnalyticsCollect cliAnalyticsCollect,
IOptions<AbpCliOptions> options,
IJsonSerializer jsonSerializer,
IApiKeyService apiKeyService)
IApiKeyService apiKeyService,
IConfiguration configuration)
{
SourceCodeStore = sourceCodeStore;
TemplateInfoProvider = templateInfoProvider;
@ -38,6 +42,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
Options = options.Value;
JsonSerializer = jsonSerializer;
ApiKeyService = apiKeyService;
_configuration = configuration;
Logger = NullLogger<TemplateProjectBuilder>.Instance;
}
@ -55,7 +60,30 @@ namespace Volo.Abp.Cli.ProjectBuilding
args.TemplateSource
);
var apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync();
DeveloperApiKeyResult apiKeyResult = null;
#if DEBUG
try
{
var apiKeyResultSection = _configuration.GetSection("apiKeyResult");
if (apiKeyResultSection.Exists())
{
apiKeyResult = apiKeyResultSection.Get<DeveloperApiKeyResult>(); //you can use user secrets
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
if (apiKeyResult == null)
{
apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync();
}
#else
apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync();
#endif
if (apiKeyResult != null)
{
if (apiKeyResult.ApiKey != null)

@ -55,7 +55,14 @@ namespace Volo.Abp.Cli.ProjectModification
if (IsAngularProject(fileDirectory))
{
await CreateNpmrcFileAsync(Path.GetDirectoryName(file));
if (includePreviews)
{
await CreateNpmrcFileAsync(Path.GetDirectoryName(file));
}
else if (switchToStable)
{
await DeleteNpmrcFileAsync(Path.GetDirectoryName(file));
}
}
RunYarn(fileDirectory);
@ -69,11 +76,21 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
private async Task DeleteNpmrcFileAsync(string directoryName)
{
var fileName = Path.Combine(directoryName, ".npmrc");
if (File.Exists(fileName))
{
File.Delete(fileName);
}
}
private async Task CreateNpmrcFileAsync(string directoryName)
{
var fileName = Path.Combine(directoryName, ".npmrc");
var abpRegistry = "@abp:registry:https://www.myget.org/F/abp-nightly/npm";
var abpRegistry = "@abp:registry=https://www.myget.org/F/abp-nightly/npm";
var voloRegistry = await GetVoloRegistryAsync();
if (File.Exists(fileName))

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
@ -9,6 +9,7 @@
<PackAsTool>true</PackAsTool>
<ToolCommandName>abp</ToolCommandName>
<RootNamespace />
<UserSecretsId>43599b00-4fa5-4b9f-a314-0757889b296b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
@ -17,11 +18,12 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj" />
</ItemGroup>
</Project>
</Project>

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

@ -108,6 +108,23 @@ namespace Volo.Abp.Reflection
?? defaultValue;
}
/// <summary>
/// Tries to gets attributes defined for a class member and it's declaring type including inherited attributes.
/// </summary>
/// <typeparam name="TAttribute">Type of the attribute</typeparam>
/// <param name="memberInfo">MemberInfo</param>
/// <param name="inherit">Inherit attribute from base classes</param>
public static IEnumerable<TAttribute> GetAttributesOfMemberOrDeclaringType<TAttribute>(MemberInfo memberInfo, bool inherit = true)
where TAttribute : class
{
var customAttributes = memberInfo.GetCustomAttributes(true).OfType<TAttribute>();
var declaringTypeCustomAttributes =
memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>();
return declaringTypeCustomAttributes != null
? customAttributes.Concat(declaringTypeCustomAttributes).Distinct()
: customAttributes;
}
/// <summary>
/// Gets value of a property by it's full path from given object
/// </summary>

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Domain.Entities
{
@ -56,6 +58,14 @@ namespace Volo.Abp.Domain.Entities
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
[Serializable]
@ -115,5 +125,13 @@ namespace Volo.Abp.Domain.Entities
{
_distributedEvents.Clear();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
}

@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
@ -11,6 +13,9 @@ namespace Volo.Abp.Domain.Entities
/// </summary>
public static class EntityHelper
{
private static readonly ConcurrentDictionary<string, PropertyInfo> CachedIdProperties =
new ConcurrentDictionary<string, PropertyInfo>();
public static bool IsEntity([NotNull] Type type)
{
return typeof(IEntity).IsAssignableFrom(type);
@ -95,32 +100,36 @@ namespace Volo.Abp.Domain.Entities
var lambdaBody = Expression.Equal(leftExpression, rightExpression);
return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
}
public static void TrySetId<TKey>(
IEntity<TKey> entity,
Func<TKey> idFactory,
bool checkForDisableGuidGenerationAttribute = false)
bool checkForDisableIdGenerationAttribute = false)
{
//TODO: Can be optimized (by caching per entity type)?
var entityType = entity.GetType();
var idProperty = entityType.GetProperty(
nameof(entity.Id)
);
var property = CachedIdProperties.GetOrAdd(
$"{entity.GetType().FullName}-{checkForDisableIdGenerationAttribute}", () =>
{
var idProperty = entity
.GetType()
.GetProperties()
.FirstOrDefault(x => x.Name == nameof(entity.Id) &&
x.GetSetMethod(true) != null);
if (idProperty == null || idProperty.GetSetMethod(true) == null)
{
return;
}
if (idProperty == null)
{
return null;
}
if (checkForDisableGuidGenerationAttribute)
{
if (idProperty.IsDefined(typeof(DisableIdGenerationAttribute), true))
{
return;
}
}
if (checkForDisableIdGenerationAttribute &&
idProperty.IsDefined(typeof(DisableIdGenerationAttribute), true))
{
return null;
}
return idProperty;
});
idProperty.SetValue(entity, idFactory());
property?.SetValue(entity, idFactory());
}
}
}

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Internal;
namespace Volo.Abp.EntityFrameworkCore
{
@ -8,7 +7,9 @@ namespace Volo.Abp.EntityFrameworkCore
{
public static bool IsRelational(this DatabaseFacade database)
{
return database.GetInfrastructure().GetService<IRelationalConnection>() != null;
#pragma warning disable EF1001 // Internal EF Core API usage.
return ((IDatabaseFacadeDependenciesAccessor)database).Dependencies is IRelationalDatabaseFacadeDependencies;
#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
}

@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Features
{
@ -17,8 +18,9 @@ namespace Volo.Abp.Features
private static bool ShouldIntercept(Type type)
{
return type.IsDefined(typeof(RequiresFeatureAttribute), true) ||
AnyMethodHasRequiresFeatureAttribute(type);
return !DynamicProxyIgnoreTypes.Contains(type) &&
(type.IsDefined(typeof(RequiresFeatureAttribute), true) ||
AnyMethodHasRequiresFeatureAttribute(type));
}
private static bool AnyMethodHasRequiresFeatureAttribute(Type implementationType)

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

@ -8,21 +8,31 @@ namespace Volo.Abp.Minify.NUglify
{
public abstract class NUglifyMinifierBase : IMinifier, ITransientDependency
{
private static void CheckErrors(UglifyResult result)
private static void CheckErrors(UglifyResult result, string originalFileName)
{
if (result.HasErrors)
{
var errorMessage = "There are some errors on uglifying the given source code!";
if (originalFileName != null)
{
errorMessage += " Original file: " + originalFileName;
}
throw new NUglifyException(
$"There are some errors on uglifying the given source code!{Environment.NewLine}{result.Errors.Select(err => err.ToString()).JoinAsString(Environment.NewLine)}",
$"{errorMessage}{Environment.NewLine}{result.Errors.Select(err => err.ToString()).JoinAsString(Environment.NewLine)}",
result.Errors
);
}
}
public string Minify(string source, string fileName = null)
public string Minify(
string source,
string fileName = null,
string originalFileName = null)
{
var result = UglifySource(source, fileName);
CheckErrors(result);
CheckErrors(result, originalFileName);
return result.Code;
}

@ -5,6 +5,8 @@ using Volo.Abp.Reflection;
namespace Volo.Abp.Data
{
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public static class HasExtraPropertiesExtensions
{
public static bool HasProperty(this IHasExtraProperties source, string name)
@ -12,17 +14,18 @@ namespace Volo.Abp.Data
return source.ExtraProperties.ContainsKey(name);
}
public static object GetProperty(this IHasExtraProperties source, string name)
public static object GetProperty(this IHasExtraProperties source, string name, object defaultValue = null)
{
return source.ExtraProperties?.GetOrDefault(name);
return source.ExtraProperties?.GetOrDefault(name)
?? defaultValue;
}
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name)
public static TProperty GetProperty<TProperty>(this IHasExtraProperties source, string name, TProperty defaultValue = default)
{
var value = source.GetProperty(name);
if (value == default)
if (value == null)
{
return default;
return defaultValue;
}
if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true))

@ -2,6 +2,8 @@
namespace Volo.Abp.Data
{
//TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0
public interface IHasExtraProperties
{
Dictionary<string, object> ExtraProperties { get; }

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
[Serializable]
public class ExtensibleObject : IHasExtraProperties
public class ExtensibleObject : IHasExtraProperties, IValidatableObject
{
public Dictionary<string, object> ExtraProperties { get; protected set; }
@ -13,5 +14,13 @@ namespace Volo.Abp.ObjectExtending
{
ExtraProperties = new Dictionary<string, object>();
}
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return ExtensibleObjectValidator.GetValidationErrors(
this,
validationContext
);
}
}
}

@ -122,8 +122,6 @@ namespace Volo.Abp.ObjectExtending
}
}
//TODO: Move these methods to a class like ObjectExtensionHelper
public static bool CanMapProperty<TSource, TDestination>(
[NotNull] string propertyName,
MappingPropertyDefinitionChecks? definitionChecks = 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);
}
}
}
}

@ -17,11 +17,15 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Dictionary<object, object> Configuration { get; }
[NotNull]
public List<Action<ObjectExtensionValidationContext>> Validators { get; }
public ObjectExtensionInfo([NotNull] Type type)
{
Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
Properties = new Dictionary<string, ObjectExtensionPropertyInfo>();
Configuration = new Dictionary<object, object>();
Validators = new List<Action<ObjectExtensionValidationContext>>();
}
public virtual bool HasProperty(string propertyName)

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending
@ -15,6 +16,12 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Type Type { get; }
[NotNull]
public List<ValidationAttribute> ValidationAttributes { get; }
[NotNull]
public List<Action<ObjectExtensionPropertyValidationContext>> Validators { get; }
/// <summary>
/// Indicates whether to check the other side of the object mapping
/// if it explicitly defines the property. This property is used in;
@ -42,6 +49,8 @@ namespace Volo.Abp.ObjectExtending
Name = Check.NotNull(name, nameof(name));
Configuration = new Dictionary<object, object>();
ValidationAttributes = new List<ValidationAttribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();
}
}
}

@ -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,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
@ -20,9 +21,19 @@ namespace Volo.Abp.Quartz
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpQuartzPreOptions>>().Value;
_scheduler = context.ServiceProvider.GetService<IScheduler>();
_scheduler.JobFactory = context.ServiceProvider.GetService<IJobFactory>();
AsyncHelper.RunSync(() => _scheduler.Start());
if (options.StartDelay.Ticks > 0)
{
AsyncHelper.RunSync(() => _scheduler.StartDelayed(options.StartDelay));
}
else
{
AsyncHelper.RunSync(() => _scheduler.Start());
}
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)

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

@ -43,6 +43,7 @@
"PagerFirst": "الأول",
"PagerLast": "الاخير",
"PagerInfo": "عرض _START_ الي _END_ من _TOTAL_ إدخالات",
"PagerInfo{0}{1}{2}": "عرض {0} الي {1} من {2} إدخالات",
"PagerInfoEmpty": "عرض 0 الي 0 من 0 إدخالات",
"PagerInfoFiltered": "(تمت تصفيته من _MAX_ مجموع الإدخالات)",
"NoDataAvailableInDatatable": "لا توجد بيانات متاحة في الجدول",

@ -39,6 +39,7 @@
"PagerFirst": "První",
"PagerLast": "Poslední",
"PagerInfo": "Zobrazeno od _START_ do _END_ z celkem _TOTAL_ záznamů",
"PagerInfo{0}{1}{2}": "Zobrazeno od {0} do {1} z celkem {2} záznamů",
"PagerInfoEmpty": "Zobrazeno od 0 do 0 z celkem 0 záznamů",
"PagerInfoFiltered": "(filtrováno ze všech _MAX_ záznamů)",
"NoDataAvailableInDatatable": "V tabulce nejsou žádná data",

@ -43,6 +43,7 @@
"PagerFirst": "First",
"PagerLast": "Last",
"PagerInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
"PagerInfo{0}{1}{2}": "Showing {0} to {1} of {2} entries",
"PagerInfoEmpty": "Showing 0 to 0 of 0 entries",
"PagerInfoFiltered": "(filtered from _MAX_ total entries)",
"NoDataAvailableInDatatable": "No data available",

@ -39,6 +39,7 @@
"PagerFirst": "Primero",
"PagerLast": "Último",
"PagerInfo": "Mostrando desde _START_ hasta _END_ total _TOTAL_",
"PagerInfo{0}{1}{2}": "Mostrando desde {0} hasta {1} total {2}",
"PagerInfoEmpty": "Mostrando desde 0 hasta 0 de 0 registros",
"PagerInfoFiltered": "(filtrado de un total de _MAX_ registros)",
"NoDataAvailableInDatatable": "No hay datos",

@ -39,6 +39,7 @@
"PagerFirst": "Pierwsza",
"PagerLast": "Ostatnia",
"PagerInfo": "Pokaż od _START_ do _END_ z _TOTAL_ rekordów",
"PagerInfo{0}{1}{2}": "Pokaż od {0} do {1} z {2} rekordów",
"PagerInfoEmpty": "Pokaż od 0 do 0 z 0 rekordów",
"PagerInfoFiltered": "(filtruj od _MAX_ wszystkich rekordów)",
"NoDataAvailableInDatatable": "Nie znaleziono danych w tabeli",

@ -39,6 +39,7 @@
"PagerFirst": "Primeira",
"PagerLast": "Última",
"PagerInfo": "Mostrando _START_ até _END_ de _TOTAL_ registros",
"PagerInfo{0}{1}{2}": "Mostrando {0} até {1} de {2} registros",
"PagerInfoEmpty": "Mostrando 0 até 0 de 0 registros",
"PagerInfoFiltered": "(filtrado de um total de _MAX_ registros)",
"NoDataAvailableInDatatable": "Nenhum dado disponível",

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

@ -43,6 +43,7 @@
"PagerFirst": "İlk",
"PagerLast": "Son",
"PagerInfo": "_TOTAL_ kayıttan _START_ ile _END_ arası gösteriliyor.",
"PagerInfo{0}{1}{2}": "{2} kayıttan {0} ile {1} arası gösteriliyor.",
"PagerInfoEmpty": "0 kayıttan 0 ile 0 arası gösteriliyor.",
"PagerInfoFiltered": "(_MAX_ kayıt arasından filtrelendi)",
"NoDataAvailableInDatatable": "Tabloda kayıt mevcut değil.",

@ -39,6 +39,7 @@
"PagerFirst": "Trang đầu",
"PagerLast": "Trang cuối",
"PagerInfo": "Hiển thị từ _START_ đến _END_ trong _TOTAL_ mục",
"PagerInfo{0}{1}{2}": "Hiển thị từ {0} đến {1} trong {2} mục",
"PagerInfoEmpty": "Hiển thị từ 0 đến 0 trong 0 mục",
"PagerInfoFiltered": "(được lọc từ tổng số _MAX_ mục)",
"NoDataAvailableInDatatable": "Không có dữ liệu trong bảng",

@ -40,6 +40,7 @@
"PagerFirst": "首页",
"PagerLast": "尾页",
"PagerInfo": "显示 _TOTAL_ 个条目中的 _START_ 到 _END_ 个.",
"PagerInfo{0}{1}{2}": "显示 {2} 个条目中的 {0} 到 {1} 个.",
"PagerInfoEmpty": "显示0个条目中的0到0",
"PagerInfoFiltered": "(从 _MAX_ 总条目中过滤掉)",
"NoDataAvailableInDatatable": "表中没有数据",

@ -39,6 +39,7 @@
"PagerFirst": "首頁",
"PagerLast": "末頁",
"PagerInfo": "顯示 _TOTAL_ 個紀錄的 _START_ 到 _END_ 個.",
"PagerInfo{0}{1}{2}": "顯示 {2} 個紀錄的 {0} 到 {1} 個.",
"PagerInfoEmpty": "顯示0個紀錄中的0到0",
"PagerInfoFiltered": "(從 _MAX_ 所有紀錄中過濾掉)",
"NoDataAvailableInDatatable": "資料表中沒有資料",

@ -1,5 +1,7 @@
using System.Reflection;
using System;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Uow
{
@ -7,10 +9,15 @@ namespace Volo.Abp.Uow
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo()))
if (ShouldIntercept(context.ImplementationType))
{
context.Interceptors.TryAdd<UnitOfWorkInterceptor>();
}
}
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && UnitOfWorkHelper.IsUnitOfWorkType(type.GetTypeInfo());
}
}
}

@ -18,6 +18,7 @@ namespace Volo.Abp.Validation
context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
AutoAddObjectValidationContributors(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>

@ -1,4 +1,6 @@
using Volo.Abp.DependencyInjection;
using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Validation
{
@ -6,10 +8,15 @@ namespace Volo.Abp.Validation
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType))
if (ShouldIntercept(context.ImplementationType))
{
context.Interceptors.TryAdd<ValidationInterceptor>();
}
}
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) && typeof(IValidationEnabled).IsAssignableFrom(type);
}
}
}

@ -24,6 +24,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc.UI\Volo.Abp.AspNetCore.Mvc.UI.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Tests\Volo.Abp.AspNetCore.Tests.csproj" />
@ -39,6 +40,28 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Update="Volo\Abp\AspNetCore\Mvc\Uow\UnitOfWorkTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Auditing\AuditTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Features\FeatureTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\ExceptionHandling\ExceptionTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="Volo\Abp\AspNetCore\Mvc\Authorization\AuthTestPage.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!-- https://github.com/NuGet/Home/issues/4412. -->
<Target Name="CopyDepsFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">

@ -1,6 +1,9 @@
using System;
using System;
using System.Linq;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Authorization;
using Volo.Abp.AspNetCore.Mvc.Localization;
@ -73,6 +76,11 @@ namespace Volo.Abp.AspNetCore.Mvc
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
});
Configure<RazorPagesOptions>(options =>
{
options.RootDirectory = "/Volo/Abp/AspNetCore/Mvc";
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

@ -51,6 +51,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing
await _auditingStore.Received().SaveAsync(Arg.Any<AuditLogInfo>());
}
[Fact]
public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object()
{

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

@ -30,9 +30,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
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]
@ -44,9 +46,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
)
);
#pragma warning disable 4014
_fakeExceptionSubscriber
.DidNotReceive()
.HandleAsync(Arg.Any<ExceptionNotificationContext>());
#pragma warning restore 4014
}
}
}

@ -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("");
}
}
}

@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.TestServices;
using Volo.Abp.Autofac;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Modularity;
namespace Volo.Abp.Authorization
@ -13,7 +14,8 @@ namespace Volo.Abp.Authorization
{
context.Services.OnRegistred(onServiceRegistredContext =>
{
if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType))
if (typeof(IMyAuthorizedService1).IsAssignableFrom(onServiceRegistredContext.ImplementationType) &&
!DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType))
{
onServiceRegistredContext.Interceptors.TryAdd<AuthorizationInterceptor>();
}

@ -1,4 +1,10 @@
using Volo.Abp.Testing;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
using Volo.Abp.Security.Claims;
using Volo.Abp.Testing;
namespace Volo.Abp.Authorization
{
@ -8,5 +14,20 @@ namespace Volo.Abp.Authorization
{
options.UseAutofac();
}
protected override void AfterAddApplication(IServiceCollection services)
{
var claims = new List<Claim>() {
new Claim(AbpClaimTypes.UserName, "Douglas"),
new Claim(AbpClaimTypes.UserId, "1fcf46b2-28c3-48d0-8bac-fa53268a2775"),
new Claim(AbpClaimTypes.Role, "MyRole")
};
var identity = new ClaimsIdentity(claims);
var claimsPrincipal = new ClaimsPrincipal(identity);
var principalAccessor = Substitute.For<ICurrentPrincipalAccessor>();
principalAccessor.Principal.Returns(ci => claimsPrincipal);
Thread.CurrentPrincipal = claimsPrincipal;
}
}
}
}

@ -9,14 +9,33 @@ namespace Volo.Abp.Authorization
public class Authorization_Tests : AuthorizationTestBase
{
private readonly IMyAuthorizedService1 _myAuthorizedService1;
private readonly IMySimpleAuthorizedService _simpleAuthorizedService;
private readonly IMyAuthorizedServiceWithRole _myAuthorizedServiceWithRole;
private readonly IPermissionDefinitionManager _permissionDefinitionManager;
public Authorization_Tests()
{
_myAuthorizedService1 = GetRequiredService<IMyAuthorizedService1>();
_simpleAuthorizedService = GetRequiredService<IMySimpleAuthorizedService>();
_myAuthorizedServiceWithRole = GetRequiredService<IMyAuthorizedServiceWithRole>();
_permissionDefinitionManager = GetRequiredService<IPermissionDefinitionManager>();
}
[Fact]
public async Task Should_Not_Allow_To_Call_Authorized_Method_For_Anonymous_User()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _simpleAuthorizedService.ProtectedByClassAsync();
});
}
[Fact]
public async Task Should_Allow_To_Call_Anonymous_Method_For_Anonymous_User()
{
(await _simpleAuthorizedService.AnonymousAsync()).ShouldBe(42);
}
[Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Permission_ProtectedByClass()
{
@ -52,5 +71,31 @@ namespace Volo.Abp.Authorization
{
_permissionDefinitionManager.GetGroups().Count.ShouldBe(1);
}
[Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Role_ProtectedByRole_Async()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _myAuthorizedServiceWithRole.ProtectedByAnotherRole().ConfigureAwait(false);
}).ConfigureAwait(false);
}
[Fact]
public async Task Should_Allow_To_Call_Method_If_Has_No_Role_ProtectedByRole_Async()
{
int result = await _myAuthorizedServiceWithRole.ProtectedByRole().ConfigureAwait(false);
result.ShouldBe(42);
}
[Fact]
public async Task Should_Not_Allow_To_Call_Method_If_Has_No_Role_ProtectedByScheme_Async()
{
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await _myAuthorizedServiceWithRole.ProtectedByScheme().ConfigureAwait(false);
}).ConfigureAwait(false);
}
}
}

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

@ -31,4 +31,4 @@ namespace Volo.Abp.Authorization.TestServices
return 42;
}
}
}
}

@ -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,4 +1,5 @@
using System;
using System.ComponentModel;
using Shouldly;
using Xunit;
@ -33,6 +34,15 @@ namespace Volo.Abp.Domain.Entities
myEntityDisablesIdGeneration.Id.ShouldBe(default);
}
[Fact]
public static void SetId_NewIdFromBaseClass()
{
var idValue = Guid.NewGuid();
var myNewIdEntity = new NewIdEntity();
EntityHelper.TrySetId(myNewIdEntity, () => idValue, true);
myNewIdEntity.Id.ShouldBe(idValue);
}
private class MyEntityDerivedFromAggregateRoot : AggregateRoot<Guid>
{
@ -53,5 +63,14 @@ namespace Volo.Abp.Domain.Entities
[DisableIdGeneration]
public override Guid Id { get; protected set; }
}
public class NewIdEntity : Entity<Guid>
{
public new Guid Id
{
get => base.Id;
set => base.Id = value;
}
}
}
}

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Autofac;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Volo.Abp.Validation;
@ -108,7 +109,8 @@ namespace Volo.Abp.FluentValidation
{
context.Services.OnRegistred(onServiceRegistredContext =>
{
if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType))
if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) &&
!DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType))
{
onServiceRegistredContext.Interceptors.TryAdd<ValidationInterceptor>();
}

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

Loading…
Cancel
Save