@ -1,34 +1,23 @@ |
|||
# ABP |
|||
# ABP Framework |
|||
|
|||
 |
|||
[](https://www.nuget.org/packages/Volo.Abp.Core) |
|||
[](https://docs.abp.io/en/abp/latest/Nightly-Builds) |
|||
[](https://www.nuget.org/packages/Volo.Abp.Core) |
|||
|
|||
This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework. See [the announcement](https://blog.abp.io/abp/Abp-vNext-Announcement). |
|||
ABP is an **open source application framework** focused on ASP.NET Core based web application development, but also supports developing other type of applications. |
|||
|
|||
See the official [web site (abp.io)](https://abp.io/) for more information. |
|||
## Links |
|||
|
|||
### Documentation |
|||
* <a href="https://abp.io/" target="_blank">Official Web Site</a> |
|||
* <a href="https://abp.io/get-started" target="_blank">Get Started</a> |
|||
* <a href="https://abp.io/features" target="_blank">Features</a> |
|||
* <a href="https://docs.abp.io/" target="_blank">Documentation</a> |
|||
* <a href="https://docs.abp.io/en/abp/latest/Samples/Index" target="_blank">Samples</a> |
|||
* <a href="https://blog.abp.io/" target="_blank">Blog</a> |
|||
* <a href="https://stackoverflow.com/questions/tagged/abp" target="_blank">Stack overflow</a> |
|||
* <a href="https://twitter.com/abpframework" target="_blank">Twitter</a> |
|||
|
|||
See the <a href="https://docs.abp.io/" target="_blank">documentation</a>. |
|||
|
|||
### Development |
|||
|
|||
#### Pre Requirements |
|||
|
|||
- Visual Studio 2019 16.4.0+ |
|||
|
|||
#### Framework |
|||
|
|||
Framework solution is located under the `framework` folder. It has no external dependency. |
|||
|
|||
#### Modules/Templates |
|||
|
|||
[Modules](modules/) and [Templates](templates/) have their own solutions and have **local references** to the framework and each other. |
|||
|
|||
Visual Studio can not work properly with the local references out of the solution folder. When you open a module/sample solution in the Visual Studio, you may get some errors related to the dependencies. In this case, run the `dotnet restore` on the command prompt for the related solution's folder. You need to run it after you first open the solution or change a dependency. |
|||
|
|||
### Contribution |
|||
## Contribution |
|||
|
|||
ABP is an open source platform. Check [the contribution guide](docs/en/Contribution/Index.md) if you want to contribute to the project. |
|||
|
|||
@ -0,0 +1,177 @@ |
|||
# ABP Framework v3.0 Has Been Released |
|||
|
|||
We are excited to announce that the **ABP Framework** & and the **ABP Commercial** version 3.0 have been released. As different than the regular release lifecycle, which is 2-weeks, this version has taken 4-weeks with **119 [issues](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)** closed, **89 [pull requests](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** merged and **798 commits** done in the main framework [repository](https://github.com/abpframework/abp). |
|||
|
|||
Since this is a **major version**, it also includes some **breaking changes**. Don't panic, the changes are easy to adapt and will be explained below. |
|||
|
|||
> See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for a detailed change log. |
|||
|
|||
## What's New with the ABP Framework 3.0? |
|||
|
|||
This post will only cover the important features/changes. You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0). |
|||
|
|||
### Angular 10! |
|||
|
|||
Angular version 10 has just been [released](https://blog.angular.io/version-10-of-angular-now-available-78960babd41) and we've immediately migrated the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) to Angular 10! So, when you [create a new solution](https://abp.io/get-started) with the Angular UI, you will take the advantage of the new Angular. |
|||
|
|||
We've prepared a [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for the projects created an older version and want to migrate to Angular 10. |
|||
|
|||
### The Oracle Integration Package |
|||
|
|||
We had created [an integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) for the Oracle for EF Core based applications using the Devart's library since the official Oracle EF Core package was not supporting the EF Core 3.1. It now supports as a [beta release](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1). While it is in beta, we've created [the integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle), so you can use it in your application. |
|||
|
|||
See [the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) for details. |
|||
|
|||
### Azure BLOB Storage Provider |
|||
|
|||
We had created a [BLOB storing system](https://docs.abp.io/en/abp/latest/Blob-Storing) in the previous version with a file system and database storage provider. This release introduces the Azure BLOB Storage provider. See [the documentation](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). |
|||
|
|||
### Distributed Cache Bulk Operations & the New Redis Cache Package |
|||
|
|||
The [standard IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) interface of the ASP.NET Core doesn't contain **bulk operations**, like setting multiple items with a single method/server call. ABP Framework introduces new methods those can be used for bulk operations on the ABP's `IDistributedCache<T>` interface: |
|||
|
|||
* GetManyAsync / GetMany |
|||
* SetManyAsync / SetMany |
|||
|
|||
Then we needed to implement these new methods for Redis cache and [had to create](https://github.com/abpframework/abp/issues/4483) a Redis integration package which extends the Microsoft's implementation. |
|||
|
|||
These methods are also used by the ABP Framework to cache settings, features and permissions for a user/role/tenant and brings a **significant performance improvement**. |
|||
|
|||
See the [caching document](https://docs.abp.io/en/abp/latest/Caching) for details. |
|||
|
|||
### Embedded Files Manifest Support for the Virtual File System |
|||
|
|||
Virtual File System now supports to use `GenerateEmbeddedFilesManifest` in your projects to add the **real file/directory structure** of your embedded resources in the compiled assembly. So, you can now access to the files without any file name restriction (previously, some special chars like `.` in the directory names was a problem in some cases) |
|||
|
|||
See [the documentation](https://docs.abp.io/en/abp/latest/Virtual-File-System) to learn how to take the advantage of new system. |
|||
|
|||
### New Samples |
|||
|
|||
Based on the requests from the community, we've prepared two new sample applications: |
|||
|
|||
* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) demonstrates how to call stored procedures, views and functions inside a custom repository. |
|||
* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample) shows how to use the organization unit system of the [Identity module](https://docs.abp.io/en/abp/latest/Modules/Identity) for your entities. |
|||
|
|||
### DynamicStringLength & DynamicMaxLength Attributes |
|||
|
|||
The standard `StringLength` and `MaxLength` data annotation attributes is useful to validate properties of a class when the class is used as a Model or [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects). However, just like any other attribute, the length values should be literal (constant) values known at **compile time**. |
|||
|
|||
**Example: Using the `StringLength`** |
|||
|
|||
````csharp |
|||
public class CreateBookDto |
|||
{ |
|||
public const int MaxNameLength = 128; //CONSTANT! |
|||
|
|||
[StringLength(MaxNameLength)] |
|||
public string Name { get; set; } |
|||
} |
|||
```` |
|||
|
|||
ABP Framework now has the `DynamicStringLength` & `DynamicMaxLength` properties to allow to determine the lengths at **runtime**. |
|||
|
|||
**Example: Using the `DynamicStringLength`** |
|||
|
|||
````csharp |
|||
public class CreateBookDto |
|||
{ |
|||
public static int MaxNameLength { get; set; } = 128; |
|||
|
|||
[DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))] |
|||
public string Name { get; set; } |
|||
} |
|||
```` |
|||
|
|||
`DynamicStringLength` gets a class **type** and the **name** of a static property on this class to read the max length (there is also a minimum length option just like the `StringLength`). |
|||
|
|||
This allows you to get the max value from a configuration and set on the application startup (generally, in the `PreConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics)): |
|||
|
|||
````csharp |
|||
CreateBookDto.MaxNameLength = 200; |
|||
```` |
|||
|
|||
This feature is used by the [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index), so you can now override the max lengths of the properties defined in these modules. |
|||
|
|||
### Auto Distributed Events |
|||
|
|||
ABP can **automatically publish distributed events** for all entities on their create, update and delete events. That's pretty useful since you commonly interest in these basic events in a distributed system. |
|||
|
|||
This feature is **mature and [documented](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)** with the v3.0. You can easily configure some or all the entities to be published. |
|||
|
|||
### IAsyncQueryableExecuter |
|||
|
|||
When you work with LINQ extension methods, you need to call `ToListAsync()`, `FirstOrDefaultAsync()`... methods on your queries. Unfortunately, these methods are **not standard** LINQ extension methods. They are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package (or in the [MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/) package if you are using the MongoDB). |
|||
|
|||
So, you need to depend on this package if you want to use the async extension methods. That breaks the layering and makes your application or domain layer depends on the EF Core / MongoDB package. |
|||
|
|||
`IAsyncQueryableExecuter` is a service defined by the ABP Framework to **execute queries asynchronously without depending the specific provider** (EF Core / MongoDB) package. |
|||
|
|||
See [the documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) to read the details and learn our recommendations. |
|||
|
|||
### API documentation |
|||
|
|||
We are now publishing [API documents](https://docs.abp.io/api-docs/abp/2.9/api/index.html) for the ABP Framework and modules in every release. So, you can explore the ABP Framework classes much more easier than before. Click the the **API Documentation** link on the navigation menu of the [documentation](https://docs.abp.io/en/abp/latest/). |
|||
|
|||
### Package List |
|||
|
|||
We have [created a page](http://abp.io/packages) to list all the ABP-related official NuGet and NPM packages. |
|||
|
|||
### Others |
|||
|
|||
* Implemented front-channel server-side clients [sign out](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) for the identity server. |
|||
* `abp.currentUser` (`CurrentUser` service in the Angular UI) now has a `roles` array that contains role names of the current user. |
|||
* Upgraded all the NuGet and NPM package dependencies. |
|||
* Introduced `ReadOnlyAppService` base class (which has only the get operations) in addition to the `CrudAppService` base class (which has get, create, update and delete operations). |
|||
|
|||
See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for others updates. |
|||
|
|||
## What's New with the ABP Commercial 3.0? |
|||
|
|||
In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9. |
|||
|
|||
### New File Management Module |
|||
|
|||
We've created a new module that is used to store and manage files in your application. This new module is based on the [BLOB Storing system](https://docs.abp.io/en/abp/latest/Blob-Storing), so it can use different storage providers to store the file contents. |
|||
|
|||
**Example screenshot** |
|||
|
|||
 |
|||
|
|||
You can upload, download and organize files in a hierarchical folder structure. It is also compatible to multi-tenancy and you can determine total size limit for your tenants. In the next versions, we will be working on a "share" system to share files between users in a more controlled way or share your files with your customers with a public link. |
|||
|
|||
> File Management module is currently available only for the MVC / Razor Pages UI. We are working on the Angular UI and it will be released in the next versions. |
|||
|
|||
## Breaking Changes |
|||
|
|||
Since this is a major version, we've redesigned some APIs and introduced a few "easy to fix" breaking changes. |
|||
|
|||
### ABP Framework |
|||
|
|||
* Changed some **consts** in the pre-built application modules to static properties that is possible to change by your code. If you've used these consts on an attribute, then use the `DynamicStringLength` as explained above. |
|||
* Changed `ConcurrencyStamp` max length to 40. You need to **add a database migration** and update your database after upgrading the ABP Framework. |
|||
* Using `~` instead of `^` for NPM package dependencies anymore, to be more stable. |
|||
|
|||
### ABP Commercial |
|||
|
|||
* Changed file names for the application logos. Previously, it was using separate logo files for each theme, like `theme1.png`, `theme1-reverse.png`, `theme2.png`, `theme2-reverse.png` (... `6`). Now, we have only two logo files: `logo-light.png` and `logo-dark.png`. So, rename your logo in the `wwwroot/images/logo/` folder for the MVC UI and `/src/assets/images/logo/` folder for the Angular UI. |
|||
* We've added the [API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html) for the ABP Commercial too. |
|||
|
|||
> **Also, see the [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for Angular UI**. |
|||
|
|||
## Known Issues |
|||
|
|||
* 3.0.0 version has a problem with tiered architecture. See [this issue](https://github.com/abpframework/abp/pull/4564) to fix it for your application until we release the v3.0.1. |
|||
|
|||
## About the Next Versions |
|||
|
|||
We will continue to release a new minor/feature version in every two weeks. So, the next expected release date is **2020-07-16** for the version **3.1**. |
|||
|
|||
In the next few versions, we will be focused on the **Blazor UI**, as promised on [the road map](https://docs.abp.io/en/abp/latest/Road-Map). We will continue to improve the documentation, create samples, add other new features and enhancements. Follow the [ABP Framework Twitter account](https://twitter.com/abpframework) for the latest news... |
|||
|
|||
## Bonus: Articles! |
|||
|
|||
Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles: |
|||
|
|||
* [What is New in Angular 10?](https://volosoft.com/blog/what-is-new-in-angular-10) |
|||
* [Real-Time Messaging In A Distributed Architecture Using ABP, SignalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) |
|||
* [How to Use Attribute Directives to Avoid Repetition in Angular Templates](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) |
|||
|
After Width: | Height: | Size: 116 KiB |
@ -0,0 +1,72 @@ |
|||
# Getting Started with the React Native |
|||
|
|||
ABP platform provide basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated to your ABP based backends**. |
|||
|
|||
When you **create a new application** as described in the [getting started document](Getting-Started.md), the solution includes the React Native application in the `react-native` folder as default. |
|||
|
|||
## Configure Your Local IP Address |
|||
|
|||
A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application on your **local IP address**. |
|||
|
|||
{{ if Tiered == "No"}} |
|||
 |
|||
|
|||
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `SelfUrl` and `Authority` properties with your local IP address. |
|||
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. |
|||
|
|||
{{ else if Tiered == "Yes" }} |
|||
|
|||
 |
|||
|
|||
* Open the `appsettings.json` in the `.IdentityServer` folder. Replace the `localhost` address on the `SelfUrl` property with your local IP address. |
|||
* Open the `launchSettings.json` in the `.IdentityServer/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. |
|||
* Open the `appsettings.json` in the `.HttpApi.Host` folder. Replace the `localhost` address on the `Authority` property with your local IP address. |
|||
* Open the `launchSettings.json` in the `.HttpApi.Host/Properties` folder. Replace the `localhost` address on the `applicationUrl` properties with your local IP address. |
|||
|
|||
{{ end }} |
|||
|
|||
## Run the Server Application |
|||
|
|||
Run the backend application as described in the [getting started document](Getting-Started.md). |
|||
|
|||
> React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development. |
|||
|
|||
Go to the `react-native` folder, open a command line terminal, type the `yarn` command (we suggest to the [yarn](https://yarnpkg.com/) package manager while `npm install` will also work): |
|||
|
|||
```bash |
|||
yarn |
|||
``` |
|||
|
|||
* Open the `Environment.js` in the `react-native` folder and replace the `localhost` address on the `apiUrl` and `issuer` properties with your local IP address as shown below: |
|||
|
|||
 |
|||
|
|||
{{ if Tiered == "Yes" }} |
|||
|
|||
> Make sure that `issuer` matches the running address of the `.IdentityServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project. |
|||
|
|||
{{else}} |
|||
|
|||
> Make sure that `issuer` and `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project. |
|||
|
|||
{{ end }} |
|||
|
|||
Once all node modules are loaded, execute `yarn start` (or `npm start`) command: |
|||
|
|||
```bash |
|||
yarn start |
|||
``` |
|||
|
|||
Wait Expo CLI to start. Expo CLI opens the management interface on the `http://localhost:19002/` address. |
|||
|
|||
 |
|||
|
|||
In the above management interface, you can start the application with an Android emulator, an iOS simulator or a physical phone by the scan the QR code with the [Expo Client](https://expo.io/tools#client). |
|||
|
|||
> See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/), [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) documents on expo.io. |
|||
|
|||
 |
|||
|
|||
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. |
|||
|
|||
The application is up and running. You can continue to develop your application based on this startup template. |
|||
@ -0,0 +1,248 @@ |
|||
# Web Application Development Tutorial - Part 4: Integration Tests |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC","NG"], |
|||
"DB": ["EF","Mongo"] |
|||
} |
|||
```` |
|||
{{ |
|||
if UI == "MVC" |
|||
UI_Text="mvc" |
|||
else if UI == "NG" |
|||
UI_Text="angular" |
|||
else |
|||
UI_Text="?" |
|||
end |
|||
if DB == "EF" |
|||
DB_Text="Entity Framework Core" |
|||
else if DB == "Mongo" |
|||
DB_Text="MongoDB" |
|||
else |
|||
DB_Text="?" |
|||
end |
|||
}} |
|||
|
|||
## About This Tutorial |
|||
|
|||
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: |
|||
|
|||
* **{{DB_Text}}** as the ORM provider. |
|||
* **{{UI_Value}}** as the UI Framework. |
|||
|
|||
This tutorial is organized as the following parts; |
|||
|
|||
- [Part 1: Creating the project and book list page](Part-1.md) |
|||
- [Part 2: The book list page](Part-2.md) |
|||
- [Part 3: Creating, updating and deleting books](Part-3.md) |
|||
- **Part 4: Integration tests (this part)** |
|||
- [Part 5: Authorization](Part-5.md) |
|||
|
|||
### Download the Source Code |
|||
|
|||
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: |
|||
|
|||
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) |
|||
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) |
|||
|
|||
## Test Projects in the Solution |
|||
|
|||
This part covers the **server side** tests. There are several test projects in the solution: |
|||
|
|||
 |
|||
|
|||
Each project is used to test the related project. Test projects use the following libraries for testing: |
|||
|
|||
* [Xunit](https://xunit.github.io/) as the main test framework. |
|||
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as the assertion library. |
|||
* [NSubstitute](http://nsubstitute.github.io/) as the mocking library. |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
> The test projects are configured to use **SQLite in-memory** as the database. A separate database instance is created and seeded (with the data seed system) to prepare a fresh database for every test. |
|||
|
|||
{{else if DB=="Mongo"}} |
|||
|
|||
> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)** library is used to mock the MongoDB database. A separate database instance is created and seeded (with the data seed system) to prepare a fresh database for every test. |
|||
|
|||
{{end}} |
|||
|
|||
## Adding Test Data |
|||
|
|||
If you had created a data seed contributor as described in the [first part](Part-1.md), the same data will be available in your tests. So, you can skip this section. If you haven't created the seed contributor, you can use the `BookStoreTestDataSeedContributor` to seed the same data to be used in the tests below. |
|||
|
|||
## Testing the BookAppService |
|||
|
|||
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: |
|||
|
|||
````csharp |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Xunit; |
|||
|
|||
namespace Acme.BookStore.Books |
|||
{ |
|||
public class BookAppService_Tests : BookStoreApplicationTestBase |
|||
{ |
|||
private readonly IBookAppService _bookAppService; |
|||
|
|||
public BookAppService_Tests() |
|||
{ |
|||
_bookAppService = GetRequiredService<IBookAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_List_Of_Books() |
|||
{ |
|||
//Act |
|||
var result = await _bookAppService.GetListAsync( |
|||
new PagedAndSortedResultRequestDto() |
|||
); |
|||
|
|||
//Assert |
|||
result.TotalCount.ShouldBeGreaterThan(0); |
|||
result.Items.ShouldContain(b => b.Name == "1984"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of books. |
|||
* We can safely check the book "1984" by its name, because we know that this books is available in the database since we've added it in the seed data. |
|||
|
|||
Add a new test method to the `BookAppService_Tests` class that creates a new **valid** book: |
|||
|
|||
````csharp |
|||
[Fact] |
|||
public async Task Should_Create_A_Valid_Book() |
|||
{ |
|||
//Act |
|||
var result = await _bookAppService.CreateAsync( |
|||
new CreateUpdateBookDto |
|||
{ |
|||
Name = "New test book 42", |
|||
Price = 10, |
|||
PublishDate = System.DateTime.Now, |
|||
Type = BookType.ScienceFiction |
|||
} |
|||
); |
|||
|
|||
//Assert |
|||
result.Id.ShouldNotBe(Guid.Empty); |
|||
result.Name.ShouldBe("New test book 42"); |
|||
} |
|||
```` |
|||
|
|||
Add a new test that tries to create an invalid book and fails: |
|||
|
|||
````csharp |
|||
[Fact] |
|||
public async Task Should_Not_Create_A_Book_Without_Name() |
|||
{ |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _bookAppService.CreateAsync( |
|||
new CreateUpdateBookDto |
|||
{ |
|||
Name = "", |
|||
Price = 10, |
|||
PublishDate = DateTime.Now, |
|||
Type = BookType.ScienceFiction |
|||
} |
|||
); |
|||
}); |
|||
|
|||
exception.ValidationErrors |
|||
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); |
|||
} |
|||
```` |
|||
|
|||
* Since the `Name` is empty, ABP will throw an `AbpValidationException`. |
|||
|
|||
The final test class should be as shown below: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Validation; |
|||
using Xunit; |
|||
|
|||
namespace Acme.BookStore.Books |
|||
{ |
|||
public class BookAppService_Tests : BookStoreApplicationTestBase |
|||
{ |
|||
private readonly IBookAppService _bookAppService; |
|||
|
|||
public BookAppService_Tests() |
|||
{ |
|||
_bookAppService = GetRequiredService<IBookAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_List_Of_Books() |
|||
{ |
|||
//Act |
|||
var result = await _bookAppService.GetListAsync( |
|||
new PagedAndSortedResultRequestDto() |
|||
); |
|||
|
|||
//Assert |
|||
result.TotalCount.ShouldBeGreaterThan(0); |
|||
result.Items.ShouldContain(b => b.Name == "1984"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Create_A_Valid_Book() |
|||
{ |
|||
//Act |
|||
var result = await _bookAppService.CreateAsync( |
|||
new CreateUpdateBookDto |
|||
{ |
|||
Name = "New test book 42", |
|||
Price = 10, |
|||
PublishDate = System.DateTime.Now, |
|||
Type = BookType.ScienceFiction |
|||
} |
|||
); |
|||
|
|||
//Assert |
|||
result.Id.ShouldNotBe(Guid.Empty); |
|||
result.Name.ShouldBe("New test book 42"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Create_A_Book_Without_Name() |
|||
{ |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _bookAppService.CreateAsync( |
|||
new CreateUpdateBookDto |
|||
{ |
|||
Name = "", |
|||
Price = 10, |
|||
PublishDate = DateTime.Now, |
|||
Type = BookType.ScienceFiction |
|||
} |
|||
); |
|||
}); |
|||
|
|||
exception.ValidationErrors |
|||
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests: |
|||
|
|||
 |
|||
|
|||
Congratulations, the **green icons** indicates that the tests have been successfully passed! |
|||
|
|||
## The Next Part |
|||
|
|||
See the [next part](part-5.md) of this tutorial. |
|||
@ -0,0 +1,401 @@ |
|||
# Web Application Development Tutorial - Part 5: Authorization |
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC","NG"], |
|||
"DB": ["EF","Mongo"] |
|||
} |
|||
```` |
|||
{{ |
|||
if UI == "MVC" |
|||
UI_Text="mvc" |
|||
else if UI == "NG" |
|||
UI_Text="angular" |
|||
else |
|||
UI_Text="?" |
|||
end |
|||
if DB == "EF" |
|||
DB_Text="Entity Framework Core" |
|||
else if DB == "Mongo" |
|||
DB_Text="MongoDB" |
|||
else |
|||
DB_Text="?" |
|||
end |
|||
}} |
|||
|
|||
## About This Tutorial |
|||
|
|||
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: |
|||
|
|||
* **{{DB_Text}}** as the ORM provider. |
|||
* **{{UI_Value}}** as the UI Framework. |
|||
|
|||
This tutorial is organized as the following parts; |
|||
|
|||
- [Part 1: Creating the project and book list page](Part-1.md) |
|||
- [Part 2: The book list page](Part-2.md) |
|||
- [Part 3: Creating, updating and deleting books](Part-3.md) |
|||
- [Part 4: Integration tests](Part-4.md) |
|||
- **Part 5: Authorization (this part)** |
|||
|
|||
### Download the Source Code |
|||
|
|||
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: |
|||
|
|||
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) |
|||
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) |
|||
|
|||
## Permissions |
|||
|
|||
ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client. |
|||
|
|||
### Permission Names |
|||
|
|||
A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name. |
|||
|
|||
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: |
|||
|
|||
````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"; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`. |
|||
|
|||
### Permission Definitions |
|||
|
|||
You should define permissions before using them. |
|||
|
|||
Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: |
|||
|
|||
````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); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This class defines a **permission group** (to group permissions on the UI, will be seen below) and **4 permissions** inside this group. Also, **Create**, **Edit** and **Delete** are children of the `BookStorePermissions.Books.Default` permission. A child permission can be selected **only if the parent was selected**. |
|||
|
|||
Finally, edit the localization file (`en.json` under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project) to define the localization keys used above: |
|||
|
|||
````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" |
|||
```` |
|||
|
|||
> Localization key names are arbitrary and no forcing rule. But we prefer the convention used above. |
|||
|
|||
### Permission Management UI |
|||
|
|||
Once you define the permissions, you can see them on the **permission management modal**. |
|||
|
|||
Go to the *Administration -> Identity -> Roles* page, select *Permissions* action for the admin role to open the permission management modal: |
|||
|
|||
 |
|||
|
|||
Grant the permissions you want and save the modal. |
|||
|
|||
## Authorization |
|||
|
|||
Now, you can use the permissions to authorize the book management. |
|||
|
|||
### Application Layer & HTTP API |
|||
|
|||
Open the `BookAppService` class and add set the policy names as the permission names defined above: |
|||
|
|||
````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; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Added code to the constructor. Base `CrudAppService` automatically uses these permissions on the CRUD operations. This makes the **application service** secure, but also makes the **HTTP API** secure since this service is automatically used as an HTTP API as explained before (see [auto API controllers](../API/Auto-API-Controllers.md)). |
|||
|
|||
{{if UI == "MVC"}} |
|||
|
|||
### Razor Page |
|||
|
|||
While securing the HTTP API & the application service prevents unauthorized users to use the services, they can still navigate to the book management page. While they will get authorization exception when the page makes the first AJAX call to the server, we should also authorize the page for a better user experience and security. |
|||
|
|||
Open the `BookStoreWebModule` and add the following code block inside the `ConfigureServices` method: |
|||
|
|||
````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); |
|||
}); |
|||
```` |
|||
|
|||
Now, unauthorized users are redirected to the **login page**. |
|||
|
|||
#### Hide the New Book Button |
|||
|
|||
The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission. |
|||
|
|||
 |
|||
|
|||
Open the `Pages/Books/Index.cshtml` file and change the content as shown below: |
|||
|
|||
````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> |
|||
```` |
|||
|
|||
* Added `@inject IAuthorizationService AuthorizationService` to access to the authorization service. |
|||
* Used `@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))` to check the book creation permission to conditionally render the *New Book* button. |
|||
|
|||
### JavaScript Side |
|||
|
|||
Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions: |
|||
|
|||
 |
|||
|
|||
We should hide an action if the current user has not granted for the related permission. Datatables row actions has a `visible` option that can be set to `false` to hide the action item. |
|||
|
|||
Open the `Pages/Books/Index.js` inside the `Acme.BookStore.Web` project and add a `visible` option to the `Edit` action as shown below: |
|||
|
|||
````js |
|||
{ |
|||
text: l('Edit'), |
|||
visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION |
|||
action: function (data) { |
|||
editModal.open({ id: data.record.id }); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Do same for the `Delete` action: |
|||
|
|||
````js |
|||
visible: abp.auth.isGranted('BookStore.Books.Delete') |
|||
```` |
|||
|
|||
* `abp.auth.isGranted(...)` is used to check a permission that is defined before. |
|||
* `visible` could also be get a function that returns a `bool` if the value will be calculated later, based on some conditions. |
|||
|
|||
### Menu Item |
|||
|
|||
Even we have secured all the layers of the book management page, it is still visible on the main menu of the application. We should hide the menu item if the current user has no permission. |
|||
|
|||
Open the `BookStoreMenuContributor` class, find the code block below: |
|||
|
|||
````csharp |
|||
context.Menu.AddItem( |
|||
new ApplicationMenuItem( |
|||
"BooksStore", |
|||
l["Menu:BookStore"], |
|||
icon: "fa fa-book" |
|||
).AddItem( |
|||
new ApplicationMenuItem( |
|||
"BooksStore.Books", |
|||
l["Menu:Books"], |
|||
url: "/Books" |
|||
) |
|||
) |
|||
); |
|||
```` |
|||
|
|||
And replace this code block with the following: |
|||
|
|||
````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" |
|||
)); |
|||
} |
|||
```` |
|||
|
|||
{{else if UI == "NG"}} |
|||
|
|||
### Angular Guard Configuration |
|||
|
|||
First step of the UI is to prevent unauthorized users to see the "Books" menu item and enter to the book management page. |
|||
|
|||
Open the `/src/app/book/book-routing.module.ts` and replace with the following content: |
|||
|
|||
````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 {} |
|||
```` |
|||
|
|||
* Imported `AuthGuard` and `PermissionGuard` from the `@abp/ng.core`. |
|||
* Added `canActivate: [AuthGuard, PermissionGuard]` to the route definition. |
|||
|
|||
Open the `/src/app/route.provider.ts` and add `requiredPolicy: 'BookStore.Books'` to the `/books` route. The `/books` route block should be following: |
|||
|
|||
````js |
|||
{ |
|||
path: '/books', |
|||
name: '::Menu:Books', |
|||
parentName: '::Menu:BookStore', |
|||
layout: eLayoutType.application, |
|||
requiredPolicy: 'BookStore.Books', |
|||
} |
|||
```` |
|||
|
|||
### Hide the New Book Button |
|||
|
|||
The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission. |
|||
|
|||
 |
|||
|
|||
Open the `/src/app/book/book.component.html` file and replace the create button HTML content as shown below: |
|||
|
|||
````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> |
|||
```` |
|||
|
|||
* Just added `abpPermission="BookStore.Books.Create"` that hides the button if the current user has no permission. |
|||
|
|||
### Hide the Edit and Delete Actions |
|||
|
|||
Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions: |
|||
|
|||
 |
|||
|
|||
We should hide an action if the current user has not granted for the related permission. |
|||
|
|||
Open the `/src/app/book/book.component.html` file and replace the edit and delete buttons contents as shown below: |
|||
|
|||
````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)"> |
|||
{{ 'AbpAccount::Delete' | abpLocalization }} |
|||
</button> |
|||
```` |
|||
|
|||
* Added `abpPermission="BookStore.Books.Edit"` that hides the edit action if the current user has no editing permission. |
|||
* Added `abpPermission="BookStore.Books.Delete"` that hides the delete action if the current user has no delete permission. |
|||
|
|||
{{end}} |
|||
|
|||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,177 @@ |
|||
# ABP框架 v3.0 已发布 |
|||
|
|||
我们很高兴地宣布,**ABP框架**和**ABP商业版**3.0版已经发布.与常规的2周发布一个版本不同的是, 这个版本用了4周的时间.关闭了**119个[issue](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)**,合并了**89个[pull request](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** 和主框架[仓库](https://github.com/abpframework/abp)中的**798次提交**. |
|||
|
|||
由于这是一个**主要版本**,它也包括了一些**重大更改**.不要害怕,这些变化都容易对应,并且下面会详细解释. |
|||
|
|||
> 详细更改日志请查看[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0). |
|||
|
|||
## ABP框架3.0有哪些新增内容? |
|||
|
|||
这篇文章将只包括重要功能/更改.你可在[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0)上看到所有的更改. |
|||
|
|||
### Angular 10! |
|||
|
|||
Angular 10刚刚[发布](https://blog.angular.io/version-10-of-angular-now-available-78960babd41),我们就立即迁移了[启动模板](https://docs.abp.io/en/abp/latest/Startup-Templates/Application)到Angular 10!所以,当你以Angular UI[创建一个新的解决方案](https://abp.io/get-started)时, 你将获得新Angular的优点. |
|||
|
|||
我们为想要迁移到Angular 10的老版本项目准备了一份[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md). |
|||
|
|||
### Oracle集成包 |
|||
|
|||
之前我们为使用Devart库, 基于EF Core for Oracle的应用程序创建了[一个集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart), 因为官方的Oracle EF Core包还未支持EF Core 3.1. 现在它以[beta发布](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1).虽然是beta版,但我们已经创建了它的[集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle),这样你就可以在你的应用程序中使用它. |
|||
|
|||
更多细节请查看[文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle). |
|||
|
|||
### Azure BLOB存储提供器 |
|||
|
|||
在上一个版本中我们创建了一个[Blob存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing), 包含一个文件系统和数据库存储提供器. 本次发布我们引入了Azure BLOB存储提供器.请查看[文档](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). |
|||
|
|||
### 分布式缓存批量操作和新的Redis缓存包 |
|||
|
|||
ASP.NET Core的[标准IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)接口不包含**批量操作**,如使用单独方法/服务器调用设置多个项目. ABP框架引入了新的方法可用于在ABP的`IDistributedCache<T>`接口中进行批量操作: |
|||
|
|||
* GetManyAsync / GetMany |
|||
* SetManyAsync / SetMany |
|||
|
|||
然后,我们需要为Redis缓存实现这些新方法并且[必须创建](https://github.com/abpframework/abp/issues/4483)一个Redis集成包用于扩展微软的实现. |
|||
|
|||
这些方法也被ABP框架所使用, 用来缓存某个用户/角色/租户的设置,功能和权限,并且带来了**显著的性能提升**. |
|||
|
|||
更多细节请查看[缓存文档](https://docs.abp.io/en/abp/latest/Caching). |
|||
|
|||
### 为虚拟文件系统支持嵌入文件清单 |
|||
|
|||
虚拟文件系统现在支持在项目中使用`GenerateEmbeddedFilesManifest`来添加编译程序集中嵌入资源的**真实的文件/目录结构**.所以,你现在可以访问文件,而没有任何文件名的限制(以前,目录名称中的一些特殊字符,比如`.`在某些情况会有问题) |
|||
|
|||
查看[文档](https://docs.abp.io/en/abp/latest/Virtual-File-System)学习如何利用新系统的优点. |
|||
|
|||
### 新示例 |
|||
|
|||
基于社区的需求,我们准备了两个新的示例应用程序: |
|||
|
|||
* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)演示了如何在自定义仓储库中调用存储过程,视图和函数. |
|||
* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample)演示了如何为你的实体使用[Identity模块](https://docs.abp.io/en/abp/latest/Modules/Identity)的组织单元系统. |
|||
|
|||
### DynamicStringLength 和 DynamicMaxLength 属性 |
|||
|
|||
当类被用作模型或[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)时, 标准的`StringLength`和`MaxLength`数据注解属性用来验证类中的属性是很有用的. 但是, 就像任何其它的属性一样, 长度的值应该是一个**编译时**已知的字面值(常量). |
|||
|
|||
**示例: 使用`StringLength`** |
|||
|
|||
````csharp |
|||
public class CreateBookDto |
|||
{ |
|||
public const int MaxNameLength = 128; //常量! |
|||
|
|||
[StringLength(MaxNameLength)] |
|||
public string Name { get; set; } |
|||
} |
|||
```` |
|||
|
|||
ABP框架现在有`DynamicStringLength` 和 `DynamicMaxLength`属性允许在**运行时**确定长度. |
|||
|
|||
**示例: 使用`DynamicStringLength`** |
|||
|
|||
````csharp |
|||
public class CreateBookDto |
|||
{ |
|||
public static int MaxNameLength { get; set; } = 128; |
|||
|
|||
[DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))] |
|||
public string Name { get; set; } |
|||
} |
|||
```` |
|||
|
|||
`DynamicStringLength`通过类的**类型**和这个类的一个静态属性的**名字**来读取最大长度(也有一个最小长度的选项, 就像`StringLength`). |
|||
|
|||
这样就允许你从配置中读取最大值并且在应用程序启动时设置(一般在[模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics)的`PreConfigureServices`方法) |
|||
|
|||
````csharp |
|||
CreateBookDto.MaxNameLength = 200; |
|||
```` |
|||
|
|||
[预建应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)使用了这个功能,所以你现在可以覆盖这些模块中定义的属性的最大长度了. |
|||
|
|||
### 自动分布式事件 |
|||
|
|||
ABP可以为所有的实体在创建,更新和删除时**自动发布分布式事件**. 这很有用因为在分布式系统中你通常会对这些基础事件感兴趣. |
|||
|
|||
这个功能在v3.0中**已成熟并[文档化了](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)**. 你可以轻松地配置一部分或全部要发布事件的实体. |
|||
|
|||
### IAsyncQueryableExecuter |
|||
|
|||
使用LINQ扩展方法,当你在查询时需要调用`ToListAsync()`,`FirstOrDefaultAsync()`... 不幸的是,这些方法并不是**标准的**LINQ扩展方法.它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)包中(或在[MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/),如果你使用MongoDB的话). |
|||
|
|||
所以,如果你想使用异步扩展方法, 你需要依赖这个包.这样做破坏了分层,并且使你的应用程序或领域层依赖于EF Core / MongoDB的包. |
|||
|
|||
`IAsyncQueryableExecuter`是ABP框架定义的服务, 用来**不依赖特定提供器地执行异步查询**(EF Core / MongoDB包). |
|||
|
|||
查看[文档](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations)来获取细节和了解我们的建议. |
|||
|
|||
### API 文档 |
|||
|
|||
现在我们为每次发布ABP框架和模块都公开[API文档](https://docs.abp.io/api-docs/abp/3.0/api/index.html).所以,你可以更容易地查看ABP框架的类.点击[文档](https://docs.abp.io/en/abp/latest/)导航菜单中的**API文档**的链接. |
|||
|
|||
### 包列表 |
|||
|
|||
我们[创建了一个页面](http://abp.io/packages)列出所有ABP相关的官方NuGet和NPM包. |
|||
|
|||
### 其他 |
|||
|
|||
* 为Identity Server实现了前端通道的服务端客户[登出](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) |
|||
* `abp.currentUser`(Angular UI中的`CurrentUser`服务)现在有一个包含当前用户的角色名的`roles`数组. |
|||
* 升级所有的NuGet和NPM软件包的依赖. |
|||
* 引入了`ReadOnlyAppService`基类(只包含get操作), 做为`CrudAppService`基类(其中有get,create,update和delete操作)的补充. |
|||
|
|||
其他更新请查看[GitHub发行说明](https://github.com/abpframework/abp/releases/tag/3.0.0). |
|||
|
|||
## ABP商业版3.0有哪些新增内容? |
|||
|
|||
除了所有ABP框架的功能以外,ABP商业版同样在本次发布中有一些额外的功能.本节介绍3.0版本中[ABP商业版](https://commercial.abp.io/)的亮点. |
|||
|
|||
### 新的文件管理模块 |
|||
|
|||
我们创建了一个用于在应用程序中存储和管理文件的新模块.这个新模块基于[BLOB存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing),因此它可以使用不同的存储提供器来存储文件内容. |
|||
|
|||
**示例截图** |
|||
|
|||
 |
|||
|
|||
您可以上传,下载和以分层文件夹结构组织文件.它也兼容多租户,你可以为租户限制总文件大小.在未来的版本中,我们将致力于“共享”系统, 用于以一个更可控的方式在用户之间或通过公开链接与你的客户共享文件. |
|||
|
|||
> 文件管理模块是目前仅适用于MVC / Razor Pages UI.我们正工作于Angular UI,它会在未来的版本中发布. |
|||
|
|||
## 重大更改 |
|||
|
|||
由于这是一个主要版本,我们重新设计了一些API,并引入了一些"容易解决"的重大更改. |
|||
|
|||
### ABP框架 |
|||
|
|||
* 更改了一些预建应用程序模块的**常量**为静态属性, 这样你就可在你的代码中对它们修改. 如果你在属性中使用了这些常量,那么请使用上面介绍的`DynamicStringLength`. |
|||
* 更改`ConcurrencyStamp`最大长度为40. 你需要在更新ABP框架后**添加一个数据库迁移**并且更新数据库. |
|||
* NPM软件包的依赖关系使用`~`代替`^`,为了更加稳定. |
|||
|
|||
### ABP商业版 |
|||
|
|||
* 更改了应用程序标识的文件名.此前,它为每个主题使用了单独的标识文件,如`theme1.png`,`theme1-reverse.png`,`theme2.png`,`theme2-reverse.png`(...`6`).现在,我们只有两个标识文件:`logo-light.png`和`logo-dark.png`.所以,重命名MVC UI的`wwwroot/images/logo/`和Angular UI的`/src/assets/images/logo/`下的标识. |
|||
* 我们也增加了ABP商业版的[API文档](https://docs.abp.io/api-docs/commercial/3.0/api/index.html). |
|||
|
|||
> **此外,Angular UI请查看[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md)**. |
|||
|
|||
## 已知问题 |
|||
|
|||
* 3.0.0版本有一个分层架构的问题.查看[这个issue](https://github.com/abpframework/abp/pull/4564)为你的应用程序解决它,直到我们发布V3.0.1. |
|||
|
|||
## 关于未来的版本 |
|||
|
|||
我们将继续在每两个星期发布一个新的小功能版本.所以,下一个预计发布日期是**2020年7月16日**的**3.1**版本. |
|||
|
|||
在接下来的几个版本中,我们将专注于**Blazor UI**,如[路线图](https://docs.abp.io/en/abp/latest/Road-Map)中承诺的.我们将继续改进文档,创建示例,添加其他新功能和改善.关注[ABP框架Twitter账号](https://twitter.com/abpframework)获取最新消息... |
|||
|
|||
## 彩蛋: 文章! |
|||
|
|||
除了开发我们的产品,我们的团队一直在不断书写各种主题的文章/教程.你可能感兴趣的最新文章: |
|||
|
|||
* [Angular 10有哪些新增内容?](https://volosoft.com/blog/what-is-new-in-angular-10) |
|||
* [在分布式架构中使用ABP, SignalR和RabbitMQ构建实时消息系统](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) |
|||
* [如何在Angular模板中使用Attribute Directives来避免重复](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) |
|||
|
After Width: | Height: | Size: 116 KiB |
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Volo.Abp.TestApp.Testing; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB.Repositories |
|||
{ |
|||
[Collection(MongoTestCollection.Name)] |
|||
public class MongoDbAsyncQueryableProvider_Tests : TestAppTestBase<AbpMongoDbTestModule> |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly IRepository<Person, Guid> _personRepository; |
|||
private readonly MongoDbAsyncQueryableProvider _mongoDbAsyncQueryableProvider; |
|||
|
|||
public MongoDbAsyncQueryableProvider_Tests() |
|||
{ |
|||
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
_personRepository = GetRequiredService<IRepository<Person, Guid>>(); |
|||
_mongoDbAsyncQueryableProvider = GetRequiredService<MongoDbAsyncQueryableProvider>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CanExecute() |
|||
{ |
|||
_mongoDbAsyncQueryableProvider.CanExecute(_personRepository).ShouldBeTrue(); |
|||
_mongoDbAsyncQueryableProvider.CanExecute(_personRepository.WithDetails()).ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FirstOrDefaultAsync() |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
(await _mongoDbAsyncQueryableProvider.FirstOrDefaultAsync(_personRepository.Where(p => p.Name == "Douglas"))).ShouldNotBeNull(); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task AnyAsync() |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
(await _mongoDbAsyncQueryableProvider.AnyAsync(_personRepository, p => p.Name == "Douglas")).ShouldBeTrue(); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CountAsync() |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
(await _mongoDbAsyncQueryableProvider.CountAsync(_personRepository.Where(p => p.Name == "Douglas"))).ShouldBeGreaterThan(0); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task LongCountAsync() |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
(await _mongoDbAsyncQueryableProvider.LongCountAsync(_personRepository)).ShouldBeGreaterThan(0); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
} |
|||
|
|||
//More MongoDbAsyncQueryableProvider's method test.
|
|||
} |
|||
} |
|||