diff --git a/docs/en/AutoMapper-Integration.md b/docs/en/AutoMapper-Integration.md deleted file mode 100644 index d197861f25..0000000000 --- a/docs/en/AutoMapper-Integration.md +++ /dev/null @@ -1,3 +0,0 @@ -## AutoMapper Integration - -TODO \ No newline at end of file diff --git a/docs/en/Best-Practices/Application-Services.md b/docs/en/Best-Practices/Application-Services.md index 0979304931..876105c214 100644 --- a/docs/en/Best-Practices/Application-Services.md +++ b/docs/en/Best-Practices/Application-Services.md @@ -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 +public class IssueDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -57,7 +58,7 @@ Example: ````C# [Serializable] -public class IssueWithDetailsDto : FullAuditedEntityDto +public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -66,14 +67,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto } [Serializable] -public class MilestoneDto : EntityDto +public class MilestoneDto : ExtensibleEntityDto { public string Name { get; set; } public bool IsClosed { get; set; } } [Serializable] -public class LabelDto : EntityDto +public class LabelDto : ExtensibleEntityDto { public string Name { get; set; } public string Color { get; set; } @@ -120,6 +121,7 @@ Task> 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. diff --git a/docs/en/Best-Practices/Data-Transfer-Objects.md b/docs/en/Best-Practices/Data-Transfer-Objects.md index 0c8580abb7..0fca0e86f2 100644 --- a/docs/en/Best-Practices/Data-Transfer-Objects.md +++ b/docs/en/Best-Practices/Data-Transfer-Objects.md @@ -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`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` and so on). + * **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto`), 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. diff --git a/docs/en/Nightly-Builds.md b/docs/en/Nightly-Builds.md index e7ebe0f33b..1a66af32e1 100644 --- a/docs/en/Nightly-Builds.md +++ b/docs/en/Nightly-Builds.md @@ -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. \ No newline at end of file diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md index fad3ff2b0c..6eff328c85 100644 --- a/docs/en/Object-Extensions.md +++ b/docs/en/Object-Extensions.md @@ -1,3 +1,365 @@ # Object Extensions -TODO \ No newline at end of file +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 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("Title"); + +if (user.GetProperty("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(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(options => + { + options.AddOrUpdateProperty("SocialSecurityNumber"); + options.AddOrUpdateProperty("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("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( + 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( + "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( + "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( + "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(objConfig => +{ + //Define two properties with their own validation rules + + objConfig.AddOrUpdateProperty("Password", propertyConfig => + { + propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); + }); + + objConfig.AddOrUpdateProperty("PasswordRepeat", propertyConfig => + { + propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); + }); + + //Write a common validation logic works on multiple properties + + objConfig.Validators.Add(context => + { + if (context.ValidatingObject.GetProperty("Password") != + context.ValidatingObject.GetProperty("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( + "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() + .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( + "SocialSecurityNumber", + options => + { + options.MapEfCore(b => b.HasMaxLength(32)); + } + ); +```` + +See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. \ No newline at end of file diff --git a/docs/en/Object-To-Object-Mapping.md b/docs/en/Object-To-Object-Mapping.md index 52260402f1..b7463607e9 100644 --- a/docs/en/Object-To-Object-Mapping.md +++ b/docs/en/Object-To-Object-Mapping.md @@ -145,6 +145,23 @@ options.AddProfile(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() + .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 Interface diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index 08e17e0e3f..a754893114 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -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'; diff --git a/docs/en/UI/Angular/Dom-Insertion-Service.md b/docs/en/UI/Angular/Dom-Insertion-Service.md index d5ea9fe3a2..489a7a4e1f 100644 --- a/docs/en/UI/Angular/Dom-Insertion-Service.md +++ b/docs/en/UI/Angular/Dom-Insertion-Service.md @@ -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, `` element will place at the **end** of ``. +In the example above, `` element will place at the **end** of `` 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 `` element will place at the **end** of ``. +In the example above, `` element will place at the **end** of `` 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, `` element **will be removed** from `` when the component is destroyed. + ## API ### insertContent ```js -insertContent(contentStrategy: ContentStrategy): void +insertContent( + contentStrategy: ContentStrategy, +): 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? diff --git a/docs/en/UI/Common/Utils/Linked-List.md b/docs/en/UI/Common/Utils/Linked-List.md index 5da9cc10e9..dd9251813c 100644 --- a/docs/en/UI/Common/Utils/Linked-List.md +++ b/docs/en/UI/Common/Utils/Linked-List.md @@ -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' */ -``` - - - +``` \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 20214b8447..0e13262b18 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -155,6 +155,10 @@ { "text": "Data Filtering", "path": "Data-Filtering.md" + }, + { + "text": "Object Extensions", + "path": "Object-Extensions.md" } ] }, diff --git a/docs/zh-Hans/AutoMapper-Integration.md b/docs/zh-Hans/AutoMapper-Integration.md deleted file mode 100644 index d197861f25..0000000000 --- a/docs/zh-Hans/AutoMapper-Integration.md +++ /dev/null @@ -1,3 +0,0 @@ -## AutoMapper Integration - -TODO \ No newline at end of file diff --git a/docs/zh-Hans/Best-Practices/Application-Services.md b/docs/zh-Hans/Best-Practices/Application-Services.md index 6c8d11b32b..6c681edde1 100644 --- a/docs/zh-Hans/Best-Practices/Application-Services.md +++ b/docs/zh-Hans/Best-Practices/Application-Services.md @@ -17,7 +17,7 @@ ##### 基础DTO -**推荐** 为实体定义一个**基础**DTO. +**推荐** 为聚合根定义一个**基础**DTO. - 直接包含实体中所有的**原始属性**. - 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`). @@ -27,7 +27,7 @@ ```c# [Serializable] -public class IssueDto : FullAuditedEntityDto +public class IssueDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -57,7 +57,7 @@ public class IssueLabelDto ````C# [Serializable] -public class IssueWithDetailsDto : FullAuditedEntityDto +public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto { public string Title { get; set; } public string Text { get; set; } @@ -66,14 +66,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto } [Serializable] -public class MilestoneDto : EntityDto +public class MilestoneDto : ExtensibleEntityDto { public string Name { get; set; } public bool IsClosed { get; set; } } [Serializable] -public class LabelDto : EntityDto +public class LabelDto : ExtensibleEntityDto { public string Name { get; set; } public string Color { get; set; } @@ -120,6 +120,7 @@ Task> GetListAsync(QuestionListQueryDto queryDto); * **推荐** 使用 `CreateAsync` 做为**方法名**. * **推荐** 使用**专门的输入DTO**来创建实体. +* **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性. * **推荐** 使用 **data annotations** 进行输入验证. * 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). * **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性. @@ -134,7 +135,7 @@ Task 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 VoteAsync(Guid id, VoteType type); * **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询. +#### 额外的属性 + +* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. + #### 操作/删除 实体 * **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作. diff --git a/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md b/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md index 0c6017e6e7..29e51501ea 100644 --- a/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md +++ b/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md @@ -2,6 +2,7 @@ * **推荐** 在 **application.contracts** 层中定义DTO. * **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto`, `CreationAuditedEntityDto`, `AuditedEntityDto`, `FullAuditedEntityDto` 等). +* **推荐** 从**聚合根**的**扩展DTO**继承(如 `ExtensibleAuditedEntityDto`), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO. * **推荐** 定义 **public getter 和 setter** 的DTO成员 . * **推荐** 使用 **data annotations** **验证** service输入DTO的属性. * **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口. diff --git a/docs/zh-Hans/Object-Extensions.md b/docs/zh-Hans/Object-Extensions.md new file mode 100644 index 0000000000..27957708f4 --- /dev/null +++ b/docs/zh-Hans/Object-Extensions.md @@ -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 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("Title"); + +if (user.GetProperty("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(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(options => + { + options.AddOrUpdateProperty("SocialSecurityNumber"); + options.AddOrUpdateProperty("IsSuperUser"); + } + ); +```` + +### AddOrUpdateProperty + +虽然可以如上所示使用 `AddOrUpdateProperty`, 但如果要定义单个额外的属性,也可以使用快捷的扩展方法: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty("SocialSecurityNumber"); +```` + +有时将单个额外属性定义为多种类型是可行的. 你可以使用以下代码,而不是一个一个地定义: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + new[] + { + typeof(IdentityUserDto), + typeof(IdentityUserCreateDto), + typeof(IdentityUserUpdateDto) + }, + "SocialSecurityNumber" + ); +```` + +#### 属性配置 + +`AddOrUpdateProperty` 还可以为属性定义执行其他配置的操作. + +Example: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "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( + "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() + .MapExtraProperties(); + } +} +```` + +它与 `MapExtraPropertiesTo()` 方法具有相同的参数。 + +## Entity Framework Core 数据库映射 + +如果你使用的是EF Core,可以将额外的属性映射到数据库中的表字段. 例: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.MapEfCore(b => b.HasMaxLength(32)); + } + ); +```` + +参阅 [Entity Framework Core 集成文档](Entity-Framework-Core.md) 了解更多内容. \ No newline at end of file diff --git a/docs/zh-Hans/Object-To-Object-Mapping.md b/docs/zh-Hans/Object-To-Object-Mapping.md index 67d580ae54..57b744e0bf 100644 --- a/docs/zh-Hans/Object-To-Object-Mapping.md +++ b/docs/zh-Hans/Object-To-Object-Mapping.md @@ -145,6 +145,23 @@ options.AddProfile(validate: true); > 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`. +### 映射对象扩展 + +[对象扩展系统](Object-Extensions.md) 允许为已存在的类定义额外属性. ABP 框架提供了一个映射定义扩展可以正确的映射两个对象的额外属性. + +````csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap() + .MapExtraProperties(); + } +} +```` + +如果两个类都是可扩展对象(实现了 `IHasExtraProperties` 接口),建议使用 `MapExtraProperties` 方法. 更多信息请参阅[对象扩展文档](Object-Extensions.md). + ## 高级主题 ### IObjectMapper 接口 diff --git a/docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md b/docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md new file mode 100644 index 0000000000..029691e720 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md @@ -0,0 +1,3 @@ +# ABP ASP.NET Core UI Datatables.Net 集成 + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/UI/Common/Utils/Linked-List.md b/docs/zh-Hans/UI/Common/Utils/Linked-List.md new file mode 100644 index 0000000000..8dc4d20af9 --- /dev/null +++ b/docs/zh-Hans/UI/Common/Utils/Linked-List.md @@ -0,0 +1,1454 @@ +# 链表 (双向) + +Core模块提供了称为[双链表](https://en.wikipedia.org/wiki/Doubly_linked_list)的实用数据结构. 简而言之双向链表是一系列记录(又称节点),这些记录具有上一个节点,下一个节点及其自身值(或数据)的信息. + +## 入门 + +要创建一个双向链表,你需要做的就是导入和创建它的一个新的实例: + +```js +import { LinkedList } from '@abp/ng.core'; + +const list = new LinkedList(); +``` + +构造函数没有任何参数. + +## 用法 + +### 如何添加新节点 + +有几种方法可以在链表中创建新节点,这些方法都可以单独使用,也可以通过 `add` 和 `addMany` 方法. + + +#### addHead(value) + +```js +addHead(value: T): ListNode\ +``` + +将给定值添加到链表的第一个节点: + +```js +list.addHead('a'); + +// "a" + +list.addHead('b'); + +// "b" <-> "a" + +list.addHead('c'); + +// "c" <-> "b" <-> "a" +``` + + + +#### addManyHead(values) + +```js +addManyHead(values: T\[\]): ListNode\\[\] +``` + +将给定的多个值添加到链表的第一个节点: + +```js +list.addManyHead(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +list.addManyHead(['x', 'y', 'z']); + +// "x" <-> "y" <-> "z" <-> "a" <-> "b" <-> "c" +``` + + + +#### addTail(value) + +```js +addTail(value: T): ListNode\ +``` + +将给定值添加到链表的最后一个节点: + +```js +list.addTail('a'); + +// "a" + +list.addTail('b'); + +// "a" <-> "b" + +list.addTail('c'); + +// "a" <-> "b" <-> "c" +``` + + + +#### addManyTail(values) + +```js +addManyTail(values: T\[\]): ListNode\\[\] +``` + +将给定多个值添加到链表的最后一个节点: + +```js +list.addManyTail(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +list.addManyTail(['x', 'y', 'z']); + +// "a" <-> "b" <-> "c" <-> "x" <-> "y" <-> "z" +``` + +#### addAfter(value, previousValue, compareFn) + +```js +addAfter(value: T, previousValue: T, compareFn = compare): ListNode\ +``` + +添加给定值到previousValue节点后: + +```js +list.addTail('a'); +list.addTail('b'); +list.addTail('b'); +list.addTail('c'); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addAfter('x', 'b'); + +// "a" <-> "b" <-> "x" <-> "b" <-> "c" +``` + + +你可以自定义比较器: + +```js +list.addTail({ x: 1 }); +list.addTail({ x: 2 }); +list.addTail({ x: 3 }); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list.addAfter( + { x: 0 }, + 2, + (value, searchedValue) => value.x === searchedValue +); + +// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3} +``` + + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + +#### addManyAfter(values, previousValue, compareFn) + +```js +addManyAfter(values: T\[\], previousValue: T, compareFn = compare): ListNode\\[\] +``` + +添加给定的多个值到previousValue节点后: + +```js +list.addManyTail(['a', 'b', 'b', 'c']); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addManyAfter(['x', 'y'], 'b'); + +// "a" <-> "b" <-> "x" <-> "y" <-> "b" <-> "c" +``` + +你可以自定义比较器: + +```js +list.addManyTail([{ x: 1 },{ x: 2 },{ x: 3 }]); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list.addManyAfter( + [{ x: 4 }, { x: 5 }], + 2, + (value, searchedValue) => value.x === searchedValue +); + +// {"x":1} <-> {"x":2} <-> {"x":4} <-> {"x":5} <-> {"x":3} +``` + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + +#### addBefore(value, nextValue, compareFn) + +```js +addBefore(value: T, nextValue: T, compareFn = compare): ListNode\ +``` + +添加给值到previousValue节点前: + +```js +list.addTail('a'); +list.addTail('b'); +list.addTail('b'); +list.addTail('c'); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addBefore('x', 'b'); + +// "a" <-> "x" <-> "b" <-> "b" <-> "c" +``` + +你可以自定义比较器: + +```js +list.addTail({ x: 1 }); +list.addTail({ x: 2 }); +list.addTail({ x: 3 }); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list.addBefore( + { x: 0 }, + 2, + (value, searchedValue) => value.x === searchedValue +); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3} +``` + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### addManyBefore(values, nextValue, compareFn) + +```js +addManyBefore(values: T\[\], nextValue: T, compareFn = compare): ListNode\\[\] +``` + + +添加给定的多个值到previousValue节点前: + +```js +list.addManyTail(['a', 'b', 'b', 'c']); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addManyBefore(['x', 'y'], 'b'); + +// "a" <-> "x" <-> "y" <-> "b" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addManyTail([{ x: 1 },{ x: 2 },{ x: 3 }]); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list.addManyBefore( + [{ x: 4 }, { x: 5 }], + 2, + (value, searchedValue) => value.x === searchedValue +); + +// {"x":1} <-> {"x":4} <-> {"x":5} <-> {"x":2} <-> {"x":3} +``` + + + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### addByIndex(value, position) + +```js +addByIndex(value: T, position: number): ListNode\ +``` + +在链表的指定位置添加节点: + +```js +list.addTail('a'); +list.addTail('b'); +list.addTail('c'); + +// "a" <-> "b" <-> "c" + +list.addByIndex('x', 2); + +// "a" <-> "b" <-> "x" <-> "c" +``` + +它也适用于负索引: + +```js +list.addTail('a'); +list.addTail('b'); +list.addTail('c'); + +// "a" <-> "b" <-> "c" + +list.addByIndex('x', -1); + +// "a" <-> "b" <-> "x" <-> "c" +``` + + + +#### addManyByIndex(values, position) + +```js +addManyByIndex(values: T\[\], position: number): ListNode\\[\] +``` + +添加多个节点到链表的指定位置: + +```js +list.addManyTail(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +list.addManyByIndex(['x', 'y'], 2); + +// "a" <-> "b" <-> "x" <-> "y" <-> "c" +``` + +它也适用于负索引: + +```js +list.addManyTail(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +list.addManyByIndex(['x', 'y'], -1); + +// "a" <-> "b" <-> "x" <-> "y" <-> "c" +``` + + + +#### add(value).head() + +```js +add(value: T).head(): ListNode\ +``` + +将添加的节点移动到链表头: + +```js +list.add('a').head(); + +// "a" + +list.add('b').head(); + +// "b" <-> "a" + +list.add('c').head(); + +// "c" <-> "b" <-> "a" +``` + + + +> 它是 `addHead` 的替代API. + + + +#### add(value).tail() + +```js +add(value: T).tail(): ListNode\ +``` + +将添加的节点移动到链表尾: + +```js +list.add('a').tail(); + +// "a" + +list.add('b').tail(); + +// "a" <-> "b" + +list.add('c').tail(); + +// "a" <-> "b" <-> "c" +``` + + + +> 它是 `addTail` 的替代API. + + + +#### add(value).after(previousValue, compareFn) + +```js +add(value: T).after(previousValue: T, compareFn = compare): ListNode\ +``` + +将添加的节点移动到指定节点后: + +```js +list.add('a').tail(); +list.add('b').tail(); +list.add('b').tail(); +list.add('c').tail(); + +// "a" <-> "b" <-> "b" <-> "c" + +list.add('x').after('b'); + +// "a" <-> "b" <-> "x" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.add({ x: 1 }).tail(); +list.add({ x: 2 }).tail(); +list.add({ x: 3 }).tail(); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list + .add({ x: 0 }) + .after(2, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3} +``` + + + +> 它是 `addAfter` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### add(value).before(nextValue, compareFn) + +```js +add(value: T).before(nextValue: T, compareFn = compare): ListNode\ +``` + +将添加的节点移动到指定节点前: + +```js +list.add('a').tail(); +list.add('b').tail(); +list.add('b').tail(); +list.add('c').tail(); + +// "a" <-> "b" <-> "b" <-> "c" + +list.add('x').before('b'); + +// "a" <-> "x" <-> "b" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.add({ x: 1 }).tail(); +list.add({ x: 2 }).tail(); +list.add({ x: 3 }).tail(); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list + .add({ x: 0 }) + .before(2, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":3} +``` + + + +> 它是 `addBefore` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### add(value).byIndex(position) + +```js +add(value: T).byIndex(position: number): ListNode\ +``` + +将添加的节点移动到链表指定位置: + +```js +list.add('a').tail(); +list.add('b').tail(); +list.add('c').tail(); + +// "a" <-> "b" <-> "c" + +list.add('x').byIndex(2); + +// "a" <-> "b" <-> "x" <-> "c" +``` + + + +它也适用于负索引: + +```js +list.add('a').tail(); +list.add('b').tail(); +list.add('c').tail(); + +// "a" <-> "b" <-> "c" + +list.add('x').byIndex(-1); + +// "a" <-> "b" <-> "x" <-> "c" +``` + + + +> 它是 `addByIndex` 的替代API. + + + +#### addMany(values).head() + +```js +addMany(values: T\[\]).head(): ListNode\\[\] +``` + +将添加的多个节点移动到链表头: + +```js +list.addMany(['a', 'b', 'c']).head(); + +// "a" <-> "b" <-> "c" + +list.addMany(['x', 'y', 'z']).head(); + +// "x" <-> "y" <-> "z" <-> "a" <-> "b" <-> "c" +``` + + + +> 它是 `addManyHead` 的替代API. + + + +#### addMany(values).tail() + +```js +addMany(values: T\[\]).tail(): ListNode\\[\] +``` + +将添加的多个节点移动到链表尾: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.addMany(['x', 'y', 'z']).tail(); + +// "a" <-> "b" <-> "c" <-> "x" <-> "y" <-> "z" +``` + + + +> 它是 `addManyTail` 的替代API. + + + +#### addMany(values).after(previousValue, compareFn) + +```js +addMany(values: T\[\]).after(previousValue: T, compareFn = compare): ListNode\\[\] +``` + +将添加的多个节点移动到指定节点后: + +```js +list.addMany(['a', 'b', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addMany(['x', 'y']).after('b'); + +// "a" <-> "b" <-> "x" <-> "y" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list + .addMany([{ x: 4 }, { x: 5 }]) + .after(2, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":4} <-> {"x":5} <-> {"x":3} +``` + + + +> 它是 `addManyAfter` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### addMany(values).before(nextValue, compareFn) + +```js +addMany(values: T\[\]).before(nextValue: T, compareFn = compare): ListNode\\[\] +``` + +将添加的多个节点移动到指定节点前: + +```js +list.addMany(['a', 'b', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "b" <-> "c" + +list.addMany(['x', 'y']).before('b'); + +// "a" <-> "x" <-> "y" <-> "b" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":2} <-> {"x":3} + +list + .addMany([{ x: 4 }, { x: 5 }]) + .before(2, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":4} <-> {"x":5} <-> {"x":2} <-> {"x":3} +``` + + + +> 它是 `addManyBefore` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### addMany(values).byIndex(position) + +```js +addMany(values: T\[\]).byIndex(position: number): ListNode\\[\] +``` + +将添加的多个节点移动到链表的指定位置: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.addMany(['x', 'y']).byIndex(2); + +// "a" <-> "b" <-> "x" <-> "y" <-> "c" +``` + + +它也适用于负索引: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.addMany(['x', 'y']).byIndex(-1); + +// "a" <-> "b" <-> "x" <-> "y" <-> "c" +``` + + + +> 它是 `addManyByIndex` 的替代API. + + + +### 如何删除节点 + +有几种方法可以在链表中删除节点,这些方法都可以单独使用,也可以通过 `drop` 方法. + + + +#### dropHead() + +```js +dropHead(): ListNode\ | undefined +``` + +删除链表的第一个节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropHead(); + +// "b" <-> "c" +``` + + + +#### dropManyHead(count) + +```js +dropManyHead(count: number): ListNode\\[\] +``` + +删除指定数量的链表的头节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropManyHead(2); + +// "c" +``` + + + +#### dropTail() + +```js +dropTail(): ListNode\ | undefined +``` + +删除链表的最后一个节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropTail(); + +// "a" <-> "b" +``` + + + +#### dropManyTail(count) + +```js +dropManyTail(count: number): ListNode\\[\] +``` + +删除指定数量的链表的尾节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropManyTail(2); + +// "a" +``` + + + +#### dropByIndex(position) + +```js +dropByIndex(position: number): ListNode\ | undefined +``` + +删除链表中给定位置的节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropByIndex(1); + +// "a" <-> "c" +``` + + +它也适用于负索引: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropByIndex(-2); + +// "a" <-> "c" +``` + + + +#### dropManyByIndex(count, position) + +```js +dropManyByIndex(count: number, position: number): ListNode\\[\] +``` + +删除链表中给定位置与数量的多个节点: + +```js +list.addMany(['a', 'b', 'c', 'd']).tail(); + +// "a" <-> "b" <-> "c" <-> "d + +list.dropManyByIndex(2, 1); + +// "a" <-> "d" +``` + + + +它也适用于负索引: + +```js +list.addMany(['a', 'b', 'c', 'd']).tail(); + +// "a" <-> "b" <-> "c" <-> "d + +list.dropManyByIndex(2, -2); + +// "a" <-> "d" +``` + + + +#### dropByValue(value, compareFn) + +```js +dropByValue(value: T, compareFn = compare): ListNode\ | undefined +``` + +删除链表中含有给定值的第一个节点: + +```js +list.addMany(['a', 'x', 'b', 'x', 'c']).tail(); + +// "a" <-> "x" <-> "b" <-> "x" <-> "c" + +list.dropByValue('x'); + +// "a" <-> "b" <-> "x" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} + +list.dropByValue(0, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3} +``` + + + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### dropByValueAll(value, compareFn) + +```js +dropByValueAll(value: T, compareFn = compare): ListNode\\[\] +``` + +删除链表中含有给定值的所有节点: + +```js +list.addMany(['a', 'x', 'b', 'x', 'c']).tail(); + +// "a" <-> "x" <-> "b" <-> "x" <-> "c" + +list.dropByValueAll('x'); + +// "a" <-> "b" <-> "c" +``` + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} + +list.dropByValue(0, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":3} +``` + + + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### drop().head() + +```js +drop().head(): ListNode\ | undefined +``` + +删除链表的头节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.drop().head(); + +// "b" <-> "c" +``` + + + +> 它是 `dropHead` 的替代API. + + + +#### drop().tail() + +```js +drop().tail(): ListNode\ | undefined +``` + +删除链表的尾节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.drop().tail(); + +// "a" <-> "b" +``` + + + +> 它是 `dropTail` 的替代API. + + + +#### drop().byIndex(position) + +```js +drop().byIndex(position: number): ListNode\ | undefined +``` + +删除链表指定位置的节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.drop().byIndex(1); + +// "a" <-> "c" +``` + + + +它也适用于负索引: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.drop().byIndex(-2); + +// "a" <-> "c" +``` + + + +> 它是 `dropByIndex` 的替代API. + + + +#### drop().byValue(value, compareFn) + +```js +drop().byValue(value: T, compareFn = compare): ListNode\ | undefined +``` + +删除链表中含有给定值的第一个节点: + +```js +list.addMany(['a', 'x', 'b', 'x', 'c']).tail(); + +// "a" <-> "x" <-> "b" <-> "x" <-> "c" + +list.drop().byValue('x'); + +// "a" <-> "b" <-> "x" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} + +list + .drop() + .byValue(0, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":0} <-> {"x":3} +``` + + + +> 它是 `dropByValue` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### drop().byValueAll(value, compareFn) + +```js +drop().byValueAll(value: T, compareFn = compare): ListNode\\[\] +``` + +删除链表中含有给定值的所有节点: + +```js +list.addMany(['a', 'x', 'b', 'x', 'c']).tail(); + +// "a" <-> "x" <-> "b" <-> "x" <-> "c" + +list.drop().byValueAll('x'); + +// "a" <-> "b" <-> "c" +``` + + + +你可以自定义比较器 + +```js +list.addMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]).tail(); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} + +list + .drop() + .byValueAll(0, (value, searchedValue) => value.x === searchedValue); + +// {"x":1} <-> {"x":2} <-> {"x":3} +``` + + + +> 它是 `dropByValueAll` 的替代API. +> +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +#### dropMany(count).head() + +```js +dropMany(count: number).head(): ListNode\\[\] +``` + +删除链表中指定数量的头节点: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropMany(2).head(); + +// "c" +``` + + + +> 它是 `dropManyHead` 的替代API. + + + +#### dropMany(count).tail() + +```js +dropMany(count: number).tail(): ListNode\\[\] +``` + +删除链表中指定数量的尾节点:: + +```js +list.addMany(['a', 'b', 'c']).tail(); + +// "a" <-> "b" <-> "c" + +list.dropMany(2).tail(); + +// "a" +``` + + + +> 它是 `dropManyTail` 的替代API. + + + +#### dropMany(count).byIndex(position) + +```js +dropMany(count: number).byIndex(position: number): ListNode\\[\] +``` + +删除链表中指定位置和数量的节点: + +```js +list.addMany(['a', 'b', 'c', 'd']).tail(); + +// "a" <-> "b" <-> "c" <-> "d + +list.dropMany(2).byIndex(1); + +// "a" <-> "d" +``` + + + +它也适用于负索引: + +```js +list.addMany(['a', 'b', 'c', 'd']).tail(); + +// "a" <-> "b" <-> "c" <-> "d + +list.dropMany(2).byIndex(-2); + +// "a" <-> "d" +``` + + + +> 它是 `dropManyByIndex` 的替代API. + + + +### 如何查找节点 + +有几个方法找到链表特定节点. + + + +#### find(predicate) + +```js +find(predicate: ListIteratorFunction\): ListNode\ | undefined +``` + +从链表中找到与给定谓词匹配的第一个节点: + +```js +list.addTailMany(['a', 'b', 'b', 'c']); + +// "a" <-> "b" <-> "b" <-> "c" + +const found = list.find(node => node.value === 'b'); + +/* +found.value === "b" +found.previous.value === "a" +found.next.value === "b" +*/ +``` + + + +#### findIndex(predicate) + +```js +findIndex(predicate: ListIteratorFunction\): number +``` + +从链表中找到与给定谓词匹配的第一个节点的位置: + +```js +list.addTailMany(['a', 'b', 'b', 'c']); + +// "a" <-> "b" <-> "b" <-> "c" + +const i0 = list.findIndex(node => node.next && node.next.value === 'b'); +const i1 = list.findIndex(node => node.value === 'b'); +const i2 = list.findIndex(node => node.previous && node.previous.value === 'b'); +const i3 = list.findIndex(node => node.value === 'x'); + +/* +i0 === 0 +i1 === 1 +i2 === 2 +i3 === -1 +*/ +``` + + + +#### get(position) + +```js +get(position: number): ListNode\ | undefined +``` + +查找并返回链表中特定位置的节点: + +```js +list.addTailMany(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +const found = list.get(1); + +/* +found.value === "b" +found.previous.value === "a" +found.next.value === "c" +*/ +``` + + + +#### indexOf(value, compareFn) + +```js +indexOf(value: T, compareFn = compare): number +``` + +在链表中找到匹配给定值的第一个节点位置: + +```js +list.addTailMany(['a', 'b', 'b', 'c']); + +// "a" <-> "b" <-> "b" <-> "c" + +const i0 = list.indexOf('a'); +const i1 = list.indexOf('b'); +const i2 = list.indexOf('c'); +const i3 = list.indexOf('x'); + +/* +i0 === 0 +i1 === 1 +i2 === 3 +i3 === -1 +*/ +``` + + + +你可以自定义比较器 + +```js +list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); + +// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} + +const i0 = indexOf(1, (value, searchedValue) => value.x === searchedValue); +const i1 = indexOf(2, (value, searchedValue) => value.x === searchedValue); +const i2 = indexOf(3, (value, searchedValue) => value.x === searchedValue); +const i3 = indexOf(0, (value, searchedValue) => value.x === searchedValue); +const i4 = indexOf(4, (value, searchedValue) => value.x === searchedValue); + +/* +i0 === 0 +i1 === 2 +i2 === 4 +i3 === 1 +i4 === -1 +*/ +``` + + + +> 默认的比较函数检查深度相等性,因此你几乎不需要传递该参数. + + + +### 如何检查所有节点 + +有几种方法来遍历或显示一个链表. + + + +#### forEach(callback) + +```js +forEach(callback: ListIteratorFunction\): void +``` + +从头到尾在链表中的所有节点上运行回调函数: + +```js +list.addTailMany(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +list.forEach((node, index) => console.log(node.value + index)); + +// 'a0' +// 'b1' +// 'c2' +``` + +#### \*\[Symbol.iterator\]\(\) + +链表是可迭代的. 换句话说你可以使用诸如`for ... of`之类的方法. + +```js +list.addTailMany(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +for(const node of list) { + console.log(node.value); +} + +// 'a' +// 'b' +// 'c' +``` + + + +#### toArray() + +```js +toArray(): T\[\] +``` + +转换链表值为数组: + +```js +list.addTailMany(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +const arr = list.toArray(); + +/* +arr === ['a', 'b', 'c'] +*/ +``` + + + +#### toNodeArray() + +```js +toNodeArray(): T\[\] +``` + +转换链表节点为数组: + +```js +list.addTailMany(['a', 'b', 'c']); + +// "a" <-> "b" <-> "c" + +const arr = list.toNodeArray(); + +/* +arr[0].value === 'a' +arr[1].value === 'a' +arr[2].value === 'a' +*/ +``` + + + +#### toString() + +```js +toString(): string +``` + +将链表转换为节点及其关系的字符串表示形式: + +```js +list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); + +// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} + +const str = list.toString(); + +/* +str === '"a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}' +*/ +``` + +你可以在对值进行字符串化之前通过自定义映射器函数来映射值: + +```js +list.addMany([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }]).tail(); + +// {"x":1} <-> {"x":2} <-> {"x":3} <-> {"x":4} <-> {"x":5} + +const str = list.toString(value => value.x); + +/* +str === '1 <-> 2 <-> 3 <-> 4 <-> 5' +*/ +``` + + + diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index f01ce04bc9..2b42110bc5 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -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" + } + ] } ] }, diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs index 25307c3fee..2ab9ccef96 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs @@ -155,7 +155,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource)); var pagerInfo = (TagHelper.ShowInfo ?? false) ? - "
" + localizer["PagerInfo", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "
\r\n" + "
" + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "
\r\n" : ""; return diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs index 115029dbe5..299e8f834e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs @@ -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) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs index fb992cb4af..f54375c844 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Mvc/Abstractions/ActionDescriptorExtensions.cs @@ -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; + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 21a675ddc4..7e469f4195 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -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(); + DynamicProxyIgnoreTypes.Add(); + context.Services.AddConventionalRegistrar(new AbpAspNetCoreMvcConventionalRegistrar()); } @@ -50,6 +61,25 @@ namespace Volo.Abp.AspNetCore.Mvc options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter)); }); + Configure(options => + { + var statusCodes = new List + { + (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(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()); + //Use DI to create razor page + context.Services.Replace(ServiceDescriptor.Singleton()); + //Add feature providers var partManager = context.Services.GetSingletonInstance(); var application = context.Services.GetSingletonInstance(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 4aedf0b087..f1225f1abf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -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()); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs new file mode 100644 index 0000000000..ae09573d60 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs @@ -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 mvcOptionsAccessor, + IOptions optionsAccessor) + { + _modelMetadataProvider = modelMetadataProvider; + _mvcOptions = mvcOptionsAccessor.Value; + _options = optionsAccessor.Value; + } + + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + + /// + /// The order -999 ensures that this provider is executed right after the + /// Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider. + /// + 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( + result.ActionDescriptor.GetMethodInfo()); + if (actionProducesResponseTypeAttributes.Any(x => x.StatusCode == apiResponseType.StatusCode)) + { + continue; + } + + result.SupportedResponseTypes.AddIfNotContains(x => x.StatusCode == apiResponseType.StatusCode, + () => apiResponseType); + } + } + } + + protected virtual IEnumerable GetApiResponseTypes() + { + foreach (var apiResponse in _options.SupportedResponseTypes) + { + apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(apiResponse.Type); + + foreach (var responseTypeMetadataProvider in _mvcOptions.OutputFormatters.OfType()) + { + 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(actionDescriptor.GetMethodInfo()); + return remoteServiceAttr != null && remoteServiceAttr.IsEnabled; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs new file mode 100644 index 0000000000..74e631c53c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProviderOptions.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ApiExplorer; + +namespace Volo.Abp.AspNetCore.Mvc.ApiExploring +{ + public class AbpRemoteServiceApiDescriptionProviderOptions + { + public HashSet SupportedResponseTypes { get; set; } = new HashSet(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs new file mode 100644 index 0000000000..6e9996574c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Auditing/AbpAuditPageFilter.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs new file mode 100644 index 0000000000..fb0a7c5a1f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs @@ -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 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.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() + .NotifyAsync( + new ExceptionNotificationContext(context.Exception) + ); + + context.Exception = null; //Handled! + } + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs new file mode 100644 index 0000000000..83e26ace9c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Features/AbpFeaturePageFilter.cs @@ -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(); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs new file mode 100644 index 0000000000..993a12b0e7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs index 94920fe1be..a407335f3d 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs @@ -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; diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs index 69a285ddc8..e33a8fca7e 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptorRegistrar.cs @@ -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) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs index 56baba8ce4..fb35d142cb 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/MethodInvocationAuthorizationService.cs @@ -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(); - 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? - } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index 0aa2587e20..393d3cda60 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -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 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)); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs index 63f63bef19..c7a6cc90f0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ProjectReferenceReplaceStep.cs @@ -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); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs index 924fdaf2c9..e8ddc804c2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs @@ -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 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.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(); //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) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index c3434e6e27..1307644acd 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -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)) diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj index 2dd1772165..aec34db41a 100644 --- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -1,5 +1,5 @@ + - @@ -9,6 +9,7 @@ true abp + 43599b00-4fa5-4b9f-a314-0757889b296b @@ -17,11 +18,12 @@ + + - - + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs new file mode 100644 index 0000000000..4a41fc409d --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/DynamicProxyIgnoreTypes.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.DynamicProxy +{ + /// + /// 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. + /// + public static class DynamicProxyIgnoreTypes + { + private static HashSet IgnoredTypes { get; } = new HashSet(); + + public static void Add() + { + 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); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs index 8d6e6a42cf..fd47d8b859 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs @@ -108,6 +108,23 @@ namespace Volo.Abp.Reflection ?? defaultValue; } + /// + /// Tries to gets attributes defined for a class member and it's declaring type including inherited attributes. + /// + /// Type of the attribute + /// MemberInfo + /// Inherit attribute from base classes + public static IEnumerable GetAttributesOfMemberOrDeclaringType(MemberInfo memberInfo, bool inherit = true) + where TAttribute : class + { + var customAttributes = memberInfo.GetCustomAttributes(true).OfType(); + var declaringTypeCustomAttributes = + memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType(); + return declaringTypeCustomAttributes != null + ? customAttributes.Concat(declaringTypeCustomAttributes).Distinct() + : customAttributes; + } + /// /// Gets value of a property by it's full path from given object /// diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs index 5d2ad56642..be02f3d398 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs @@ -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 Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } [Serializable] @@ -115,5 +125,13 @@ namespace Volo.Abp.Domain.Entities { _distributedEvents.Clear(); } + + public virtual IEnumerable Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index 2b38059ef8..ad082c3fb0 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -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 /// public static class EntityHelper { + private static readonly ConcurrentDictionary CachedIdProperties = + new ConcurrentDictionary(); + 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>(lambdaBody, lambdaParam); } - + public static void TrySetId( IEntity entity, Func 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()); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs index f663f43a0d..55bb9428e8 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs @@ -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() != 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. } } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs index 8cb6cd7ea5..c20b98c143 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs @@ -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) diff --git a/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs b/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs index 6e298d4d82..206640870b 100644 --- a/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs +++ b/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/IMinifier.cs @@ -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); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs b/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs index 6f52e383a6..491cff1a66 100644 --- a/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs +++ b/framework/src/Volo.Abp.Minify/Volo/Abp/Minify/NUglify/NUglifyMinifierBase.cs @@ -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; } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs index 15915cc94b..2b75e5fc8f 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs @@ -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(this IHasExtraProperties source, string name) + public static TProperty GetProperty(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)) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs index ccdb0b4137..1ba519680c 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs @@ -2,6 +2,8 @@ namespace Volo.Abp.Data { + //TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0 + public interface IHasExtraProperties { Dictionary ExtraProperties { get; } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs index 07243c9dca..c9c601fce9 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs @@ -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 ExtraProperties { get; protected set; } @@ -13,5 +14,13 @@ namespace Volo.Abp.ObjectExtending { ExtraProperties = new Dictionary(); } + + public virtual IEnumerable Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtendedObjectMapper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs similarity index 99% rename from framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtendedObjectMapper.cs rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs index f2b7aeecdb..671aff40f2 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtendedObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs @@ -122,8 +122,6 @@ namespace Volo.Abp.ObjectExtending } } - //TODO: Move these methods to a class like ObjectExtensionHelper - public static bool CanMapProperty( [NotNull] string propertyName, MappingPropertyDefinitionChecks? definitionChecks = null, diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs new file mode 100644 index 0000000000..07d6556d10 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -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 GetValidationErrors( + [NotNull] IHasExtraProperties extensibleObject, + [CanBeNull] ValidationContext objectValidationContext = null) + { + var validationErrors = new List(); + + AddValidationErrors( + extensibleObject, + validationErrors, + objectValidationContext + ); + + return validationErrors; + } + + public static void AddValidationErrors( + [NotNull] IHasExtraProperties extensibleObject, + [NotNull] List 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() + ); + } + + 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 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 validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionPropertyInfo property) + { + AddPropertyValidationAttributeErrors( + extensibleObject, + validationErrors, + objectValidationContext, + property + ); + + ExecuteCustomPropertyValidationActions( + extensibleObject, + validationErrors, + objectValidationContext, + property + ); + } + + private static void AddPropertyValidationAttributeErrors( + IHasExtraProperties extensibleObject, + List 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 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 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); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs index 89ecbab574..2159e06d5e 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -17,11 +17,15 @@ namespace Volo.Abp.ObjectExtending [NotNull] public Dictionary Configuration { get; } + [NotNull] + public List> Validators { get; } + public ObjectExtensionInfo([NotNull] Type type) { Type = Check.AssignableTo(type, nameof(type)); Properties = new Dictionary(); Configuration = new Dictionary(); + Validators = new List>(); } public virtual bool HasProperty(string propertyName) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs index 2edae9a3f8..e6dbbfd6ce 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -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 ValidationAttributes { get; } + + [NotNull] + public List> Validators { get; } + /// /// 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(); + ValidationAttributes = new List(); + Validators = new List>(); } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs new file mode 100644 index 0000000000..ab2a7ccb38 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs @@ -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 + { + /// + /// Related property extension information. + /// + [NotNull] + public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; } + + /// + /// Reference to the validating object. + /// + [NotNull] + public IHasExtraProperties ValidatingObject { get; } + + /// + /// Add validation errors to this list. + /// + [NotNull] + public List ValidationErrors { get; } + + /// + /// Validation context comes from the method. + /// + [NotNull] + public ValidationContext ValidationContext { get; } + + /// + /// The value of the validating property of the . + /// + [CanBeNull] + public object Value { get; } + + /// + /// Can be used to resolve services from the dependency injection container. + /// + [CanBeNull] + public IServiceProvider ServiceProvider => ValidationContext; + + public ObjectExtensionPropertyValidationContext( + [NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo, + [NotNull] IHasExtraProperties validatingObject, + [NotNull] List 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; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs new file mode 100644 index 0000000000..ce10f3fe3c --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs @@ -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 + { + /// + /// Related object extension information. + /// + [NotNull] + public ObjectExtensionInfo ObjectExtensionInfo { get; } + + /// + /// Reference to the validating object. + /// + [NotNull] + public IHasExtraProperties ValidatingObject { get; } + + /// + /// Add validation errors to this list. + /// + [NotNull] + public List ValidationErrors { get; } + + /// + /// Validation context comes from the method. + /// + [NotNull] + public ValidationContext ValidationContext { get; } + + /// + /// Can be used to resolve services from the dependency injection container. + /// + [CanBeNull] + public IServiceProvider ServiceProvider => ValidationContext; + + public ObjectExtensionValidationContext( + [NotNull] ObjectExtensionInfo objectExtensionInfo, + [NotNull] IHasExtraProperties validatingObject, + [NotNull] List 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)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs index 0c6c65b1ac..9112f2baaf 100644 --- a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs +++ b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzModule.cs @@ -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>().Value; + _scheduler = context.ServiceProvider.GetService(); _scheduler.JobFactory = context.ServiceProvider.GetService(); - 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) diff --git a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs index e86f993d1d..9e48744d96 100644 --- a/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs +++ b/framework/src/Volo.Abp.Quartz/Volo/Abp/Quartz/AbpQuartzPreOptions.cs @@ -1,14 +1,23 @@ -using System.Collections.Specialized; +using System; +using System.Collections.Specialized; namespace Volo.Abp.Quartz { public class AbpQuartzPreOptions { + /// + /// The quartz configuration. Available properties can be found within Quartz.Impl.StdSchedulerFactory. + /// public NameValueCollection Properties { get; set; } + /// + /// How long Quartz should wait before starting. Default: 0. + /// + public TimeSpan StartDelay { get; set; } public AbpQuartzPreOptions() { Properties = new NameValueCollection(); + StartDelay = new TimeSpan(0); } } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index 3fb22139b7..8fdc29ad8c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -43,6 +43,7 @@ "PagerFirst": "الأول", "PagerLast": "الاخير", "PagerInfo": "عرض _START_ الي _END_ من _TOTAL_ إدخالات", + "PagerInfo{0}{1}{2}": "عرض {0} الي {1} من {2} إدخالات", "PagerInfoEmpty": "عرض 0 الي 0 من 0 إدخالات", "PagerInfoFiltered": "(تمت تصفيته من _MAX_ مجموع الإدخالات)", "NoDataAvailableInDatatable": "لا توجد بيانات متاحة في الجدول", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 28c9a5e33f..3fba79a2b3 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index e2547eb67a..77e3e37570 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json index 4997156c8d..b52c56510b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json index 5f369760a1..cdaf556168 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index 34682c9d28..6966b565c2 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json new file mode 100644 index 0000000000..7fffcb73c6 --- /dev/null +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json @@ -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" + } +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index 4fe44a40c6..1369491bc4 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -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.", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json index 21a466ccda..00f6c8f195 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json @@ -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", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index d71cac388c..d1d6aa1b81 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -40,6 +40,7 @@ "PagerFirst": "首页", "PagerLast": "尾页", "PagerInfo": "显示 _TOTAL_ 个条目中的 _START_ 到 _END_ 个.", + "PagerInfo{0}{1}{2}": "显示 {2} 个条目中的 {0} 到 {1} 个.", "PagerInfoEmpty": "显示0个条目中的0到0", "PagerInfoFiltered": "(从 _MAX_ 总条目中过滤掉)", "NoDataAvailableInDatatable": "表中没有数据", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index d19dc7cbc6..7839409b4c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -39,6 +39,7 @@ "PagerFirst": "首頁", "PagerLast": "末頁", "PagerInfo": "顯示 _TOTAL_ 個紀錄的 _START_ 到 _END_ 個.", + "PagerInfo{0}{1}{2}": "顯示 {2} 個紀錄的 {0} 到 {1} 個.", "PagerInfoEmpty": "顯示0個紀錄中的0到0", "PagerInfoFiltered": "(從 _MAX_ 所有紀錄中過濾掉)", "NoDataAvailableInDatatable": "資料表中沒有資料", diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs index 849ca8ffd8..5331cb1d0e 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkInterceptorRegistrar.cs @@ -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(); } } + + private static bool ShouldIntercept(Type type) + { + return !DynamicProxyIgnoreTypes.Contains(type) && UnitOfWorkHelper.IsUnitOfWorkType(type.GetTypeInfo()); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs index ad2be911de..9da6766a5d 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.Validation context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); AutoAddObjectValidationContributors(context.Services); } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs index 81f6eb6597..1474efd710 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs @@ -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(); } } + + private static bool ShouldIntercept(Type type) + { + return !DynamicProxyIgnoreTypes.Contains(type) && typeof(IValidationEnabled).IsAssignableFrom(type); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj index ddb3f5bc44..bb841bc085 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj @@ -24,6 +24,7 @@ + @@ -39,6 +40,28 @@ PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 5e74ec4bfa..39b7d9af5f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -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(options => + { + options.RootDirectory = "/Volo/Abp/AspNetCore/Mvc"; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs index 49bc689e14..3f934d5fe8 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestController_Tests.cs @@ -51,6 +51,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Auditing await _auditingStore.Received().SaveAsync(Arg.Any()); } + [Fact] public async Task Should_Trigger_Middleware_And_AuditLog_Exception_When_Returns_Object() { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml new file mode 100644 index 0000000000..bf85af77ef --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Auditing.AuditTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs new file mode 100644 index 0000000000..6425d6585b --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage.cshtml.cs @@ -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 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!"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs new file mode 100644 index 0000000000..7d5298e253 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Auditing/AuditTestPage_Tests.cs @@ -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>().Value; + _auditingStore = ServiceProvider.GetRequiredService(); + } + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + _auditingStore = Substitute.For(); + 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()); + } + + [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()); + } + + [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()); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml new file mode 100644 index 0000000000..cf94310510 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Authorization.AuthTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs new file mode 100644 index 0000000000..5ae66edb5f --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage.cshtml.cs @@ -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"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs new file mode 100644 index 0000000000..ec8a043fbe --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestPage_Tests.cs @@ -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(); + } + + [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"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs index 0de46fbe55..46cdd073e7 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs @@ -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()); +#pragma warning restore 4014 } [Fact] @@ -44,9 +46,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling ) ); +#pragma warning disable 4014 _fakeExceptionSubscriber .DidNotReceive() .HandleAsync(Arg.Any()); +#pragma warning restore 4014 } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml new file mode 100644 index 0000000000..7ba36da037 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.ExceptionHandling.ExceptionTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs new file mode 100644 index 0000000000..717d31effe --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage.cshtml.cs @@ -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!"); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs new file mode 100644 index 0000000000..7013c0c676 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestPage_Tests.cs @@ -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(); + + services.AddSingleton(_fakeExceptionSubscriber); + } + + [Fact] + public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() + { + var result = await GetResponseAsObjectAsync("/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()); +#pragma warning restore 4014 + } + + [Fact] + public async Task Should_Not_Handle_Exceptions_For_ActionResult_Return_Values() + { + await Assert.ThrowsAsync( + async () => await GetResponseAsObjectAsync( + "/ExceptionHandling/ExceptionTestPage?handler=UserFriendlyException2" + ) + ); + +#pragma warning disable 4014 + _fakeExceptionSubscriber + .DidNotReceive() + .HandleAsync(Arg.Any()); +#pragma warning restore 4014 + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml new file mode 100644 index 0000000000..8f0450becb --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Features.FeatureTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs new file mode 100644 index 0000000000..6a051a29ee --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage.cshtml.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs new file mode 100644 index 0000000000..91226f1938 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Features/FeatureTestPage_Tests.cs @@ -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" + ); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs new file mode 100644 index 0000000000..b09d3ca2c9 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Exception_Rollback_Tests.cs @@ -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("/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().Deserialize(resultAsString); + + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe(TestUnitOfWorkConfig.ExceptionOnCompleteMessage); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs new file mode 100644 index 0000000000..820ccf91b7 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkPageFilter_Tests.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml new file mode 100644 index 0000000000..da30a64818 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml @@ -0,0 +1,19 @@ +@page +@model Volo.Abp.AspNetCore.Mvc.Uow.UnitOfWorkTestPage + +@{ + Layout = null; +} + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs new file mode 100644 index 0000000000..ce54e8d34f --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestPage.cshtml.cs @@ -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(""); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index 51337ae871..492a36f8b9 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -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(); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs index 8ab051d97e..5cf9976e46 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs @@ -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() { + 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(); + principalAccessor.Principal.Returns(ci => claimsPrincipal); + Thread.CurrentPrincipal = claimsPrincipal; + } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs index 104d75c11b..66b5f5a2e3 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs @@ -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(); + _simpleAuthorizedService = GetRequiredService(); + _myAuthorizedServiceWithRole = GetRequiredService(); _permissionDefinitionManager = GetRequiredService(); } + [Fact] + public async Task Should_Not_Allow_To_Call_Authorized_Method_For_Anonymous_User() + { + await Assert.ThrowsAsync(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(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(async () => + { + await _myAuthorizedServiceWithRole.ProtectedByScheme().ConfigureAwait(false); + }).ConfigureAwait(false); + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMyAuthorizedServiceWithRole.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMyAuthorizedServiceWithRole.cs new file mode 100644 index 0000000000..52b860d774 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMyAuthorizedServiceWithRole.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.TestServices +{ + public interface IMyAuthorizedServiceWithRole + { + Task ProtectedByRole(); + + Task ProtectedByScheme(); + + Task ProtectedByAnotherRole(); + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMySimpleAuthorizedService.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMySimpleAuthorizedService.cs new file mode 100644 index 0000000000..4b00cdfbae --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/IMySimpleAuthorizedService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Authorization.TestServices +{ + public interface IMySimpleAuthorizedService + { + Task ProtectedByClassAsync(); + + Task AnonymousAsync(); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs index b1b2a4c43f..4254b45609 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedService1.cs @@ -31,4 +31,4 @@ namespace Volo.Abp.Authorization.TestServices return 42; } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedServiceWithRole.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedServiceWithRole.cs new file mode 100644 index 0000000000..d660b040c1 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MyAuthorizedServiceWithRole.cs @@ -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 ProtectedByRole() + { + return Task.FromResult(42); + } + + [Authorize(Roles = "MyAnotherRole")] + public virtual Task ProtectedByAnotherRole() + { + return Task.FromResult(42); + } + + [Authorize(AuthenticationSchemes = "Bearer")] + public virtual Task ProtectedByScheme() + { + return Task.FromResult(42); + } + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MySimpleAuthorizedService.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MySimpleAuthorizedService.cs new file mode 100644 index 0000000000..41cd4e5b85 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/MySimpleAuthorizedService.cs @@ -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 ProtectedByClassAsync() + { + return Task.FromResult(42); + } + + [AllowAnonymous] + public Task AnonymousAsync() + { + return Task.FromResult(42); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs index 2ad6a45b05..8d405f6907 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Entities/EntityHelper_Tests.cs @@ -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 { @@ -53,5 +63,14 @@ namespace Volo.Abp.Domain.Entities [DisableIdGeneration] public override Guid Id { get; protected set; } } + + public class NewIdEntity : Entity + { + public new Guid Id + { + get => base.Id; + set => base.Id = value; + } + } } } diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs index 5bf11b6d5e..a93eabda1d 100644 --- a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs @@ -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(); } diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs index 86620366b3..cfa7b033b7 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; using Volo.Abp.TestApp; @@ -15,13 +14,11 @@ namespace Volo.Abp.MongoDB )] public class AbpMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs index c5d7b87752..b08c2120a4 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Auditing/Auditing_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Auditing { + [Collection(MongoTestCollection.Name)] public class Auditing_Tests : Auditing_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs index a1ee94a943..0ab365a568 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Creation_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DataFiltering { + [Collection(MongoTestCollection.Name)] public class MultiTenant_Creation_Tests : MultiTenant_Creation_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs index c23761a05c..c32b640e03 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/MultiTenant_Filter_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DataFiltering { + [Collection(MongoTestCollection.Name)] public class MultiTenant_Filter_Tests : MultiTenant_Filter_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs index 3636b998bb..5de6caf661 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Filter_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DataFiltering { + [Collection(MongoTestCollection.Name)] public class SoftDelete_Filter_Tests : SoftDelete_Filter_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs index fdd8ec1a5e..bffb38f858 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/SoftDelete_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DataFiltering { + [Collection(MongoTestCollection.Name)] public class SoftDelete_Tests : SoftDelete_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs index 117cd5dde1..88bf889b7c 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ConcurrencyStamp_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Domain { + [Collection(MongoTestCollection.Name)] public class ConcurrencyStamp_Tests : ConcurrencyStamp_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs index b389ad61b9..5f8f8784c3 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/ExtraProperties_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Domain { + [Collection(MongoTestCollection.Name)] public class ExtraProperties_Tests : ExtraProperties_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs index b01cb690f6..ed480a229e 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/DomainEvents_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DomainEvents { + [Collection(MongoTestCollection.Name)] public class DomainEvents_Tests : DomainEvents_Tests { } diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs index 0df187f34e..184142d77c 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DomainEvents/EntityChangeEvents_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DomainEvents { + [Collection(MongoTestCollection.Name)] public class EntityChangeEvents_Tests : EntityChangeEvents_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..f401d0d6d4 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoTestCollection.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..ae23dc9450 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs index efa64b3617..dd441bd99d 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs @@ -9,6 +9,7 @@ using Xunit; namespace Volo.Abp.MongoDB.Repositories { + [Collection(MongoTestCollection.Name)] public class Repository_Basic_Tests : Repository_Basic_Tests { [Fact] diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs index c5885dfb4d..a34aa49c0d 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests_With_Int_Pk.cs @@ -4,6 +4,7 @@ using Xunit; namespace Volo.Abp.MongoDB.Repositories { + [Collection(MongoTestCollection.Name)] public class Repository_Basic_Tests_With_Int_Pk : Repository_Basic_Tests_With_Int_Pk { [Fact(Skip = "Int PKs are not working for MongoDb")] diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs index d396471bed..05ffb6ae27 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Queryable_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Repositories { + [Collection(MongoTestCollection.Name)] public class Repository_Queryable_Tests : Repository_Queryable_Tests { diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs index 6529b4c5a9..4670ac5d00 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs @@ -1,7 +1,9 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Repositories { + [Collection(MongoTestCollection.Name)] public class Repository_Specifications_Tests : Repository_Specifications_Tests { } diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs index 4773644885..cef724b9bd 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/Uow_Completed_Tests.cs @@ -1,9 +1,10 @@ using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.Uow { + [Collection(MongoTestCollection.Name)] public class Uow_Completed_Tests : Uow_Completed_Tests { - } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs new file mode 100644 index 0000000000..8c27844e22 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs @@ -0,0 +1,172 @@ +using System.ComponentModel.DataAnnotations; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Threading; +using Xunit; + +namespace Volo.Abp.ObjectExtending +{ + public class ExtensibleObjectValidator_Tests + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + static ExtensibleObjectValidator_Tests() + { + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .AddOrUpdate(options => + { + options.AddOrUpdateProperty("Name", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); + propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 }); + }); + + options.AddOrUpdateProperty("Address", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255)); + }); + + options.AddOrUpdateProperty("Age", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); + propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99)); + }); + + options.AddOrUpdateProperty("IsMarried", propertyInfo => + { + + }); + + options.AddOrUpdateProperty("Password", propertyInfo => + { + }); + + options.AddOrUpdateProperty("PasswordRepeat", propertyInfo => + { + propertyInfo.Validators.Add(context => + { + if (context.ValidatingObject.HasProperty("Password")) + { + if (context.ValidatingObject.GetProperty("Password") != + context.Value as string) + { + context.ValidationErrors.Add( + new ValidationResult( + "If you specify a password, then please correctly repeat it!", + new[] {"Password", "PasswordRepeat"} + ) + ); + } + } + }); + }); + + options.Validators.Add(context => + { + if (context.ValidatingObject.GetProperty("Name") == "BadValue") + { + context.ValidationErrors.Add( + new ValidationResult( + "Name can not be 'BadValue', sorry :(", + new[] { "Name" } + ) + ); + } + }); + }); + }); + } + + [Fact] + public void Should_Validate_If_The_Properties_Are_Valid() + { + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "John"}, + {"Age", "42"}, + } + } + ).Count.ShouldBe(0); //All Valid + } + + [Fact] + public void Should_Not_Validate_If_The_Properties_Are_Not_Valid() + { + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject() + ).Count.ShouldBe(2); // Name & Age + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Address", new string('x', 256) } + } + } + ).Count.ShouldBe(3); // Name, Age & Address + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Age", "42" } + } + } + ).Count.ShouldBe(1); // Name + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Address", new string('x', 256) }, + {"Age", "100" } + } + } + ).Count.ShouldBe(3); // Name, Age & Address + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "John"}, + {"Age", "42"}, + {"Password", "123"}, + {"PasswordRepeat", "1256"} + } + } + ).Count.ShouldBe(1); // PasswordRepeat != Password + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "BadValue"}, + {"Age", "42"}, + } + } + ).Count.ShouldBe(1); //Name is 'BadValue'! + } + + private class ExtensiblePersonObject : ExtensibleObject + { + + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs index cc80c24bc0..918e96581b 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs @@ -16,18 +16,22 @@ namespace Volo.Abp.TestApp.Testing city.HasProperty("UnknownProperty").ShouldBeFalse(); city.GetProperty("UnknownProperty").ShouldBeNull(); city.GetProperty("UnknownProperty").ShouldBe(0); + city.GetProperty("UnknownProperty", 42).ShouldBe(42); city.SetProperty("IsHot", true); city.HasProperty("IsHot").ShouldBeTrue(); city.GetProperty("IsHot").ShouldBeTrue(); + city.GetProperty("IsHot").ShouldBeTrue(); city.SetProperty("IsHot", false); city.HasProperty("IsHot").ShouldBeTrue(); city.GetProperty("IsHot").ShouldBeFalse(); + city.GetProperty("IsHot", true).ShouldBeFalse(); city.RemoveProperty("IsHot"); city.HasProperty("IsHot").ShouldBeFalse(); city.GetProperty("IsHot").ShouldBeFalse(); + city.GetProperty("IsHot", true).ShouldBeTrue(); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs index 0d13867819..de9067e2f7 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs +++ b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs @@ -8,6 +8,7 @@ using Shouldly; using Volo.Abp.Application.Dtos; using Volo.Abp.Autofac; using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; using Volo.Abp.Modularity; using Volo.Abp.Testing; using Xunit; @@ -198,7 +199,8 @@ namespace Volo.Abp.Validation { context.Services.OnRegistred(onServiceRegistredContext => { - if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) + if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType) && + !DynamicProxyIgnoreTypes.Contains(onServiceRegistredContext.ImplementationType)) { onServiceRegistredContext.Interceptors.TryAdd(); } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 24e7732c9c..34a084bd3d 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -76,7 +76,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public virtual async Task OnPostAsync(string action) { await CheckLocalLoginAsync(); @@ -147,7 +146,6 @@ namespace Volo.Abp.Account.Web.Pages.Account .ToList(); } - [UnitOfWork] public virtual async Task OnPostExternalLogin(string provider) { var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); @@ -157,7 +155,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return await Task.FromResult(Challenge(properties, provider)); } - [UnitOfWork] public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { //TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 0cb71f2ed9..db085d6587 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -36,7 +36,6 @@ namespace Volo.Abp.Account.Web.Pages.Account await CheckSelfRegistrationAsync(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public virtual async Task OnPostAsync() { ValidateModel(); diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs index 7c70d720ec..5cac32faae 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs @@ -1,6 +1,4 @@ using System; -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -12,11 +10,9 @@ namespace Volo.Abp.AuditLogging.MongoDB )] public class AbpAuditLoggingMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogRepository_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogRepository_Tests.cs index ffcc2094ab..a0edd58a45 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogRepository_Tests.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogRepository_Tests.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Text; +using Xunit; namespace Volo.Abp.AuditLogging.MongoDB { + [Collection(MongoTestCollection.Name)] public class AuditLogRepository_Tests : AuditLogRepository_Tests { diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditStore_Basic_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditStore_Basic_Tests.cs index fa7a41ecfd..ad590c61e1 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditStore_Basic_Tests.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditStore_Basic_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.AuditLogging.MongoDB +using Xunit; + +namespace Volo.Abp.AuditLogging.MongoDB { + [Collection(MongoTestCollection.Name)] public class AuditStore_Basic_Tests : AuditStore_Basic_Tests { diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..33803c1af4 --- /dev/null +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.AuditLogging.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoTestCollection.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..1d89caa8b0 --- /dev/null +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.AuditLogging.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs index 4147917829..07be76b792 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs @@ -1,6 +1,4 @@ using System; -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -12,11 +10,9 @@ namespace Volo.Abp.BackgroundJobs.MongoDB )] public class AbpBackgroundJobsMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobRepositoryTests.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobRepositoryTests.cs index 429cf4821f..f3827c50e2 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobRepositoryTests.cs +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobRepositoryTests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.BackgroundJobs.MongoDB +using Xunit; + +namespace Volo.Abp.BackgroundJobs.MongoDB { + [Collection((MongoTestCollection.Name))] public class BackgroundJobRepositoryTests : BackgroundJobRepository_Tests { diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..3ae1d8fc10 --- /dev/null +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.BackgroundJobs.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoTestCollection.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..0096ae6f10 --- /dev/null +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.BackgroundJobs.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index 23e2ecb5d3..511b683e0e 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -17,7 +17,7 @@ - + diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Blogs/BlogRepository_Tests.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Blogs/BlogRepository_Tests.cs index 76044f257c..060d3b86fc 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Blogs/BlogRepository_Tests.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Blogs/BlogRepository_Tests.cs @@ -1,8 +1,10 @@ using Volo.Blogging.Blogs; using Volo.Blogging.MongoDB; +using Xunit; namespace Volo.Blogging { + [Collection(MongoTestCollection.Name)] public class BlogRepository_Tests : BlogRepository_Tests { } diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Comments/CommentRepository_Tests.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Comments/CommentRepository_Tests.cs index a001f9b0d9..d956cf1b15 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Comments/CommentRepository_Tests.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Comments/CommentRepository_Tests.cs @@ -1,8 +1,10 @@ using Volo.Blogging.Comments; using Volo.Blogging.MongoDB; +using Xunit; namespace Volo.Blogging { + [Collection(MongoTestCollection.Name)] public class CommentRepository_Tests : CommentRepository_Tests { } diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs index 4c4b34ef54..1e68cebf21 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs @@ -1,6 +1,4 @@ using System; -using Mongo2Go; -using Volo.Abp; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -12,11 +10,9 @@ namespace Volo.Blogging.MongoDB )] public class BloggingMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..d5fbfb4451 --- /dev/null +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Blogging.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoTestCollection.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..f075e5dd07 --- /dev/null +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Blogging.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Posts/PostRepository_Tests.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Posts/PostRepository_Tests.cs index d0e13ba403..814f0d206e 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Posts/PostRepository_Tests.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Posts/PostRepository_Tests.cs @@ -1,8 +1,10 @@ using Volo.Blogging.Posts; using Volo.Blogging.MongoDB; +using Xunit; namespace Volo.Blogging { + [Collection(MongoTestCollection.Name)] public class PostRepository_Tests : PostRepository_Tests { } diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Tagging/TagRepository_Tests.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Tagging/TagRepository_Tests.cs index 8f13c6c6b0..66cb0d52c5 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Tagging/TagRepository_Tests.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/Tagging/TagRepository_Tests.cs @@ -1,8 +1,10 @@ using Volo.Blogging.Tagging; using Volo.Blogging.MongoDB; +using Xunit; namespace Volo.Blogging { + [Collection(MongoTestCollection.Name)] public class TagRepository_Tests : TagRepository_Tests { } diff --git a/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj b/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj index 9eb89ff5bb..b7b70d8b03 100644 --- a/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj +++ b/modules/docs/app/VoloDocs.Web/VoloDocs.Web.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs index d5aef3e614..436c9d1c76 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs @@ -47,6 +47,11 @@ namespace Volo.Docs.Admin.Projects [Authorize(DocsAdminPermissions.Projects.Create)] public async Task CreateAsync(CreateProjectDto input) { + if (await _projectRepository.ShortNameExistsAsync(input.ShortName)) + { + throw new ProjectShortNameAlreadyExistsException(input.ShortName); + } + var project = new Project(_guidGenerator.Create(), input.Name, input.ShortName, @@ -64,9 +69,9 @@ namespace Volo.Docs.Admin.Projects foreach (var extraProperty in input.ExtraProperties) { - project.ExtraProperties.Add(extraProperty.Key,extraProperty.Value); + project.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); } - + project = await _projectRepository.InsertAsync(project); return ObjectMapper.Map(project); @@ -101,5 +106,6 @@ namespace Volo.Docs.Admin.Projects { await _projectRepository.DeleteAsync(id); } + } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainSharedModule.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainSharedModule.cs index 6c12059eff..d0df3af2fb 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainSharedModule.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/DocsDomainSharedModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Docs.Localization; @@ -13,6 +14,11 @@ namespace Volo.Docs { options.Resources.Add("en"); }); + + Configure(options => + { + options.MapCodeNamespace("Volo.Docs.Domain", typeof(DocsResource)); + }); } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json index e8c7122ec3..6299a9aab2 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json @@ -23,6 +23,7 @@ "New": "New", "Upd": "Upd", "NewExplanation": "Created in the last two weeks.", - "UpdatedExplanation": "Updated in the last two weeks." + "UpdatedExplanation": "Updated in the last two weeks.", + "Volo.Docs.Domain:010002": "ShortName {ShortName} already exists." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json index 3d9df18295..ef4e95f3dc 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json @@ -22,6 +22,7 @@ "New": "新文档", "Upd": "更新", "NewExplanation": "在最近两周内创建.", - "UpdatedExplanation": "在最近两周内更新." + "UpdatedExplanation": "在最近两周内更新.", + "Volo.Docs.Domain:010002": "简称 {ShortName} 已经存在." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json index 9fbc677120..6a2f4d8799 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json @@ -18,9 +18,11 @@ "DocumentNotFoundInSelectedLanguage": "本文件不適用於所選語系,將以預設語系顯示.", "FilterTopics": "過濾主題", "FullSearch": "搜索文件", + "Volo.Docs.Domain:010001": "Elastic search未啟用.", "New": "新文檔", "Upd": "更新", "NewExplanation": "在最近兩周內創建.", - "UpdatedExplanation": "在最近兩周內更新." + "UpdatedExplanation": "在最近兩周內更新.", + "Volo.Docs.Domain:010002": "簡稱 {ShortName} 已經存在." } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs index 4a6ad77ddd..add77749b4 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/IProjectRepository.cs @@ -10,5 +10,7 @@ namespace Volo.Docs.Projects Task> GetListAsync(string sorting, int maxResultCount, int skipCount); Task GetByShortNameAsync(string shortName); + + Task ShortNameExistsAsync(string shortName); } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Project.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Project.cs index f1cfd0d777..8bc36a2106 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Project.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Project.cs @@ -76,6 +76,8 @@ namespace Volo.Docs.Projects ParametersDocumentName = Check.NotNullOrWhiteSpace(parametersDocumentName, nameof(parametersDocumentName)); ExtraProperties = new Dictionary(); + + NormalizeShortName(); } public void SetName(string name) @@ -102,5 +104,10 @@ namespace Volo.Docs.Projects { DefaultDocumentName = Check.NotNullOrWhiteSpace(defaultDocumentName, nameof(defaultDocumentName)); } + + private void NormalizeShortName() + { + ShortName = ShortName.ToLower(); + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/ProjectShortNameAlreadyExistsException.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/ProjectShortNameAlreadyExistsException.cs new file mode 100644 index 0000000000..b61972d60b --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/ProjectShortNameAlreadyExistsException.cs @@ -0,0 +1,13 @@ +using Volo.Abp; + +namespace Volo.Docs.Projects +{ + public class ProjectShortNameAlreadyExistsException : BusinessException + { + public ProjectShortNameAlreadyExistsException(string shortName) + : base("Volo.Docs.Domain:010002") + { + WithData("ShortName", shortName); + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs index b54869edcd..835172e679 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs @@ -13,10 +13,9 @@ namespace Volo.Docs.Projects { public class EfCoreProjectRepository : EfCoreRepository, IProjectRepository { - public EfCoreProjectRepository(IDbContextProvider dbContextProvider) + public EfCoreProjectRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { - } public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) @@ -30,7 +29,9 @@ namespace Volo.Docs.Projects public async Task GetByShortNameAsync(string shortName) { - var project = await DbSet.FirstOrDefaultAsync(p => p.ShortName == shortName); + var normalizeShortName = NormalizeShortName(shortName); + + var project = await DbSet.FirstOrDefaultAsync(p => p.ShortName == normalizeShortName); if (project == null) { @@ -39,5 +40,17 @@ namespace Volo.Docs.Projects return project; } + + public async Task ShortNameExistsAsync(string shortName) + { + var normalizeShortName = NormalizeShortName(shortName); + + return await DbSet.AnyAsync(x => x.ShortName == normalizeShortName); + } + + private string NormalizeShortName(string shortName) + { + return shortName.ToLower(); + } } -} +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs index 6f2359e339..f1312b9e5d 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs @@ -14,7 +14,8 @@ namespace Volo.Docs.Projects { public class MongoProjectRepository : MongoDbRepository, IProjectRepository { - public MongoProjectRepository(IMongoDbContextProvider dbContextProvider) : base(dbContextProvider) + public MongoProjectRepository(IMongoDbContextProvider dbContextProvider) : base( + dbContextProvider) { } @@ -29,7 +30,9 @@ namespace Volo.Docs.Projects public async Task GetByShortNameAsync(string shortName) { - var project = await GetMongoQueryable().FirstOrDefaultAsync(p => p.ShortName == shortName); + var normalizeShortName = NormalizeShortName(shortName); + + var project = await GetMongoQueryable().FirstOrDefaultAsync(p => p.ShortName == normalizeShortName); if (project == null) { @@ -38,5 +41,17 @@ namespace Volo.Docs.Projects return project; } + + public async Task ShortNameExistsAsync(string shortName) + { + var normalizeShortName = NormalizeShortName(shortName); + + return await GetMongoQueryable().AnyAsync(x => x.ShortName == normalizeShortName); + } + + private string NormalizeShortName(string shortName) + { + return shortName.ToLower(); + } } -} +} \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/ProjectAdminAppService_Tests.cs b/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/ProjectAdminAppService_Tests.cs index 33d887de9b..442a72f18b 100644 --- a/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/ProjectAdminAppService_Tests.cs +++ b/modules/docs/test/Volo.Docs.Admin.Application.Tests/Volo/Docs/ProjectAdminAppService_Tests.cs @@ -45,7 +45,7 @@ namespace Volo.Docs var createProjectDto = new CreateProjectDto { Name = "ABP vNext", - ShortName = "ABP", + ShortName = "ABPvNext", Format = "md", DefaultDocumentName = "index", NavigationDocumentName = "docs-nav.json", @@ -67,7 +67,7 @@ namespace Volo.Docs //Assert projectDto.ShouldNotBeNull(); projectDto.Name.ShouldBe(createProjectDto.Name); - projectDto.ShortName.ShouldBe(createProjectDto.ShortName); + projectDto.ShortName.ShouldBe(createProjectDto.ShortName.ToLower()); projectDto.Format.ShouldBe(createProjectDto.Format); projectDto.DefaultDocumentName.ShouldBe(createProjectDto.DefaultDocumentName); projectDto.NavigationDocumentName.ShouldBe(createProjectDto.NavigationDocumentName); @@ -120,5 +120,32 @@ namespace Volo.Docs (await _projectRepository.GetListAsync()).ShouldBeEmpty(); } + + [Fact] + public async Task Should_Throw_ProjectShortNameAlready_Exception_For_Duplicate_ShortName() + { + var createProjectDto = new CreateProjectDto + { + Name = "ABP vNext", + ShortName = "ABP", + Format = "md", + DefaultDocumentName = "index", + NavigationDocumentName = "docs-nav.json", + ParametersDocumentName = "docs-params.json", + MinimumVersion = "1", + MainWebsiteUrl = "abp.io", + LatestVersionBranchName = "", + DocumentStoreType = "GitHub", + ExtraProperties = new Dictionary() + }; + createProjectDto.ExtraProperties.Add("GitHubRootUrl", + "https://github.com/abpframework/abp/tree/{version}/docs/en/"); + createProjectDto.ExtraProperties.Add("GitHubAccessToken", "123456"); + createProjectDto.ExtraProperties.Add("GitHubUserAgent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + + //Act + await Assert.ThrowsAsync(() => + _projectAdminAppService.CreateAsync(createProjectDto)); + } } } diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs index c5fef83ddb..b31f9b5acd 100644 --- a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs @@ -1,6 +1,4 @@ using System; -using Mongo2Go; -using Volo.Abp; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -12,11 +10,9 @@ namespace Volo.Docs.MongoDB )] public class DocsMongoDBTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..fc920813ab --- /dev/null +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Docs.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoTestCollection.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..86505c5a73 --- /dev/null +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Docs.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Project/ProjectRepository_Tests.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Project/ProjectRepository_Tests.cs index 6f2df01a11..696dd31185 100644 --- a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Project/ProjectRepository_Tests.cs +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/Project/ProjectRepository_Tests.cs @@ -1,7 +1,9 @@ using Volo.Docs.MongoDB; +using Xunit; namespace Volo.Docs.Project { + [Collection(MongoTestCollection.Name)] public class ProjectRepository_Tests : ProjectRepository_Tests { } diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs index 00b5b22871..04d4ac916f 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs @@ -1,5 +1,4 @@ using System; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -11,11 +10,9 @@ namespace Volo.Abp.FeatureManagement.MongoDB )] public class AbpFeatureManagementMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementStore_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementStore_Tests.cs index 7df6e3516b..0337bc8822 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementStore_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureManagementStore_Tests.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Xunit; namespace Volo.Abp.FeatureManagement.MongoDB { + [Collection(MongoTestCollection.Name)] public class FeatureManagementStore_Tests : FeatureManagementStore_Tests { diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureValueRepositoryTests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureValueRepositoryTests.cs index 6184e4c90f..9654d8ab27 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureValueRepositoryTests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/FeatureValueRepositoryTests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.FeatureManagement.MongoDB +using Xunit; + +namespace Volo.Abp.FeatureManagement.MongoDB { + [Collection(MongoTestCollection.Name)] public class FeatureValueRepositoryTests : FeatureValueRepository_Tests { diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..bfd7fa9c04 --- /dev/null +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.FeatureManagement.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoTestCollection.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..c8a6e1c58c --- /dev/null +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.FeatureManagement.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs index 6e9f3806a2..f730e5f445 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs @@ -1,6 +1,4 @@ using System; -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.MongoDB; @@ -14,11 +12,9 @@ namespace Volo.Abp.Identity.MongoDB )] public class AbpIdentityMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs index b7926aae00..cc66856c06 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityClaimTypeRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.Identity.MongoDB +using Xunit; + +namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class IdentityClaimTypeRepository_Tests : IdentityClaimTypeRepository_Tests { diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs index e97269c7e2..d614807395 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.Identity.MongoDB +using Xunit; + +namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class IdentityDataSeeder_Tests : IdentityDataSeeder_Tests { diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs index 4873aa7298..8fde9dbb2c 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.Identity.MongoDB +using Xunit; + +namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class IdentityRoleRepository_Tests : IdentityRoleRepository_Tests { diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs index 214d8f6986..e29a216ca7 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.Identity.MongoDB +using Xunit; + +namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class IdentityUserRepository_Tests : IdentityUserRepository_Tests { diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs index 5d6742adef..42020a4170 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.Identity.MongoDB +using Xunit; + +namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class Identity_Repository_Resolve_Tests : Identity_Repository_Resolve_Tests { } diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..26de0a9399 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.Identity.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoTestCollection.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..ae01262908 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.Identity.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs index ebfa34404d..d1721c720f 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs @@ -1,5 +1,4 @@ using System; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Identity.MongoDB; using Volo.Abp.IdentityServer.MongoDB; @@ -15,11 +14,9 @@ namespace Volo.Abp.IdentityServer )] public class AbpIdentityServerMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ApiResourceRepository_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ApiResourceRepository_Tests.cs index 032505266d..dfe00ab4dd 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ApiResourceRepository_Tests.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ApiResourceRepository_Tests.cs @@ -1,6 +1,8 @@ - +using Xunit; + namespace Volo.Abp.IdentityServer { + [Collection(MongoTestCollection.Name)] public class ApiResourceRepository_Tests : ApiResourceRepository_Tests { } diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ClientRepository_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ClientRepository_Tests.cs index 29b006cd99..766448daeb 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ClientRepository_Tests.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/ClientRepository_Tests.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Xunit; namespace Volo.Abp.IdentityServer { + [Collection(MongoTestCollection.Name)] public class ClientRepository_Tests : ClientRepository_Tests { diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/IdentityResourceRepository_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/IdentityResourceRepository_Tests.cs index 2b44b2c6a0..36ca210b36 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/IdentityResourceRepository_Tests.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/IdentityResourceRepository_Tests.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Xunit; namespace Volo.Abp.IdentityServer { + [Collection(MongoTestCollection.Name)] public class IdentityResourceRepository_Tests : IdentityResourceRepository_Tests { } diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs new file mode 100644 index 0000000000..22bbf08ef4 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.IdentityServer +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoTestCollection.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoTestCollection.cs new file mode 100644 index 0000000000..8ac0a73624 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.IdentityServer +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/PersistentGrantRepository_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/PersistentGrantRepository_Tests.cs index fd2763dc6c..834d18fcf9 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/PersistentGrantRepository_Tests.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/PersistentGrantRepository_Tests.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Xunit; namespace Volo.Abp.IdentityServer { + [Collection(MongoTestCollection.Name)] public class PersistentGrantRepository_Tests : PersistentGrantRepository_Tests { diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs index d3d4d5d6e9..8e3c7b2c22 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs @@ -1,5 +1,4 @@ using System; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -10,11 +9,9 @@ namespace Volo.Abp.PermissionManagement.MongoDB typeof(AbpPermissionManagementTestBaseModule))] public class AbpPermissionManagementMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs new file mode 100644 index 0000000000..3a09e6e1a4 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.PermissionManagement.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoTestCollection.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoTestCollection.cs new file mode 100644 index 0000000000..a1f2eb6558 --- /dev/null +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.PermissionManagement.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/PermissionGrantRepository_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/PermissionGrantRepository_Tests.cs index 98db0640c5..dd3c42da15 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/PermissionGrantRepository_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/PermissionGrantRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.PermissionManagement.MongoDB +using Xunit; + +namespace Volo.Abp.PermissionManagement.MongoDB { + [Collection(MongoTestCollection.Name)] public class PermissionGrantRepository_Tests : PermissionGrantRepository_Tests { diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs index 1b9faefdbd..ec221eba15 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs @@ -1,5 +1,4 @@ using System; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -11,11 +10,9 @@ namespace Volo.Abp.SettingManagement.MongoDB )] public class AbpSettingManagementMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs new file mode 100644 index 0000000000..31ccdcc4f7 --- /dev/null +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.SettingManagement.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoTestCollection.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoTestCollection.cs new file mode 100644 index 0000000000..11ed1cbb55 --- /dev/null +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.SettingManagement.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/SettingRepository_Tests.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/SettingRepository_Tests.cs index 7e35eff799..a1a2d1428c 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/SettingRepository_Tests.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/SettingRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.SettingManagement.MongoDB +using Xunit; + +namespace Volo.Abp.SettingManagement.MongoDB { + [Collection(MongoTestCollection.Name)] public class SettingRepository_Tests : SettingRepository_Tests { diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs index 911839d510..9584ec2d44 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs @@ -1,6 +1,4 @@ using System; -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.Modularity; @@ -12,11 +10,9 @@ namespace Volo.Abp.TenantManagement.MongoDB )] public class AbpTenantManagementMongoDbTestModule : AbpModule { - private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); - public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbRunner.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs new file mode 100644 index 0000000000..be115d9dff --- /dev/null +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs @@ -0,0 +1,16 @@ +using System; +using Mongo2Go; + +namespace Volo.Abp.TenantManagement.MongoDB +{ + public class MongoDbFixture : IDisposable + { + private static readonly MongoDbRunner MongoDbRunner = MongoDbRunner.Start(); + public static readonly string ConnectionString = MongoDbRunner.ConnectionString; + + public void Dispose() + { + MongoDbRunner?.Dispose(); + } + } +} \ No newline at end of file diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoTestCollection.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoTestCollection.cs new file mode 100644 index 0000000000..1719b7dbdd --- /dev/null +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoTestCollection.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.TenantManagement.MongoDB +{ + [CollectionDefinition(Name)] + public class MongoTestCollection : ICollectionFixture + { + public const string Name = "MongoDB Collection"; + } +} \ No newline at end of file diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs index b1ae97b021..0a980be37d 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/TenantRepository_Tests.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.TenantManagement.MongoDB +using Xunit; + +namespace Volo.Abp.TenantManagement.MongoDB { + [Collection(MongoTestCollection.Name)] public class TenantRepository_Tests : TenantRepository_Tests { diff --git a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts index d4b30b731d..71f59798ae 100644 --- a/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts @@ -6,12 +6,23 @@ import { generateHash } from '../utils'; export class DomInsertionService { readonly inserted = new Set(); - insertContent(contentStrategy: ContentStrategy) { + insertContent( + contentStrategy: ContentStrategy, + ): T { const hash = generateHash(contentStrategy.content); if (this.inserted.has(hash)) return; - contentStrategy.insertElement(); + const element = contentStrategy.insertElement(); this.inserted.add(hash); + + return element; + } + + removeContent(element: HTMLScriptElement | HTMLStyleElement) { + const hash = generateHash(element.textContent); + this.inserted.delete(hash); + + element.parentNode.removeChild(element); } } diff --git a/npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts index 1d58f6bb3d..05261bc5e4 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts @@ -10,11 +10,13 @@ export abstract class ContentStrategy { domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any; const strategy = new StyleContentStrategy('', domStrategy, contentSecurityStrategy); - const element = strategy.createElement(); - strategy.insertElement(); + strategy.createElement(); + const element = strategy.insertElement(); expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element); expect(domStrategy.insertElement).toHaveBeenCalledWith(element); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts index f8e8565496..556583643e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts @@ -9,7 +9,7 @@ describe('DomInsertionService', () => { beforeEach(() => (spectator = createService())); - afterEach(() => styleElements.forEach(element => element.remove())); + afterEach(() => (document.head.innerHTML = '')); describe('#insertContent', () => { it('should be able to insert given content', () => { @@ -19,6 +19,11 @@ describe('DomInsertionService', () => { expect(styleElements[0].textContent).toBe('.test {}'); }); + it('should set a hash for the inserted content', () => { + spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead('.test {}')); + expect(spectator.service.inserted.has(1437348290)).toBe(true); + }); + it('should insert only once', () => { expect(spectator.service.inserted.has(1437348290)).toBe(false); @@ -37,9 +42,25 @@ describe('DomInsertionService', () => { expect(spectator.service.inserted.has(1437348290)).toBe(true); }); - it('should be able to insert given content', () => { - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead('.test {}')); + it('should return inserted element', () => { + const element = spectator.service.insertContent( + CONTENT_STRATEGY.AppendStyleToHead('.test {}'), + ); + expect(element.tagName).toBe('STYLE'); + }); + }); + + describe('#removeContent', () => { + it('should remove inserted element and the hash for the content', () => { + expect(document.head.querySelector('style')).toBeNull(); + const element = spectator.service.insertContent( + CONTENT_STRATEGY.AppendStyleToHead('.test {}'), + ); expect(spectator.service.inserted.has(1437348290)).toBe(true); + + spectator.service.removeContent(element); + expect(spectator.service.inserted.has(1437348290)).toBe(false); + expect(document.head.querySelector('style')).toBeNull(); }); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-container.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-container.component.ts new file mode 100644 index 0000000000..bd24bd84d2 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-container.component.ts @@ -0,0 +1,12 @@ +import { Component, ViewChild, ViewContainerRef } from '@angular/core'; + +@Component({ + selector: 'abp-modal-container', + template: ` + + `, +}) +export class ModalContainerComponent { + @ViewChild('container', { static: true, read: ViewContainerRef }) + container: ViewContainerRef; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html index c3168bad92..b37f5b6ce3 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.html @@ -1,34 +1,43 @@ -