@ -0,0 +1,137 @@ |
|||||
|
# EditorConfig is awesome: https://EditorConfig.org |
||||
|
# Please feel free to update it (by considering the code style of ABP Team). |
||||
|
# top-most EditorConfig file |
||||
|
root = true |
||||
|
|
||||
|
[*.cs] |
||||
|
|
||||
|
#Namespace settings |
||||
|
csharp_style_namespace_declarations = file_scoped |
||||
|
dotnet_diagnostic.IDE0161.severity = warning |
||||
|
|
||||
|
#Core editorconfig formatting - indentation |
||||
|
|
||||
|
#use tabs for indentation |
||||
|
indent_style = tabs |
||||
|
|
||||
|
#Formatting - indentation options |
||||
|
|
||||
|
#indent switch case contents. |
||||
|
csharp_indent_case_contents = true |
||||
|
#indent switch labels |
||||
|
csharp_indent_switch_labels = true |
||||
|
|
||||
|
#Formatting - new line options |
||||
|
|
||||
|
#place catch statements on a new line |
||||
|
csharp_new_line_before_catch = true |
||||
|
#place else statements on a new line |
||||
|
csharp_new_line_before_else = true |
||||
|
#require members of object intializers to be on separate lines |
||||
|
csharp_new_line_before_members_in_object_initializers = true |
||||
|
#require braces to be on a new line for object_collection_array_initializers, methods, control_blocks, types, and lambdas (also known as "Allman" style) |
||||
|
csharp_new_line_before_open_brace = object_collection_array_initializers, methods, control_blocks, types, lambdas |
||||
|
|
||||
|
#Formatting - organize using options |
||||
|
|
||||
|
#sort System.* using directives alphabetically, and place them before other usings |
||||
|
dotnet_sort_system_directives_first = true |
||||
|
|
||||
|
#Formatting - spacing options |
||||
|
|
||||
|
#require NO space between a cast and the value |
||||
|
csharp_space_after_cast = false |
||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||
|
csharp_space_after_colon_in_inheritance_clause = true |
||||
|
#require a space after a keyword in a control flow statement such as a for loop |
||||
|
csharp_space_after_keywords_in_control_flow_statements = true |
||||
|
#require a space before the colon for bases or interfaces in a type declaration |
||||
|
csharp_space_before_colon_in_inheritance_clause = true |
||||
|
#remove space within empty argument list parentheses |
||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false |
||||
|
#remove space between method call name and opening parenthesis |
||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false |
||||
|
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call |
||||
|
csharp_space_between_method_call_parameter_list_parentheses = false |
||||
|
#remove space within empty parameter list parentheses for a method declaration |
||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false |
||||
|
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. |
||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false |
||||
|
|
||||
|
#Formatting - wrapping options |
||||
|
|
||||
|
#leave code block on single line |
||||
|
csharp_preserve_single_line_blocks = true |
||||
|
|
||||
|
#Style - Code block preferences |
||||
|
|
||||
|
#prefer curly braces even for one line of code |
||||
|
csharp_prefer_braces = true:suggestion |
||||
|
|
||||
|
#Style - expression bodied member options |
||||
|
|
||||
|
#prefer block bodies for constructors |
||||
|
csharp_style_expression_bodied_constructors = false:suggestion |
||||
|
#prefer block bodies for methods |
||||
|
csharp_style_expression_bodied_methods = false:suggestion |
||||
|
#prefer expression-bodied members for properties |
||||
|
csharp_style_expression_bodied_properties = true:suggestion |
||||
|
|
||||
|
#Style - expression level options |
||||
|
|
||||
|
#prefer out variables to be declared inline in the argument list of a method call when possible |
||||
|
csharp_style_inlined_variable_declaration = true:suggestion |
||||
|
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them |
||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion |
||||
|
|
||||
|
#Style - Expression-level preferences |
||||
|
|
||||
|
#prefer default over default(T) |
||||
|
csharp_prefer_simple_default_expression = true:suggestion |
||||
|
#prefer objects to be initialized using object initializers when possible |
||||
|
dotnet_style_object_initializer = true:suggestion |
||||
|
#prefer inferred tuple element names |
||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion |
||||
|
|
||||
|
#Style - implicit and explicit types |
||||
|
|
||||
|
#prefer var over explicit type in all cases, unless overridden by another code style rule |
||||
|
csharp_style_var_elsewhere = true:suggestion |
||||
|
#prefer var is used to declare variables with built-in system types such as int |
||||
|
csharp_style_var_for_built_in_types = true:suggestion |
||||
|
#prefer var when the type is already mentioned on the right-hand side of a declaration expression |
||||
|
csharp_style_var_when_type_is_apparent = true:suggestion |
||||
|
|
||||
|
#Style - language keyword and framework type options |
||||
|
|
||||
|
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them |
||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
||||
|
|
||||
|
#Style - Miscellaneous preferences |
||||
|
|
||||
|
#prefer local functions over anonymous functions |
||||
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion |
||||
|
|
||||
|
#Style - modifier options |
||||
|
|
||||
|
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. |
||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion |
||||
|
|
||||
|
#Style - Modifier preferences |
||||
|
|
||||
|
#when this rule is set to a list of modifiers, prefer the specified ordering. |
||||
|
csharp_preferred_modifier_order = public,protected,private,virtual,async,readonly,static,override,abstract:suggestion |
||||
|
|
||||
|
#Style - Pattern matching |
||||
|
|
||||
|
#prefer pattern matching instead of is expression with type casts |
||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion |
||||
|
|
||||
|
#Style - qualification options |
||||
|
|
||||
|
#prefer fields not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_field = false:suggestion |
||||
|
#prefer methods not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_method = false:suggestion |
||||
|
#prefer properties not to be prefaced with this. or Me. in Visual Basic |
||||
|
dotnet_style_qualification_for_property = false:suggestion |
||||
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 61 KiB |
@ -0,0 +1,47 @@ |
|||||
|
# ABP.IO Platform 5.0 Final Has Been Released! |
||||
|
|
||||
|
[ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) 5.0 versions have been released today. |
||||
|
|
||||
|
## What's new with 5.0? |
||||
|
|
||||
|
Since all the new features are already explained in details with the [5.0 RC.1 Announcement Post](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released), I will not repeat all the details again. See the [RC Blog Post](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released) for all the features and enhancements. |
||||
|
|
||||
|
## Getting started with 5.0 |
||||
|
|
||||
|
### Creating new solutions |
||||
|
|
||||
|
You can create a new solution with the ABP Framework version 5.0 by either using the `abp new` command or using the **direct download** tab on the [get started page](https://abp.io/get-started). |
||||
|
|
||||
|
Type the following command in a command-line terminal to install the ABP CLI version 5.0: |
||||
|
|
||||
|
````bash |
||||
|
dotnet tool install -g Volo.Abp.Cli --version 5.0.0 |
||||
|
```` |
||||
|
|
||||
|
To upgrade your existing ABP CLI installation: |
||||
|
|
||||
|
````bash |
||||
|
dotnet tool update -g Volo.Abp.Cli --version 5.0.0 |
||||
|
```` |
||||
|
|
||||
|
Then you can create a new solution using the `abp new` command: |
||||
|
|
||||
|
````bash |
||||
|
abp new Acme.BookStore |
||||
|
```` |
||||
|
|
||||
|
> See the [getting started document](https://docs.abp.io/en/abp/latest/Getting-Started) for details. |
||||
|
|
||||
|
### Upgrading existing solutions |
||||
|
|
||||
|
Check [the migration guide](https://docs.abp.io/en/abp/latest/Migration-Guides/Abp-5_0) for the applications with the version 4.x upgrading to the version 5.0. Also see [the upgrading guide](https://docs.abp.io/en/abp/latest/Upgrading) to understand how to update existing solutions. |
||||
|
|
||||
|
## ABP Community Talks 2021.12 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
As the core ABP development team, we've decided to organize monthly live meetings with the ABP community. The first live meeting will be at **December 16, 2021, 17:00 (UTC)** on YouTube. ABP core team members will present some of the new features coming with ABP 5.0. |
||||
|
|
||||
|
**Join this event on the Kommunity platform: https://kommunity.com/volosoft/events/abp-community-talks-4afca9c9** |
||||
|
|
||||
|
See you in the event! |
||||
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 33 KiB |
@ -0,0 +1,321 @@ |
|||||
|
# Integrating the Syncfusion MVC Components to the ABP MVC UI |
||||
|
|
||||
|
## Introduction |
||||
|
|
||||
|
In this article we will see how we can integrate the Syncfusion MVC Components into our ABP application. |
||||
|
|
||||
|
## Source Code |
||||
|
|
||||
|
You can find the source code of the application at https://github.com/EngincanV/ABP-Syncfusion-Components-Demo. |
||||
|
|
||||
|
## Prerequisites |
||||
|
|
||||
|
* [.NET 6](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) |
||||
|
|
||||
|
* In this article, we will create a new startup template in v5.0.0-rc.2 and if you follow this article from top to bottom and create a new startup template with me, you need to install the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) before starting. |
||||
|
|
||||
|
**NOTE:** ABP v5.X stable version has been released. You can replace v5.0.0-rc.2 with the latest stable version in your steps. |
||||
|
|
||||
|
Also, you need to update your ABP CLI to the v5.0.0-rc.2, you can use the command below to update your CLI version: |
||||
|
|
||||
|
```bash |
||||
|
dotnet tool update Volo.Abp.Cli -g --version 5.0.0-rc.2 |
||||
|
``` |
||||
|
|
||||
|
or install it if you haven't installed it before: |
||||
|
|
||||
|
```bash |
||||
|
dotnet tool install Volo.Abp.Cli -g --version 5.0.0-rc.2 |
||||
|
``` |
||||
|
|
||||
|
## Creating the Solution |
||||
|
|
||||
|
In this article, we will create a new startup template with EF Core as a database provider and MVC for the UI framework. But if you already have a project with MVC UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project. |
||||
|
|
||||
|
> If you already have a project with MVC/Razor Pages UI, you can skip this section. |
||||
|
|
||||
|
We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): |
||||
|
|
||||
|
```bash |
||||
|
abp new SyncfusionComponentsDemo -t app --preview |
||||
|
``` |
||||
|
|
||||
|
Our project boilerplate will be ready after the download is finished. Then, we can open the solution and start developing. |
||||
|
|
||||
|
## Starting the Development |
||||
|
|
||||
|
### Pre-requisite |
||||
|
|
||||
|
> If you've already had a license from Syncfusion, you can skip this section. |
||||
|
|
||||
|
* The first thing we need to do is create an account to be able to get a license from Syncfusion. |
||||
|
|
||||
|
* So, let's navigate to https://www.syncfusion.com/aspnet-core-ui-controls and click the "Download Free Trial" button. |
||||
|
|
||||
|
* Then fill the form and start your 30-day free trial. |
||||
|
|
||||
|
* After that, navigate to https://www.syncfusion.com/account/manage-trials/downloads to get our license key that will be used in our application. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Click the "Get License Key" link for "ASP.NET Core (Essential JS 2)". |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Then a modal will be opened like in the above image, select a version and click the "Get License Key" button. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Lastly, copy the generated license key value. |
||||
|
|
||||
|
In order to use the relevant components, Syncfusion needs to check this license key to know that our license is valid. |
||||
|
|
||||
|
### Configurations |
||||
|
|
||||
|
After providing a license key from Syncfusion, we can start with the configuration that needs to be done in our application. |
||||
|
|
||||
|
#### 1-) Install the Syncfusion.EJ2.AspNet.Core package |
||||
|
|
||||
|
We need to install the `Syncfusion.EJ2.AspNet.Core` Nuget package to our Web project (*.Web). |
||||
|
|
||||
|
We can install it via **Visual Studio's Nuget Package Manager**: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
or via dotnet cli: |
||||
|
|
||||
|
```bash |
||||
|
dotnet add package Syncfusion.EJ2.AspNet.Core --version 19.3.0.57 |
||||
|
``` |
||||
|
|
||||
|
> In this article, I've used the package in version 19.3.0.57. |
||||
|
|
||||
|
#### 2-) Register the License Key |
||||
|
|
||||
|
* After installing the package, we need to register our license key to be able to use the Syncfusion Components. |
||||
|
|
||||
|
* To register the license key, open your web module class and update the `ConfigureServices` method as follows: |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
//Register Syncfusion license |
||||
|
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: configuration["Syncfusion:LicenseKey"].ToString()); |
||||
|
|
||||
|
ConfigureUrls(configuration); |
||||
|
ConfigureBundles(); |
||||
|
ConfigureAuthentication(context, configuration); |
||||
|
ConfigureAutoMapper(); |
||||
|
ConfigureVirtualFileSystem(hostingEnvironment); |
||||
|
ConfigureLocalizationServices(); |
||||
|
ConfigureNavigationServices(); |
||||
|
ConfigureAutoApiControllers(); |
||||
|
ConfigureSwaggerServices(context.Services); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Instead of writing the license key directly in here we can define it in the **appsettings.json** file and use it here by using the Configuration system of .NET. |
||||
|
|
||||
|
|
||||
|
* Open your **appsettings.json** file and add a new section named "Syncfusion" as below: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
//... |
||||
|
|
||||
|
"Syncfusion": { |
||||
|
"LicenseKey": "<your-license-key>" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
> Replace the `<your-license-key> part with your license key that we've obtained in the previous section.` |
||||
|
|
||||
|
* To be able to use the Syncfusion Components we need to define them in our **_ViewImports.cshtml** file. By doing that we can use the Syncfusion components everywhere in our application. |
||||
|
|
||||
|
* Open your **/Pages/_ViewImports.cshtml** file and add a new tag helper: |
||||
|
|
||||
|
```cshtml |
||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
||||
|
@addTagHelper *, Syncfusion.EJ2 //use Syncfusion components |
||||
|
``` |
||||
|
|
||||
|
#### 3-) Adding Syncfusion styles and scripts to our application |
||||
|
|
||||
|
Firstly, let's install the `@syncfusion/ej2` package from **npm**. |
||||
|
|
||||
|
* Open your **package.json** file and add the `@syncfusion/ej2` package with version **19.3.57**: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"version": "1.0.0", |
||||
|
"name": "my-app", |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0-rc.2", |
||||
|
"@syncfusion/ej2": "^19.3.57" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
* Then, open the **abp.resourcemapping.js** file and update the **mappings** section: |
||||
|
|
||||
|
```js |
||||
|
module.exports = { |
||||
|
aliases: { |
||||
|
|
||||
|
}, |
||||
|
mappings: { |
||||
|
"@node_modules/@syncfusion/ej2/dist/ej2.min.js": "@libs/syncfusion/", |
||||
|
"@node_modules/@syncfusion/ej2/material.css": "@libs/syncfusion/" |
||||
|
} |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
> ABP copies related packages from **node_modules** folder to the **libs** folder by examining this file. You can read this [document](docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management#mapping-the-library-resources) for more info. |
||||
|
|
||||
|
* Then run the `abp install-libs` to install the dependencies and copy them into the libs folder by your mappings configuration. After running this command, in your **libs** folder it should be a folder named **syncfusion** folder. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
The last thing we need to do is, add some style and script files provided by Syncfusion, between our head-body tags. |
||||
|
|
||||
|
* We can do this by creating two view components (one for Styles and the other for Scripts). Let's do that. |
||||
|
|
||||
|
First, create a folder structure as shown below under the **Components** folder. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Then open the related files and add the following codes to each of these files. |
||||
|
|
||||
|
* **Default.cshtml** (/Components/Syncfusion/Script/Default.cshtml) |
||||
|
|
||||
|
```cshtml |
||||
|
@addTagHelper *, Syncfusion.EJ2 //add this line |
||||
|
|
||||
|
<!-- Syncfusion Essential JS 2 Scripts --> |
||||
|
<script src="/libs/syncfusion/ej2.min.js"></script> |
||||
|
|
||||
|
<!-- Syncfusion Essential JS 2 ScriptManager --> |
||||
|
<ejs-scripts></ejs-scripts> |
||||
|
``` |
||||
|
|
||||
|
* **SyncfusionScriptComponent.cs** |
||||
|
|
||||
|
```csharp |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Script |
||||
|
{ |
||||
|
public class SyncfusionScriptComponent : AbpViewComponent |
||||
|
{ |
||||
|
public IViewComponentResult Invoke() |
||||
|
{ |
||||
|
return View("~/Components/Syncfusion/Script/Default.cshtml"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
* **Default.cshtml** (/Components/Syncfusion/Style/Default.cshtml) |
||||
|
|
||||
|
```cshtml |
||||
|
<!-- Syncfusion Essential JS 2 Styles --> |
||||
|
<link rel="stylesheet" href="/libs/syncfusion/material.css"> |
||||
|
``` |
||||
|
|
||||
|
* SyncfusionStyleComponent.cs |
||||
|
|
||||
|
```csharp |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Style |
||||
|
{ |
||||
|
public class SyncfusionStyleComponent : AbpViewComponent |
||||
|
{ |
||||
|
public IViewComponentResult Invoke() |
||||
|
{ |
||||
|
return View("~/Components/Syncfusion/Style/Default.cshtml"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
After creating these two components, we can use the [**Layout Hooks**](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Layout-Hooks) system of ABP to inject these two components between head and script tags. |
||||
|
|
||||
|
To do this, open your web module class and update the `ConfigureServices` method as below: |
||||
|
|
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
//Register Syncfusion license |
||||
|
var licenseKey = configuration["Syncfusion:LicenseKey"].ToString(); |
||||
|
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: licenseKey); |
||||
|
|
||||
|
Configure<AbpLayoutHookOptions>(options => |
||||
|
{ |
||||
|
//Now, the SyncfusionStyleComponent code will be inserted in the head of the page as the last item. |
||||
|
options.Add(LayoutHooks.Head.Last, typeof(SyncfusionStyleComponent)); |
||||
|
|
||||
|
//the SyncfusionScriptComponent will be inserted in the body of the page as the last item. |
||||
|
options.Add(LayoutHooks.Body.Last, typeof(SyncfusionScriptComponent)); |
||||
|
}); |
||||
|
|
||||
|
ConfigureUrls(configuration); |
||||
|
ConfigureBundles(); |
||||
|
ConfigureAuthentication(context, configuration); |
||||
|
ConfigureAutoMapper(); |
||||
|
ConfigureVirtualFileSystem(hostingEnvironment); |
||||
|
ConfigureLocalizationServices(); |
||||
|
ConfigureNavigationServices(); |
||||
|
ConfigureAutoApiControllers(); |
||||
|
ConfigureSwaggerServices(context.Services); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
After injecting the Syncfusion style and script into our application, our configurations have been completed. We can try with a simple component to see if it works as we expected. |
||||
|
|
||||
|
* Let's try with the [Calendar](https://www.syncfusion.com/aspnet-core-ui-controls/calendar) component. Open your **Index.cshtml** file and update with the below content: |
||||
|
|
||||
|
```cshtml |
||||
|
@page |
||||
|
@using Microsoft.AspNetCore.Mvc.Localization |
||||
|
@using SyncfusionComponentsDemo.Localization |
||||
|
@using Volo.Abp.Users |
||||
|
@model SyncfusionComponentsDemo.Web.Pages.IndexModel |
||||
|
|
||||
|
@section styles { |
||||
|
<abp-style src="/Pages/Index.css" /> |
||||
|
} |
||||
|
|
||||
|
@section scripts { |
||||
|
<abp-script src="/Pages/Index.js" /> |
||||
|
} |
||||
|
|
||||
|
<div class="container"> |
||||
|
<h2>Syncfusion - Calendar Component</h2> |
||||
|
<ejs-calendar id="calendar"></ejs-calendar> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
* Then when we run the application, we need to see the relevant calendar component as below. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### Conclusion |
||||
|
|
||||
|
In this article, we've explained how to integrate the **Syncfusion Components** into our applications. After following this article, you can use the Syncfusion components in your application. |
||||
|
|
||||
|
Thanks for reading the article, I hope you've found it useful :) |
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 53 KiB |
@ -0,0 +1,73 @@ |
|||||
|
# 入门教程 |
||||
|
|
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
||||
|
"DB": ["EF", "Mongo"], |
||||
|
"Tiered": ["Yes", "No"] |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> 本文档假设你更喜欢使用 **{{ UI_Value }}** 作为 UI 框架, 使用 **{{ DB_Value }}** 作为数据库提供程序. 对于其他选项, 请更改本文档顶部的首选项. |
||||
|
|
||||
|
## 创建新项目 |
||||
|
|
||||
|
我们将使用 ABP CLI 创建一个新的 ABP 项目. |
||||
|
|
||||
|
> 或者, 你可以使用[ABP Framework 网站](https://abp.io/get-started)页面上的选项轻松的 **创建并下载** 项目. |
||||
|
|
||||
|
使用 ABP CLI 的 `new` 命令创建一个新项目: |
||||
|
|
||||
|
````shell |
||||
|
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor{{else if UI == "BlazorServer"}} -u blazor-server{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC" || UI == "BlazorServer"}} --tiered{{else}} --separate-identity-server{{end}}{{end}} |
||||
|
```` |
||||
|
|
||||
|
*你可以使用不同级别的命名空间, 例如: BookStore、Acme.BookStore或 Acme.Retail.BookStore.* |
||||
|
|
||||
|
{{ if Tiered == "Yes" }} |
||||
|
|
||||
|
{{ if UI == "MVC" || UI == "BlazorServer" }} |
||||
|
|
||||
|
* `--tified` 参数用于创建认证服务器、 UI 和 API 实际分隔的 N-层解决方案. |
||||
|
|
||||
|
{{ else }} |
||||
|
|
||||
|
* `--separate-identity-server` 参数用于将Identity Server应用程序与API主机应用程序分隔开. 如果未指定, 则服务器上将只有一个端点. |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
> [ABP CLI 文档](./CLI.md) 涵盖了所有可用的命令和选项. |
||||
|
|
||||
|
## 移动端开发 |
||||
|
|
||||
|
如果你想要在你的解决方案中包含 [React Native](https://reactnative.dev/) 项目, 将 `-m react-native` (or `--mobile react-native`) 参数添加到项目创建命令. 这是一个基础的 React Native 启动模板, 用于开发基于你的 ABP 后端的移动应用程序. |
||||
|
|
||||
|
请参阅 [React Native 入门](Getting-Started-React-Native.md) 文档, 了解如何配置和运行 React Native 应用程序. |
||||
|
|
||||
|
### 解决方案结构 |
||||
|
|
||||
|
该解决方案具有分层结构 (基于 [域驱动设计](Domain-Driven-Design.md)), 并包含单元 & 集成测试项目. 请参阅 [应用程序模板文档](Startup-Templates/Application.md) 以详细了解解决方案结构. |
||||
|
|
||||
|
{{ if DB == "Mongo" }} |
||||
|
|
||||
|
#### MongoDB 事务 |
||||
|
|
||||
|
[启动模板](Startup-templates/Index.md) 默认在`.MongoDB`项目中**禁用**事务. 如果你的MongoDB服务器支持事务, 你可以在*YourProjectMongoDbModule*类中的`ConfigureServices`方法开启它: |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpUnitOfWorkDefaultOptions>(options => |
||||
|
{ |
||||
|
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto; |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
> 或者你可以删除该代码, 因为 `Auto` 已经是默认行为. |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
## 下一步 |
||||
|
|
||||
|
* [运行解决方案](Getting-Started-Running-Solution.md) |
||||
@ -0,0 +1,199 @@ |
|||||
|
# 入门教程 |
||||
|
|
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
||||
|
"DB": ["EF", "Mongo"], |
||||
|
"Tiered": ["Yes", "No"] |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> 本文档假设你更喜欢使用 **{{ UI_Value }}** 作为 UI 框架, 使用 **{{ DB_Value }}** 作为数据库提供程序. 对于其他选项, 请更改本文档顶部的首选项. |
||||
|
|
||||
|
## 创建数据库 |
||||
|
|
||||
|
### 连接字符串 |
||||
|
|
||||
|
检查在 {{if Tiered == "Yes"}}`.IdentityServer` 和`.HttpApi.Host` 项目{{else}}{{if UI=="MVC"}}`.Web` 项目{{else if UI=="BlazorServer"}}`.Blazor` 项目{{else}}`.HttpApi.Host` 项目{{end}}{{end}} 中 `appsettings.json` 文件里的**连接字符串**. |
||||
|
|
||||
|
{{ if DB == "EF" }} |
||||
|
|
||||
|
````json |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True" |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> **关于连接字符串和数据库管理系统** |
||||
|
> |
||||
|
> 解决方案配置为默认使用 **Entity Framework Core** 与 **MS SQL Server**. 但是, 如果在执行ABP CLI 的`new`命令时使用了`-dbms`参数来选择其他DBMS (如`-dbms MySQL`), 那么连接字符串可能不同. |
||||
|
> |
||||
|
> EF Core 支持 [多种](https://docs.microsoft.com/en-us/ef/core/providers/) 据库提供程序, 因此你可以使用任何受支持的DBMS. 你可以需要时候参阅[Entity Framework 集成文档](Entity-Framework-Core.md) 来学习如何[切换到另一个DBMS](Entity-Framework-Core-Other-DBMS.md). |
||||
|
|
||||
|
### 数据库迁移 |
||||
|
|
||||
|
该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 带有 `.DbMigrator` 的控制台程序用于 **应用迁移** 和 **初始化种子数据**. 它在**开发**和**生产**环境中都很有用. |
||||
|
|
||||
|
> `.dbMigator` 项目有自己的 `appsettings.json`. 因此, 如果你更改了之前的连接字符串, 那么也应该更改这个连接字符串. |
||||
|
|
||||
|
### 初次迁移 |
||||
|
|
||||
|
`.dbMigator` 应用程序在首次运行时自动**创建初始迁移**. |
||||
|
|
||||
|
**如果你使用的是 Visual Studio, 你可以跳到 *运行 dbMigrator* 部分.** 但是, 其他 IDE (例如 Rider) 在首次运行时可能会遇到问题, 因为它会添加初始迁移并编译项目. 在这种情况下, 请在 `.dbMigration` 项目的文件夹中打开命令行终端, 然后运行以下命令: |
||||
|
|
||||
|
````bash |
||||
|
dotnet run |
||||
|
```` |
||||
|
|
||||
|
下次, 你可以像往常一样在 IDE 中运行它. |
||||
|
|
||||
|
### 运行迁移 |
||||
|
|
||||
|
右键单击 `.dbMigration` 项目, 然后选择 **设置为启动项目** |
||||
|
|
||||
|
 |
||||
|
|
||||
|
按F5(或Ctrl + F5) 运行应用程序. 它将具有如下所示的输出: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
> 初始的[种子数据](Data-Seeding.md)在数据库中创建了 `admin` 用户(密码为`1q2w3E*`) 用于登录应用程序. 所以, 对于新数据库至少使用 `.DbMigrator` 一次. |
||||
|
|
||||
|
{{ else if DB == "Mongo" }} |
||||
|
|
||||
|
````json |
||||
|
"ConnectionStrings": { |
||||
|
"Default": "mongodb://localhost:27017/BookStore" |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
该解决方案配置为在本地计算机中使用 **MongoDB**, 因此你需要启动并运行 MongoDB 服务器实例, 或者将连接字符串更改为另一台 MongoDB 服务器. |
||||
|
|
||||
|
### 种子初始数据 |
||||
|
|
||||
|
该解决方案带有 `.DbMigrator` 的控制台程序用于 **初始化种子数据**. 它在**开发**和**生产**环境中都很有用. |
||||
|
|
||||
|
> `.dbMigator` 项目有自己的 `appsettings.json`. 因此, 如果你更改了之前的连接字符串, 那么也应该更改这个连接字符串. |
||||
|
|
||||
|
右键单击 `.dbMigration` 项目, 然后选择 **设置为启动项目** |
||||
|
|
||||
|
 |
||||
|
|
||||
|
按F5(或Ctrl + F5) 运行应用程序. 它将具有如下所示的输出: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
> 初始的[种子数据](Data-Seeding.md)在数据库中创建了 `admin` 用户(密码为`1q2w3E*`) 用于登录应用程序. 所以, 对于新数据库至少使用 `.DbMigrator` 一次. |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
## 运行应用程序 |
||||
|
|
||||
|
{{ if UI == "MVC" || UI == "BlazorServer" }} |
||||
|
|
||||
|
{{ if Tiered == "Yes" }} |
||||
|
|
||||
|
> 分层解决方案使用 **Redis** 作为分布式缓存. 确保它已安装并在本地计算机上运行. 如果你使用的是远程 Redis 服务器, 请修改项目的 ` appsettings.json` 文件中的配置. |
||||
|
|
||||
|
1. 确保 `.IdentityServer` 项目是启动项目. 运行此应用程序, 它将在浏览器中打开 **登录** 页面. |
||||
|
|
||||
|
> 在 Visual Studio 中使用 Ctrl+F5(而不是F5) 在不进行调试的情况下运行应用程序. 如果你没有调试目的, 这会更快. |
||||
|
|
||||
|
你可以登录, 但不能在这里进入主应用程序. 这 **只是身份验证服务器**. |
||||
|
|
||||
|
2. 确保 `.httpapi.Host` 项目是启动项目, 然后运行应用将在浏览器中打开 **Swagger UI**. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
这是 Web 应用程序使用的 HTTP API. |
||||
|
|
||||
|
3. 最后, 确保 {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} 项目是启动项目,然后运行应用程序,它将在浏览器中打开 **欢迎** 页面 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
单击 **login** 按钮, 它将重定向到 *身份验证服务器* 以登录到应用程序: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{ else # Tiered != "Yes" }} |
||||
|
|
||||
|
确保 {{if UI=="MVC"}}`.Web`{{else}}`.Blazor`{{end}} 项目是启动项目. 运行应用程序将会在浏览器中打开 **login** 页面: |
||||
|
|
||||
|
> 在 Visual Studio 中使用 Ctrl+F5(而不是F5) 在不进行调试的情况下运行应用程序. 如果你没有调试目的, 这会更快. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{ end # Tiered }} |
||||
|
|
||||
|
{{ else # UI != MVC || BlazorServer }} |
||||
|
|
||||
|
### 运行 HTTP API 主机(服务器端) |
||||
|
|
||||
|
{{ if Tiered == "Yes" }} |
||||
|
|
||||
|
> 分层解决方案使用 Redis 作为分布式缓存. 确保它已安装并在本地计算机上运行. 如果你使用的是远程 Redis 服务器, 请修改项目的 `appsettings.json` 文件中的配置. |
||||
|
|
||||
|
确保 `.IdentityServer` 项目是启动项目. 运行此应用程序, 它将在浏览器中打开 **登录** 页面. |
||||
|
|
||||
|
> 在 Visual Studio 中使用 Ctrl+F5(而不是F5) 在不进行调试的情况下运行应用程序. 如果你没有调试目的, 这会更快. |
||||
|
|
||||
|
你可以登录, 但不能在这里进入主应用程序. 这 **只是身份验证服务器**. |
||||
|
|
||||
|
确保 `.HttpApi.Host` 项目是启动项目, 然后运行应用程序将打开 Swagger UI 的: |
||||
|
|
||||
|
{{ else # Tiered == "No" }} |
||||
|
|
||||
|
确保 `.HttpApi.Host` 项目是启动项目, 然后运行应用程序将打开 Swagger UI 的: |
||||
|
|
||||
|
> 在 Visual Studio 中使用 Ctrl+F5(而不是F5) 在不进行调试的情况下运行应用程序. 如果你没有调试目的, 这会更快. |
||||
|
|
||||
|
{{ end # Tiered }} |
||||
|
|
||||
|
 |
||||
|
|
||||
|
你可以在这里查看应用程序的API并测试它们. 获取Swagger UI的[更多信息](https://swagger.io/tools/swagger-ui/). |
||||
|
|
||||
|
{{ end # UI }} |
||||
|
|
||||
|
{{ if UI == "Blazor" }} |
||||
|
|
||||
|
### 运行 Blazor 应用程序 (客户端) |
||||
|
|
||||
|
确保 `.Blazor` 项目是启动项目并运行应用程序. |
||||
|
|
||||
|
> 在 Visual Studio 中使用 Ctrl+F5(而不是F5) 在不进行调试的情况下运行应用程序. 如果你没有调试目的, 这会更快. |
||||
|
|
||||
|
应用程序启动后, 单击页头上的 **Login** 链接, 你将重定向到身份验证服务器以输入用户名和密码: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{ else if UI == "NG" }} |
||||
|
|
||||
|
### 运行 Angular 应用程序 (客户端) |
||||
|
|
||||
|
转到 `Angular` 文件夹, 打开命令行终端, 键入 `yarn` 命令(我们建议使用 [yarn](https://yarnpkg.com/) 软件包管理器, 而 `npm install` 也可以使用) |
||||
|
|
||||
|
```bash |
||||
|
yarn |
||||
|
``` |
||||
|
|
||||
|
当所有node模块加载完毕后, 执行 `yarn start` (或 `npm start`) 命令: |
||||
|
|
||||
|
```bash |
||||
|
yarn start |
||||
|
``` |
||||
|
|
||||
|
初次构建可能需要更长的时间. 完成后, 它会在默认浏览器中使用 [localhost:4200](http://localhost:4200/) 地址打开 Angular UI. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
输入用户名 **admin** 和 密码 **1q2w3E*** 登录应用程序. 应用程序已启动并正在运行. 你可以根据此启动模板开始开发应用程序. |
||||
|
|
||||
|
## 另请参见 |
||||
|
|
||||
|
* [Web 应用程序开发教程](Tutorials/Part-1.md) |
||||
|
* [应用程序启动模板](Startup-Templates/Application.md) |
||||
@ -0,0 +1,53 @@ |
|||||
|
# 入门教程 |
||||
|
|
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
||||
|
"DB": ["EF", "Mongo"], |
||||
|
"Tiered": ["Yes", "No"] |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> 本文档假设你更喜欢使用 **{{ UI_Value }}** 作为 UI 框架, 使用 **{{ DB_Value }}** 作为数据库提供程序. 对于其他选项, 请更改本文档顶部的首选项. |
||||
|
|
||||
|
## 设置你的开发环境 |
||||
|
|
||||
|
第一件事! 在创建项目之前, 让我们先设置你的开发环境. |
||||
|
|
||||
|
### 先决条件 |
||||
|
|
||||
|
开发计算机上应安装以下工具: |
||||
|
|
||||
|
* 一个集成开发环境 (比如: [Visual Studio](https://visualstudio.microsoft.com/vs/)) 它需要支持 [.NET 6.0+](https://dotnet.microsoft.com/download/dotnet) 的开发. |
||||
|
{{ if UI != "Blazor" }} |
||||
|
* [Node v12 或 v14](https://nodejs.org/) |
||||
|
* [Yarn v1.20+ (不是v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[1](#f-yarn)</sup> 或 npm v6+ (已跟随Node一起安装) |
||||
|
{{ end }} |
||||
|
{{ if Tiered == "Yes" }} |
||||
|
* [Redis](https://redis.io/) (启动解决方案使用 Redis 作为 [分布式缓存](Caching.md)). |
||||
|
{{ end }} |
||||
|
|
||||
|
{{ if UI != "Blazor" }} |
||||
|
|
||||
|
<sup id="f-yarn"><b>1</b></sup> _Yarn v2 工作方式不同, 不被支持._ <sup>[↩](#a-yarn)</sup> |
||||
|
|
||||
|
{{ end }} |
||||
|
|
||||
|
### 安装 ABP CLI |
||||
|
|
||||
|
[ABP CLI](./CLI.md) 是一个命令行界面, 用于自动执行基于 ABP 的解决方案的一些常见任务. 首先, 你需要使用以下命令安装 ABP CLI: |
||||
|
|
||||
|
````shell |
||||
|
dotnet tool install -g Volo.Abp.Cli |
||||
|
```` |
||||
|
|
||||
|
如果已安装, 则可以使用以下命令对其进行更新: |
||||
|
|
||||
|
````shell |
||||
|
dotnet tool update -g Volo.Abp.Cli |
||||
|
```` |
||||
|
|
||||
|
## 下一步 |
||||
|
|
||||
|
* [创建新的解决方案](Getting-Started-Create-Solution.md) |
||||
@ -1,374 +1,20 @@ |
|||||
## 入门 |
# 入门教程 |
||||
|
|
||||
````json |
````json |
||||
//[doc-params] |
//[doc-params] |
||||
{ |
{ |
||||
"UI": ["MVC","NG"], |
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
||||
"DB": ["EF", "Mongo"], |
"DB": ["EF", "Mongo"], |
||||
"Tiered": ["Yes", "No"] |
"Tiered": ["Yes", "No"] |
||||
} |
} |
||||
```` |
```` |
||||
|
|
||||
本教程介绍了如何创建一个新的{{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}}. 配置并运行它. |
> 本文档假设你更喜欢使用 **{{ UI_Value }}** 作为 UI 框架, 使用 **{{ DB_Value }}** 作为数据库提供程序. 对于其他选项, 请更改本文档顶部的首选项. |
||||
|
|
||||
## 设置你的开发环境 |
## 内容 |
||||
|
|
||||
创建第一个项目之前,需要正确的设置你的开发环境. |
本教程介绍如何使用 ABP 框架 **创建和运行** 新的 Web 应用程序. 请按照以下步骤操作; |
||||
|
|
||||
### 预先要求 |
1. [设置你的开发环境](Getting-Started-Setup-Environment.md) |
||||
|
2. [创建新的解决方案](Getting-Started-Create-Solution.md) |
||||
你需要安装以下工具: |
3. [运行解决方案](Getting-Started-Running-Solution.md) |
||||
|
|
||||
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).<sup id="a-editor">[1](#f-editor)</sup> |
|
||||
* [.NET Core 3.1+](https://www.microsoft.com/net/download/dotnet-core/) |
|
||||
|
|
||||
* [Node v12 或 v14](https://nodejs.org/en/) |
|
||||
* [Yarn v1.19+](https://classic.yarnpkg.com/) |
|
||||
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> 或 npm v6+ (与Node一起安装) |
|
||||
{{ if Tiered == "Yes" }} |
|
||||
|
|
||||
* [Redis](https://redis.io/): 入门解决方案将Redis用作[分布式缓存](Caching.md). 因此你需要安装并运行Redis. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
<sup id="f-editor"><b>1</b></sup> _只要支持.NET Core和ASP.NET Core,就可以使用其他编辑器代替Visual Studio._ <sup>[↩](#a-editor)</sup> |
|
||||
|
|
||||
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 的工作方式不同,不受支持._ <sup>[↩](#a-yarn)</sup> |
|
||||
|
|
||||
### 安装ABP CLI |
|
||||
|
|
||||
[ABP CLI](./CLI.md)是一个命令行页面,用于自动执行一些基于ABP的应用程序的常见任务. |
|
||||
|
|
||||
> ABP CLI是ABP框架一个免费开源的工具. |
|
||||
|
|
||||
你需要使用以下命令安排ABP CLI: |
|
||||
|
|
||||
````shell |
|
||||
dotnet tool install -g Volo.Abp.Cli |
|
||||
```` |
|
||||
|
|
||||
如果你已经安装,你可以使用以下命令更新到最新版本: |
|
||||
|
|
||||
````shell |
|
||||
dotnet tool update -g Volo.Abp.Cli |
|
||||
```` |
|
||||
|
|
||||
## 创建新项目 |
|
||||
|
|
||||
> 本文假设你使用 **{{ UI_Value }}** 做为UI框架 **{{ DB_Value }}** 做为数据库提供程序,对于其它选项,你可以更改文档顶部的首选项. |
|
||||
|
|
||||
### 使用ABP CLI创建一个新项目 |
|
||||
|
|
||||
使用ABP CLI的 `new` 命令创建新项目: |
|
||||
|
|
||||
````shell |
|
||||
abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} --mobile react-native |
|
||||
```` |
|
||||
|
|
||||
* 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它或从`abp new`命令中删除`--mobile react-native`选项, 以使其完全不包含在解决方案中. |
|
||||
|
|
||||
{{ if UI == "NG" }} |
|
||||
|
|
||||
* `-u` 指定UI框架, 本例中是 `angular`. |
|
||||
|
|
||||
{{ if Tiered == "Yes" }} |
|
||||
|
|
||||
* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,则服务器上将只有一个端点. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
{{ if DB == "Mongo" }} |
|
||||
|
|
||||
* `-d` 指定数据库提供程序, 本例中是 `mongodb`. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
{{ if Tiered == "Yes" && UI != "NG" }} |
|
||||
|
|
||||
* `--tiered` 参数用于创建n层解决方案,其中身份验证服务器层,UI层和API层在物理上是分离的. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. |
|
||||
|
|
||||
#### ABP CLI 命令 & 选项 |
|
||||
|
|
||||
[ABP CLI文档](./CLI.md)涵盖了ABP CLI的所有可用命令和选项. 本文档使用[应用程序启动模板](Startup-Templates/Application.md)创建新的Web应用程序. 有关其他模板,请参见[ABP启动模板](Startup-Templates/Index.md)文档. |
|
||||
|
|
||||
> 或者,您可以从[ABP Framework网站](https://abp.io/get-started)中选择"直接下载"选项卡创建新的解决方案. |
|
||||
|
|
||||
## 解决方案结构 |
|
||||
|
|
||||
{{ if UI == "MVC" }} |
|
||||
|
|
||||
创建项目后你会有以下解决方案目录和文件: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构: |
|
||||
|
|
||||
{{if DB == "Mongo"}} |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{else}} |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{end}} |
|
||||
|
|
||||
{{ else if UI == "NG" }} |
|
||||
|
|
||||
在创建的解决方案中有三个文件夹: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
* `angular` 文件夹包含Angular UI应用程序. |
|
||||
* `aspnet-core` 文件夹包含后端应用程序. |
|
||||
* `react-native` 文件夹包含React Native UI 应用程序. |
|
||||
|
|
||||
打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
> ###### 关于解决方案中的项目 |
|
||||
> |
|
||||
> 根据你的**UI**,**数据库**和其他选项,你的解决方案的结构可能略有不同. |
|
||||
|
|
||||
该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目. |
|
||||
|
|
||||
{{ if DB == "EF" }} |
|
||||
|
|
||||
集成测试项目已配置为可与 **EF Core** & **SQLite 内存** database同时使用. |
|
||||
|
|
||||
{{ else if DB == "Mongo" }} |
|
||||
|
|
||||
集成测试项目已配置为每个测试创建的内存中的**MongoDB**数据库(使用的[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)库). |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构. |
|
||||
|
|
||||
{{ if DB == "Mongo" }} |
|
||||
|
|
||||
> [启动模板](Startup-templates/Index.md)默认在 `.MongoDB` 项目中**禁用**了工作单元事务. 如果你的MongoDB服务器支持事务,你可以手动启用工作单元的事务: |
|
||||
|
|
||||
```csharp |
|
||||
Configure<AbpUnitOfWorkDefaultOptions>(options => |
|
||||
{ |
|
||||
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled; |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
## 创建数据库 |
|
||||
|
|
||||
### 连接字符串 |
|
||||
|
|
||||
检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: |
|
||||
|
|
||||
{{ if DB == "EF" }} |
|
||||
|
|
||||
````json |
|
||||
"ConnectionStrings": { |
|
||||
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" |
|
||||
} |
|
||||
```` |
|
||||
|
|
||||
该解决方案配置为**Entity Framework Core**与**MS SQL Server**一起使用. EF Core支持[各种](https://docs.microsoft.com/en-us/ef/core/providers/)数据库提供程序,因此你可以使用任何受支持的DBMS. 请参阅[Entity Framework集成文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)了解如何切换到另一个DBMS. |
|
||||
|
|
||||
### 数据库连接字符串 |
|
||||
|
|
||||
查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**: |
|
||||
|
|
||||
````json |
|
||||
{ |
|
||||
"ConnectionStrings": { |
|
||||
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" |
|
||||
} |
|
||||
} |
|
||||
```` |
|
||||
|
|
||||
解决方案使用 **Entity Framework Core** 和 **MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串. |
|
||||
|
|
||||
### 应用迁移 |
|
||||
|
|
||||
该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 你需要应用迁移来创建数据库,有两种方法迁移数据库. |
|
||||
|
|
||||
#### 使用DbMigrator应用程序应用迁移 |
|
||||
|
|
||||
该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用. |
|
||||
|
|
||||
> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串. |
|
||||
|
|
||||
右键单击`.DbMigrator`项目并选择 **设置为启动项目**: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
#### 使用EF Core Update-Database命令 |
|
||||
|
|
||||
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: |
|
||||
|
|
||||
{{ if UI == "MVC" }} |
|
||||
|
|
||||
右键单击{{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}}项目并选择**设置为启动项目**: |
|
||||
|
|
||||
{{ else if UI != "MVC" }} |
|
||||
|
|
||||
右键单击`.HttpApi.Host`项目并选择**设置为启动项目**: |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
 |
|
||||
|
|
||||
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
这将基于配置的连接字符串创建新数据库. |
|
||||
|
|
||||
> **使用`.DbMigrator`工具是建议的方法**, 因为它能初始化初始数据能够正确运行Web应用程序. |
|
||||
> |
|
||||
> 如果你只是使用 `Update-Database` 命令,你会得到一个空数据库,所以你无法登录到应用程序因为数据库中没有初始管理用户. 不需要种子数据库时,可以在开发期间使用 `Update-Database` 命令. 但是使用 `.DbMigrator` 应用程序会更简单,你始终可以使用它来迁移模式并为数据库添加种子. |
|
||||
|
|
||||
{{ else if DB == "Mongo" }} |
|
||||
|
|
||||
````json |
|
||||
"ConnectionStrings": { |
|
||||
"Default": "mongodb://localhost:27017/BookStore" |
|
||||
} |
|
||||
```` |
|
||||
|
|
||||
该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器. |
|
||||
|
|
||||
### 初始化种子数据 |
|
||||
|
|
||||
该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用. |
|
||||
|
|
||||
> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个. |
|
||||
|
|
||||
右键点击 `.DbMigrator` 并选择 **设置为启动项目**. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
按F5(或Ctrl+F5)启动应用程序,你会看到以下输出: |
|
||||
|
|
||||
 |
|
||||
|
|
||||
> 数据库创建后会初始化[种子数据](Data-Seeding.md), 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
### 运行应用程序 |
|
||||
|
|
||||
{{ if UI == "MVC" }} |
|
||||
|
|
||||
{{ if Tiered == "Yes" }} |
|
||||
|
|
||||
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. |
|
||||
|
|
||||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|
||||
|
|
||||
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. |
|
||||
|
|
||||
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
这里是Web应用程序使用的API应用程序. |
|
||||
|
|
||||
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{ else }} |
|
||||
|
|
||||
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. |
|
||||
|
|
||||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
{{ else if UI != "MVC" }} |
|
||||
|
|
||||
#### 运行HTTP API Host (服务器端) |
|
||||
|
|
||||
{{ if Tiered == "Yes" }} |
|
||||
|
|
||||
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. |
|
||||
|
|
||||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|
||||
|
|
||||
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. |
|
||||
|
|
||||
{{ if Tiered == "No" }} |
|
||||
|
|
||||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
 |
|
||||
|
|
||||
你可以看到应用程序的API并进行测试. 更多信息,请参阅[Swagger UI](https://swagger.io/tools/swagger-ui/). |
|
||||
|
|
||||
> ##### Swagger UI 授权 |
|
||||
> |
|
||||
> 大多数的HTTP API都需要身份验证和授权. 如果你要测试授权API, 请手动进入 `/Account/Login` 页面, 输入用户名: `admin` 和密码: `1q2w3E*` 登录到应用程序. 然后你可以访问授权API. |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
{{ if UI == "NG" }} |
|
||||
#### 运行 Angular 应用程序 (客户端) |
|
||||
|
|
||||
在 `angular` 下打开命令行终端, 输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). |
|
||||
|
|
||||
```bash |
|
||||
yarn |
|
||||
``` |
|
||||
|
|
||||
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: |
|
||||
|
|
||||
```bash |
|
||||
yarn start |
|
||||
``` |
|
||||
|
|
||||
等待 `Angular CLI` 使用 `BrowserSync` 启动 `Webpack` dev-server. |
|
||||
它会负责编译你的 `TypeScript`代码, 并自动重新加载浏览器. |
|
||||
完成后 `Angular Live Development Server` 会监听 localhost:4200. |
|
||||
打开你的浏览器并导航到[localhost:4200](http://localhost:4200/). |
|
||||
|
|
||||
 |
|
||||
|
|
||||
{{ end }} |
|
||||
|
|
||||
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序,应用程序已经启动并执行,你可以基于此启动模板开始开发应用程序. |
|
||||
|
|
||||
#### 移动开发 |
|
||||
|
|
||||
当你创建一个新的应用程序时. 可以添加`-m react-native`选项以在解决方案中包含 `react-native`项目. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序. |
|
||||
|
|
||||
|
|
||||
请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序. |
|
||||
|
|
||||
## 下一步是什么? |
|
||||
|
|
||||
[Web应用程序开发教程](Tutorials/Part-1.md) |
|
||||
@ -1,28 +1,320 @@ |
|||||
# 身份管理模块 |
# 身份管理模块 |
||||
|
|
||||
身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. |
身份模块基于 [Microsoft Identity 库](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/identity) 用于管理角色,用户及其权限. |
||||
|
|
||||
> 参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. |
## 如何安装 |
||||
|
|
||||
## Identity安全日志 |
当你使用 ABP 框架 [创建一个新的解决方案](https://abp.io/get-started) 时,此模块将被预安装(作为 NuGet/NPM 包)。你可以继续用其作为包并轻松地获取更新,也可以将其源代码包含在解决方案中(请参阅 `get-source` [CLI](../CLI.md))以开发自定义模块。 |
||||
|
|
||||
安全日志可以记录账户的一些重要的操作或者改动, 你可以在在一些功能中保存安全日志. |
### 源代码 |
||||
|
|
||||
你可以注入和使用 `IdentitySecurityLogManager` 或 `ISecurityLogManager` 来保存安全日志. 默认它会创建一个安全日志对象并填充常用的值. 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`等等. 当然你可以自定义这些值. |
可以 [在此处](https://github.com/abpframework/abp/tree/dev/modules/identity) 访问源代码。源代码使用 [MIT](https://choosealicense.com/licenses/mit/) 许可, 所以你可以免费使用和自定义它. |
||||
|
|
||||
|
## 用户界面 |
||||
|
|
||||
|
此模块提供了 [Blazor](../UI/Blazor/Overall.md), [Angular](../UI/Angular/Quick-Start.md) 和 [MVC / Razor Pages](../UI/AspNetCore/Overall.md) 的 UI 可选. |
||||
|
|
||||
|
### 菜单项 |
||||
|
|
||||
|
此模块在 *管理* 菜单下添加了一个 *身份管理* 菜单项: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
这个菜单项和相关页面已获得授权. 这意味着当前用户必须拥有相关权限才能使其可见. `admin` 角色 (和拥有此角色的用户, 如 `admin` 用户) 已经拥有这些权限. 如果你想要使其他角色/用户也启用权限, 请打开 *角色* 或 *用户* 页面的 *权限* 对话框, 并检查如下所示的权限: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
请参阅 [授权文档](../Authorization.md) 以了解权限系统. |
||||
|
|
||||
|
### 页面 |
||||
|
|
||||
|
本节介绍此模块提供的主要页面. |
||||
|
|
||||
|
#### 用户 |
||||
|
|
||||
|
此页用于查看用户列表. 您可以创建/编辑和删除用户, 将角色分配给用户. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
一个用户可以有零个或多个角色. 用户从其角色继承权限. 此外, 你可以给用户直接分配权限 (通过点击 *操作* 按钮, 然后选择 *权限*) . |
||||
|
|
||||
|
#### 角色 |
||||
|
|
||||
|
角色用于按分组给用户分配权限. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
角色除了角色名称之外还有2个属性: |
||||
|
|
||||
|
* `Default`: 如果某个角色被标记为 "default", 那么当新用户 (使用 [账户模块](Account.md)) 注册到应用程序时, 其将被作为默认角色分配给新用户. |
||||
|
* `Public`: 用户的公共角色可以被应用程序中的其他用户看到. 这个功能在身份模块中没有用到, 但被提供作为你可能希望在你自己的应用程序中使用的功能. |
||||
|
|
||||
|
## 其他功能 |
||||
|
|
||||
|
本节包括此模块提供的没有 UI 页面的其他功能. |
||||
|
|
||||
|
### 组织单元 |
||||
|
|
||||
|
组织单元 (OU) 可被用于 **按层级对用户和实体进行分组**. |
||||
|
|
||||
|
#### 组织单元实体 |
||||
|
|
||||
|
组织单元由 **OrganizationUnit** 实体表示. 它的基本属性是: |
||||
|
|
||||
|
- **TenantId**: 组织单元的租户Id. 对于宿主可以是 null. |
||||
|
- **ParentId**: 父级组织单元的Id. 如果这是一个根级组织单元,它可以是 null. |
||||
|
- **Code**: 对于租户唯一的层级字符串编码. |
||||
|
- **DisplayName**: 组织单元的显示名称. |
||||
|
|
||||
|
#### 组织树 |
||||
|
|
||||
|
由于组织单元可以有父级, 因此租户的所有组织单元是一个 **树** 结构. 这个树有一些规则; |
||||
|
|
||||
|
- 可以有多个根级 (`ParentId` 是 `null` 的) . |
||||
|
- 一个组织单元的第一级子项数量有限制 (因为下面解释的固定的组织单元编码单位长度) . |
||||
|
|
||||
|
#### 组织单元编码 |
||||
|
|
||||
|
组织单元编码是通过 `OrganizationUnitManager` 服务自动生成和维护的. 它是一个字符串, 像这样: |
||||
|
|
||||
|
"**00001.00042.00005**" |
||||
|
|
||||
|
通常用这种编码可以轻易地 (递归) 查询出数据库中组织单元的所有子项. 这个编码有一些规则 (当你使用 `OrganizationUnitManager` 时自动被应用的) : |
||||
|
|
||||
|
- 对 [租户](../Multi-Tenancy.md) 是 **唯一的**. |
||||
|
- 同一组织单元的全部子项都具有 **以父级组织单元编码开头的** 编码. |
||||
|
- 如示例中所示, 它是固定长度的且是基于树中组织单元的级别的. |
||||
|
- 尽管组织单元编码是唯一的, 但如果你移动了相关的组织单元, 它也可以被更改. |
||||
|
|
||||
|
请注意, 你必须根据Id引用一个组织单元, 而不是编码, 因为编码后续是可以被更改的. |
||||
|
|
||||
|
#### 组织单元管理 |
||||
|
|
||||
|
`OrganizationUnitManager` 类可以被 [注入](../Dependency-Injection.md) 并用来管理组织单元. 常见用例如下: |
||||
|
|
||||
|
- 创建, 更新和删除组织单元. |
||||
|
- 在组织单元树中移动一个组织单元. |
||||
|
- 获取关于组织单元树及其项的信息. |
||||
|
|
||||
|
### 身份安全日志 |
||||
|
|
||||
|
安全日志系统可以记录账户的一些重要的操作或者改动 (例如 *登录* 和 *更改密码*) . 如果需要, 你也可以保存安全日志. |
||||
|
|
||||
|
你可以注入和使用 `IdentitySecurityLogManager` 或 `ISecurityLogManager` 来写入安全日志. 默认它会创建一个日志对象并填充常用的值, 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant` 等等. 当然你可以覆盖这些值. |
||||
|
|
||||
```cs |
```cs |
||||
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() |
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() |
||||
{ |
{ |
||||
Identity = "IdentityServer"; |
Identity = "IdentityServer"; |
||||
Action = "ChangePassword"; |
Action = "ChangePassword"; |
||||
}); |
}); |
||||
``` |
``` |
||||
|
|
||||
通过配置 `AbpSecurityLogOptions` 来提供应用程序的名称或者禁用安全日志功能. 默认是**启用**状态. |
通过配置 `AbpSecurityLogOptions` 来为日志提供应用程序的名称 (如果你有多个应用程序并且想要在日志中区分应用程序) 或者禁用安全日志功能. |
||||
|
|
||||
```cs |
```cs |
||||
Configure<AbpSecurityLogOptions>(options => |
Configure<AbpSecurityLogOptions>(options => |
||||
{ |
{ |
||||
options.ApplicationName = "AbpSecurityTest"; |
options.ApplicationName = "AbpSecurityTest"; |
||||
}); |
}); |
||||
``` |
``` |
||||
|
## 选项 |
||||
|
|
||||
|
`IdentityOptions` 是由 Microsoft [Identity 库](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/identity) 提供的标准 [选项类](../Options.md) . 所以, 你可以在 [模块](../Module-Development-Basics.md) 类的 `ConfigureServices` 方法中设置这些选项. |
||||
|
|
||||
|
**例如: 设置所需的密码最小长度** |
||||
|
|
||||
|
````csharp |
||||
|
Configure<IdentityOptions>(options => |
||||
|
{ |
||||
|
options.Password.RequiredLength = 5; |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
ABP 允许你在运行时通过使用 [设置系统](../Settings.md) 更进一步地更改这些选项. 你可以 [注入](../Dependency-Injection.md) `ISettingManager` 并使用 `Set...` 方法中的一种来更改用户, 租户或全局所有用户的选项值. |
||||
|
|
||||
|
**例如: 更改当前租户所需的密码最小长度** |
||||
|
|
||||
|
````csharp |
||||
|
public class MyService : ITransientDependency |
||||
|
{ |
||||
|
private readonly ISettingManager _settingManager; |
||||
|
|
||||
|
public MyService(ISettingManager settingManager) |
||||
|
{ |
||||
|
_settingManager = settingManager; |
||||
|
} |
||||
|
|
||||
|
public async Task ChangeMinPasswordLength(int minLength) |
||||
|
{ |
||||
|
await _settingManager.SetForCurrentTenantAsync( |
||||
|
IdentitySettingNames.Password.RequiredLength, |
||||
|
minLength.ToString() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`IdentitySettingNames` 类 (在 `Volo.Abp.Identity.Settings` 命名空间中) 为设置名称定义了一些常量. |
||||
|
|
||||
|
## 分布式事件 |
||||
|
|
||||
|
此模块定义了如下 ETOs (事件传输对象) 以允许你去订阅模块中实体的改动. |
||||
|
|
||||
|
* `UserEto` 在 `IdentityUser` 实体更改完成时发布. |
||||
|
* `IdentityRoleEto` 在 `IdentityRole` 实体更改完成时发布. |
||||
|
* `IdentityClaimTypeEto` 在 `IdentityClaimType` 实体更改完成时发布. |
||||
|
* `OrganizationUnitEto` 在 `OrganizationUnit` 实体更改完成时发布. |
||||
|
|
||||
|
**例如: 当一个新用户被创建时接收通知** |
||||
|
|
||||
|
````csharp |
||||
|
public class MyHandler : |
||||
|
IDistributedEventHandler<EntityCreatedEto<UserEto>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
public async Task HandleEventAsync(EntityCreatedEto<UserEto> eventData) |
||||
|
{ |
||||
|
UserEto user = eventData.Entity; |
||||
|
// TODO: ... |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`UserEto` 和 `IdentityRoleEto` 默认自动发布事件. 你应该自己配置其他的 Eto. 请参阅 [分布式事件总线文档](../Distributed-Event-Bus.md) 了解预定义事件的详细信息. |
||||
|
|
||||
|
> 订阅分布式事件对于分布式场景 (如微服务架构) 特别有用. 如果你正在构建单体式应用程序, 或者在运行了身份模块的同一进程内监听事件, 那么订阅 [本地事件](../Local-Event-Bus.md) 更高效, 更简单. |
||||
|
|
||||
|
## 内部构件 |
||||
|
|
||||
|
本节包括模块的一些内部详细信息, 您不是那么需要, 但在一些情况下可能需要使用. |
||||
|
|
||||
|
### 领域层 |
||||
|
|
||||
|
#### 聚合 |
||||
|
|
||||
|
##### 用户 |
||||
|
|
||||
|
用户通常是一个登录并使用应用程序的人. |
||||
|
|
||||
|
* `IdentityUser` (聚合根): 表示系统中的一个用户. |
||||
|
* `IdentityUserRole` (集合): 分配给用户的角色. |
||||
|
* `IdentityUserClaim` (集合): 用户的自定义声明. |
||||
|
* `IdentityUserLogin` (集合): 用户的外部登录. |
||||
|
* `IdentityUserToken` (集合): 用户的令牌 (用于 Microsoft Identity 服务). |
||||
|
|
||||
|
##### 角色 |
||||
|
|
||||
|
角色通常是分配给用户的一组权限. |
||||
|
|
||||
|
* `IdentityRole` (聚合根): 表示系统中的角色. |
||||
|
* `IdentityRoleClaim` (集合): 角色的自定义声明. |
||||
|
|
||||
|
##### 声明类型 |
||||
|
|
||||
|
声明类型是可以被分配给系统中其他实体 (如角色和用户) 的自定义声明的定义. |
||||
|
|
||||
|
* `IdentityClaimType` (聚合根): 表示声明类型的定义. 它包括一些用于定义声明类型和验证规则的属性 (例如, 必须的, 正则表达式, 描述, 值类型) . |
||||
|
|
||||
|
##### 身份安全日志 |
||||
|
|
||||
|
`IdentitySecurityLog` 对象表示系统中与授权相关的操作 (如 *登录*) . |
||||
|
|
||||
|
* `IdentitySecurityLog` (聚合根): 表示系统中的安全日志. |
||||
|
|
||||
|
##### 组织单元 |
||||
|
|
||||
|
组织单元是一个有层级结构的实体. |
||||
|
|
||||
|
* ```OrganizationUnit``` (聚合根): 表示系统中的组织单元. |
||||
|
* ```Roles``` (集合): 组织单元的角色. |
||||
|
|
||||
|
#### 仓储 |
||||
|
|
||||
|
此模块定义了以下自定义仓储: |
||||
|
|
||||
|
* `IIdentityUserRepository` |
||||
|
* `IIdentityRoleRepository` |
||||
|
* `IIdentityClaimTypeRepository` |
||||
|
* ```IIdentitySecurityLogRepository``` |
||||
|
* ```IOrganizationUnitRepository``` |
||||
|
|
||||
|
#### 领域服务 |
||||
|
|
||||
|
##### 用户管理 |
||||
|
|
||||
|
`IdentityUserManager` 常用于管理用户, 他们的权限, 声明, 密码, 电子邮件等等. 它派生自 Microsoft Identity 的 `UserManager<T>` 类, 其中 `T` 是 `IdentityUser`. |
||||
|
|
||||
|
##### 角色管理 |
||||
|
|
||||
|
`IdentityRoleManager` 常用于管理角色和他们的声明. 它派生自 Microsoft Identity 的 `RoleManager<T>` 类, 其中 `T` 是 `IdentityRole`. |
||||
|
|
||||
|
##### 声明类型管理 |
||||
|
|
||||
|
`IdenityClaimTypeManager` 常用于对聚合根 `IdentityClaimType` 执行某些操作. |
||||
|
|
||||
|
##### 组织单元管理 |
||||
|
|
||||
|
```OrganizationUnitManager``` 常用于对聚合根 `OrganizationUnit` 执行某些操作. |
||||
|
|
||||
|
##### 安全日志管理 |
||||
|
|
||||
|
```IdentitySecurityLogManager``` 常用于保存安全日志. |
||||
|
|
||||
|
### 服务层 |
||||
|
|
||||
|
#### 应用服务 |
||||
|
|
||||
|
* `IdentityUserAppService` (实现 `IIdentityUserAppService`): 实现了用户管理 UI 的用例. |
||||
|
* `IdentityRoleAppService` (实现 `IIdentityRoleAppService`): 实现了角色管理 UI 的用例. |
||||
|
* `IdentityClaimTypeAppService` (实现 `IIdentityClaimTypeAppService`): 实现了声明类型管理 UI 的用例. |
||||
|
* `IdentitySettingsAppService` (实现 `IIdentitySettingsAppService`): 用于获取和更新身份模块的设置. |
||||
|
* `IdentityUserLookupAppService` (实现 `IIdentityUserLookupAppService`): 用于根据 `id` 或 `userName` 获取用户信息. 它旨在由ABP内部使用. |
||||
|
* `ProfileAppService` (实现 `IProfileAppService`): 用于更改用户的简介和密码. |
||||
|
* ```IdentitySecurityLogAppService``` (实现 ```IIdentitySecurityLogAppService```): 实现了安全日志 UI 的用例. |
||||
|
* ```OrganizationUnitAppService``` (实现 ```OrganizationUnitAppService```): 实现了组织单元管理 UI 的用例. |
||||
|
|
||||
|
### 数据库提供程序 |
||||
|
|
||||
|
此模块为数据库提供 [Entity Framework Core](../Entity-Framework-Core.md) 和 [MongoDB](../MongoDB.md) 两种选择. |
||||
|
|
||||
|
#### EF Core |
||||
|
|
||||
|
NuGet 包 [Volo.Abp.Identity.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore) 实现了 EF Core 的集成. |
||||
|
|
||||
|
##### 数据库表 |
||||
|
|
||||
|
* **AbpRoles** |
||||
|
* AbpRoleClaims |
||||
|
* **AbpUsers** |
||||
|
* AbpUserClaims |
||||
|
* AbpUserLogins |
||||
|
* AbpUserRoles |
||||
|
* AbpUserTokens |
||||
|
* **AbpClaimTypes** |
||||
|
* **AbpOrganizationUnits** |
||||
|
* AbpOrganizationUnitRoles |
||||
|
* AbpUserOrganizationUnits |
||||
|
* **AbpSecurityLogs** |
||||
|
|
||||
|
#### MongoDB |
||||
|
|
||||
|
NuGet 包 [Volo.Abp.Identity.MongoDB](https://www.nuget.org/packages/Volo.Abp.Identity.MongoDB) 实现了 MongoDB 的集成. |
||||
|
|
||||
|
##### 数据库集合 |
||||
|
|
||||
|
* **AbpRoles** |
||||
|
* **AbpUsers** |
||||
|
* **AbpClaimTypes** |
||||
|
* **AbpOrganizationUnits** |
||||
|
* **AbpSecurityLogs** |
||||
|
|
||||
|
#### 常用数据库属性 |
||||
|
|
||||
|
你可以设置 `AbpIdentityDbProperties` 中的以下属性来更改数据库选项: |
||||
|
|
||||
|
* `DbTablePrefix` (`Abp` 作为默认值) 是表/集合名称的前缀. |
||||
|
* `DbSchema` (`null` 作为默认值) 是数据库架构. |
||||
|
* `ConnectionStringName` (`AbpIdentity` 作为默认值) 是此模块的 [连接字符串](../Connection-Strings.md) 名称. |
||||
|
|
||||
|
它们是静态属性. 你需要在开始运行应用程序前设置它们 (通常在 `Program.cs` 中). |
||||
|
|||||
@ -1 +1,173 @@ |
|||||
TODO... |
# 身份服务器模块 |
||||
|
|
||||
|
身份服务器模块提供了一个 [IdentityServer](https://github.com/IdentityServer/IdentityServer4) (IDS) 的完全集成, 该框架提供高级身份验证功能, 如单点登录和API访问控制.此模块将客户端,资源以及其他 IDS 相关的对象保存到数据库中. |
||||
|
|
||||
|
## 如何安装 |
||||
|
|
||||
|
当你使用 ABP 框架 [创建一个新的解决方案](https://abp.io/get-started) 时, 此模块将被预安装(作为 NuGet/NPM 包).你可以继续用其作为包并轻松地获取更新, 也可以将其源代码包含在解决方案中(请参阅 `get-source` [CLI](../CLI.md))以开发自定义模块. |
||||
|
|
||||
|
### 源代码 |
||||
|
|
||||
|
可以 [在此处](https://github.com/abpframework/abp/tree/dev/modules/identityserver) 访问源代码.源代码使用 [MIT](https://choosealicense.com/licenses/mit/) 许可, 所以你可以免费使用和自定义它. |
||||
|
|
||||
|
## 用户界面 |
||||
|
|
||||
|
此模块使用了领域逻辑和数据库集成, 但没有提供任何 UI.如果你需要动态添加客户端和资源, 管理 UI 是非常有用的.在这种情况下, 你可以自己构建管理 UI, 或者考虑购买为此模块提供了管理 UI 的 [ABP 商业版](https://commercial.abp.io/). |
||||
|
|
||||
|
## 与其他模块的关系 |
||||
|
|
||||
|
此模块基于 [身份模块](Identity.md) 并且[账户模块](Account.md) 有一个 [集成包](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer). |
||||
|
|
||||
|
## 选项 |
||||
|
|
||||
|
### AbpIdentityServerBuilderOptions |
||||
|
|
||||
|
`AbpIdentityServerBuilderOptions` 在你的身份服务器 [模块](https://docs.abp.io/zh-Hans/abp/latest/Module-Development-Basics) 中的 `PreConfigureServices` 方法中配置.例如: |
||||
|
|
||||
|
````csharp |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<AbpIdentityServerBuilderOptions>(builder => |
||||
|
{ |
||||
|
//Set options here... |
||||
|
}); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`AbpIdentityServerBuilderOptions` 属性: |
||||
|
|
||||
|
* `UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap` (默认值:true):更新 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap` 使其与身份服务器声明兼容. |
||||
|
* `UpdateAbpClaimTypes` (默认值:true):更新 `AbpClaimTypes` 与身份服务器声明兼容. |
||||
|
* `IntegrateToAspNetIdentity` (默认值:true):集成到 ASP.NET Identity. |
||||
|
* `AddDeveloperSigningCredential` (默认值:true):设置为 false 禁止调用 IIdentityServerBuilder 中的 `AddDeveloperSigningCredential()`. |
||||
|
|
||||
|
`IIdentityServerBuilder` 可以在你的身份服务器 [模块](https://docs.abp.io/zh-Hans/abp/latest/Module-Development-Basics) 中的 `PreConfigureServices` 方法中配置.例如: |
||||
|
|
||||
|
````csharp |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<IIdentityServerBuilder>(builder => |
||||
|
{ |
||||
|
builder.AddSigningCredential(...); |
||||
|
}); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## 内部结构 |
||||
|
|
||||
|
### 领域层 |
||||
|
|
||||
|
#### 聚合 |
||||
|
|
||||
|
##### API 资源 |
||||
|
|
||||
|
需要 API 资源来允许客户端请求访问令牌. |
||||
|
|
||||
|
* `ApiResource` (聚合根):表示系统中的 API 资源. |
||||
|
* `ApiSecret` (集合):API 资源的密钥. |
||||
|
* `ApiScope` (集合):API 资源的作用域. |
||||
|
* `ApiResourceClaim` (集合):API 资源的声明. |
||||
|
|
||||
|
##### 客户端 |
||||
|
|
||||
|
客户端表示可以从你的身份服务器请求令牌的应用程序. |
||||
|
|
||||
|
* `Client` (聚合根):表示一个身份服务器的客户端应用程序. |
||||
|
* `ClientScope` (集合):客户端的作用域. |
||||
|
* `ClientSecret` (集合):客户端的密钥. |
||||
|
* `ClientGrantType` (集合):客户端的授权类型. |
||||
|
* `ClientCorsOrigin` (集合):客户端的 CORS 源. |
||||
|
* `ClientRedirectUri` (集合):客户端的重定向 URIs. |
||||
|
* `ClientPostLogoutRedirectUri` (集合):客户端的登出重定向 URIs. |
||||
|
* `ClientIdPRestriction` (集合):客户端的提供程序约束. |
||||
|
* `ClientClaim` (集合):客户端的声明. |
||||
|
* `ClientProperty` (集合):客户端的自定义属性. |
||||
|
|
||||
|
##### 持续化授权 |
||||
|
|
||||
|
持续化授权存储了授权码,刷新令牌和用户准许. |
||||
|
|
||||
|
* `PersistedGrant` (聚合根):表示为身份服务器持续化授权. |
||||
|
|
||||
|
##### 身份资源 |
||||
|
|
||||
|
身份资源是用户的用户 ID ,名称或邮件地址等数据. |
||||
|
|
||||
|
* `IdentityResource` (聚合根):表示与身份服务器的身份资源. |
||||
|
* `IdentityClaim` (集合):身份资源的声明. |
||||
|
|
||||
|
#### 仓储 |
||||
|
|
||||
|
为此模块定义了以下自定义仓储: |
||||
|
|
||||
|
* `IApiResourceRepository` |
||||
|
* `IClientRepository` |
||||
|
* `IPersistentGrantRepository` |
||||
|
* `IIdentityResourceRepository` |
||||
|
|
||||
|
#### 领域服务 |
||||
|
|
||||
|
此模块不包含任何领域服务, 但重写了下面的服务; |
||||
|
|
||||
|
* `AbpProfileService` (当 `AbpIdentityServerBuilderOptions.IntegrateToAspNetIdentity` 为 true 时使用) |
||||
|
* `AbpClaimsService` |
||||
|
* `AbpCorsPolicyService` |
||||
|
|
||||
|
### 设置 |
||||
|
|
||||
|
此模块未定义任何设置. |
||||
|
|
||||
|
### 应用层 |
||||
|
|
||||
|
#### 应用服务 |
||||
|
|
||||
|
* `ApiResourceAppService` (实现 `IApiResourceAppService`):实现了 API 资源管理 UI 的用例. |
||||
|
* `IdentityServerClaimTypeAppService` (实现 `IIdentityServerClaimTypeAppService`):用于获取声明列表. |
||||
|
* `ApiResourceAppService` (实现 `IApiResourceAppService`):实现了 API 管理资源 UI 的用例. |
||||
|
* `IdentityResourceAppService` (实现 `IIdentityResourceAppService`):实现了身份资源管理 UI 的用例. |
||||
|
|
||||
|
### 数据库提供程序 |
||||
|
|
||||
|
#### 公共 |
||||
|
|
||||
|
##### 表/集合 前缀 & 架构 |
||||
|
|
||||
|
所有表/集合都使用 `IdentityServer` 作为默认前缀.如果你需要改变表的前缀或设置一个架构名称(如果你的数据库提供程序支持), 请设置 `AbpIdentityServerDbProperties` 类的静态属性. |
||||
|
|
||||
|
##### 连接字符串 |
||||
|
|
||||
|
此模块使用 `AbpIdentityServer` 作为连接字符串的名称.如果你没有用这个名称定义连接字符串, 它将回退到 `Default` 连接字符串. |
||||
|
|
||||
|
有关详细信息, 请参阅 [连接字符串](https://docs.abp.io/zh-Hans/abp/latest/Connection-Strings) 文档. |
||||
|
|
||||
|
#### EF Core |
||||
|
|
||||
|
##### 表 |
||||
|
|
||||
|
* **IdentityServerApiResources** |
||||
|
* IdentityServerApiSecrets |
||||
|
* IdentityServerApiScopes |
||||
|
* IdentityServerApiScopeClaims |
||||
|
* IdentityServerApiClaims |
||||
|
* **IdentityServerClients** |
||||
|
* IdentityServerClientScopes |
||||
|
* IdentityServerClientSecrets |
||||
|
* IdentityServerClientGrantTypes |
||||
|
* IdentityServerClientCorsOrigins |
||||
|
* IdentityServerClientRedirectUris |
||||
|
* IdentityServerClientPostLogoutRedirectUris |
||||
|
* IdentityServerClientIdPRestrictions |
||||
|
* IdentityServerClientClaims |
||||
|
* IdentityServerClientProperties |
||||
|
* **IdentityServerPersistedGrants** |
||||
|
* **IdentityServerIdentityResources** |
||||
|
* IdentityServerIdentityClaims |
||||
|
|
||||
|
#### MongoDB |
||||
|
|
||||
|
##### 集合 |
||||
|
|
||||
|
* **IdentityServerApiResources** |
||||
|
* **IdentityServerClients** |
||||
|
* **IdentityServerPersistedGrants** |
||||
|
* **IdentityServerIdentityResources** |
||||
|
|||||
@ -1 +1,635 @@ |
|||||
TODO.. |
# Web应用程序开发教程 - 第五章: 授权 |
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC","Blazor","BlazorServer","NG"], |
||||
|
"DB": ["EF","Mongo"] |
||||
|
} |
||||
|
```` |
||||
|
## 关于本教程 |
||||
|
|
||||
|
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: |
||||
|
|
||||
|
* **{{DB_Text}}** 做为ORM提供程序. |
||||
|
* **{{UI_Value}}** 做为UI框架. |
||||
|
|
||||
|
本教程分为以下部分: |
||||
|
|
||||
|
- [Part 1: 创建服务端](Part-1.md) |
||||
|
- [Part 2: 图书列表页面](Part-2.md) |
||||
|
- [Part 3: 创建,更新和删除图书](Part-2.md) |
||||
|
- [Part 4: 集成测试](Part-4.md) |
||||
|
- **Part 5: 授权**(本章) |
||||
|
- [Part 6: 作者: 领域层](Part-6.md) |
||||
|
- [Part 7: 作者: 数据库集成](Part-7.md) |
||||
|
- [Part 8: 作者: 应用服务层](Part-8.md) |
||||
|
- [Part 9: 作者: 用户页面](Part-9.md) |
||||
|
- [Part 10: 图书到作者的关系](Part-10.md) |
||||
|
|
||||
|
## 下载源码 |
||||
|
|
||||
|
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: |
||||
|
|
||||
|
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) |
||||
|
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) |
||||
|
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) |
||||
|
|
||||
|
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). |
||||
|
|
||||
|
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path |
||||
|
> `git config --system core.longpaths true` |
||||
|
|
||||
|
{{if UI == "MVC" && DB == "EF"}} |
||||
|
|
||||
|
### 视频教程 |
||||
|
|
||||
|
本章也被录制为视频教程 **<a href="https://www.youtube.com/watch?v=1WsfMITN_Jk&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=5" target="_blank">发布在YouTube</a>**. |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## 权限 |
||||
|
|
||||
|
ABP框架提供了一个基于ASP.NET Core[授权基础架构](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction)的[授权系统](../Authorization.md). 基于标准授权基础架构的一个主要功能是添加了 **权限系统**, 这个系统允许定义权限并且根据角色, 用户或客户端启用/禁用权限. |
||||
|
|
||||
|
### 权限名称 |
||||
|
|
||||
|
权限必须有唯一的名称 (一个 `字符串`). 最好的方法是把它定义为一个 `常量`, 这样我们就可以重用这个权限名称了. |
||||
|
|
||||
|
打开 `Acme.BookStore.Application.Contracts` 项目中的 `BookStorePermissions` 类 (位于 `Permissions` 文件夹) 并替换为以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
namespace Acme.BookStore.Permissions |
||||
|
{ |
||||
|
public static class BookStorePermissions |
||||
|
{ |
||||
|
public const string GroupName = "BookStore"; |
||||
|
|
||||
|
public static class Books |
||||
|
{ |
||||
|
public const string Default = GroupName + ".Books"; |
||||
|
public const string Create = Default + ".Create"; |
||||
|
public const string Edit = Default + ".Edit"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
权限名称具有层次结构. 例如, "创建图书" 权限被定义为 `BookStore.Books.Create`. ABP不强制必须如此, 但这是一种有益的做法. |
||||
|
|
||||
|
### 权限定义 |
||||
|
|
||||
|
在使用权限前必须定义它们. |
||||
|
|
||||
|
打开 `Acme.BookStore.Application.Contracts` 项目中的 `BookStorePermissionDefinitionProvider` 类 (位于 `Permissions` 文件夹) 并替换为以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
using Acme.BookStore.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace Acme.BookStore.Permissions |
||||
|
{ |
||||
|
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IPermissionDefinitionContext context) |
||||
|
{ |
||||
|
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore")); |
||||
|
|
||||
|
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books")); |
||||
|
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create")); |
||||
|
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit")); |
||||
|
booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete")); |
||||
|
} |
||||
|
|
||||
|
private static LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<BookStoreResource>(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
这个类定义了一个 **权限组** (在UI上分组权限, 下文会看到) 和 权限组中的**4个权限**. 而且, **创建**, **编辑** 和 **删除** 是 `BookStorePermissions.Books.Default` 权限的子权限. **仅当父权限被选择**时, 子权限才能被选择. |
||||
|
|
||||
|
最后, 编辑本地化文件 (`Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹中的 `en.json`) 定义上面使用的本地化键: |
||||
|
|
||||
|
````json |
||||
|
"Permission:BookStore": "Book Store", |
||||
|
"Permission:Books": "Book Management", |
||||
|
"Permission:Books.Create": "Creating new books", |
||||
|
"Permission:Books.Edit": "Editing the books", |
||||
|
"Permission:Books.Delete": "Deleting the books" |
||||
|
```` |
||||
|
|
||||
|
> 本地化键名可以是任意的, 并没有强制的规则. 但我们推荐上面使用的约定. |
||||
|
|
||||
|
### 权限管理界面 |
||||
|
|
||||
|
完成权限定义后, 可以在**权限管理模态窗口**看到它们. |
||||
|
|
||||
|
在*管理 -> Identity -> 角色* 页面, 选择admin角色的 *权限* 操作, 打开权限管理模态窗口: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
授予你希望的权限并保存. |
||||
|
|
||||
|
> **提示**: 如果运行 `Acme.BookStore.DbMigrator` 应用程序, 新权限会被自动授予admin. |
||||
|
|
||||
|
## 授权 |
||||
|
|
||||
|
现在, 你可以使用权限授权图书管理. |
||||
|
|
||||
|
### 应用层 和 HTTP API |
||||
|
|
||||
|
打开 the `BookAppService` 类, 设置策略名称为上面定义的权限名称. |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using Acme.BookStore.Permissions; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Acme.BookStore.Books |
||||
|
{ |
||||
|
public class BookAppService : |
||||
|
CrudAppService< |
||||
|
Book, //The Book entity |
||||
|
BookDto, //Used to show books |
||||
|
Guid, //Primary key of the book entity |
||||
|
PagedAndSortedResultRequestDto, //Used for paging/sorting |
||||
|
CreateUpdateBookDto>, //Used to create/update a book |
||||
|
IBookAppService //implement the IBookAppService |
||||
|
{ |
||||
|
public BookAppService(IRepository<Book, Guid> repository) |
||||
|
: base(repository) |
||||
|
{ |
||||
|
GetPolicyName = BookStorePermissions.Books.Default; |
||||
|
GetListPolicyName = BookStorePermissions.Books.Default; |
||||
|
CreatePolicyName = BookStorePermissions.Books.Create; |
||||
|
UpdatePolicyName = BookStorePermissions.Books.Edit; |
||||
|
DeletePolicyName = BookStorePermissions.Books.Delete; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
加入代码到构造器. 基类中的 `CrudAppService` 自动在CRUD操作中使用这些权限. 这不仅实现了 **应用服务** 的安全性, 也实现了 **HTTP API** 安全性, 因为如前解释的, HTTP API 自动使用这些服务. (参阅 [自动 API controllers](../API/Auto-API-Controllers.md)). |
||||
|
|
||||
|
> 在稍后开发作者管理功能时, 你将会看到声明式授权, 使用 `[Authorize(...)]` 特性. |
||||
|
|
||||
|
{{if UI == "MVC"}} |
||||
|
|
||||
|
### Razor 页面 |
||||
|
|
||||
|
虽然安全的 HTTP API和应用服务阻止未授权用户使用服务, 但他们依然可以导航到图书管理页面. 虽然当页面发起第一个访问服务器的AJAX请求时会收到授权异常, 但为了更好的用户体验和安全性, 我们应该对页面进行授权. |
||||
|
|
||||
|
打开 `BookStoreWebModule` 在 `ConfigureServices` 方法中加入以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
Configure<RazorPagesOptions>(options => |
||||
|
{ |
||||
|
options.Conventions.AuthorizePage("/Books/Index", BookStorePermissions.Books.Default); |
||||
|
options.Conventions.AuthorizePage("/Books/CreateModal", BookStorePermissions.Books.Create); |
||||
|
options.Conventions.AuthorizePage("/Books/EditModal", BookStorePermissions.Books.Edit); |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
现在未授权用户会被重定向至**登录页面**. |
||||
|
|
||||
|
#### 隐藏新建图书按钮 |
||||
|
|
||||
|
图书管理页面有一个 *新建图书* 按钮, 当用户没有 *图书新建* 权限时就不可见的. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
打开 the `Pages/Books/Index.cshtml` 文件, 替换内容为以下代码: |
||||
|
|
||||
|
````html |
||||
|
@page |
||||
|
@using Acme.BookStore.Localization |
||||
|
@using Acme.BookStore.Permissions |
||||
|
@using Acme.BookStore.Web.Pages.Books |
||||
|
@using Microsoft.AspNetCore.Authorization |
||||
|
@using Microsoft.Extensions.Localization |
||||
|
@model IndexModel |
||||
|
@inject IStringLocalizer<BookStoreResource> L |
||||
|
@inject IAuthorizationService AuthorizationService |
||||
|
@section scripts |
||||
|
{ |
||||
|
<abp-script src="/Pages/Books/Index.js"/> |
||||
|
} |
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-row> |
||||
|
<abp-column size-md="_6"> |
||||
|
<abp-card-title>@L["Books"]</abp-card-title> |
||||
|
</abp-column> |
||||
|
<abp-column size-md="_6" class="text-right"> |
||||
|
@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create)) |
||||
|
{ |
||||
|
<abp-button id="NewBookButton" |
||||
|
text="@L["NewBook"].Value" |
||||
|
icon="plus" |
||||
|
button-type="Primary"/> |
||||
|
} |
||||
|
</abp-column> |
||||
|
</abp-row> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<abp-table striped-rows="true" id="BooksTable"></abp-table> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
```` |
||||
|
|
||||
|
* 加入 `@inject IAuthorizationService AuthorizationService` 以访问授权服务. |
||||
|
* 使用 `@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))` 检查图书创建权限, 条件显示 *新建图书* 按钮. |
||||
|
|
||||
|
### JavaScript端 |
||||
|
|
||||
|
图书管理页面中的图书表格每行都有操作按钮. 操作按钮包括 *编辑* 和 *删除* 操作: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
如果用户没有权限, 应该隐藏相关的操作. 表格行中的操作有一个 `visible` 属性, 可以设置为 `false` 隐藏操作项. |
||||
|
|
||||
|
打开 `Acme.BookStore.Web` 项目中的 `Pages/Books/Index.js`, 为 `编辑` 操作加入 `visible` 属性: |
||||
|
|
||||
|
````js |
||||
|
{ |
||||
|
text: l('Edit'), |
||||
|
visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION |
||||
|
action: function (data) { |
||||
|
editModal.open({ id: data.record.id }); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
对 `Delete` 操作进行同样的操作: |
||||
|
|
||||
|
````js |
||||
|
visible: abp.auth.isGranted('BookStore.Books.Delete') |
||||
|
```` |
||||
|
|
||||
|
* `abp.auth.isGranted(...)` 检查前面定义的权限. |
||||
|
* `visible` 也可以是一个返回 `bool` 值的函数. 这个函数可以稍后根据某些条件计算. |
||||
|
|
||||
|
### 菜单项 |
||||
|
|
||||
|
即使我们在图书管理页面的所有层都控制了权限, 应用程序的主菜单依然会显示. 我们应该隐藏用户没有权限的菜单项. |
||||
|
|
||||
|
打开 `BookStoreMenuContributor` 类, 找到下面的代码: |
||||
|
|
||||
|
````csharp |
||||
|
context.Menu.AddItem( |
||||
|
new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
).AddItem( |
||||
|
new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/Books" |
||||
|
) |
||||
|
) |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
替换为以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
var bookStoreMenu = new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
); |
||||
|
|
||||
|
context.Menu.AddItem(bookStoreMenu); |
||||
|
|
||||
|
//CHECK the PERMISSION |
||||
|
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) |
||||
|
{ |
||||
|
bookStoreMenu.AddItem(new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/Books" |
||||
|
)); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
你需要为 `ConfigureMenuAsync` 方法加入 `async` 关键字, 并重新组织返回值. 最终的 `BookStoreMenuContributor` 类应该如下: |
||||
|
|
||||
|
````csharp |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Acme.BookStore.Localization; |
||||
|
using Acme.BookStore.MultiTenancy; |
||||
|
using Acme.BookStore.Permissions; |
||||
|
using Volo.Abp.TenantManagement.Web.Navigation; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
|
||||
|
namespace Acme.BookStore.Web.Menus |
||||
|
{ |
||||
|
public class BookStoreMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
public async Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name == StandardMenus.Main) |
||||
|
{ |
||||
|
await ConfigureMainMenuAsync(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (!MultiTenancyConsts.IsEnabled) |
||||
|
{ |
||||
|
var administration = context.Menu.GetAdministration(); |
||||
|
administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName); |
||||
|
} |
||||
|
|
||||
|
var l = context.GetLocalizer<BookStoreResource>(); |
||||
|
|
||||
|
context.Menu.Items.Insert(0, new ApplicationMenuItem("BookStore.Home", l["Menu:Home"], "~/")); |
||||
|
|
||||
|
var bookStoreMenu = new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
); |
||||
|
|
||||
|
context.Menu.AddItem(bookStoreMenu); |
||||
|
|
||||
|
//CHECK the PERMISSION |
||||
|
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) |
||||
|
{ |
||||
|
bookStoreMenu.AddItem(new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/Books" |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
{{else if UI == "NG"}} |
||||
|
|
||||
|
### Angular Guard 配置 |
||||
|
|
||||
|
UI的第一步是防止未认证用户看见"图书"菜单项并进入图书管理页面. |
||||
|
|
||||
|
打开 `/src/app/book/book-routing.module.ts` 替换为以下代码: |
||||
|
|
||||
|
````js |
||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { Routes, RouterModule } from '@angular/router'; |
||||
|
import { AuthGuard, PermissionGuard } from '@abp/ng.core'; |
||||
|
import { BookComponent } from './book.component'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] }, |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule], |
||||
|
}) |
||||
|
export class BookRoutingModule {} |
||||
|
```` |
||||
|
|
||||
|
* 从 `@abp/ng.core` 引入 `AuthGuard` 和 `PermissionGuard`. |
||||
|
* 在路由定义中添加 `canActivate: [AuthGuard, PermissionGuard]`. |
||||
|
|
||||
|
打开 `/src/app/route.provider.ts`, 在 `/books` 路由中添加 `requiredPolicy: 'BookStore.Books'`. `/books` 路由应该如以下配置: |
||||
|
|
||||
|
````js |
||||
|
{ |
||||
|
path: '/books', |
||||
|
name: '::Menu:Books', |
||||
|
parentName: '::Menu:BookStore', |
||||
|
layout: eLayoutType.application, |
||||
|
requiredPolicy: 'BookStore.Books', |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### 隐藏新建图书按钮 |
||||
|
|
||||
|
当用户没有 *图书新建* 权限时, 图书管理页面上的 *新建图书* 按钮应该不可见. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
打开 `/src/app/book/book.component.html` 文件, 替换创建按钮的HTML内容如下: |
||||
|
|
||||
|
````html |
||||
|
<!-- Add the abpPermission directive --> |
||||
|
<button *abpPermission="'BookStore.Books.Create'" id="create" class="btn btn-primary" type="button" (click)="createBook()"> |
||||
|
<i class="fa fa-plus mr-1"></i> |
||||
|
<span>{%{{{ '::NewBook' | abpLocalization }}}%}</span> |
||||
|
</button> |
||||
|
```` |
||||
|
|
||||
|
* 加入 `*abpPermission="'BookStore.Books.Create'"`, 当用户没有权限时隐藏按钮. |
||||
|
|
||||
|
### 隐藏编辑和删除操作 |
||||
|
|
||||
|
图书管理页面中的图书表格每行都有操作按钮. 操作按钮包括 *编辑* 和 *删除* 操作: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
如果用户没有权限, 应该隐藏相关的操作. |
||||
|
|
||||
|
打开 `/src/app/book/book.component.html` 文件, 替换编辑和删除按钮的内容如下: |
||||
|
|
||||
|
````html |
||||
|
<!-- Add the abpPermission directive --> |
||||
|
<button *abpPermission="'BookStore.Books.Edit'" ngbDropdownItem (click)="editBook(row.id)"> |
||||
|
{%{{{ '::Edit' | abpLocalization }}}%} |
||||
|
</button> |
||||
|
|
||||
|
<!-- Add the abpPermission directive --> |
||||
|
<button *abpPermission="'BookStore.Books.Delete'" ngbDropdownItem (click)="delete(row.id)"> |
||||
|
{%{{{ '::Delete' | abpLocalization }}}%} |
||||
|
</button> |
||||
|
```` |
||||
|
|
||||
|
* 加入 `*abpPermission="'BookStore.Books.Create'"`, 当用户没有编辑权限时隐藏按钮. |
||||
|
* 加入 `*abpPermission="'BookStore.Books.Delete'"`, 当用户没有删除权限时隐藏按钮. |
||||
|
|
||||
|
{{else if UI == "Blazor"}} |
||||
|
|
||||
|
### Razor验证组件 |
||||
|
|
||||
|
打开 `Acme.BookStore.Blazor` 项目中的 `/Pages/Books.razor` 文件, 在` @page` 指令和命名空间引入(`@using` 行)后添加 `Authorize` 特性, 如下所示: |
||||
|
|
||||
|
````html |
||||
|
@page "/books" |
||||
|
@attribute [Authorize(BookStorePermissions.Books.Default)] |
||||
|
@using Acme.BookStore.Permissions |
||||
|
@using Microsoft.AspNetCore.Authorization |
||||
|
... |
||||
|
```` |
||||
|
|
||||
|
添加这个特性阻止未登录用户或未授权用户访问这个页面. 用户重试后, 会被重定向到登录页面. |
||||
|
|
||||
|
### 显示/隐藏操作 |
||||
|
|
||||
|
图书管理页面上的每一种图书都有 *新建* 按钮和 *编辑*, *删除* 操作. 如果用户没有相关权限, 这些按钮/操作应该被隐藏. |
||||
|
|
||||
|
基类 `AbpCrudPageBase` 已经具有这些操作需要的功能. |
||||
|
|
||||
|
#### 设置策略 (权限) 名称 |
||||
|
|
||||
|
加入以下代码到 `Books.razor` 文件结尾: |
||||
|
|
||||
|
````csharp |
||||
|
@code |
||||
|
{ |
||||
|
public Books() // Constructor |
||||
|
{ |
||||
|
CreatePolicyName = BookStorePermissions.Books.Create; |
||||
|
UpdatePolicyName = BookStorePermissions.Books.Edit; |
||||
|
DeletePolicyName = BookStorePermissions.Books.Delete; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
基类 `AbpCrudPageBase` 自动检查相关操作的权限. 如果需要手动检查, 它也定义了相应的属性. |
||||
|
|
||||
|
* `HasCreatePermission`: True, 如果用户具有新建实体的权限. |
||||
|
* `HasUpdatePermission`: True, 如果用户具有编辑/更新实体的权限. |
||||
|
* `HasDeletePermission`: True, 如果用户具有删除实体的权限. |
||||
|
|
||||
|
> **Blazor 提示**: 当添加少量代码到 `@code` 是没有问题的. 当添加的代码变长时, 建议使用代码后置方法以便于维护. 我们将在作者部分使用这个方法. |
||||
|
|
||||
|
#### 隐藏新建图书按钮 |
||||
|
|
||||
|
检查 *新建图书* 按钮权限: |
||||
|
|
||||
|
````xml |
||||
|
@if (HasCreatePermission) |
||||
|
{ |
||||
|
<Button Color="Color.Primary" |
||||
|
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button> |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
#### 隐藏编辑/删除操作 |
||||
|
|
||||
|
`EntityAction` 组件定义了 `Visible` 属性 (参数) 以条件显示操作. |
||||
|
|
||||
|
更新 `EntityActions` 部分: |
||||
|
|
||||
|
````xml |
||||
|
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn"> |
||||
|
<EntityAction TItem="BookDto" |
||||
|
Text="@L["Edit"]" |
||||
|
Visible=HasUpdatePermission |
||||
|
Clicked="() => OpenEditModalAsync(context)" /> |
||||
|
<EntityAction TItem="BookDto" |
||||
|
Text="@L["Delete"]" |
||||
|
Visible=HasDeletePermission |
||||
|
Clicked="() => DeleteEntityAsync(context)" |
||||
|
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" /> |
||||
|
</EntityActions> |
||||
|
```` |
||||
|
|
||||
|
#### 关于权限缓存 |
||||
|
|
||||
|
你可以运行和测试权限. 从admin角色中移除一个图书相关权限, 观察到相关按钮/操作从UI上消失. |
||||
|
|
||||
|
在客户端, **ABP框架缓存当前用户的权限** . 所以, 当你修改了你的权限, 你需要手工 **刷新页面**. 如果不刷新并试图使用被禁的操作, 你会从服务器收到一个HTTP 403 (forbidden) 响应. |
||||
|
|
||||
|
> 修改角色或用户的权限在服务端立即生效. 所以, 缓存系统不会导致安全问题. |
||||
|
|
||||
|
### 菜单项 |
||||
|
|
||||
|
即使我们在图书管理页面的所有层都控制了权限, 应用程序的主菜单依然会显示. 我们应该隐藏用户没有权限的菜单项. |
||||
|
|
||||
|
打开 `Acme.BookStore.Blazor` 项目中的 `BookStoreMenuContributor` 类, 找到以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
context.Menu.AddItem( |
||||
|
new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
).AddItem( |
||||
|
new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/books" |
||||
|
) |
||||
|
) |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
替换为以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
var bookStoreMenu = new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
); |
||||
|
|
||||
|
context.Menu.AddItem(bookStoreMenu); |
||||
|
|
||||
|
//CHECK the PERMISSION |
||||
|
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) |
||||
|
{ |
||||
|
bookStoreMenu.AddItem(new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/books" |
||||
|
)); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
你需要为 `ConfigureMenuAsync` 方法加入 `async` 关键字并重新整理返回值. 最终的 `ConfigureMainMenuAsync` 方法如下: |
||||
|
|
||||
|
````csharp |
||||
|
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
var l = context.GetLocalizer<BookStoreResource>(); |
||||
|
|
||||
|
context.Menu.Items.Insert( |
||||
|
0, |
||||
|
new ApplicationMenuItem( |
||||
|
"BookStore.Home", |
||||
|
l["Menu:Home"], |
||||
|
"/", |
||||
|
icon: "fas fa-home" |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
var bookStoreMenu = new ApplicationMenuItem( |
||||
|
"BooksStore", |
||||
|
l["Menu:BookStore"], |
||||
|
icon: "fa fa-book" |
||||
|
); |
||||
|
|
||||
|
context.Menu.AddItem(bookStoreMenu); |
||||
|
|
||||
|
//CHECK the PERMISSION |
||||
|
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) |
||||
|
{ |
||||
|
bookStoreMenu.AddItem(new ApplicationMenuItem( |
||||
|
"BooksStore.Books", |
||||
|
l["Menu:Books"], |
||||
|
url: "/books" |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## 下一章 |
||||
|
|
||||
|
查看本教程的[下一章](Part-6.md). |
||||
|
|||||
@ -1 +1,278 @@ |
|||||
TODO.. |
# Web应用程序开发教程 - 第六章: 作者: 领域层 |
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC","Blazor","BlazorServer","NG"], |
||||
|
"DB": ["EF","Mongo"] |
||||
|
} |
||||
|
```` |
||||
|
## 关于本教程 |
||||
|
|
||||
|
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: |
||||
|
|
||||
|
* **{{DB_Text}}** 做为ORM提供程序. |
||||
|
* **{{UI_Value}}** 做为UI框架. |
||||
|
|
||||
|
本教程分为以下部分: |
||||
|
|
||||
|
- [Part 1: 创建服务端](Part-1.md) |
||||
|
- [Part 2: 图书列表页面](Part-2.md) |
||||
|
- [Part 3: 创建,更新和删除图书](Part-2.md) |
||||
|
- [Part 4: 集成测试](Part-4.md) |
||||
|
- [Part 5: 授权](Part-5.md) |
||||
|
- **Part 6: 作者: 领域层**(本章) |
||||
|
- [Part 7: 作者: 数据库集成](Part-7.md) |
||||
|
- [Part 8: 作者: 应用服务层](Part-8.md) |
||||
|
- [Part 9: 作者: 用户页面](Part-9.md) |
||||
|
- [Part 10: 图书到作者的关系](Part-10.md) |
||||
|
|
||||
|
## 下载源码 |
||||
|
|
||||
|
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: |
||||
|
|
||||
|
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) |
||||
|
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) |
||||
|
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) |
||||
|
|
||||
|
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). |
||||
|
|
||||
|
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path |
||||
|
> `git config --system core.longpaths true` |
||||
|
|
||||
|
## 简介 |
||||
|
|
||||
|
在前面的章节中, 我们使用 ABP 框架轻松地构建了一些服务; |
||||
|
|
||||
|
* 使用 [CrudAppService](../Application-Services.md) 基类, 而不是为标准的增删改查操作手工开发应用服务. |
||||
|
* 使用 [generic repositories](../Repositories.md) 自动完成数据层功能. |
||||
|
|
||||
|
对于 "作者" 部分; |
||||
|
|
||||
|
* 我们将要展示在需要的情况下, 如何 **手工做一些事情**. |
||||
|
* 我们将要实现一些 **领域驱动设计 (DDD) 最佳实践**. |
||||
|
|
||||
|
> **开发将会逐层完成, 一次聚焦一层. 在真实项目中, 你会逐个功能(垂直)开发, 如同前面的教程. 通过这种方式, 你可以体验这两种方式** |
||||
|
|
||||
|
## 作者实体 |
||||
|
|
||||
|
在 `Acme.BookStore.Domain` 项目中创建 `Authors` 文件夹 (命名空间), 在其中加入 `Author` 类: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public class Author : FullAuditedAggregateRoot<Guid> |
||||
|
{ |
||||
|
public string Name { get; private set; } |
||||
|
public DateTime BirthDate { get; set; } |
||||
|
public string ShortBio { get; set; } |
||||
|
|
||||
|
private Author() |
||||
|
{ |
||||
|
/* This constructor is for deserialization / ORM purpose */ |
||||
|
} |
||||
|
|
||||
|
internal Author( |
||||
|
Guid id, |
||||
|
[NotNull] string name, |
||||
|
DateTime birthDate, |
||||
|
[CanBeNull] string shortBio = null) |
||||
|
: base(id) |
||||
|
{ |
||||
|
SetName(name); |
||||
|
BirthDate = birthDate; |
||||
|
ShortBio = shortBio; |
||||
|
} |
||||
|
|
||||
|
internal Author ChangeName([NotNull] string name) |
||||
|
{ |
||||
|
SetName(name); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
private void SetName([NotNull] string name) |
||||
|
{ |
||||
|
Name = Check.NotNullOrWhiteSpace( |
||||
|
name, |
||||
|
nameof(name), |
||||
|
maxLength: AuthorConsts.MaxNameLength |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* 由 `FullAuditedAggregateRoot<Guid>` 继承使得实体支持[软删除](../Data-Filtering.md) (指实体被删除时, 它并没有从数据库中被删除, 而只是被标记删除), 实体也具有了 [审计](../Entities.md) 属性. |
||||
|
* `Name` 属性的 `private set` 限制从类的外部设置这个属性. 有两种方法设置名字 (两种都进行了验证): |
||||
|
* 当新建一个作者时, 通过构造器. |
||||
|
* 使用 `ChangeName` 方法更新名字. |
||||
|
* `构造器` 和 `ChangeName` 方法的访问级别是 `internal`, 强制这些方法只能在领域层由 `AuthorManager` 使用. 稍后将对此进行解释. |
||||
|
* `Check` 类是一个ABP框架工具类, 用于检查方法参数 (如果参数非法会抛出 `ArgumentException`). |
||||
|
|
||||
|
`AuthorConsts` 是一个简单的类, 它位于 `Acme.BookStore.Domain.Shared` 项目的 `Authors` 命名空间 (文件夹)中: |
||||
|
|
||||
|
````csharp |
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public static class AuthorConsts |
||||
|
{ |
||||
|
public const int MaxNameLength = 64; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
在 `Acme.BookStore.Domain.Shared` 项目中创建这个类, 因为[数据传输类](../Data-Transfer-Objects.md) (DTOs) 稍后会再一次用到它. |
||||
|
|
||||
|
## AuthorManager: 领域服务 |
||||
|
|
||||
|
`Author` 构造器和 `ChangeName` 方法的访问级别是 `internal`, 所以它们只能在领域层使用. 在 `Acme.BookStore.Domain` 项目中的 `Authors` 文件夹 (命名空间)创建 `AuthorManager` 类: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public class AuthorManager : DomainService |
||||
|
{ |
||||
|
private readonly IAuthorRepository _authorRepository; |
||||
|
|
||||
|
public AuthorManager(IAuthorRepository authorRepository) |
||||
|
{ |
||||
|
_authorRepository = authorRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<Author> CreateAsync( |
||||
|
[NotNull] string name, |
||||
|
DateTime birthDate, |
||||
|
[CanBeNull] string shortBio = null) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
|
||||
|
var existingAuthor = await _authorRepository.FindByNameAsync(name); |
||||
|
if (existingAuthor != null) |
||||
|
{ |
||||
|
throw new AuthorAlreadyExistsException(name); |
||||
|
} |
||||
|
|
||||
|
return new Author( |
||||
|
GuidGenerator.Create(), |
||||
|
name, |
||||
|
birthDate, |
||||
|
shortBio |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public async Task ChangeNameAsync( |
||||
|
[NotNull] Author author, |
||||
|
[NotNull] string newName) |
||||
|
{ |
||||
|
Check.NotNull(author, nameof(author)); |
||||
|
Check.NotNullOrWhiteSpace(newName, nameof(newName)); |
||||
|
|
||||
|
var existingAuthor = await _authorRepository.FindByNameAsync(newName); |
||||
|
if (existingAuthor != null && existingAuthor.Id != author.Id) |
||||
|
{ |
||||
|
throw new AuthorAlreadyExistsException(newName); |
||||
|
} |
||||
|
|
||||
|
author.ChangeName(newName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* `AuthorManager` 强制使用一种可控的方式创建作者和修改作者的名字. 应用层 (后面会介绍) 将会使用这些方法. |
||||
|
|
||||
|
> **DDD 提示**: 如非必须并且用于执行核心业务规则, 不要引入领域服务方法. 对于这个场景, 我们使用这个服务保证名字的唯一性. |
||||
|
|
||||
|
两个方法都检查是否存在同名用户, 如果存在, 抛出业务异常 `AuthorAlreadyExistsException`, 这个异常定义在 `Acme.BookStore.Domain` 项目 (`Authors` 文件夹中): |
||||
|
|
||||
|
````csharp |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public class AuthorAlreadyExistsException : BusinessException |
||||
|
{ |
||||
|
public AuthorAlreadyExistsException(string name) |
||||
|
: base(BookStoreDomainErrorCodes.AuthorAlreadyExists) |
||||
|
{ |
||||
|
WithData("name", name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`BusinessException` 是一个特殊的异常类型. 在需要时抛出领域相关异常是一个好的实践. ABP框架会自动处理它, 并且它也容易本地化. `WithData(...)` 方法提供额外的数据给异常对象, 这些数据将会在本地化中或出于其它一些目的被使用. |
||||
|
|
||||
|
打开 `Acme.BookStore.Domain.Shared` 项目中的 `BookStoreDomainErrorCodes` 并修改为: |
||||
|
|
||||
|
````csharp |
||||
|
namespace Acme.BookStore |
||||
|
{ |
||||
|
public static class BookStoreDomainErrorCodes |
||||
|
{ |
||||
|
public const string AuthorAlreadyExists = "BookStore:00001"; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
这里定义了一个字符串, 表示应用程序抛出的错误码, 这个错误码可以被客户端应用程序处理. 为了用户, 你可能希望本地化它. 打开 `Acme.BookStore.Domain.Shared` 项目中的 `Localization/BookStore/en.json` , 加入以下项: |
||||
|
|
||||
|
````json |
||||
|
"BookStore:00001": "There is already an author with the same name: {name}" |
||||
|
```` |
||||
|
|
||||
|
当 `AuthorAlreadyExistsException` 被抛出, 终端用户将会在UI上看到组织好的错误消息. |
||||
|
|
||||
|
## IAuthorRepository |
||||
|
|
||||
|
`AuthorManager` 注入了 `IAuthorRepository`, 所以我们需要定义它. 在 `Acme.BookStore.Domain` 项目的 `Authors` 文件夹 (命名空间) 中创建这个新接口: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public interface IAuthorRepository : IRepository<Author, Guid> |
||||
|
{ |
||||
|
Task<Author> FindByNameAsync(string name); |
||||
|
|
||||
|
Task<List<Author>> GetListAsync( |
||||
|
int skipCount, |
||||
|
int maxResultCount, |
||||
|
string sorting, |
||||
|
string filter = null |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* `IAuthorRepository` 扩展了标准 `IRepository<Author, Guid>` 接口, 所以所有的标准 [repository](../Repositories.md) 方法对于 `IAuthorRepository` 都是可用的. |
||||
|
* `FindByNameAsync` 在 `AuthorManager` 中用来根据姓名查询用户. |
||||
|
* `GetListAsync` 用于应用层以获得一个排序的, 经过过滤的作者列表, 显示在UI上. |
||||
|
|
||||
|
我们会在下一章实现这个repository. |
||||
|
|
||||
|
> 这两个方法似乎 **看上去没有必要**, 因为标准repositories已经是 `IQueryable`, 你可以直接使用它们, 而不是自定义方法. 在实际应用程序中, 这么做是没问题的. 但在这个 **学习指南**中, 解释如何在需要时创建自定义repository方法是有价值的. |
||||
|
|
||||
|
## 结论 |
||||
|
|
||||
|
这一章覆盖了图书管理程序作者相关功能的领域层. 在这一章中创建/更新的文件在下图中被高亮: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 下一章 |
||||
|
|
||||
|
查看本教程的[下一章](Part-7.md). |
||||
|
|||||
@ -1 +1,238 @@ |
|||||
TODO.. |
# Web应用程序开发教程 - 第七章: 数据库集成 |
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC","Blazor","BlazorServer","NG"], |
||||
|
"DB": ["EF","Mongo"] |
||||
|
} |
||||
|
```` |
||||
|
## 关于本教程 |
||||
|
|
||||
|
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: |
||||
|
|
||||
|
* **{{DB_Text}}** 做为ORM提供程序. |
||||
|
* **{{UI_Value}}** 做为UI框架. |
||||
|
|
||||
|
本教程分为以下部分: |
||||
|
|
||||
|
- [Part 1: 创建服务端](Part-1.md) |
||||
|
- [Part 2: 图书列表页面](Part-2.md) |
||||
|
- [Part 3: 创建,更新和删除图书](Part-2.md) |
||||
|
- [Part 4: 集成测试](Part-4.md) |
||||
|
- [Part 5: 授权](Part-5.md) |
||||
|
- [Part 6: 作者: 领域层](Part-6.md) |
||||
|
- **Part 7: 数据库集成**(本章) |
||||
|
- [Part 8: 作者: 应用服务层](Part-8.md) |
||||
|
- [Part 9: 作者: 用户页面](Part-9.md) |
||||
|
- [Part 10: 图书到作者的关系](Part-10.md) |
||||
|
|
||||
|
## 下载源码 |
||||
|
|
||||
|
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: |
||||
|
|
||||
|
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) |
||||
|
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) |
||||
|
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) |
||||
|
|
||||
|
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). |
||||
|
|
||||
|
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path |
||||
|
> `git config --system core.longpaths true` |
||||
|
|
||||
|
## 简介 |
||||
|
|
||||
|
这章阐述如何为前一章介绍的 `作者` 实体配置数据库集成. |
||||
|
|
||||
|
{{if DB=="EF"}} |
||||
|
|
||||
|
## DB Context |
||||
|
|
||||
|
打开 `Acme.BookStore.EntityFrameworkCore` 项目中的 `BookStoreDbContext` 加入 `DbSet` 属性: |
||||
|
|
||||
|
````csharp |
||||
|
public DbSet<Author> Authors { get; set; } |
||||
|
```` |
||||
|
|
||||
|
定位到相同项目中的 `BookStoreDbContext` 类中的 `OnModelCreating` 方法, 加入以下代码到方法的结尾: |
||||
|
|
||||
|
````csharp |
||||
|
builder.Entity<Author>(b => |
||||
|
{ |
||||
|
b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", |
||||
|
BookStoreConsts.DbSchema); |
||||
|
|
||||
|
b.ConfigureByConvention(); |
||||
|
|
||||
|
b.Property(x => x.Name) |
||||
|
.IsRequired() |
||||
|
.HasMaxLength(AuthorConsts.MaxNameLength); |
||||
|
|
||||
|
b.HasIndex(x => x.Name); |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
这和前面的 `Book` 实体做的一样, 所以不再赘述. |
||||
|
|
||||
|
## 创建数据库迁移 |
||||
|
|
||||
|
配置启动解决方案为使用 [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). 因为我们还没有修改数据库映射配置,所以需要创建一个新的迁移并对数据库应用变更. |
||||
|
|
||||
|
打开命令行终端, 切换当前目录为 `Acme.BookStore.EntityFrameworkCore` 项目目录, 输入以下命令: |
||||
|
|
||||
|
````bash |
||||
|
dotnet ef migrations add Added_Authors |
||||
|
```` |
||||
|
|
||||
|
这会在项目中添加一个迁移类: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
你可以在同一个命令行终端中使用以下命令对数据库应用更改: |
||||
|
|
||||
|
````bash |
||||
|
dotnet ef database update |
||||
|
```` |
||||
|
|
||||
|
> 如果你使用 Visual Studio, 可能希望在 *Package Manager Console (PMC)* 使用 `Add-Migration Added_Authors -c BookStoreMigrationsDbContext` 和 `Update-Database -c BookStoreMigrationsDbContext` 命令. 如果这样, 保证 {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} 是启动项目并且在PMC中 `Acme.BookStore.EntityFrameworkCore` 是 *默认项目* . |
||||
|
|
||||
|
{{else if DB=="Mongo"}} |
||||
|
|
||||
|
## DB Context |
||||
|
|
||||
|
打开 `Acme.BookStore.MongoDB` 项目 `MongoDb 文件夹`中的 `BookStoreMongoDbContext`, 在类中加入以下属性: |
||||
|
|
||||
|
````csharp |
||||
|
public IMongoCollection<Author> Authors => Collection<Author>(); |
||||
|
```` |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## 实现 IAuthorRepository |
||||
|
|
||||
|
{{if DB=="EF"}} |
||||
|
|
||||
|
在 `Acme.BookStore.EntityFrameworkCore` 项目 (`Authors` 文件夹)中创建一个新类 `EfCoreAuthorRepository`, 粘贴以下代码: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading.Tasks; |
||||
|
using Acme.BookStore.EntityFrameworkCore; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public class EfCoreAuthorRepository |
||||
|
: EfCoreRepository<BookStoreDbContext, Author, Guid>, |
||||
|
IAuthorRepository |
||||
|
{ |
||||
|
public EfCoreAuthorRepository( |
||||
|
IDbContextProvider<BookStoreDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<Author> FindByNameAsync(string name) |
||||
|
{ |
||||
|
var dbSet = await GetDbSetAsync(); |
||||
|
return await dbSet.FirstOrDefaultAsync(author => author.Name == name); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<Author>> GetListAsync( |
||||
|
int skipCount, |
||||
|
int maxResultCount, |
||||
|
string sorting, |
||||
|
string filter = null) |
||||
|
{ |
||||
|
var dbSet = await GetDbSetAsync(); |
||||
|
return await dbSet |
||||
|
.WhereIf( |
||||
|
!filter.IsNullOrWhiteSpace(), |
||||
|
author => author.Name.Contains(filter) |
||||
|
) |
||||
|
.OrderBy(sorting) |
||||
|
.Skip(skipCount) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* 继承自 `EfCoreRepository`, 所以继承了标准repository的方法实现. |
||||
|
* `WhereIf` 是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行 `Where` 查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率. |
||||
|
* `sorting` 可以是一个字符串, 如 `Name`, `Name ASC` 或 `Name DESC`. 通过使用 [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet 包是可能的. |
||||
|
|
||||
|
> 参阅 [EF Core 集成文档](../Entity-Framework-Core.md) 获得基于EF Core的repositories的更多信息. |
||||
|
|
||||
|
{{else if DB=="Mongo"}} |
||||
|
|
||||
|
在 `Acme.BookStore.MongoDB` 项目 (`Authors` 文件夹)中创建一个新类 `MongoDbAuthorRepository`, 粘贴以下代码: |
||||
|
|
||||
|
```csharp |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Acme.BookStore.MongoDB; |
||||
|
using MongoDB.Driver; |
||||
|
using MongoDB.Driver.Linq; |
||||
|
using Volo.Abp.Domain.Repositories.MongoDB; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
|
||||
|
namespace Acme.BookStore.Authors |
||||
|
{ |
||||
|
public class MongoDbAuthorRepository |
||||
|
: MongoDbRepository<BookStoreMongoDbContext, Author, Guid>, |
||||
|
IAuthorRepository |
||||
|
{ |
||||
|
public MongoDbAuthorRepository( |
||||
|
IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider |
||||
|
) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<Author> FindByNameAsync(string name) |
||||
|
{ |
||||
|
var queryable = await GetMongoQueryableAsync(); |
||||
|
return await queryable.FirstOrDefaultAsync(author => author.Name == name); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<Author>> GetListAsync( |
||||
|
int skipCount, |
||||
|
int maxResultCount, |
||||
|
string sorting, |
||||
|
string filter = null) |
||||
|
{ |
||||
|
var queryable = await GetMongoQueryableAsync(); |
||||
|
return await queryable |
||||
|
.WhereIf<Author, IMongoQueryable<Author>>( |
||||
|
!filter.IsNullOrWhiteSpace(), |
||||
|
author => author.Name.Contains(filter) |
||||
|
) |
||||
|
.OrderBy(sorting) |
||||
|
.As<IMongoQueryable<Author>>() |
||||
|
.Skip(skipCount) |
||||
|
.Take(maxResultCount) |
||||
|
.ToListAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
* 继承自 `MongoDbRepository`, 所以继承了标准repository的方法实现. |
||||
|
* `WhereIf` 是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行 `Where` 查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率. |
||||
|
* `sorting` 可以是一个字符串, 如 `Name`, `Name ASC` 或 `Name DESC`. 通过使用 [System.Linq.Dynamic.Core](https://www.nuget.org/packages/System.Linq.Dynamic.Core) NuGet 包是可能的. |
||||
|
|
||||
|
> 参阅 [MongoDB 集成文档](../MongoDB.md) 获得基于MongoDB的repositories的更多信息. |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## 下一章 |
||||
|
|
||||
|
查看本教程的[下一章](Part-8.md). |
||||
|
|||||
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
@ -1,13 +1,12 @@ |
|||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
|
|
||||
namespace Volo.Abp.ApiVersioning |
namespace Volo.Abp.ApiVersioning; |
||||
|
|
||||
|
public class AbpApiVersioningAbstractionsModule : AbpModule |
||||
{ |
{ |
||||
public class AbpApiVersioningAbstractionsModule : AbpModule |
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
{ |
{ |
||||
public override void ConfigureServices(ServiceConfigurationContext context) |
context.Services.AddSingleton<IRequestedApiVersion>(NullRequestedApiVersion.Instance); |
||||
{ |
|
||||
context.Services.AddSingleton<IRequestedApiVersion>(NullRequestedApiVersion.Instance); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,7 +1,6 @@ |
|||||
namespace Volo.Abp.ApiVersioning |
namespace Volo.Abp.ApiVersioning; |
||||
|
|
||||
|
public interface IRequestedApiVersion |
||||
{ |
{ |
||||
public interface IRequestedApiVersion |
string Current { get; } |
||||
{ |
|
||||
string Current { get; } |
|
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -1,14 +1,13 @@ |
|||||
namespace Volo.Abp.ApiVersioning |
namespace Volo.Abp.ApiVersioning; |
||||
|
|
||||
|
public class NullRequestedApiVersion : IRequestedApiVersion |
||||
{ |
{ |
||||
public class NullRequestedApiVersion : IRequestedApiVersion |
public static NullRequestedApiVersion Instance = new NullRequestedApiVersion(); |
||||
{ |
|
||||
public static NullRequestedApiVersion Instance = new NullRequestedApiVersion(); |
|
||||
|
|
||||
public string Current => null; |
public string Current => null; |
||||
|
|
||||
private NullRequestedApiVersion() |
private NullRequestedApiVersion() |
||||
{ |
{ |
||||
|
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
@ -1,25 +1,24 @@ |
|||||
using Microsoft.AspNetCore.Authentication; |
using Microsoft.AspNetCore.Authentication; |
||||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
|
||||
namespace Microsoft.AspNetCore.Builder |
namespace Microsoft.AspNetCore.Builder; |
||||
|
|
||||
|
public static class ApplicationBuilderAbpJwtTokenMiddlewareExtension |
||||
{ |
{ |
||||
public static class ApplicationBuilderAbpJwtTokenMiddlewareExtension |
public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string schema = JwtBearerDefaults.AuthenticationScheme) |
||||
{ |
{ |
||||
public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string schema = JwtBearerDefaults.AuthenticationScheme) |
return app.Use(async (ctx, next) => |
||||
{ |
{ |
||||
return app.Use(async (ctx, next) => |
if (ctx.User.Identity?.IsAuthenticated != true) |
||||
{ |
{ |
||||
if (ctx.User.Identity?.IsAuthenticated != true) |
var result = await ctx.AuthenticateAsync(schema); |
||||
|
if (result.Succeeded && result.Principal != null) |
||||
{ |
{ |
||||
var result = await ctx.AuthenticateAsync(schema); |
ctx.User = result.Principal; |
||||
if (result.Succeeded && result.Principal != null) |
|
||||
{ |
|
||||
ctx.User = result.Principal; |
|
||||
} |
|
||||
} |
} |
||||
|
} |
||||
|
|
||||
await next(); |
await next(); |
||||
}); |
}); |
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,11 +1,10 @@ |
|||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
using Volo.Abp.Security; |
using Volo.Abp.Security; |
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Authentication.JwtBearer |
namespace Volo.Abp.AspNetCore.Authentication.JwtBearer; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSecurityModule))] |
||||
|
public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule |
||||
{ |
{ |
||||
[DependsOn(typeof(AbpSecurityModule))] |
|
||||
public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -1,57 +1,56 @@ |
|||||
using Volo.Abp.AspNetCore.Authentication.OAuth.Claims; |
using Volo.Abp.AspNetCore.Authentication.OAuth.Claims; |
||||
using Volo.Abp.Security.Claims; |
using Volo.Abp.Security.Claims; |
||||
|
|
||||
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims |
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims; |
||||
|
|
||||
|
public static class AbpClaimActionCollectionExtensions |
||||
{ |
{ |
||||
public static class AbpClaimActionCollectionExtensions |
public static void MapAbpClaimTypes(this ClaimActionCollection claimActions) |
||||
{ |
{ |
||||
public static void MapAbpClaimTypes(this ClaimActionCollection claimActions) |
if (AbpClaimTypes.UserName != "name") |
||||
{ |
{ |
||||
if (AbpClaimTypes.UserName != "name") |
claimActions.MapJsonKey(AbpClaimTypes.UserName, "name"); |
||||
{ |
claimActions.DeleteClaim("name"); |
||||
claimActions.MapJsonKey(AbpClaimTypes.UserName, "name"); |
claimActions.RemoveDuplicate(AbpClaimTypes.UserName); |
||||
claimActions.DeleteClaim("name"); |
|
||||
claimActions.RemoveDuplicate(AbpClaimTypes.UserName); |
|
||||
} |
|
||||
|
|
||||
if (AbpClaimTypes.Email != "email") |
|
||||
{ |
|
||||
claimActions.MapJsonKey(AbpClaimTypes.Email, "email"); |
|
||||
claimActions.DeleteClaim("email"); |
|
||||
claimActions.RemoveDuplicate(AbpClaimTypes.Email); |
|
||||
} |
|
||||
|
|
||||
if (AbpClaimTypes.EmailVerified != "email_verified") |
|
||||
{ |
|
||||
claimActions.MapJsonKey(AbpClaimTypes.EmailVerified, "email_verified"); |
|
||||
} |
|
||||
|
|
||||
if (AbpClaimTypes.PhoneNumber != "phone_number") |
|
||||
{ |
|
||||
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumber, "phone_number"); |
|
||||
} |
|
||||
|
|
||||
if (AbpClaimTypes.PhoneNumberVerified != "phone_number_verified") |
|
||||
{ |
|
||||
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumberVerified, "phone_number_verified"); |
|
||||
} |
|
||||
|
|
||||
if (AbpClaimTypes.Role != "role") |
|
||||
{ |
|
||||
claimActions.MapJsonKeyMultiple(AbpClaimTypes.Role, "role"); |
|
||||
} |
|
||||
|
|
||||
claimActions.RemoveDuplicate(AbpClaimTypes.Name); |
|
||||
} |
} |
||||
|
|
||||
public static void MapJsonKeyMultiple(this ClaimActionCollection claimActions, string claimType, string jsonKey) |
if (AbpClaimTypes.Email != "email") |
||||
{ |
{ |
||||
claimActions.Add(new MultipleClaimAction(claimType, jsonKey)); |
claimActions.MapJsonKey(AbpClaimTypes.Email, "email"); |
||||
|
claimActions.DeleteClaim("email"); |
||||
|
claimActions.RemoveDuplicate(AbpClaimTypes.Email); |
||||
} |
} |
||||
|
|
||||
public static void RemoveDuplicate(this ClaimActionCollection claimActions, string claimType) |
if (AbpClaimTypes.EmailVerified != "email_verified") |
||||
{ |
{ |
||||
claimActions.Add(new RemoveDuplicateClaimAction(claimType)); |
claimActions.MapJsonKey(AbpClaimTypes.EmailVerified, "email_verified"); |
||||
} |
} |
||||
|
|
||||
|
if (AbpClaimTypes.PhoneNumber != "phone_number") |
||||
|
{ |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumber, "phone_number"); |
||||
|
} |
||||
|
|
||||
|
if (AbpClaimTypes.PhoneNumberVerified != "phone_number_verified") |
||||
|
{ |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumberVerified, "phone_number_verified"); |
||||
|
} |
||||
|
|
||||
|
if (AbpClaimTypes.Role != "role") |
||||
|
{ |
||||
|
claimActions.MapJsonKeyMultiple(AbpClaimTypes.Role, "role"); |
||||
|
} |
||||
|
|
||||
|
claimActions.RemoveDuplicate(AbpClaimTypes.Name); |
||||
|
} |
||||
|
|
||||
|
public static void MapJsonKeyMultiple(this ClaimActionCollection claimActions, string claimType, string jsonKey) |
||||
|
{ |
||||
|
claimActions.Add(new MultipleClaimAction(claimType, jsonKey)); |
||||
|
} |
||||
|
|
||||
|
public static void RemoveDuplicate(this ClaimActionCollection claimActions, string claimType) |
||||
|
{ |
||||
|
claimActions.Add(new RemoveDuplicateClaimAction(claimType)); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,11 +1,10 @@ |
|||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
using Volo.Abp.Security; |
using Volo.Abp.Security; |
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Authentication.OAuth |
namespace Volo.Abp.AspNetCore.Authentication.OAuth; |
||||
|
|
||||
|
[DependsOn(typeof(AbpSecurityModule))] |
||||
|
public class AbpAspNetCoreAuthenticationOAuthModule : AbpModule |
||||
{ |
{ |
||||
[DependsOn(typeof(AbpSecurityModule))] |
|
||||
public class AbpAspNetCoreAuthenticationOAuthModule : AbpModule |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
} |
||||
|
|||||