mirror of https://github.com/abpframework/abp.git
177 changed files with 16098 additions and 2939 deletions
@ -1,3 +1,448 @@ |
|||
# Features |
|||
|
|||
TODO |
|||
ABP Feature system is used to **enable**, **disable** or **change the behavior** of the application features **on runtime**. |
|||
|
|||
The runtime value for a feature is generally a `boolean` value, like `true` (enabled) or `false` (disabled). However, you can get/set **any kind** of value for feature. |
|||
|
|||
Feature system was originally designed to control the tenant features in a **[multi-tenant](Multi-Tenancy.md)** application. However, it is **extensible** and capable of determining the features by any condition. |
|||
|
|||
> The feature system is implemented with the [Volo.Abp.Features](https://www.nuget.org/packages/Volo.Abp.Features) NuGet package. Most of the times you don't need to manually [install it](https://abp.io/package-detail/Volo.Abp.Features) since it comes pre-installed with the [application startup template](Startup-Templates/Application.md). |
|||
|
|||
## Checking for the Features |
|||
|
|||
Before explaining to define features, let's see how to check a feature value in your application code. |
|||
|
|||
### RequiresFeature Attribute |
|||
|
|||
`[RequiresFeature]` attribute (defined in the `Volo.Abp.Features` namespace) is used to declaratively check if a feature is `true` (enabled) or not. It is a useful shortcut for the `boolean` features. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
[RequiresFeature("MyApp.PdfReporting")] |
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `RequiresFeature(...)` simply gets a feature name to check if it is enabled or not. If not enabled, an authorization [exception](Exception-Handling.md) is thrown and a proper response is returned to the client side. |
|||
* `[RequiresFeature]` can be used for a **method** or a **class**. When you use it for a class, all the methods of that class require the given feature. |
|||
* `RequiresFeature` may get multiple feature names, like `[RequiresFeature("Feature1", "Feature2")]`. In this case ABP checks if any of the features enabled. Use `RequiresAll` option, like `[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]` to force to check all of the features to be enabled. |
|||
* Multiple usage of `[RequiresFeature]` attribute is supported for a method or class. ABP check checks all of them in that case. |
|||
|
|||
> Feature name can be any arbitrary string. It should be unique for a feature. |
|||
|
|||
#### About the Interception |
|||
|
|||
ABP Framework uses the interception system to make the `[RequiresFeature]` attribute working. So, it can work with any class (application services, controllers...) that is injected from the [dependency injection](Dependency-Injection.md). |
|||
|
|||
However, there are **some rules should be followed** in order to make it working; |
|||
|
|||
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work. |
|||
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted. |
|||
|
|||
> There is an exception for the **controller and razor page methods**. They **don't require** the following the rules above, since ABP Framework uses the action/page filters to implement the feature checking in this case. |
|||
|
|||
### IFeatureChecker Service |
|||
|
|||
`IFeatureChecker` allows to check a feature in your application code. |
|||
|
|||
#### IsEnabledAsync |
|||
|
|||
Returns `true` if the given feature is enabled. So, you can conditionally execute your business flow. |
|||
|
|||
**Example: Check if the "PDF Reporting" feature enabled** |
|||
|
|||
```csharp |
|||
public class ReportingAppService : ApplicationService, IReportingAppService |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ReportingAppService(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<PdfReportResultDto> GetPdfReportAsync() |
|||
{ |
|||
if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) |
|||
{ |
|||
//TODO... |
|||
} |
|||
else |
|||
{ |
|||
//TODO... |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`IsEnabledAsync` has overloads to check multiple features in one method call. |
|||
|
|||
#### GetOrNullAsync |
|||
|
|||
Gets the current value for a feature. This method returns a `string`, so you store any kind of value inside it, by converting to or from `string`. |
|||
|
|||
**Example: Check the maximum product count allowed** |
|||
|
|||
```csharp |
|||
public class ProductController : AbpController |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
|
|||
public ProductController(IFeatureChecker featureChecker) |
|||
{ |
|||
_featureChecker = featureChecker; |
|||
} |
|||
|
|||
public async Task<IActionResult> Create(CreateProductModel model) |
|||
{ |
|||
var currentProductCount = await GetCurrentProductCountFromDatabase(); |
|||
|
|||
//GET THE FEATURE VALUE |
|||
var maxProductCountLimit = |
|||
await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); |
|||
|
|||
if (currentProductCount >= Convert.ToInt32(maxProductCountLimit)) |
|||
{ |
|||
throw new BusinessException( |
|||
"MyApp:ReachToMaxProductCountLimit", |
|||
$"You can not create more than {maxProductCountLimit} products!" |
|||
); |
|||
} |
|||
|
|||
//TODO: Create the product in the database... |
|||
} |
|||
|
|||
private async Task<int> GetCurrentProductCountFromDatabase() |
|||
{ |
|||
throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This example uses a numeric value as a feature limit product counts for a user/tenant in a SaaS application. |
|||
|
|||
Instead of manually converting the value to `int`, you can use the generic overload of the `GetAsync` method: |
|||
|
|||
```csharp |
|||
var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount"); |
|||
``` |
|||
|
|||
#### Extension Methods |
|||
|
|||
There are some useful extension methods for the `IFeatureChecker` interface; |
|||
|
|||
* `Task<T> GetAsync<T>(string name, T defaultValue = default)`: Used to get a value of a feature with the given type `T`. Allows to specify a `defaultValue` that is returned when the feature value is `null`. |
|||
* `CheckEnabledAsync(string name)`: Checks if given feature is enabled. Throws an `AbpAuthorizationException` if the feature was not `true` (enabled). |
|||
|
|||
## Defining the Features |
|||
|
|||
A feature should be defined to be able to check it. |
|||
|
|||
### FeatureDefinitionProvider |
|||
|
|||
Create a class inheriting the `FeatureDefinitionProvider` to define permissions. |
|||
|
|||
**Example: Defining permissions** |
|||
|
|||
```csharp |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature("MyApp.PdfReporting", defaultValue: "false"); |
|||
myGroup.AddFeature("MyApp.MaxProductCount", defaultValue: "10"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> ABP automatically discovers this class and registers the features. No additional configuration required. |
|||
|
|||
> This class is generally created in the `Application.Contracts` project of your solution. |
|||
|
|||
* In the `Define` method, you first need to add a **feature group** for your application/module or get an existing group then add **features** to this group. |
|||
* First feature, named `MyApp.PdfReporting`, is a `boolean` feature with `false` as the default value. |
|||
* Second feature, named `MyApp.MaxProductCount`, is a numeric feature with `10` as the default value. |
|||
|
|||
Default value is used if there is no other value set for the current user/tenant. |
|||
|
|||
### Other Feature Properties |
|||
|
|||
While these minimal definitions are enough to make the feature system working, you can specify the **optional properties** for the features; |
|||
|
|||
* `DisplayName`: A localizable string that will be used to show the feature name on the user interface. |
|||
* `Description`: A longer localizable text to describe the feature. |
|||
* `ValueType`: Type of the feature value. Can be a class implementing the `IStringValueType`. Built-in types: |
|||
* `ToggleStringValueType`: Used to define `true`/`false`, `on`/`off`, `enabled`/`disabled` style features. A checkbox is shown on the UI. |
|||
* `FreeTextStringValueType`: Used to define free text values. A textbox is shown on the UI. |
|||
* `SelectionStringValueType`: Used to force the value to be selected from a list. A dropdown list is shown on the UI. |
|||
* `IsVisibleToClients` (default: `true`): Set false to hide the value of this feature from clients (browsers). Sharing the value with the clients helps them to conditionally show/hide/change the UI parts based on the feature value. |
|||
* `Properties`: A dictionary to set/get arbitrary key-value pairs related to this feature. This can be a point for customization. |
|||
|
|||
So, based on these descriptions, it would be better to define these features as shown below: |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
myGroup.AddFeature( |
|||
"MyApp.MaxProductCount", |
|||
defaultValue: "10", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("MaxProductCount"), |
|||
valueType: new FreeTextStringValueType( |
|||
new NumericValueValidator(0, 1000000)) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* `FeaturesDemoResource` is the project name in this example code. See the [localization document](Localization.md) for details about the localization system. |
|||
* First feature is set to `ToggleStringValueType`, while the second one is set to `FreeTextStringValueType` with a numeric validator that allows to the values from `0` to `1,000,000`. |
|||
|
|||
Remember to define the localization the keys in your localization file: |
|||
|
|||
````json |
|||
"PdfReporting": "PDF Reporting", |
|||
"MaxProductCount": "Maximum number of products" |
|||
```` |
|||
|
|||
See the [localization document](Localization.md) for details about the localization system. |
|||
|
|||
### Feature Management Modal |
|||
|
|||
The [application startup template](Startup-Templates/Application.md) comes with the [tenant management](Modules/Tenant-Management.md) and the [feature management](Modules/Feature-Management.md) modules pre-installed. |
|||
|
|||
Whenever you define a new feature, it will be available on the **feature management modal**. To open this modal, navigate to the **tenant management page** and select the `Features` action for a tenant (create a new tenant if there is no tenant yet): |
|||
|
|||
 |
|||
|
|||
This action opens a modal to manage the feature values for the selected tenant: |
|||
|
|||
 |
|||
|
|||
So, you can enable, disable and set values for a tenant. These values will be used whenever a user of this tenant uses the application. |
|||
|
|||
See the *Feature Management* section below to learn more about managing the features. |
|||
|
|||
### Child Features |
|||
|
|||
A feature may have child features. This is especially useful if you want to create a feature that is selectable only if another feature was enabled. |
|||
|
|||
**Example: Defining child features** |
|||
|
|||
```csharp |
|||
using FeaturesDemo.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup("MyApp"); |
|||
|
|||
var reportingFeature = myGroup.AddFeature( |
|||
"MyApp.Reporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("Reporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.PdfReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("PdfReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
|
|||
reportingFeature.CreateChild( |
|||
"MyApp.ExcelReporting", |
|||
defaultValue: "false", |
|||
displayName: LocalizableString |
|||
.Create<FeaturesDemoResource>("ExcelReporting"), |
|||
valueType: new ToggleStringValueType() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The example above defines a *Reporting* feature with two children: *PDF Reporting* and *Excel Reporting*. |
|||
|
|||
### Changing Features Definitions of a Depended Module |
|||
|
|||
A class deriving from the `FeatureDefinitionProvider` (just like the example above) can also get the existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. |
|||
|
|||
**Example: Manipulate an existing feature definition** |
|||
|
|||
```csharp |
|||
var someGroup = context.GetGroupOrNull("SomeModule"); |
|||
var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); |
|||
if (feature != null) |
|||
{ |
|||
feature.Description = ... |
|||
feature.CreateChild(...); |
|||
} |
|||
``` |
|||
|
|||
## Check a Feature in the Client Side |
|||
|
|||
A feature value is available at the client side too, unless you set `IsVisibleToClients` to `false` on the feature definition. The feature values are exposed from the [Application Configuration API](API/Application-Configuration.md) and usable via some services on the UI. |
|||
|
|||
### ASP.NET Core MVC / Razor Pages UI |
|||
|
|||
Use `abp.features` API to get the feature values. |
|||
|
|||
**Example: Get feature values in the JavaScript code** |
|||
|
|||
````js |
|||
var isEnabled = abp.features.values["MyApp.ExcelReporting"] === "true"; |
|||
var count = abp.features.values["MyApp.MaxProductCount"]; |
|||
```` |
|||
|
|||
### Angular UI |
|||
|
|||
See the [features](Features.md) document for the Angular UI. |
|||
|
|||
## Feature Management |
|||
|
|||
Feature management is normally done by an admin user using the feature management modal: |
|||
|
|||
 |
|||
|
|||
This modal is available on the related entities, like tenants in a multi-tenant application. To open it, navigate to the **Tenant Management** page (for a multi-tenant application), click to the **Actions** button left to the Tenant and select the **Features** action. |
|||
|
|||
If you need to manage features by code, inject the `IFeatureManager` service. |
|||
|
|||
**Example: Enable PDF reporting for a tenant** |
|||
|
|||
```csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IFeatureManager _featureManager; |
|||
|
|||
public MyService(IFeatureManager featureManager) |
|||
{ |
|||
_featureManager = featureManager; |
|||
} |
|||
|
|||
public async Task EnablePdfReporting(Guid tenantId) |
|||
{ |
|||
await _featureManager.SetForTenantAsync( |
|||
tenantId, |
|||
"MyApp.PdfReporting", |
|||
true.ToString() |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`IFeatureManager` is defined by the Feature Management module. It comes pre-installed with the application startup template. See the [feature management module documentation](Modules/Feature-Management.md) for more information. |
|||
|
|||
## Advanced Topics |
|||
|
|||
### Feature Value Providers |
|||
|
|||
Feature system is extensible. Any class derived from `FeatureValueProvider` (or implements `IFeatureValueProvider`) can contribute to the feature system. A value provider is responsible to **obtain the current value** of a given feature. |
|||
|
|||
Feature value providers are **executed one by one**. If one of them return a non-null value, then this feature value is used and the other providers are not executed. |
|||
|
|||
There are three pre-defined value providers, executed by the given order: |
|||
|
|||
* `TenantFeatureValueProvider` tries to get if the feature value is explicitly set for the **current tenant**. |
|||
* `EditionFeatureValueProvider` tries to get the feature value for the current edition. Edition Id is obtained from the current principal identity (`ICurrentPrincipalAccessor`) with the claim name `editionid` (a constant defined as`AbpClaimTypes.EditionId`). Editions are not implemented for the [tenant management](Modules/Tenant-Management.md) module. You can implement it yourself or consider to use the [SaaS module](https://commercial.abp.io/modules/Volo.Saas) of the ABP Commercial. |
|||
* `DefaultValueFeatureValueProvider` gets the default value of the feature. |
|||
|
|||
You can write your own provider by inheriting the `FeatureValueProvider`. |
|||
|
|||
**Example: Enable all features for a user with "SystemAdmin" as a "User_Type" claim value** |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Security.Claims; |
|||
using Volo.Abp.Validation.StringValues; |
|||
|
|||
namespace FeaturesDemo |
|||
{ |
|||
public class SystemAdminFeatureValueProvider : FeatureValueProvider |
|||
{ |
|||
public override string Name => "SA"; |
|||
|
|||
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; |
|||
|
|||
public SystemAdminFeatureValueProvider( |
|||
IFeatureStore featureStore, |
|||
ICurrentPrincipalAccessor currentPrincipalAccessor) |
|||
: base(featureStore) |
|||
{ |
|||
_currentPrincipalAccessor = currentPrincipalAccessor; |
|||
} |
|||
|
|||
public override Task<string> GetOrNullAsync(FeatureDefinition feature) |
|||
{ |
|||
if (feature.ValueType is ToggleStringValueType && |
|||
_currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin") |
|||
{ |
|||
return Task.FromResult("true"); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
If a provider returns `null`, then the next provider is executed. |
|||
|
|||
Once a provider is defined, it should be added to the `AbpFeatureOptions` as shown below: |
|||
|
|||
```csharp |
|||
Configure<AbpFeatureOptions>(options => |
|||
{ |
|||
options.ValueProviders.Add<SystemAdminFeatureValueProvider>(); |
|||
}); |
|||
``` |
|||
|
|||
Use this code inside the `ConfigureServices` of your [module](Module-Development-Basics.md) class. |
|||
|
|||
### Feature Store |
|||
|
|||
`IFeatureStore` is the only interface that needs to be implemented to read the value of features from a persistence source, generally a database system. The Feature Management module implements it and pre-installed in the application startup template. See the [feature management module documentation](https://docs.abp.io/en/abp/latest/Modules/Feature-Management) for more information |
|||
@ -1,3 +1,5 @@ |
|||
# Feature Management Module |
|||
|
|||
> This module implements the `IFeatureStore` to store and manage feature values in a database. See the [Features System document](../Features.md) to understand the features first. |
|||
|
|||
TODO |
|||
@ -1,3 +1,5 @@ |
|||
# Permission Management Module |
|||
|
|||
This module implements the `IPermissionStore` to store and manage feature values in a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems first. |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
# Angular UI: Features |
|||
|
|||
> This document explains how to get feature values in an Angular application. See the [Features document](../../Features.md) to learn the feature system. |
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 35 KiB |
@ -1,3 +0,0 @@ |
|||
## 在控制台应用中使用ABP |
|||
|
|||
ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息. |
|||
@ -0,0 +1,3 @@ |
|||
# Global Features |
|||
|
|||
TODO... |
|||
@ -1,9 +0,0 @@ |
|||
# "如何" 指南 |
|||
|
|||
本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和ABP并不直接相关,但我们认为有一些具体的示例可以直接与基于ABP的应用程序一起使用. |
|||
|
|||
## Authentication |
|||
|
|||
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md) |
|||
* [如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证](Azure-Active-Directory-Authentication-MVC.md) |
|||
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md) |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1,3 @@ |
|||
# Module Entity Extensions |
|||
|
|||
参阅 https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (文档会在近期完成). |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
@ -0,0 +1 @@ |
|||
TODO... |
|||
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,46 @@ |
|||
var abp = abp || {}; |
|||
(function () { |
|||
|
|||
if (!luxon) { |
|||
throw "abp/luxon library requires the luxon library included to the page!"; |
|||
} |
|||
|
|||
/* TIMING *************************************************/ |
|||
|
|||
abp.timing = abp.timing || {}; |
|||
|
|||
var setObjectValue = function (obj, property, value) { |
|||
if (typeof property === "string") { |
|||
property = property.split('.'); |
|||
} |
|||
|
|||
if (property.length > 1) { |
|||
var p = property.shift(); |
|||
setObjectValue(obj[p], property, value); |
|||
} else { |
|||
obj[property[0]] = value; |
|||
} |
|||
} |
|||
|
|||
var getObjectValue = function (obj, property) { |
|||
return property.split('.').reduce((a, v) => a[v], obj) |
|||
} |
|||
|
|||
abp.timing.convertFieldsToIsoDate = function (form, fields) { |
|||
for (var field of fields) { |
|||
var dateTime = luxon.DateTime |
|||
.fromFormat( |
|||
getObjectValue(form, field), |
|||
abp.localization.currentCulture.dateTimeFormat.shortDatePattern, |
|||
{locale: abp.localization.currentCulture.cultureName} |
|||
); |
|||
|
|||
if (!dateTime.invalid) { |
|||
setObjectValue(form, field, dateTime.toFormat("yyyy-MM-dd HH:mm:ss")) |
|||
} |
|||
} |
|||
|
|||
return form; |
|||
} |
|||
|
|||
})(jQuery); |
|||
@ -0,0 +1,10 @@ |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Http.Localization |
|||
{ |
|||
[LocalizationResourceName("HttpClientTest")] |
|||
public class HttpClientTestResource |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Volo.Abp.Http.DynamicProxying:10001": "Business exception with data: {0}" |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password |
|||
{ |
|||
public class AccountProfilePasswordManagementGroupViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IProfileAppService _profileAppService; |
|||
|
|||
public AccountProfilePasswordManagementGroupViewComponent( |
|||
IProfileAppService profileAppService) |
|||
{ |
|||
_profileAppService = profileAppService; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync() |
|||
{ |
|||
var user = await _profileAppService.GetAsync(); |
|||
|
|||
var model = new ChangePasswordInfoModel |
|||
{ |
|||
HideOldPasswordInput = !user.HasPassword |
|||
}; |
|||
|
|||
return View("~/Pages/Account/Components/ProfileManagementGroup/Password/Default.cshtml", model); |
|||
} |
|||
|
|||
public class ChangePasswordInfoModel |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] |
|||
[Display(Name = "DisplayName:CurrentPassword")] |
|||
[DataType(DataType.Password)] |
|||
[DisableAuditing] |
|||
public string CurrentPassword { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] |
|||
[Display(Name = "DisplayName:NewPassword")] |
|||
[DataType(DataType.Password)] |
|||
[DisableAuditing] |
|||
public string NewPassword { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))] |
|||
[Display(Name = "DisplayName:NewPasswordConfirm")] |
|||
[DataType(DataType.Password)] |
|||
[DisableAuditing] |
|||
public string NewPasswordConfirm { get; set; } |
|||
|
|||
public bool HideOldPasswordInput { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
@using Volo.Abp.Account.Localization |
|||
@using Volo.Abp.Users |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@inject IHtmlLocalizer<AccountResource> L |
|||
@inject ICurrentUser CurrentUser |
|||
@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password.AccountProfilePasswordManagementGroupViewComponent.ChangePasswordInfoModel |
|||
|
|||
<h4>@L["ChangePassword"]</h4><hr/> |
|||
<form id="ChangePasswordForm"> |
|||
@if (!Model.HideOldPasswordInput) |
|||
{ |
|||
<abp-input asp-for="CurrentPassword"/> |
|||
} |
|||
<abp-input asp-for="NewPassword"/> |
|||
<abp-input asp-for="NewPasswordConfirm"/> |
|||
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/> |
|||
</form> |
|||
@ -0,0 +1,31 @@ |
|||
(function ($) { |
|||
$(function () { |
|||
var l = abp.localization.getResource("AbpAccount"); |
|||
|
|||
$('#ChangePasswordForm').submit(function (e) { |
|||
e.preventDefault(); |
|||
|
|||
if (!$('#ChangePasswordForm').valid()) { |
|||
return false; |
|||
} |
|||
|
|||
var input = $('#ChangePasswordForm').serializeFormToObject(); |
|||
|
|||
if ( |
|||
input.newPassword != input.newPasswordConfirm || |
|||
input.newPassword == '' |
|||
) { |
|||
abp.message.error(l('NewPasswordConfirmFailed')); |
|||
return; |
|||
} |
|||
|
|||
if (input.currentPassword && input.currentPassword == ''){ |
|||
return; |
|||
} |
|||
|
|||
volo.abp.identity.profile.changePassword(input).then(function (result) { |
|||
abp.message.success(l('PasswordChanged')); |
|||
}); |
|||
}); |
|||
}); |
|||
})(jQuery); |
|||
@ -0,0 +1,56 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo |
|||
{ |
|||
public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IProfileAppService _profileAppService; |
|||
|
|||
public AccountProfilePersonalInfoManagementGroupViewComponent( |
|||
IProfileAppService profileAppService) |
|||
{ |
|||
_profileAppService = profileAppService; |
|||
|
|||
ObjectMapperContext = typeof(AbpAccountWebModule); |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync() |
|||
{ |
|||
var user = await _profileAppService.GetAsync(); |
|||
|
|||
var model = ObjectMapper.Map<ProfileDto, PersonalInfoModel>(user); |
|||
|
|||
return View("~/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml", model); |
|||
} |
|||
|
|||
public class PersonalInfoModel |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))] |
|||
[Display(Name = "DisplayName:UserName")] |
|||
public string UserName { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))] |
|||
[Display(Name = "DisplayName:Email")] |
|||
public string Email { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))] |
|||
[Display(Name = "DisplayName:Name")] |
|||
public string Name { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))] |
|||
[Display(Name = "DisplayName:Surname")] |
|||
public string Surname { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))] |
|||
[Display(Name = "DisplayName:PhoneNumber")] |
|||
public string PhoneNumber { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
@using Volo.Abp.Account.Localization |
|||
@using Volo.Abp.Users |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Theming |
|||
@using Volo.Abp.Identity.Settings |
|||
@using Volo.Abp.Settings |
|||
@inject IHtmlLocalizer<AccountResource> L |
|||
@inject ICurrentUser CurrentUser |
|||
@inject ISettingProvider SettingManager |
|||
@inject IThemeManager ThemeManager |
|||
@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel |
|||
@{ |
|||
var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true", |
|||
StringComparison.OrdinalIgnoreCase); |
|||
|
|||
var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true", |
|||
StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
<h4>@L["PersonalSettings"]</h4><hr/> |
|||
<form method="post" id="PersonalSettingsForm"> |
|||
|
|||
<abp-input asp-for="UserName" readonly="!isUserNameUpdateEnabled"/> |
|||
|
|||
<abp-row> |
|||
<abp-column size-md="_6"> |
|||
<abp-input asp-for="Name"/> |
|||
</abp-column> |
|||
<abp-column size-md="_6"> |
|||
<abp-input asp-for="Surname"/> |
|||
</abp-column> |
|||
</abp-row> |
|||
|
|||
<abp-input asp-for="Email" readonly="!isEmailUpdateEnabled"/> |
|||
|
|||
<abp-input asp-for="PhoneNumber"/> |
|||
|
|||
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value"/> |
|||
</form> |
|||
@ -0,0 +1,19 @@ |
|||
(function ($) { |
|||
$(function () { |
|||
var l = abp.localization.getResource("AbpAccount"); |
|||
|
|||
$('#PersonalSettingsForm').submit(function (e) { |
|||
e.preventDefault(); |
|||
|
|||
if (!$('#PersonalSettingsForm').valid()) { |
|||
return false; |
|||
} |
|||
|
|||
var input = $('#PersonalSettingsForm').serializeFormToObject(); |
|||
|
|||
volo.abp.identity.profile.update(input).then(function (result) { |
|||
abp.notify.success(l('PersonalSettingsSaved')); |
|||
}); |
|||
}); |
|||
}); |
|||
})(jQuery); |
|||
@ -1,47 +0,0 @@ |
|||
(function ($) { |
|||
var l = abp.localization.getResource('AbpAccount'); |
|||
|
|||
var _profileService = volo.abp.identity.profile; |
|||
|
|||
$('#ChangePasswordForm').submit(function (e) { |
|||
e.preventDefault(); |
|||
|
|||
if (!$('#ChangePasswordForm').valid()) { |
|||
return false; |
|||
} |
|||
|
|||
var input = $('#ChangePasswordForm').serializeFormToObject() |
|||
.changePasswordInfoModel; |
|||
|
|||
if ( |
|||
input.newPassword != input.newPasswordConfirm || |
|||
input.newPassword == '' |
|||
) { |
|||
abp.message.error(l('NewPasswordConfirmFailed')); |
|||
return; |
|||
} |
|||
|
|||
if (input.currentPassword && input.currentPassword == ''){ |
|||
return; |
|||
} |
|||
|
|||
_profileService.changePassword(input).then(function (result) { |
|||
abp.message.success(l('PasswordChanged')); |
|||
}); |
|||
}); |
|||
|
|||
$('#PersonalSettingsForm').submit(function (e) { |
|||
e.preventDefault(); |
|||
|
|||
if (!$('#PersonalSettingsForm').valid()) { |
|||
return false; |
|||
} |
|||
|
|||
var input = $('#PersonalSettingsForm').serializeFormToObject() |
|||
.personalSettingsInfoModel; |
|||
|
|||
_profileService.update(input).then(function (result) { |
|||
abp.notify.success(l('PersonalSettingsSaved')); |
|||
}); |
|||
}); |
|||
})(jQuery); |
|||
@ -0,0 +1,48 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.Account.Localization; |
|||
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password; |
|||
using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.Abp.Account.Web.ProfileManagement |
|||
{ |
|||
public class AccountProfileManagementPageContributor : IProfileManagementPageContributor |
|||
{ |
|||
public async Task ConfigureAsync(ProfileManagementPageCreationContext context) |
|||
{ |
|||
var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AccountResource>>(); |
|||
|
|||
if (await IsPasswordChangeEnabled(context)) |
|||
{ |
|||
context.Groups.Add( |
|||
new ProfileManagementPageGroup( |
|||
"Volo.Abp.Account.Password", |
|||
l["ProfileTab:Password"], |
|||
typeof(AccountProfilePasswordManagementGroupViewComponent) |
|||
) |
|||
); |
|||
} |
|||
|
|||
context.Groups.Add( |
|||
new ProfileManagementPageGroup( |
|||
"Volo.Abp.Account.PersonalInfo", |
|||
l["ProfileTab:PersonalInfo"], |
|||
typeof(AccountProfilePersonalInfoManagementGroupViewComponent) |
|||
) |
|||
); |
|||
} |
|||
|
|||
protected virtual async Task<bool> IsPasswordChangeEnabled(ProfileManagementPageCreationContext context) |
|||
{ |
|||
var userManager = context.ServiceProvider.GetRequiredService<IdentityUserManager>(); |
|||
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>(); |
|||
|
|||
var user = await userManager.GetByIdAsync(currentUser.GetId()); |
|||
|
|||
return !user.IsExternal; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Account.Web.ProfileManagement |
|||
{ |
|||
public interface IProfileManagementPageContributor |
|||
{ |
|||
Task ConfigureAsync(ProfileManagementPageCreationContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Account.Web.ProfileManagement |
|||
{ |
|||
public class ProfileManagementPageCreationContext |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
public List<ProfileManagementPageGroup> Groups { get; } |
|||
|
|||
public ProfileManagementPageCreationContext(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
|
|||
Groups = new List<ProfileManagementPageGroup>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Account.Web.ProfileManagement |
|||
{ |
|||
public class ProfileManagementPageGroup |
|||
{ |
|||
public string Id |
|||
{ |
|||
get => _id; |
|||
set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id)); |
|||
} |
|||
private string _id; |
|||
|
|||
public string DisplayName |
|||
{ |
|||
get => _displayName; |
|||
set => _displayName = Check.NotNullOrWhiteSpace(value, nameof(DisplayName)); |
|||
} |
|||
private string _displayName; |
|||
|
|||
public Type ComponentType |
|||
{ |
|||
get => _componentType; |
|||
set => _componentType = Check.NotNull(value, nameof(ComponentType)); |
|||
} |
|||
private Type _componentType; |
|||
|
|||
public object Parameter { get; set; } |
|||
|
|||
public ProfileManagementPageGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object parameter = null) |
|||
{ |
|||
Id = id; |
|||
DisplayName = displayName; |
|||
ComponentType = componentType; |
|||
Parameter = parameter; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Account.Web.ProfileManagement |
|||
{ |
|||
public class ProfileManagementPageOptions |
|||
{ |
|||
public List<IProfileManagementPageContributor> Contributors { get; } |
|||
|
|||
public ProfileManagementPageOptions() |
|||
{ |
|||
Contributors = new List<IProfileManagementPageContributor>(); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace Volo.CmsKit.Migrations |
|||
{ |
|||
public partial class CmsRatings_Added : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.CreateTable( |
|||
name: "CmsRatings", |
|||
columns: table => new |
|||
{ |
|||
Id = table.Column<Guid>(nullable: false), |
|||
TenantId = table.Column<Guid>(nullable: true), |
|||
EntityType = table.Column<string>(maxLength: 64, nullable: false), |
|||
EntityId = table.Column<string>(maxLength: 64, nullable: false), |
|||
StarCount = table.Column<short>(nullable: false), |
|||
CreatorId = table.Column<Guid>(nullable: false), |
|||
CreationTime = table.Column<DateTime>(nullable: false) |
|||
}, |
|||
constraints: table => |
|||
{ |
|||
table.PrimaryKey("PK_CmsRatings", x => x.Id); |
|||
}); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_CmsRatings_TenantId_EntityType_EntityId", |
|||
table: "CmsRatings", |
|||
columns: new[] { "TenantId", "EntityType", "EntityId" }); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropTable( |
|||
name: "CmsRatings"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,37 @@ |
|||
.jq-stars { |
|||
display: inline-block; |
|||
} |
|||
|
|||
.jq-rating-label { |
|||
font-size: 22px; |
|||
display: inline-block; |
|||
position: relative; |
|||
vertical-align: top; |
|||
font-family: helvetica, arial, verdana; |
|||
} |
|||
|
|||
.jq-star { |
|||
width: 100px; |
|||
height: 100px; |
|||
display: inline-block; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.jq-star-svg { |
|||
width: 100%; |
|||
height: 100% ; |
|||
} |
|||
|
|||
.jq-star:hover .fs-star-svg polygon { |
|||
} |
|||
|
|||
.jq-star-svg polygon { |
|||
stroke: #000; |
|||
stroke-linejoin: round; |
|||
} |
|||
|
|||
/* un-used */ |
|||
.jq-shadow { |
|||
-webkit-filter: drop-shadow( -2px -2px 2px #888 ); |
|||
filter: drop-shadow( -2px -2px 2px #888 ); |
|||
} |
|||
@ -0,0 +1 @@ |
|||
!function(a){"use strict";var b="starRating",c=function(){},d={totalStars:5,useFullStars:!1,emptyColor:"lightgray",hoverColor:"orange",activeColor:"gold",useGradient:!0,readOnly:!1,disableAfterRate:!0,starGradient:{start:"#FEF7CD",end:"#FF9511"},strokeWidth:0,strokeColor:"black",initialRating:0,starSize:40,callback:c,onHover:c,onLeave:c},e=function(c,e){var f;this.element=c,this.$el=a(c),this.settings=a.extend({},d,e),f=this.$el.data("rating")||this.settings.initialRating,this._state={rating:(Math.round(2*f)/2).toFixed(1)},this._uid=Math.floor(999*Math.random()),e.starGradient||this.settings.useGradient||(this.settings.starGradient.start=this.settings.starGradient.end=this.settings.activeColor),this._defaults=d,this._name=b,this.init()},f={init:function(){this.renderMarkup(),this.addListeners(),this.initRating()},addListeners:function(){this.settings.readOnly||(this.$stars.on("mouseover",this.hoverRating.bind(this)),this.$stars.on("mouseout",this.restoreState.bind(this)),this.$stars.on("click",this.handleRating.bind(this)))},hoverRating:function(a){var b=this.getIndex(a);this.paintStars(b,"hovered"),this.settings.onHover(b+1,this._state.rating,this.$el)},handleRating:function(a){var b=this.getIndex(a),c=b+1;this.applyRating(c,this.$el),this.executeCallback(c,this.$el),this.settings.disableAfterRate&&this.$stars.off()},applyRating:function(a){var b=a-1;this.paintStars(b,"active"),this._state.rating=b+1},restoreState:function(a){var b=this.getIndex(a),c=this._state.rating||-1;this.paintStars(c-1,"active"),this.settings.onLeave(b+1,this._state.rating,this.$el)},getIndex:function(b){var c=a(b.currentTarget),d=c.width(),e=a(b.target).attr("data-side");e=e?e:this.getOffsetByPixel(b,c,d),e=this.settings.useFullStars?"right":e;var f=c.index()-("left"===e?.5:0);return f=.5>f&&b.offsetX<d/4?-1:f},getOffsetByPixel:function(a,b,c){var d=a.pageX-b.offset().left;return c/2>=d&&!this.settings.useFullStars?"left":"right"},initRating:function(){this.paintStars(this._state.rating-1,"active")},paintStars:function(b,c){var d,e,f,g;a.each(this.$stars,function(h,i){d=a(i).find('polygon[data-side="left"]'),e=a(i).find('polygon[data-side="right"]'),f=g=b>=h?c:"empty",f=h-b===.5?c:f,d.attr("class","svg-"+f+"-"+this._uid),e.attr("class","svg-"+g+"-"+this._uid)}.bind(this))},renderMarkup:function(){for(var a='<div class="jq-star" style="width:'+this.settings.starSize+"px; height:"+this.settings.starSize+'px;"><svg version="1.0" class="jq-star-svg" shape-rendering="geometricPrecision" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="305px" height="305px" viewBox="60 -62 309 309" style="enable-background:new 64 -59 305 305; stroke-width:'+this.settings.strokeWidth+'px;" xml:space="preserve"><style type="text/css">.svg-empty-'+this._uid+"{fill:url(#"+this._uid+"_SVGID_1_);}.svg-hovered-"+this._uid+"{fill:url(#"+this._uid+"_SVGID_2_);}.svg-active-"+this._uid+"{fill:url(#"+this._uid+"_SVGID_3_);}</style>"+this.getLinearGradient(this._uid+"_SVGID_1_",this.settings.emptyColor,this.settings.emptyColor)+this.getLinearGradient(this._uid+"_SVGID_2_",this.settings.hoverColor,this.settings.hoverColor)+this.getLinearGradient(this._uid+"_SVGID_3_",this.settings.starGradient.start,this.settings.starGradient.end)+'<polygon data-side="left" class="svg-empty-'+this._uid+'" points="281.1,129.8 364,55.7 255.5,46.8 214,-59 172.5,46.8 64,55.4 146.8,129.7 121.1,241 213.9,181.1 213.9,181 306.5,241 " style="stroke: '+this.settings.strokeColor+'"/><polygon data-side="right" class="svg-empty-'+this._uid+'" points="364,55.7 255.5,46.8 214,-59 213.9,181 306.5,241 281.1,129.8 " style="stroke-dasharray: 230 232 210 0; stroke: '+this.settings.strokeColor+'"/></svg></div>',b="",c=0;c<this.settings.totalStars;c++)b+=a;this.$el.append(b),this.$stars=this.$el.find(".jq-star")},getLinearGradient:function(a,b,c){return'<linearGradient id="'+a+'" gradientUnits="userSpaceOnUse" x1="121.1501" y1="-70.35" x2="121.15" y2="125.0045"><stop offset="0" style="stop-color:'+b+'"/><stop offset="1" style="stop-color:'+c+'"/> </linearGradient>'},executeCallback:function(a,b){var c=this.settings.callback;c(a,b)}},g={unload:function(){var c="plugin_"+b,d=a(this),e=d.data(c).$stars;e.off(),d.removeData(c).remove()},setRating:function(c,d){var e="plugin_"+b,f=a(this),g=f.data(e);c>g.settings.totalStars||0>c||(d&&(c=Math.round(c)),g.applyRating(c))},getRating:function(){var c="plugin_"+b,d=a(this),e=d.data(c);return e._state.rating}};a.extend(e.prototype,f),a.fn[b]=function(c){if(!a.isPlainObject(c)){if(g.hasOwnProperty(c))return g[c].apply(this,Array.prototype.slice.call(arguments,1));a.error("Method "+c+" does not exist on "+b+".js")}return this.each(function(){a.data(this,"plugin_"+b)||a.data(this,"plugin_"+b,new e(this,c))})}}(jQuery,window,document); |
|||
@ -0,0 +1,18 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.GlobalFeatures; |
|||
|
|||
namespace Volo.CmsKit.GlobalFeatures |
|||
{ |
|||
[GlobalFeatureName(Name)] |
|||
public class RatingsFeature : GlobalFeature |
|||
{ |
|||
public const string Name = "CmsKit.Ratings"; |
|||
|
|||
internal RatingsFeature( |
|||
[NotNull] GlobalCmsKitFeatures cmsKit |
|||
) : base(cmsKit) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.CmsKit.Entities; |
|||
|
|||
namespace Volo.CmsKit.Ratings |
|||
{ |
|||
public static class RatingConsts |
|||
{ |
|||
public static int MaxEntityTypeLength { get; set; } = CmsEntityConsts.MaxEntityTypeLength; |
|||
|
|||
public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength; |
|||
|
|||
public static int MaxStarCount { get; set; } = 5; |
|||
|
|||
public static int MinStarCount { get; set; } = 1; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.CmsKit.Ratings |
|||
{ |
|||
public interface IRatingRepository : IBasicRepository<Rating, Guid> |
|||
{ |
|||
Task<Rating> GetCurrentUserRatingAsync( |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
|
|||
Task<List<RatingWithStarCountQueryResultItem>> GetGroupedStarCountsAsync( |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId, |
|||
CancellationToken cancellationToken = default |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace Volo.CmsKit.Ratings |
|||
{ |
|||
public class Rating : BasicAggregateRoot<Guid>, IHasCreationTime, IMustHaveCreator |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
|
|||
public virtual string EntityType { get; protected set; } |
|||
|
|||
public virtual string EntityId { get; protected set; } |
|||
|
|||
public virtual short StarCount { get; protected set; } |
|||
|
|||
public virtual Guid CreatorId { get; set; } |
|||
|
|||
public virtual DateTime CreationTime { get; set; } |
|||
|
|||
protected Rating() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Rating( |
|||
Guid id, |
|||
[NotNull] string entityType, |
|||
[NotNull] string entityId, |
|||
short starCount, |
|||
Guid creatorId, |
|||
Guid? tenantId = null |
|||
) |
|||
: base(id) |
|||
{ |
|||
EntityType = Check.NotNullOrWhiteSpace(entityType, nameof(entityType), RatingConsts.MaxEntityTypeLength); |
|||
EntityId = Check.NotNullOrWhiteSpace(entityId, nameof(entityId), RatingConsts.MaxEntityIdLength); |
|||
SetStarCount(starCount); |
|||
CreatorId = creatorId; |
|||
TenantId = tenantId; |
|||
} |
|||
|
|||
public virtual void SetStarCount(short starCount) |
|||
{ |
|||
if(starCount <= RatingConsts.MaxStarCount && starCount >= RatingConsts.MinStarCount) |
|||
{ |
|||
StarCount = starCount; |
|||
} |
|||
else |
|||
{ |
|||
throw new ArgumentOutOfRangeException($"Choosen star must between {RatingConsts.MinStarCount} and {RatingConsts.MaxStarCount}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.CmsKit.Ratings |
|||
{ |
|||
public class RatingWithStarCountQueryResultItem |
|||
{ |
|||
public short StarCount { get; set; } |
|||
|
|||
public int Count { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.CmsKit.EntityFrameworkCore; |
|||
|
|||
namespace Volo.CmsKit.Ratings |
|||
{ |
|||
public class EfCoreRatingRepository : EfCoreRepository<ICmsKitDbContext, Rating, Guid>, IRatingRepository |
|||
{ |
|||
public EfCoreRatingRepository(IDbContextProvider<ICmsKitDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async Task<Rating> GetCurrentUserRatingAsync(string entityType, string entityId, Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); |
|||
Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); |
|||
|
|||
var rating = await DbSet.FirstOrDefaultAsync( |
|||
r => r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, |
|||
GetCancellationToken(cancellationToken)); |
|||
|
|||
return rating; |
|||
} |
|||
|
|||
public async Task<List<RatingWithStarCountQueryResultItem>> GetGroupedStarCountsAsync(string entityType, |
|||
string entityId, CancellationToken cancellationToken = default) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(entityType, nameof(entityType)); |
|||
Check.NotNullOrWhiteSpace(entityId, nameof(entityId)); |
|||
|
|||
var query = ( |
|||
from rating in DbSet |
|||
where rating.EntityType == entityType && rating.EntityId == entityId |
|||
group rating by rating.StarCount |
|||
into g |
|||
select new RatingWithStarCountQueryResultItem |
|||
{ |
|||
StarCount = g.Key, |
|||
Count = g.Count() |
|||
} |
|||
).OrderByDescending(r => r.StarCount); |
|||
|
|||
var ratings = await query.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
|
|||
return ratings; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue