@ -1,16 +1,16 @@ |
|||
<Project> |
|||
<PropertyGroup> |
|||
<LangVersion>latest</LangVersion> |
|||
<Version>3.1.0</Version> |
|||
<NoWarn>$(NoWarn);CS1591</NoWarn> |
|||
<Version>3.2.0</Version> |
|||
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> |
|||
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> |
|||
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> |
|||
<PackageLicenseFile>LICENSE.md</PackageLicenseFile> |
|||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> |
|||
<RepositoryType>git</RepositoryType> |
|||
<RepositoryUrl>https://github.com/abpframework/abp/</RepositoryUrl> |
|||
<GenerateDocumentationFile>true</GenerateDocumentationFile> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" /> |
|||
<None Include="LICENSE.md" Pack="true" PackagePath=""/> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,62 @@ |
|||
# ABP Framework 3.1 Final Has Been Released |
|||
|
|||
It is exciting for us to announce that we've released the ABP Framework & ABP Commercial 3.1 today. |
|||
|
|||
Since all the new features are already explained in details with the [3.1 RC Announcement Post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released), I will not repeat all the details here. Please read [the RC post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) for **new feature and changes** you may need to do for your solution while upgrading to the version 3.1. |
|||
|
|||
## Creating New Solutions |
|||
|
|||
You can create a new solution with the ABP Framework version 3.1 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). |
|||
|
|||
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. |
|||
|
|||
## How to Upgrade an Existing Solution |
|||
|
|||
### Install/Update the ABP CLI |
|||
|
|||
First of all, install the ABP CLI or upgrade to the latest version. |
|||
|
|||
If you haven't installed yet: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
To update an existing installation: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
### ABP UPDATE Command |
|||
|
|||
[ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: |
|||
|
|||
````bash |
|||
abp update |
|||
```` |
|||
|
|||
After the update command, check [the RC blog post](https://blog.abp.io/abp/ABP-Framework-v3.1-RC-Has-Been-Released) to learn if you need to make any changes in your solution. |
|||
|
|||
> You may want to see the new [upgrading document](https://docs.abp.io/en/abp/latest/Upgrading). |
|||
|
|||
## About the version 3.2 |
|||
|
|||
The planned schedule for the version 3.2 is like that; |
|||
|
|||
* **September 17, 2020**: 3.2.0-rc.1 (release candidate) |
|||
* **October 1, 2020**: 3.2.0 final (stable) |
|||
|
|||
You can check [the GitHub milestone](https://github.com/abpframework/abp/milestone/39) to see the features/issues we are working on. |
|||
|
|||
## ABP Community & Articles |
|||
|
|||
We had lunched the [ABP Community web site](https://community.abp.io/) a few weeks before. The core ABP team and the ABP community have started to create content for the community. |
|||
|
|||
Here, the last three articles from the ABP Community: |
|||
|
|||
* [ABP Suite: How to Add the User Entity as a Navigation Property of Another Entity](https://community.abp.io/articles/abp-suite-how-to-add-the-user-entity-as-a-navigation-property-of-another-entity-furp75ex) by [@ebicoglu](https://github.com/ebicoglu) |
|||
* [Reuse ABP vNext Modules to Quickly Implement Application Features](https://community.abp.io/articles/reuse-abp-vnext-modules-to-quickly-implement-application-features-tdtmwd9w) by [@gdlcf88](https://github.com/gdlcf88) |
|||
* [Using DevExtreme Components With the ABP Framework](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) by [@cotur](https://github.com/cotur). |
|||
|
|||
We are looking for your contributions; You can [submit your article](https://community.abp.io/articles/submit)! We will promote your article to the community. |
|||
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 539 KiB |
|
After Width: | Height: | Size: 175 KiB |
@ -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 |
|||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -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. |
|||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 83 KiB |
@ -0,0 +1,41 @@ |
|||
# Upgrading the ABP Framework |
|||
|
|||
This document explains how to upgrade your existing solution when a new ABP Framework version is published. |
|||
|
|||
## ABP UPDATE Command |
|||
|
|||
ABP Framework & module ecosystem consist of hundreds of NuGet and NPM packages. It would be tedious to manually update all these packages to upgrade your application. |
|||
|
|||
[ABP CLI](CLI.md) provides a handy command to update all the ABP related NuGet and NPM packages in your solution with a single command: |
|||
|
|||
````bash |
|||
abp update |
|||
```` |
|||
|
|||
Run this command in the terminal while you are in the root folder of your solution. |
|||
|
|||
> If your solution has the Angular UI, you probably have `aspnet-core` and `angular` folders in the solution. Run this command in the parent folder of these two folders. |
|||
|
|||
## The Blog Posts |
|||
|
|||
Sometimes we introduce new features/changes that requires to make changes in the startup template. We already implement the changes in the startup template for new applications. However, in some cases you need to manually make some minor changes in your solution. |
|||
|
|||
Whenever you upgrade your solution, it is strongly suggested to check the [ABP BLOG](https://blog.abp.io/?_ga=2.177248992.411298747.1597771169-1910388957.1594128976) to learn the new features and changes coming with the new version. We regularly publish posts and write these kind of changes. If the changes are not trivial, we also provide migration guides. |
|||
|
|||
## Semantic Versioning & Breaking Changes |
|||
|
|||
We are working hard to keep the semantic versioning rules, so you don't get breaking changes for minor (feature) versions like 3.1, 3.2, 3.3... |
|||
|
|||
However, there are some cases we may introduce breaking changes in feature versions too; |
|||
|
|||
* ABP has many integration packages and sometimes the integrated libraries/frameworks releases major versions and makes breaking changes. In such cases, we carefully check these changes and decide to upgrade the integration package or not. If the impact of the change is relatively small, we update the integration package and explain the change in the release blog post. In such a case, if you've used this integration package, you should follow the instructions explained in the blog post. If the change may break many applications and not easy to fix, we decide to wait this upgrade until the next major ABP Framework release. |
|||
* Sometimes we have to make breaking change to fix a major bug or usage problem. In this case, we think that developer already can't properly use that feature, so no problem to fix it with a breaking change. In such cases, the feature will generally be a rarely used feature. Again, we try to keep the impact minimum. |
|||
|
|||
## Preview Releases & Nightly Builds |
|||
|
|||
Preview releases and nightly builds can help you to try new features and adapt your solution earlier than a new stable release. |
|||
|
|||
* [Preview releases](Previews.md) are typically published ~2 weeks before a minor (feature) version (our minor version development cycle is about ~4 weeks). |
|||
* [Nightly builds](Nightly-Builds.md) are published in every night (except weekends) from the development branch. That means you can try the previous day's development. |
|||
|
|||
Refer to the their documents to learn details about these kind of releases. |
|||
|
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... |
|||
@ -1,5 +1,28 @@ |
|||
# 身份管理模块 |
|||
|
|||
身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. |
|||
身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. |
|||
|
|||
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. |
|||
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. |
|||
|
|||
## Identity安全日志 |
|||
|
|||
安全日志可以记录账户的一些重要的操作或者改动, 你可以在在一些功能中保存安全日志. |
|||
|
|||
你可以注入和使用 `IdentitySecurityLogManager` 或 `ISecurityLogManager` 来保存安全日志. 默认它会创建一个安全日志对象并填充常用的值. 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`等等. 当然你可以自定义这些值. |
|||
|
|||
```cs |
|||
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() |
|||
{ |
|||
Identity = "IdentityServer"; |
|||
Action = "ChangePassword"; |
|||
}); |
|||
``` |
|||
|
|||
通过配置 `AbpSecurityLogOptions` 来提供应用程序的名称或者禁用安全日志功能. 默认是**启用**状态. |
|||
|
|||
```cs |
|||
Configure<AbpSecurityLogOptions>(options => |
|||
{ |
|||
options.ApplicationName = "AbpSecurityTest"; |
|||
}); |
|||
``` |
|||
|
|||
@ -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,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authentication.OAuth.Claims; |
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.MultiTenancy; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
public static class AbpOpenIdConnectExtensions |
|||
{ |
|||
public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder) |
|||
=> builder.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, _ => { }); |
|||
|
|||
public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, Action<OpenIdConnectOptions> configureOptions) |
|||
=> builder.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, configureOptions); |
|||
|
|||
public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, Action<OpenIdConnectOptions> configureOptions) |
|||
=> builder.AddAbpOpenIdConnect(authenticationScheme, OpenIdConnectDefaults.DisplayName, configureOptions); |
|||
|
|||
public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<OpenIdConnectOptions> configureOptions) |
|||
{ |
|||
return builder.AddOpenIdConnect(authenticationScheme, displayName, options => |
|||
{ |
|||
options.ClaimActions.MapAbpClaimTypes(); |
|||
|
|||
options.Events = new OpenIdConnectEvents |
|||
{ |
|||
OnAuthorizationCodeReceived = receivedContext => |
|||
{ |
|||
var tenantKey = receivedContext.HttpContext.RequestServices |
|||
.GetRequiredService<IOptionsSnapshot<AbpAspNetCoreMultiTenancyOptions>>().Value.TenantKey; |
|||
|
|||
if (receivedContext.HttpContext.Request != null && |
|||
receivedContext.Request.Cookies.ContainsKey(tenantKey)) |
|||
{ |
|||
receivedContext.TokenEndpointRequest.SetParameter(tenantKey, |
|||
receivedContext.Request.Cookies[tenantKey]); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
}; |
|||
|
|||
configureOptions?.Invoke(options); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.1.6" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.MultiTenancy\Volo.Abp.AspNetCore.MultiTenancy.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Authentication.OAuth\Volo.Abp.AspNetCore.Authentication.OAuth.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.AspNetCore.Authentication.OAuth; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Authentication.OpenIdConnect |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpMultiTenancyModule), |
|||
typeof(AbpAspNetCoreAuthenticationOAuthModule))] |
|||
public class AbpAspNetCoreAuthenticationOpenIdConnectModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; |
|||
using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule) |
|||
)] |
|||
public class AbpAspNetCoreComponentsWebAssemblyBasicThemeModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRouterOptions>(options => |
|||
{ |
|||
options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,7 @@ |
|||
@page "/authentication/{action}" |
|||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication |
|||
<RemoteAuthenticatorView Action="@Action" /> |
|||
|
|||
@code{ |
|||
[Parameter] public string Action { get; set; } |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
@using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing |
|||
@using Microsoft.Extensions.Options |
|||
@inject IOptions<AbpRouterOptions> RouterOptions |
|||
<CascadingAuthenticationState> |
|||
<Router AppAssembly="RouterOptions.Value.AppAssembly" |
|||
AdditionalAssemblies="RouterOptions.Value.AdditionalAssemblies"> |
|||
<Found Context="routeData"> |
|||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> |
|||
<NotAuthorized> |
|||
@if (!context.User.Identity.IsAuthenticated) |
|||
{ |
|||
<RedirectToLogin /> |
|||
} |
|||
else |
|||
{ |
|||
<p>You are not authorized to access this resource.</p> |
|||
} |
|||
</NotAuthorized> |
|||
</AuthorizeRouteView> |
|||
</Found> |
|||
<NotFound> |
|||
<LayoutView Layout="@typeof(MainLayout)"> |
|||
<p>Sorry, there's nothing at this address.</p> |
|||
</LayoutView> |
|||
</NotFound> |
|||
</Router> |
|||
</CascadingAuthenticationState> |
|||
@ -0,0 +1,65 @@ |
|||
@using Volo.Abp.Localization |
|||
@using System.Globalization |
|||
@using System.Collections.Immutable |
|||
@inject ILanguageProvider LanguageProvider |
|||
@inject IJSRuntime JsRuntime |
|||
@if (_otherLanguages != null && _otherLanguages.Any()) |
|||
{ |
|||
<Dropdown> |
|||
<DropdownToggle Color="Color.None"> |
|||
@_currentLanguage.DisplayName |
|||
</DropdownToggle> |
|||
<DropdownMenu> |
|||
@foreach (var language in _otherLanguages) |
|||
{ |
|||
<DropdownItem Clicked="() => ChangeLanguageAsync(language)">@language.DisplayName</DropdownItem> |
|||
} |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
} |
|||
@code { |
|||
private IReadOnlyList<LanguageInfo> _otherLanguages; |
|||
private LanguageInfo _currentLanguage; |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
var selectedLanguageName = await JsRuntime.InvokeAsync<string>( |
|||
"localStorage.getItem", |
|||
"Abp.SelectedLanguage" |
|||
); |
|||
|
|||
_otherLanguages = await LanguageProvider.GetLanguagesAsync(); |
|||
|
|||
if (!_otherLanguages.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!selectedLanguageName.IsNullOrWhiteSpace()) |
|||
{ |
|||
_currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == selectedLanguageName); |
|||
} |
|||
|
|||
if (_currentLanguage == null) |
|||
{ |
|||
_currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == CultureInfo.CurrentUICulture.Name); |
|||
} |
|||
|
|||
if (_currentLanguage == null) |
|||
{ |
|||
_currentLanguage = _otherLanguages.FirstOrDefault(); |
|||
} |
|||
|
|||
_otherLanguages = _otherLanguages.Where(l => l != _currentLanguage).ToImmutableList(); |
|||
} |
|||
|
|||
private async Task ChangeLanguageAsync(LanguageInfo language) |
|||
{ |
|||
await JsRuntime.InvokeVoidAsync( |
|||
"localStorage.setItem", |
|||
"Abp.SelectedLanguage", language.UiCultureName |
|||
); |
|||
|
|||
await JsRuntime.InvokeVoidAsync("location.reload"); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication |
|||
@using Volo.Abp.Users |
|||
@inject ICurrentUser CurrentUser |
|||
@inject NavigationManager Navigation |
|||
@inject SignOutSessionStateManager SignOutManager |
|||
<AuthorizeView> |
|||
<Authorized> |
|||
<Dropdown> |
|||
<DropdownToggle Color="Color.None"> |
|||
@CurrentUser.UserName |
|||
</DropdownToggle> |
|||
<DropdownMenu> |
|||
@if (Menu != null) |
|||
{ |
|||
@foreach (var menuItem in Menu.Items) |
|||
{ |
|||
<DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem> |
|||
} |
|||
} |
|||
<Divider /> |
|||
<DropdownItem Clicked="BeginSignOut">Logout</DropdownItem> |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
</Authorized> |
|||
<NotAuthorized> |
|||
<a class="nav-link" href="authentication/login">Log in</a> |
|||
</NotAuthorized> |
|||
</AuthorizeView> |
|||
@code{ |
|||
|
|||
private void NavigateTo(string uri) |
|||
{ |
|||
Navigation.NavigateTo(uri); |
|||
} |
|||
|
|||
private async Task BeginSignOut() |
|||
{ |
|||
await SignOutManager.SetSignOutState(); |
|||
NavigateTo("authentication/logout"); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Components; |
|||
using Volo.Abp.UI.Navigation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic |
|||
{ |
|||
public partial class LoginDisplay |
|||
{ |
|||
[Inject] protected IMenuManager MenuManager { get; set; } |
|||
|
|||
protected ApplicationMenu Menu { get; set; } |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
Menu = await MenuManager.GetAsync(StandardMenus.User); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
@inherits LayoutComponentBase |
|||
@using Volo.Abp.Ui.Branding |
|||
@inject IBrandingProvider BrandingProvider |
|||
<nav class="navbar navbar-expand-md navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4" id="main-navbar" style="min-height: 4rem;"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/">@BrandingProvider.AppName</a> |
|||
<button class="navbar-toggler" type="button" data-toggle="collapse" |
|||
data-target="#main-navbar-collapse" aria-controls="main-navbar-collapse" |
|||
aria-expanded="false" aria-label="Toggle navigation"> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="collapse navbar-collapse" id="main-navbar-collapse"> |
|||
<ul class="navbar-nav mx-auto"> |
|||
<NavMenu/> |
|||
</ul> |
|||
<ul class="navbar-nav"> |
|||
<li class="nav-item"> |
|||
<LanguageSwitch/> |
|||
</li> |
|||
<li class="nav-item"> |
|||
<LoginDisplay/> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
<div class="container"> |
|||
@Body |
|||
</div> |
|||
@ -0,0 +1,7 @@ |
|||
@if (Menu != null) |
|||
{ |
|||
foreach (var menuItem in Menu.Items) |
|||
{ |
|||
<NavMenuItem MenuItem="@menuItem" /> |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Components; |
|||
using Volo.Abp.UI.Navigation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic |
|||
{ |
|||
public partial class NavMenu |
|||
{ |
|||
[Inject] protected IMenuManager MenuManager { get; set; } |
|||
|
|||
protected ApplicationMenu Menu { get; set; } |
|||
|
|||
private bool collapseNavMenu = true; |
|||
|
|||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
Menu = await MenuManager.GetAsync(StandardMenus.Main); |
|||
} |
|||
|
|||
private void ToggleNavMenu() |
|||
{ |
|||
collapseNavMenu = !collapseNavMenu; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
@using Volo.Abp.UI.Navigation |
|||
@{ |
|||
var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_"); |
|||
var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass; |
|||
var url = string.IsNullOrEmpty(MenuItem.Url) ? "#" : MenuItem.Url; |
|||
} |
|||
@if (MenuItem.IsLeaf) |
|||
{ |
|||
if (MenuItem.Url != null) |
|||
{ |
|||
<li class="nav-item @cssClass" disabled="@MenuItem.IsDisabled"> |
|||
<NavLink class="nav-link" href="@url" id="@elementId"> |
|||
@if (MenuItem.Icon != null) |
|||
{ |
|||
if (MenuItem.Icon.StartsWith("fa")) |
|||
{ |
|||
<i class="@MenuItem.Icon"></i> |
|||
} |
|||
} |
|||
@MenuItem.DisplayName |
|||
</NavLink> |
|||
</li> |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
<li class="nav-item"> |
|||
<div class="dropdown"> |
|||
<a class="nav-link dropdown-toggle" href="#" id="@elementId" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |
|||
@if (MenuItem.Icon != null) |
|||
{ |
|||
if (MenuItem.Icon.StartsWith("fa")) |
|||
{ |
|||
<i class="@MenuItem.Icon"></i> |
|||
} |
|||
} |
|||
@MenuItem.DisplayName |
|||
</a> |
|||
<div class="dropdown-menu border-0 shadow-sm" aria-labelledby="@elementId"> |
|||
@foreach (var childMenuItem in MenuItem.Items) |
|||
{ |
|||
<NavMenuItem MenuItem="@childMenuItem" /> |
|||
} |
|||
</div> |
|||
</div> |
|||
</li> |
|||
|
|||
} |
|||
@code { |
|||
[Parameter] |
|||
public ApplicationMenuItem MenuItem { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
@inject NavigationManager Navigation |
|||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication |
|||
@code { |
|||
protected override void OnInitialized() |
|||
{ |
|||
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}"); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
@using System.Net.Http |
|||
@using Microsoft.AspNetCore.Components.Authorization |
|||
@using Microsoft.AspNetCore.Components.Forms |
|||
@using Microsoft.AspNetCore.Components.Routing |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Microsoft.AspNetCore.Components.WebAssembly.Http |
|||
@using Microsoft.JSInterop |
|||
@using Volo.Abp.AspNetCore.Components.WebAssembly |
|||
@using Blazorise |
|||
@using Blazorise.DataGrid |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.1</TargetFramework> |
|||
<RazorLangVersion>3.0</RazorLangVersion> |
|||
<PackageId>Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme</PackageId> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,105 @@ |
|||
|
|||
#main-navbar-tools a.dropdown-toggle { |
|||
text-decoration: none; |
|||
color: #fff; |
|||
} |
|||
|
|||
.navbar .dropdown-submenu { |
|||
position: relative; |
|||
} |
|||
.navbar .dropdown-menu { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
.navbar .dropdown-menu a { |
|||
font-size: .9em; |
|||
padding: 10px 15px; |
|||
display: block; |
|||
min-width: 210px; |
|||
text-align: left; |
|||
border-radius: 0.25rem; |
|||
min-height: 44px; |
|||
} |
|||
.navbar .dropdown-submenu a::after { |
|||
transform: rotate(-90deg); |
|||
position: absolute; |
|||
right: 16px; |
|||
top: 18px; |
|||
} |
|||
.navbar .dropdown-submenu .dropdown-menu { |
|||
top: 0; |
|||
left: 100%; |
|||
} |
|||
|
|||
.card-header .btn { |
|||
padding: 2px 6px; |
|||
} |
|||
.card-header h5 { |
|||
margin: 0; |
|||
} |
|||
.container > .card { |
|||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; |
|||
} |
|||
|
|||
@media screen and (min-width: 768px) { |
|||
.navbar .dropdown:hover > .dropdown-menu { |
|||
display: block; |
|||
} |
|||
|
|||
.navbar .dropdown-submenu:hover > .dropdown-menu { |
|||
display: block; |
|||
} |
|||
} |
|||
.input-validation-error { |
|||
border-color: #dc3545; |
|||
} |
|||
.field-validation-error { |
|||
font-size: 0.8em; |
|||
} |
|||
|
|||
.dataTables_scrollBody { |
|||
min-height: 248px; |
|||
} |
|||
|
|||
div.dataTables_wrapper div.dataTables_info { |
|||
padding-top: 11px; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
div.dataTables_wrapper div.dataTables_length label { |
|||
padding-top: 10px; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.rtl .dropdown-menu-right { |
|||
right: auto; |
|||
left: 0; |
|||
} |
|||
|
|||
.rtl .dropdown-menu-right a { |
|||
text-align: right; |
|||
} |
|||
|
|||
.rtl .navbar .dropdown-menu a { |
|||
text-align: right; |
|||
} |
|||
.rtl .navbar .dropdown-submenu .dropdown-menu { |
|||
top: 0; |
|||
left: auto; |
|||
right: 100%; |
|||
} |
|||
|
|||
/* TEMP */ |
|||
|
|||
.navbar-dark .navbar-nav .nav-link { |
|||
color: #000 !important; |
|||
} |
|||
|
|||
.navbar-nav > .nav-item > .nav-link, |
|||
.navbar-nav > .nav-item > .dropdown > .nav-link { |
|||
color: #fff !important; |
|||
} |
|||
|
|||
.navbar-nav>.nav-item>div>button{ |
|||
color:#fff; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
$(function () { |
|||
$('.dropdown-menu a.dropdown-toggle').on('click', function (e) { |
|||
if (!$(this).next().hasClass('show')) { |
|||
$(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); |
|||
} |
|||
|
|||
var $subMenu = $(this).next(".dropdown-menu"); |
|||
$subMenu.toggleClass('show'); |
|||
|
|||
$(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) { |
|||
$('.dropdown-submenu .show').removeClass("show"); |
|||
}); |
|||
|
|||
return false; |
|||
}); |
|||
}); |
|||
@ -0,0 +1,17 @@ |
|||
using Volo.Abp.BlazoriseUI; |
|||
using Volo.Abp.Http.Client.IdentityModel.WebAssembly; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.UI.Navigation; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpBlazoriseUIModule), |
|||
typeof(AbpHttpClientIdentityModelWebAssemblyModule), |
|||
typeof(AbpUiNavigationModule) |
|||
)] |
|||
public class AbpAspNetCoreComponentsWebAssemblyThemingModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,16 @@ |
|||
using System.Reflection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing |
|||
{ |
|||
public class AbpRouterOptions |
|||
{ |
|||
public Assembly AppAssembly { get; set; } |
|||
|
|||
public RouterAssemblyList AdditionalAssemblies { get; } |
|||
|
|||
public AbpRouterOptions() |
|||
{ |
|||
AdditionalAssemblies = new RouterAssemblyList(); |
|||
} |
|||
} |
|||
} |
|||