Browse Source

Merge branch 'dev' into maliming/linkuser

pull/5190/head
maliming 6 years ago
parent
commit
97305d6d71
  1. 2
      common.props
  2. 2
      docs/en/API/Dynamic-CSharp-API-Clients.md
  3. 2
      docs/en/Blob-Storing.md
  4. 240
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md
  5. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/abp-community-20200917.png
  6. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/account-profile-picture.jpg
  7. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazor-role-management.png
  8. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazorise-github.png
  9. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/file-management-module-angular.png
  10. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/host-features.png
  11. BIN
      docs/en/Blog-Posts/2020-09-17 v3_2_Preview/visual-studio-solution-with-blazor.png
  12. 122
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
  13. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/app-login.JPG
  14. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/auth-diagram.jpeg
  15. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-authentication.jpg
  16. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-overview.jpg
  17. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-register.JPG
  18. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-registration.jpg
  19. BIN
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-secret.jpg
  20. 188
      docs/en/Getting-Started.md
  21. 11
      docs/en/MongoDB.md
  22. 1
      docs/en/Multi-Tenancy.md
  23. 8
      docs/en/Road-Map.md
  24. 1
      docs/en/Startup-Templates/Application.md
  25. 39
      docs/en/Tutorials/Part-1.md
  26. 148
      docs/en/Tutorials/Part-10.md
  27. 162
      docs/en/Tutorials/Part-2.md
  28. 410
      docs/en/Tutorials/Part-3.md
  29. 32
      docs/en/Tutorials/Part-4.md
  30. 277
      docs/en/Tutorials/Part-5.md
  31. 35
      docs/en/Tutorials/Part-6.md
  32. 26
      docs/en/Tutorials/Part-7.md
  33. 26
      docs/en/Tutorials/Part-8.md
  34. 377
      docs/en/Tutorials/Part-9.md
  35. BIN
      docs/en/Tutorials/images/blazor-add-book-button.png
  36. BIN
      docs/en/Tutorials/images/blazor-add-books-component.png
  37. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors.png
  38. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list.png
  39. BIN
      docs/en/Tutorials/images/blazor-edit-book-action.png
  40. BIN
      docs/en/Tutorials/images/blazor-edit-book-modal.png
  41. BIN
      docs/en/Tutorials/images/blazor-menu-bookstore.png
  42. BIN
      docs/en/Tutorials/images/blazor-new-book-modal.png
  43. BIN
      docs/en/Tutorials/images/book-create-modal-with-author.png
  44. 4
      docs/en/UI/Angular/Environment.md
  45. 1
      docs/en/docs-params.json
  46. 13
      docs/zh-Hans/Getting-Started.md
  47. 13
      docs/zh-Hans/MongoDB.md
  48. 1
      docs/zh-Hans/Multi-Tenancy.md
  49. 10
      docs/zh-Hans/Tutorials/Part-1.md
  50. 2
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs
  51. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml
  52. 79
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  53. 4
      framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs
  54. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs
  55. 14
      framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
  56. 3
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs
  57. 441
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs
  58. 9
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  59. 17
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  60. 21
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  61. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json
  62. 46
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/luxon/abp.luxon.js
  63. 8258
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.js
  64. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.js.map
  65. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.min.js
  66. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.min.js.map
  67. 223
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock
  68. 3
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json
  69. 46
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js
  70. 973
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/clipboard/clipboard.js
  71. 7
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/clipboard/clipboard.min.js
  72. 8258
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.js
  73. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.js.map
  74. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.min.js
  75. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.min.js.map
  76. 2174
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/CHANGELOG.md
  77. 32
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/README.md
  78. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components.js
  79. 1386
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components.json
  80. 56
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/index.js
  81. 48
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abap.js
  82. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abap.min.js
  83. 54
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abnf.js
  84. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abnf.min.js
  85. 15
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-actionscript.js
  86. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-actionscript.min.js
  87. 19
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-ada.js
  88. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-ada.min.js
  89. 24
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-agda.js
  90. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-agda.min.js
  91. 25
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-al.js
  92. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-al.min.js
  93. 65
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-antlr4.js
  94. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-antlr4.min.js
  95. 47
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apacheconf.js
  96. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apacheconf.min.js
  97. 32
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apl.js
  98. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apl.min.js
  99. 20
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-applescript.js
  100. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-applescript.min.js

2
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>3.2.0</Version>
<Version>3.3.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

2
docs/en/API/Dynamic-CSharp-API-Clients.md

@ -162,4 +162,4 @@ context.Services.AddHttpClientProxies(
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy.
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above).
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies. See the *IHttpClientProxy Interface* section above.

2
docs/en/Blob-Storing.md

@ -20,7 +20,7 @@ The ABP Framework has already the following storage provider implementations;
* [Database](Blob-Storing-Database.md): Stores BLOBs in a database.
* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
* [Aliyun](Blob-Storing-Aliyun.md): Stores BLOBs on the [Aliyun Storage Service](https://help.aliyun.com/product/31815.html).
* [Ninio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/).
* [Minio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/).
* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the[Amazon Simple Storage Service](https://min.io/).
More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework.

240
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md

@ -0,0 +1,240 @@
# ABP Framework & ABP Commercial 3.2 RC With The New Blazor UI
We are extremely excited today to release the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/), as always) version `3.2.0-rc.1` (Release Candidate). This release includes an early preview version of the **Blazor UI** for the ABP.IO Platform.
## The Blazor UI
While the Blazor UI **should be considered as experimental** for now, it is possible to start to develop your application today.
### Fundamental Services
Currently, implemented some important framework features;
* **Authentication** through the MVC backend using the OpenId Connect authorization code flow. So, all the current login options (login, register, forgot password, external/social logins...) are supported.
* **Authorization**, using the ABP Framework **permissions** as well as the standard authorization system.
* **Localization** just works like the MVC UI.
* **Basic Theme** with top main menu.
* **Dynamic C# HTTP API proxies**, so you can directly consume your backend API by injecting the application service interfaces.
* Some other **fundamental services** like `ISettingProvider`, `IFeatureChecker`, `ICurrentUser`...
Also, the standard .net services are already available, like caching, logging, validation and much more. Since the ABP Framework is layered itself, all the non MVC UI related features are already usable for the Blazor UI.
### Pre-Built Modules
Some modules have been implemented;
* **Identity** module is pre-installed and provides **user, role and permission management**.
* **Profile management** page is implemented to allow to change password and personal settings.
### About the Blazorise Library
We've selected the [Blazorise](https://blazorise.com/) as a fundamental UI library for the Blazor UI. It already supports different HTML/CSS frameworks (like Bootstrap, Bulma, Ant Design...) and significantly increases the developer productivity.
![blazorise-github](blazorise-github.png)
We also have a good news: **[Mladen Macanović](https://github.com/stsrki)**, the creator of the Blazorise, is **joining to the core ABP Framework team** in the next weeks. We are excited to work with him to bring the power of these two successful projects together.
### The Tutorial
We are currently in progress of updating the [web application development tutorial](https://docs.abp.io/en/abp/3.2/Tutorials/Part-1) for the Blazor UI. Follow the [@abpframework](https://twitter.com/abpframework) Twitter account to get informed once it's ready.
### Get started with the Blazor UI
If you want to try the Blazor UI today, follow the instructions below.
#### Upgrade the ABP CLI
> **Known issue**: When you upgrade the ABP CLI to `3.2.0-rc.1`, you won't be able to create new solutions with a stable version ([#5453](https://github.com/abpframework/abp/issues/5453)). Downgrade to `3.1.0` back after trying the preview version, if you want to create solutions with a stable version later.
Install the latest [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI) preview version:
````bash
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.1
````
#### Create a new Solution
Then you can create a new solution using the *abp new* command:
````bash
abp new AbpBlazorDemo -u blazor --preview
````
Also specify the `-t app-pro` parameter if you are an ABP Commercial user.
> See the ABP CLI documentation for the additional options, like MongoDB database or separated authentication server.
#### Open the Solution
Open the generated solution using the latest Visual Studio 2019. You will see a solution structure like the picture below:
![visual-studio-solution-with-blazor](visual-studio-solution-with-blazor.png)
> **A fix for the 3.2.0-rc.1**
>
> There is a bug in the `3.2.0-rc.1` that prevents `HttpApi.Host` project run properly, when you try to login to the application. **Follow the steps explained in the [#5457](https://github.com/abpframework/abp/issues/5457) for the `HttpApi.Host` project** to fix it for your solution, before running it. It will be resolved with the `3.2.0-rc.2`.
#### Run the Application
* Run the `DbMigrator` project to create the database and seed the initial data.
* Run the `HttpApi.Host` project for the server side.
* Run the `Blazor` project to start the Blazor UI.
Use `admin` as the username and `1q2w3E*` as the password to login to the application.
Here, a screenshot from the role management page of the Blazor UI:
![blazor-role-management](blazor-role-management.png)
## What's New with the ABP Framework 3.2
Beside the Blazor UI, there are a lot of issues have been closed with [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1). I will highlight some of the major features and changes released with this version.
### MongoDB ACID Transactions
[MongoDB integration](https://docs.abp.io/en/abp/3.2/MongoDB) now supports multi-document transactions that comes with the MongoDB 4.x.
> Transactions are disabled for automated integration tests coming with the application startup template, since the Mongo2Go library (we use in the test projects) has a problem with the transactions. We've sent a [Pull Request](https://github.com/Mongo2Go/Mongo2Go/pull/101) to fix it and will enable the transactions again when they merge & release it.
>
> If you are upgrading an existing solution and using MongoDB, please disable transactions for the test projects by following the [Unit Of Work](https://docs.abp.io/en/abp/3.2/Unit-Of-Work) documentation.
### Kafka Integration for the Distributed Event Bus
ABP Framework's [distributed event system](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus) has been [integrated to RabbitMQ](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus-RabbitMQ-Integration) before. By the version 3.2, it has a Kafka integration package, named [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka).
See the [Kafka integration documentation](https://docs.abp.io/en/abp/3.2/Distributed-Event-Bus-Kafka-Integration) to learn how to install and configure it.
### Host Features
[ABP Feature System](https://docs.abp.io/en/abp/3.2/Features) allows you to define features in your application. Then you can enable/disable a feature dynamically on the runtime. It is generally used in a [multi-tenant](https://docs.abp.io/en/abp/3.2/Multi-Tenancy) system to restrict features for tenants, so you can charge extra money for some features in a SaaS application.
In some cases, you may want to use the same features in the host side (host is you as you are managing the tenants). For this case, we've added a "**Manage Host Features**" button to the Tenant Management page so you can open a modal dialog to select the features for the host side.
![host-features](host-features.png)
### AbpHttpClientBuilderOptions
ABP Framework provides a system to dynamically create C# proxies to consume HTTP APIs from your client applications. `AbpHttpClientBuilderOptions` is a new option class to configure the `HttpClient`s used by the proxy system.
**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to retry up to 3 times for a failed HTTP request**
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3,
i => TimeSpan.FromSeconds(Math.Pow(2, i))
)
);
});
});
}
````
See the issue [#5304](https://github.com/abpframework/abp/issues/5304) for the details.
### ABP Build Command
We are using **mono repository** approach and the [abp repository](https://github.com/abpframework/abp) has tens of solutions and hundreds of projects (the framework, modules, tooling, templates...) with all of them are referencing to each other.
It gets a significant time to build the whole repository for every Git push. To **optimize** this process, we've created the **abp build** command in the [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI):
````bash
abp build
````
We will use this command to build the abp repository or a solution inside it. However it is available to everyone in case of need.
> **Most of the people will not need it**. If you need it, see the [ABP CLI](https://docs.abp.io/en/abp/3.2/CLI) document to learn all the details and options.
### Other Features, Improvements and Changes
* Introduced the `DynamicRangeAttribute` that can be used to determine the range values on runtime, just like the `DynamicStringLengthAttribute` was introduced before.
* Improved the feature management modal for multi-tenant applications to group features on the UI and show hierarchically.
* Added `--skip-cli-version-check` option to ABP CLI to improve the performance by bypassing the online version check.
* Angular UI now redirect to MVC UI (the authentication server side) for profile management page, if the authorization code flow is used (which is the default).
* Account module profile management page is now extensible. You can implement the `IProfileManagementPageContributor` interface and register it using the `ProfileManagementPageOptions` class.
* Improvements and optimizations for the [Angular service proxy generation](https://blog.abp.io/abp/Introducing-the-Angular-Service-Proxy-Generation).
And a lot of minor improvements and bug fixes. You can see [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1) for all issues & PRs closed with this version.
## What's New with the ABP Commercial 3.2
### Breaking Changes
The new *profile picture management* feature uses the [BLOB storing](https://docs.abp.io/en/abp/3.2/Blob-Storing) system, so it needs a Storage Provider. The new **startup template comes with the [Database BLOB Provider](https://docs.abp.io/en/abp/3.2/Blob-Storing-Database) pre-installed**. You can change it if you want to use another BLOB provider (like Azure, AWS or a simple file system).
**Existing solutions must configure a BLOB provider** after upgrading to the version 3.2. Follow the [BLOB Storing document](https://docs.abp.io/en/abp/3.2/Blob-Storing#blob-storage-providers) to configure the provider yourself.
### The Blazor UI
The **experimental** Blazor UI is also available for the ABP Commercial. The [Lepton Theme](https://commercial.abp.io/themes) hasn't been implemented with this initial preview, however we are working on it with the highest priority.
You can use the [ABP Suite](https://docs.abp.io/en/commercial/latest/abp-suite/index) or the following ABP CLI command to create a new solution with the Blazor UI:
````bash
abp new AbpBlazorDemo -u blazor -t app-pro --preview
````
Please try it and provide feedback to us. Thanks in advance.
> See the instructions in the *Get started with the Blazor UI* section above to properly create and run your application.
### File Management Angular UI
Angular UI for the [File Management](https://commercial.abp.io/modules/Volo.FileManagement) module is available with the version 3.2. You can add it to your solution using the ABP Suite.
![file-management-module-angular](file-management-module-angular.png)
### Profile Picture Management
We've added profile picture management for the account module, so a user can select one of the options below for her profile picture;
* Use the default placeholder as the avatar.
* Use [Gravatar](https://gravatar.com/) service to get the picture matching the email address of the user.
* Upload a file as the profile picture.
![account-profile-picture](account-profile-picture.jpg)
### Two Factor Authentication Features
Created [features](https://docs.abp.io/en/abp/3.2/Features) and [settings](https://docs.abp.io/en/abp/3.2/Settings) to disable, enable or force to use 2FA on login for the tenants and users.
### Upgrading the ABP Suite
You can use the following command to upgrade the ABP Suite to the latest preview version:
````
abp suite update --preview
````
## Other News
### The ABP Community
**ABP Community** web site is constantly being improved and new articles are added. We will add "**commenting**" and "**rating**" features to the articles soon to increase the interactivity between the people.
![abp-community-20200917](abp-community-20200917.png)
If you have something to share with the ABP community or want to follow the project progress, please check the **[community.abp.io](https://community.abp.io/)**!
### CMS Kit Project
We are silently working on a project, named [CMS Kit](https://github.com/abpframework/abp/tree/dev/modules/cms-kit), for a few months. CMS Kit is a set of reusable CMS (Content Management System) components based on the ABP Framework. Some of the components currently being developed:
* **Comments**; Allows users to comment under something (a blog post, a document, an image... etc).
* **Reactions**; Allows users to give reactions to something (a comment, a picture... etc.) using simple emoji icons.
* **Rating**; Allows users to rate some content from 1 to 5.
* **Newsletter**; Allows you to put a newsletter box to your web site to collect emails from users.
* **Contact**; Put a form to get message from the web site visitors.
There are more planned components like articles, tags, votes, favorites, portfolio, image gallery, FAQ... etc. We will document and deploy these components when they get matured and ready to use. Some of them will be open source & free while some of them are paid (included in the [ABP Commercial](https://commercial.abp.io/) license).
## Feedback
Please try the ABP Framework 3.2.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. The planned release date for the [3.2.0 final](https://github.com/abpframework/abp/milestone/43) version is October 01.

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/abp-community-20200917.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/account-profile-picture.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazor-role-management.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/blazorise-github.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/file-management-module-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/host-features.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/Blog-Posts/2020-09-17 v3_2_Preview/visual-studio-solution-with-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

122
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md

@ -0,0 +1,122 @@
# How to Setup Azure Active Directory and Integrate Abp Angular Application
This guide demonstrates how to register an application to Azure Active Directory and integrate AzureAD to an ABP angular application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
## Authentication Flow
Abp angular applications use **Authentication Code with PKCE** (specs [here](https://tools.ietf.org/html/rfc7636)) which is the most suitable flow for spa applications by the time this article is written since implicit flow is deprecated.
The most common question is;
> Where to put OpenId connection code in angular project?
The answer is, **you don't**. Abp angular application is integrated with backend code (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below.
<img src="auth-diagram.jpeg" alt="auth-diagram" style="zoom:50%;" />
> What if I want Azure AD as my authorization server and not IdentityServer?
This means your application will be using AzureAD user store for authentication. By registering both angular app and HttpApi to AzureAD, authentication might work but **authorization won't**. Users need to be registered to Abp identity system for auditing, permissions etc. So the flow should be 3rd party registration.
## Setting up OpenId Connection
Lets start with adding OpenId connection. Open the **HttpApiHostModule.cs** and update the **ConfigureAuthentication** method as below:
```csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "NonTieredAngular";
options.JwtBackChannelHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
})
.AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ClientSecret = configuration["AzureAd:ClientSecret"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
```
> If you are using tiered (separate identity server) application, open **IdentityServerModule.cs** and add the OpenIdConnection manually to **ConfigureServices** method as following:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddOpenIdConnect("AzureOpenId", "Azure AD OpenId", options =>
{
... // Same configuration above
});
```
Now we can add AzureAD settings. Open the **appsettings.json** located in **HttpApi.Host** project (or **IdentityServer** project if you are using tiered application). Add the following;
```csharp
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<azureAd-tenant-id>",
"ClientId": "<azureAd-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc",
"ClientSecret": "<azureAd-client-secret>"
},
```
Keep on mind that **App.SelfUrl** + **AzureAd.CallbackPath** will be used in AzureAD app registration. We'll update the AzureAd settings after registering the application in Azure Portal.
## Setting up Azure Active Directory
Navigate to Manage Azure Active Directory in [azure portal](https://portal.azure.com/). Go to **App registrations** on left side menu and hit New registration.
![azure-app-registration](azure-app-registration.jpg)
Enter a name for your application and **App.SelfUrl** + **AzureAd.CallbackPath** as redirect uri then register.
<img src="azure-app-register.JPG" alt="azure-app-register" style="zoom:75%;" />
Now navigate to **Authentication** on the left menu and enable **ID tokens**.
![azure-app-authentication](azure-app-authentication.jpg)
We also need to set a client secret. Navigate to **Certificates & secrets** menu on the left and create a new client secret.
![azure-app-secret](azure-app-secret.jpg)
**Copy** the secret and update your appsettings.json **AzureAd.ClientSecret** field.
![azure-app-overview](azure-app-overview.jpg)
Also update your **AzureAd.TenantId** and **AzureAd.ClientId** fields with the information located under **Overview** menu. And that's all.
Next time you hit login, you should be seeing login screen enabled Azure AD like below.
![app-login](app-login.JPG)
# FAQ
* I am getting errors when trying to login to AzureAD.
* You can check [this article FAQ](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) which covers many errors.
* But I don't want my users to see default login screen. I want my users to login **only** from AzureAD.
* You can **mimic** this behaviour by customizing the login page and instantly trigger Azure AD provider click. For more info, you can check [this article](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd).

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/app-login.JPG

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/auth-diagram.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-authentication.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-overview.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-register.JPG

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-registration.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/azure-app-secret.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

188
docs/en/Getting-Started.md

@ -3,13 +3,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC", "Blazor", "NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
This tutorial explains how to create a new {{if UI == "MVC"}} ASP.NET Core MVC (Razor Pages) web {{else if UI == "NG"}} Angular {{end}} application using the startup template.
This tutorial explains how to create a new web application using the [application startup template](Startup-Templates/Application.md).
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
## Setup Your Development Environment
@ -20,11 +22,11 @@ First things first! Let's setup your development environment before creating the
The following tools should be installed on your development machine:
* [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>
* [Visual Studio 2019](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 or v14](https://nodejs.org/en/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (installed with Node)
* [Node v12 or v14](https://nodejs.org/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (already installed with Node)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
@ -37,123 +39,102 @@ The following tools should be installed on your development machine:
### Install the ABP CLI
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based applications.
[ABP CLI](./CLI.md) is a command line interface that is used to automate some common tasks for ABP based solutions.
> ABP CLI is a free & open source tool for the ABP framework.
First, you need to install the ABP CLI using the following command:
{{if UI == "Blazor"}}
BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. SO, YOU NEED TO INSTALL THE PREVIEW VERSION OF THE ABP CLI
First, you need to install the ABP CLI **RC.2** using the following command:
````shell
dotnet tool install -g Volo.Abp.Cli
dotnet tool install Volo.Abp.Cli -g --version 3.2.0-rc.2
````
If you've already installed, you can update it using the following command:
````shell
dotnet tool update -g Volo.Abp.Cli
dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.2
````
## Create a New Project
> This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider. For other options, please change the preference on top of this document.
### Using the ABP CLI to Create a New Project
{{else}}
Use the `new` command of the ABP CLI to create a new project:
First, you need to install the ABP CLI using the following command:
````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}}
dotnet tool install -g Volo.Abp.Cli
````
{{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case.
{{ if Tiered == "Yes" }}
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
If you've already installed, you can update it using the following command:
{{ end }}
````shell
dotnet tool update -g Volo.Abp.Cli
````
{{ end }}
{{end}}
{{ if DB == "Mongo" }}
## Create a New Project
* `-d` argument specifies the database provider, `mongodb` in this case.
Use the `new` command of the ABP CLI to create a new project:
{{ end }}
````shell
abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u blazor --preview{{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes"}}{{if UI == "MVC"}} --tiered{{else}} --separate-identity-server{{end}}{{end}}
````
{{ if Tiered == "Yes" && UI != "NG" }}
{{if UI == "Blazor"}}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
> BLAZOR UI IS ONLY AVAILABLE WITH THE PREVIEW VERSION. THIS IS WHY IT IS REQUIRED TO SET THE `--PREVIEW` OPTION.
{{ end }}
{{end}}
> You can use different level of namespaces; e.g. BookStore, Acme.BookStore or Acme.Retail.BookStore.
#### ABP CLI Commands & Options
[ABP CLI document](./CLI.md) covers all of the available commands and options for the ABP CLI. This document uses the [application startup template](Startup-Templates/Application.md) to create a new web application. See the [ABP Startup Templates](Startup-Templates/Index.md) document for other templates.
> Alternatively, you can select the "Direct Download" tab from the [ABP Framework web site](https://abp.io/get-started) to create a new solution.
## The Solution Structure
{{ if Tiered == "Yes" }}
{{ if UI == "MVC" }}
After creating your project, you will have the following solution folders & files:
![](images/solution-files-mvc.png)
You will see the following solution structure when you open the `.sln` file in the Visual Studio:
{{if DB == "Mongo"}}
![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png)
{{else}}
![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png)
{{end}}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ else if UI == "NG" }}
There are three folders in the created solution:
{{ else }}
![](images/solution-files-non-mvc.png)
* `--separate-identity-server` argument is used to separate the identity server application from the API host application. If not specified, you will have a single endpoint on the server.
* `angular` folder contains the Angular UI application.
* `aspnet-core` folder contains the backend solution.
{{ end }}
Open the `.sln` (Visual Studio solution) file under the `aspnet-core` folder:
{{ end }}
![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png)
### ABP CLI Commands & Options
{{ end }}
[ABP CLI document](./CLI.md) covers all of the available commands and options for the ABP CLI. This document uses the [application startup template](Startup-Templates/Application.md) to create a new web application. See the [ABP Startup Templates](Startup-Templates/Index.md) document for other templates.
> ###### About the projects in your solution
>
> Your solution may have slightly different structure based on your **UI**, **database** and other preferences.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and also contains unit & integration test projects.
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
{{ if DB == "EF" }}
{{ if DB == "Mongo" }}
Integration tests projects are properly configured to work with **EF Core** & **SQLite in-memory** database.
#### MongoDB Transactions
{{ else if DB == "Mongo" }}
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class:
Integration tests projects are properly configured to work with in-memory **MongoDB** database created per test (used [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) library).
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
});
```
{{ end }}
> See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
## Create the Database
### Connection String
Check the **connection string** in the `appsettings.json` file under the {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}`.Web` project{{end}}{{else if UI == "NG" }}`.HttpApi.Host` project{{end}}:
Check the **connection string** in the `appsettings.json` file under the {{if Tiered == "Yes"}}`.IdentityServer` and `.HttpApi.Host` projects{{else}}{{if UI=="MVC"}}`.Web` project{{else}}`.HttpApi.Host` project{{end}}{{end}}
{{ if DB == "EF" }}
@ -163,7 +144,7 @@ Check the **connection string** in the `appsettings.json` file under the {{if UI
}
````
The solution is configured to use **Entity Framework Core** with **MS SQL Server**. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
The solution is configured to use **Entity Framework Core** with **MS SQL Server** by default. EF Core supports [various](https://docs.microsoft.com/en-us/ef/core/providers/) database providers, so you can use any supported DBMS. See [the Entity Framework integration document](Entity-Framework-Core.md) to learn how to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
### Apply the Migrations
@ -171,7 +152,7 @@ The solution uses the [Entity Framework Core Code First Migrations](https://docs
#### Apply Migrations Using the DbMigrator
The solution comes with a `.DbMigrator` console application which applies migrations and also seed the initial data. It is useful on development as well as on production environment.
The solution comes with a `.DbMigrator` console application which applies migrations and also **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
@ -183,7 +164,7 @@ Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
#### Using EF Core Update-Database Command
@ -223,7 +204,7 @@ The solution is configured to use **MongoDB** in your local computer, so you nee
### Seed Initial Data
The solution comes with a `.DbMigrator` console application which seeds the initial data. It is useful on development as well as on production environment.
The solution comes with a `.DbMigrator` console application which **seeds the initial data**. It is useful on **development** as well as on **production** environment.
> `.DbMigrator` project has its own `appsettings.json`. So, if you have changed the connection string above, you should also change this one.
@ -235,7 +216,7 @@ Right click to the `.DbMigrator` project and select **Set as StartUp Project**
![db-migrator-output](images/db-migrator-output.png)
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
> Initial [seed data](Data-Seeding.md) creates the `admin` user in the database (with the password is `1q2w3E*`) which is then used to login to the application. So, you need to use `.DbMigrator` at least once for a new database.
{{ end }}
@ -245,27 +226,27 @@ Right click to the `.DbMigrator` project and select **Set as StartUp Project**
{{ if Tiered == "Yes" }}
Ensure that the `.IdentityServer` project is the startup project. Run the application which will open a **login** page in your browser.
1. Ensure that the `.IdentityServer` project is the startup project. Run this application that will open a **login** page in your browser.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
You can login, but you cannot enter to the main application here. This is just the authentication server.
You can login, but you cannot enter to the main application here. This is **just the authentication server**.
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
2. Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a **Swagger UI** in your browser.
![swagger-ui](images/swagger-ui.png)
This is the API application that is used by the web application.
This is the HTTP API that is used by the web application.
Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
3. Lastly, ensure that the `.Web` project is the startup project and run the application which will open a **welcome** page in your browser
![mvc-tiered-app-home](images/bookstore-home.png)
Click to the **login** button which will redirect you to the `Identity Server` to login to the application:
Click to the **login** button which will redirect you to the *authentication server* to login to the application:
![bookstore-login](images/bookstore-login.png)
{{ else }}
{{ else # Tiered != "Yes" }}
Ensure that the `.Web` project is the startup project. Run the application which will open the **login** page in your browser:
@ -273,11 +254,11 @@ Ensure that the `.Web` project is the startup project. Run the application which
![bookstore-login](images/bookstore-login.png)
{{ end }}
{{ end # Tiered }}
{{ else if UI != "MVC" }}
{{ else # UI != "MVC" }}
#### Running the HTTP API Host (server-side)
### Running the HTTP API Host (Server Side)
{{ if Tiered == "Yes" }}
@ -287,15 +268,15 @@ Ensure that the `.IdentityServer` project is the startup project. Run the applic
You can login, but you cannot enter to the main application here. This is just the authentication server.
{{ end }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
{{ if Tiered == "No" }}
{{ else # Tiered == "No" }}
Ensure that the `.HttpApi.Host` project is the startup project and run the application which will open a Swagger UI:
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
{{ end }}
{{ end # Tiered }}
![swagger-ui](images/swagger-ui.png)
@ -305,12 +286,25 @@ You can see the application APIs and test them here. Get [more info](https://swa
>
> Most of the HTTP APIs require authentication & authorization. If you want to test authorized APIs, manually go to the `/Account/Login` page, enter `admin` as the username and `1q2w3E*` as the password to login to the application. Then you will be able to execute authorized APIs too.
{{ end }}
{{ end # UI }}
{{ if UI == "NG" }}
#### Running the Angular application (client-side)
{{ if UI == "Blazor" }}
Go to the `angular` 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 in most cases)
### Running the Blazor Application (Client Side)
Ensure that the `.Blazor` project is the startup project and run the application.
> Use Ctrl+F5 in Visual Studio (instead of F5) to run the application without debugging. If you don't have a debug purpose, this will be faster.
Once the application starts, click to the **Login** link on to header, which redirects you to the authentication server to enter a username and password:
![bookstore-login](images/bookstore-login.png)
{{ else if UI == "NG" }}
### Running the Angular Application (Client Side)
Go to the `angular` 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
@ -322,11 +316,7 @@ Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
yarn start
```
This will take care of compiling your `TypeScript` code, and automatically reloading your browser.
After it finishes, `Angular Live Development Server` will be listening on localhost:4200,
open your web browser and navigate to [localhost:4200](http://localhost:4200/)
It may take a longer time for the first build. Once it finishes, it opens the Angular UI in your default browser with the [localhost:4200](http://localhost:4200/) address.
![bookstore-login](images/bookstore-login.png)
@ -338,7 +328,7 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the "[Getting Started with the React Native](Getting-Started-React-Native.md)" document to learn how to configure and run the React Native application.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## Next

11
docs/en/MongoDB.md

@ -275,6 +275,17 @@ public class BookService
> Important: You must reference to the `Volo.Abp.MongoDB` package from the project you want to access to the MongoDB API. This breaks encapsulation, but this is what you want in that case.
### Transactions
MongoDB supports multi-document transactions starting from the version 4.0 and the ABP Framework supports it. However, the [startup template](Startup-templates/Index.md) **disables** transactions by default. If your MongoDB **server** supports transactions, you can enable the it in the *YourProjectMongoDbModule* class:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
});
```
### Advanced Topics
#### Set Default Repository Classes

1
docs/en/Multi-Tenancy.md

@ -304,6 +304,7 @@ Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to dete
* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**.
* **QueryStringTenantResolveContributor**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default.
* **FormTenantResolveContributor**: Tries to find current tenant id from form parameter. Parameter name is "__tenant" by default.
* **RouteTenantResolveContributor**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route.
* **HeaderTenantResolveContributor**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default.
* **CookieTenantResolveContributor**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default.

8
docs/en/Road-Map.md

@ -4,22 +4,18 @@ You can always check the milestone planning and the prioritized backlog issues o
While we will **continue to add other exciting features**, we will work on the following major items in the **middle term**:
* **Blazor UI** for the framework and all the pre-built modules.
* **Blazor UI** for the framework and all the pre-built modules (in progress).
* **.NET 5.0**! As Microsoft has announced that the .NET 5.0 will be released in November 2020, we will prepare for this change before and move to the .NET 5.0 just after Microsoft releases it. We hope a smooth transition.
Beside this middle term goals, there are many features in the [backlog](https://github.com/abpframework/abp/milestone/2). Here, a list of some major items in the backlog;
* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a **gRPC integration** infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index))
* [#2882](https://github.com/abpframework/abp/issues/2882) / Providing a gRPC integration infrastructure (while it is [already possible](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) to create or consume gRPC endpoints for your application, we plan to create endpoints for the [standard application modules](https://docs.abp.io/en/abp/latest/Modules/Index))
* [#236](https://github.com/abpframework/abp/issues/236) Resource based authorization system
* [#1754](https://github.com/abpframework/abp/issues/1754) / Multi-lingual entities
* [#347](https://github.com/abpframework/abp/issues/347) / Support MongoDB ACID transactions
* [#633](https://github.com/abpframework/abp/issues/633) / Realtime notification system
* [#57](https://github.com/abpframework/abp/issues/57) / Built-in CQRS infrastructure
* [#4222](https://github.com/abpframework/abp/issues/4222) / Kafka integration for the Distributed Event Bus
* [#336](https://github.com/abpframework/abp/issues/336) / Health Check abstraction
* [#2532](https://github.com/abpframework/abp/issues/2532), [#2564](https://github.com/abpframework/abp/issues/2465) / CosmosDB integration with EF Core and MongoDB API
* [#1168](https://github.com/abpframework/abp/issues/1168) / Official Vue UI startup template
* [#1638](https://github.com/abpframework/abp/issues/1638) Official React startup template
* [#4223](https://github.com/abpframework/abp/issues/4223) / WebHook system
* [#162](https://github.com/abpframework/abp/issues/162) / Azure ElasticDB Integration for multitenancy
* [#2296](https://github.com/abpframework/abp/issues/2296) / Feature toggling infrastructure

1
docs/en/Startup-Templates/Application.md

@ -33,6 +33,7 @@ abp new Acme.BookStore -t app
This template provides multiple UI frameworks:
* `mvc`: ASP.NET Core MVC UI with Razor Pages (default)
* `blazor`: Blazor UI
* `angular`: Angular UI
Use `-u` or `--ui` option to specify the UI framework:

39
docs/en/Tutorials/Part-1.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the database provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
@ -217,7 +201,7 @@ Before updating the database, read the section below to learn how to seed some i
> It's good to have some initial data in the database before running the application. This section introduces the [Data Seeding](../Data-Seeding.md) system of the ABP framework. You can skip this section if you don't want to create seed data, but it is suggested to follow it to learn this useful ABP Framework feature.
Create a class deriving from the `IDataSeedContributor` in the `*.Domain` project and copy the following code:
Create a class deriving from the `IDataSeedContributor` in the `*.Domain` project by copying the following code:
```csharp
using System;
@ -278,18 +262,9 @@ Run the `Acme.BookStore.DbMigrator` application to update the database:
![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png)
{{if DB == "EF"}}
`.DbMigrator` is a console application that can be run to **migrate the database schema** and **seed the data** on **development** and **production** environments.
{{end}}
{{if DB == "Mongo"}}
While MongoDB **doesn't require** a database schema migration, it is still good to run this application since it **seeds the initial data** on the database. This application can be used on **development** and **production** environments.
{{end}}
## Create the Application Service
The application layer is separated into two projects:
@ -426,7 +401,7 @@ namespace Acme.BookStore.Books
### BookAppService
It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the Acme.BookStore.Application project:
It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the `Acme.BookStore.Application` project:
````csharp
using System;
@ -466,7 +441,7 @@ ABP can [**automagically**](../API/Auto-API-Controllers.md) configures your appl
### Swagger UI
The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application by pressing `CTRL+F5` and navigate to `https://localhost:<port>/swagger/` on your browser. (Replace `<port>` with your own port number.)
The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application ({{if UI=="MVC"}}`Acme.BookStore.Web`{{else}}`Acme.BookStore.HttpApi.Host`{{end}}) by pressing `CTRL+F5` and navigate to `https://localhost:<port>/swagger/` on your browser. Replace `<port>` with your own port number.
You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints:

148
docs/en/Tutorials/Part-10.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### 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:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Introduction
@ -66,21 +50,21 @@ public Guid AuthorId { get; set; }
{{if DB=="EF"}}
> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their entities (just like we will done below) which makes your application code simpler.
> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (just like we will done below) which makes your application code simpler.
{{end}}
## Database & Data Migration
Added a new, required `AuthorId` property to the `Book` entity. But, what about the existing books on the database? They currently don't have `AuthorId`s and this will be a problem when we try to run the application.
Added a new, required `AuthorId` property to the `Book` entity. But, **what about the existing books** on the database? They currently don't have `AuthorId`s and this will be a problem when we try to run the application.
This is a typical migration problem and the decision depends on your case;
This is a **typical migration problem** and the decision depends on your case;
* If you haven't published your application to the production yet, you can just delete existing books in the database, or you can even delete the entire database in your development environment.
* You can do it programmatically on data migration or seed phase.
* You can update the existing data programmatically on data migration or seed phase.
* You can manually handle it on the database.
We prefer to **delete the database** {{if DB=="EF"}}(run the `Drop-Database` in the *Package Manager Console*){{end}} since this is just an example project and data loss is not important. Since this topic is not related to the ABP Framework, we don't go deeper for all the scenarios.
We prefer to **delete the database** {{if DB=="EF"}}(you can run the `Drop-Database` in the *Package Manager Console*){{end}} since this is just an example project and data loss is not important. Since this topic is not related to the ABP Framework, we don't go deeper for all the scenarios.
{{if DB=="EF"}}
@ -108,6 +92,8 @@ Run the following command in the Package Manager Console (of the Visual Studio)
Add-Migration "Added_AuthorId_To_Book"
````
> Ensure that the `Acme.BookStore.EntityFrameworkCore.DbMigrations` is the Default project and the `Acme.BookStore.DbMigrator` is the startup project, as always.
This should create a new migration class with the following code in its `Up` method:
````csharp
@ -222,6 +208,8 @@ namespace Acme.BookStore
The only change is that we set the `AuthorId` properties of the `Book` entities.
> Delete existing books or delete the database before executing the `DbMigrator`. See the *Database & Data Migration* section above for more info.
{{if DB=="EF"}}
You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data.
@ -299,7 +287,7 @@ namespace Acme.BookStore.Books
}
````
This will be used in a new method will be added to the `IBookAppService`.
This will be used in a new method that will be added to the `IBookAppService`.
### IBookAppService
@ -1068,4 +1056,112 @@ Open the `/src/app/book/book.component.html` and add the following form group ju
That's all. Just run the application and try to create or edit an author.
{{end}}
{{if UI == "Blazor"}}
### The Book List
It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the `Acme.BookStore.Blazor` project and add the following `DataGridColumn` definition just after the `Name` (book name) column:
````xml
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.AuthorName)"
Caption="@L["Author"]"></DataGridColumn>
````
When you run the application, you can see the *Author* column on the table:
![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors.png)
### Create Book Modal
Add the following field to the `@code` section of the `Books.razor` file:
````csharp
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
````
And fill it in the `OnInitializedAsync` method, by adding the following code to the end of the method:
````csharp
authorList = (await AppService.GetAuthorLookupAsync()).Items;
````
The final `@code` block should be the following:
````csharp
@code
{
bool canCreateBook;
bool canEditBook;
bool canDeleteBook;
//ADDED A NEW FIELD
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
canCreateBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create);
canEditBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
//GET AUTHORS
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
}
````
Finally, add the following `Field` definition into the `ModalBody` of the *Create* modal, as the first item, before the `Name` field:
````xml
<Field>
<FieldLabel>@L["Author"]</FieldLabel>
<Select TValue="Guid" @bind-SelectedValue="@NewEntity.AuthorId">
<SelectItem TValue="Guid" Value="Guid.Empty">@L["PickAnAuthor"]</SelectItem>
@foreach (var author in authorList)
{
<SelectItem TValue="Guid" Value="@author.Id">
@author.Name
</SelectItem>
}
</Select>
</Field>
````
This requires to add a new localization key to the `en.json` file:
````js
"PickAnAuthor": "Pick an author"
````
You can run the application to see the *Author Selection* while creating a new book:
![book-create-modal-with-author](images/book-create-modal-with-author.png)
### Edit Book Modal
Add the following `Field` definition into the `ModalBody` of the *Edit* modal, as the first item, before the `Name` field:
````xml
<Field>
<FieldLabel>@L["Author"]</FieldLabel>
<Select TValue="Guid" @bind-SelectedValue="@EditingEntity.AuthorId">
@foreach (var author in authorList)
{
<SelectItem TValue="Guid" Value="@author.Id">
@author.Name
</SelectItem>
}
</Select>
</Field>
````
That's all. We are reusing the `authorList` defined for the *Create* modal.
{{end}}

162
docs/en/Tutorials/Part-2.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
@ -115,7 +99,7 @@ We will use these dynamic proxy functions in the next sections to communicate to
## Localization
Before starting to the UI development, we first want to prepare the localization texts (you normally do when needed while developing your application).
Before starting to the UI development, we first want to prepare the localization texts (you normally do this when needed while developing your application).
Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project:
@ -341,9 +325,7 @@ You can run the application! The final UI of this part is shown below:
This is a fully working, server side paged, sorted and localized table of books.
{{end}}
{{if UI == "NG"}}
{{else if UI == "NG"}}
## Install NPM packages
@ -551,7 +533,137 @@ Now you can see the final result on your browser:
![Book list final result](./images/bookstore-book-list.png)
{{end}}
{{else if UI == "Blazor"}}
## Create a Books Page
It's time to create something visible and usable! Right click to the `Pages` folder under the `Acme.BookStore.Blazor` project and add a new **razor component**, named `Books.razor`:
![blazor-add-books-component](images/blazor-add-books-component.png)
Replace the contents of this component as shown below:
````html
@page "/books"
<h2>Books</h2>
@code {
}
````
### Add Books Page to the Main Menu
Open the `BookStoreMenuContributor` class in the `Blazor` project add the following code to the end of the `ConfigureMainMenuAsync` method:
````csharp
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
)
)
);
````
Run the project, login to the application with the username `admin` and the password `1q2w3E*` and see the new menu item has been added to the main menu:
![blazor-menu-bookstore](images/blazor-menu-bookstore.png)
When you click to the Books menu item under the Book Store parent, you are being redirected to the new empty Books Page.
### Book List
We will use the [Blazorise library](https://blazorise.com/) as the UI component kit. It is a very powerful library that supports major HTML/CSS frameworks, including the Bootstrap.
ABP Framework provides a generic base class, `BlazoriseCrudPageBase<...>`, to create CRUD style pages. This base class is compatible to the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `BlazoriseCrudPageBase` to automate the standard CRUD stuff.
Open the `Books.razor` and replace the content as the following:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inherits BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<h2>@L["Books"]</h2>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType:{(int)context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
<DisplayTemplate>
@context.CreationTime.ToLongDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
````
> If you see some syntax errors, you can ignore them if your application property built and run. Visual Studio still has some bugs with Blazor.
* Inherited from the `BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass.
* Injected `IStringLocalizer<BookStoreResource>` (as `L` object) and used for localization.
While the code above pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better.
#### About the BlazoriseCrudPageBase
We will continue to benefit from the `BlazoriseCrudPageBase` for the books page. You could just inject the `IBookAppService` and perform all the server side calls yourself (thanks to the [Dynamic C# HTTP API Client Proxy](../API/Dynamic-CSharp-API-Clients.md) system of the ABP Framework). We will do it manually for the authors page to demonstrate how to call server side HTTP APIs in your Blazor applications.
## Run the Final Application
You can run the application! The final UI of this part is shown below:
![blazor-bookstore-book-list](images/blazor-bookstore-book-list.png)
This is a fully working, server side paged, sorted and localized table of books.
{{end # UI }}
## The Next Part

410
docs/en/Tutorials/Part-3.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
@ -1175,6 +1159,392 @@ Clicking the "Delete" action calls the `delete` method which then shows a confir
{{end}}
{{if UI == "Blazor"}}
## Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book. Since we've inherited from the `BlazoriseCrudPage`, we only need to develop the view part.
### Add "New Button" Button
Open the `Books.razor` and replace the `<CardHeader>` section with the following code:
````xml
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Paragraph>
</Column>
</Row>
</CardHeader>
````
This will change the card header by adding a "New book" button to the right side:
![blazor-add-book-button](images/blazor-add-book-button.png)
Now, we can add a modal that will be opened when we click to the button.
### Book Creation Modal
Open the `Books.razor` and add the following code to the end of the page:
````xml
<Modal @ref="CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="NewEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</ModalContent>
</Modal>
````
* `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` method are defined by the base class. See the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components.
That's all. Run the application and try to add a new book:
![blazor-new-book-modal](images/blazor-new-book-modal.png)
## Updating a Book
Editing a books is similar to the creating a new book.
### Actions Dropdown
Open the `Books.razor` and add the following `DataGridColumn` section inside the `DataGridColumns` as the first item:
````xml
<DataGridColumn Width="150px"
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
<DropdownItem Clicked="() => OpenEditModalAsync(context.Id)">
@L["Edit"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
</DisplayTemplate>
</DataGridColumn>
````
* `OpenEditModalAsync` is defined in the base class which takes the `Id` of the entity (book) to edit.
This adds an "Actions" dropdown to all the books inside the `DataGrid` with an `Edit` action:
![blazor-edit-book-action](images/blazor-edit-book-action.png)
### Edit Modal
We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page:
````xml
<Modal @ref="EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="EditingEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</ModalContent>
</Modal>
````
### AutoMapper Configuration
The base `BlazoriseCrudPage` uses the [object to object mapping](../Object-To-Object-Mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping.
Open the `BookStoreBlazorAutoMapperProfile` inside the `Acme.BookStore.Blazor` project and change the content as the following:
````csharp
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Blazor
{
public class BookStoreBlazorAutoMapperProfile : Profile
{
public BookStoreBlazorAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
````
* We've just added the `CreateMap<BookDto, CreateUpdateBookDto>();` line to define the mapping.
### Test the Editing Modal
You can now run the application and try to edit a book.
![blazor-edit-book-modal](images/blazor-edit-book-modal.png)
## Deleting a Book
Open the `Books.razor` page and add the following `DropdownItem` under the "Edit" action inside the "Actions" `DropdownMenu`:
````xml
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
````
* `DeleteEntityAsync` is defined in the base class.
Run the application and try to delete a book.
## Full CRUD UI Code
Here the complete code to create the book management CRUD page, that has been developed in the last two parts:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inherits BlazoriseCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Paragraph>
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn Width="150px"
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
<DropdownItem Clicked="() => OpenEditModalAsync(context.Id)">
@L["Edit"]
</DropdownItem>
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType:{(int) context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
<DisplayTemplate>
@context.CreationTime.ToLongDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="NewEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Modal @ref="EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="EditingEntity.Name" />
</Field>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</ModalContent>
</Modal>
````
{{end}}
## The Next Part
See the [next part](Part-4.md) of this tutorial.

32
docs/en/Tutorials/Part-4.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
@ -62,7 +46,9 @@ This part is also recorded as a video tutorial and **<a href="https://www.youtub
This part covers the **server side** tests. There are several test projects in the solution:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
![bookstore-test-projects-v2](./images/bookstore-test-projects-mvc.png)
> Test projects slightly differs based on your UI and Database selection. For example, if you select MongoDB, then the `Acme.BookStore.EntityFrameworkCore.Tests` will be `Acme.BookStore.MongoDB.Tests`.
Each project is used to test the related project. Test projects use the following libraries for testing:
@ -72,11 +58,11 @@ Each project is used to test the related project. Test projects use the followin
{{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.
> 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](../Data-Seeding.md)) 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.
> **[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](../Data-Seeding.md)) to prepare a fresh database for every test.
{{end}}

277
docs/en/Tutorials/Part-5.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### Download the Source Code
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
{{if UI == "MVC" && DB == "EF"}}
@ -145,6 +129,8 @@ Go to the *Administration -> Identity -> Roles* page, select *Permissions* actio
Grant the permissions you want and save the modal.
> **Tip**: New permissions are automatically granted to the admin role if you run the `Acme.BookStore.DbMigrator` application.
## Authorization
Now, you can use the permissions to authorize the book management.
@ -186,6 +172,8 @@ namespace Acme.BookStore.Books
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)).
> You will see the declarative authorization, using the `[Authorize(...)]` attribute, later while developing the author management functionality.
{{if UI == "MVC"}}
### Razor Page
@ -327,6 +315,64 @@ if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
}
````
You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return values. The final `BookStoreMenuContributor` class should be the following:
````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 Configuration
@ -412,6 +458,197 @@ Open the `/src/app/book/book.component.html` file and replace the edit and delet
{{end}}
{{else if UI == "Blazor"}}
### Authorize the Razor Component
Open the `/Pages/Books.razor` file in the `Acme.BookStore.Blazor` project and add an `Authorize` attribute just after the `@page` directive, as shown below:
````html
@page "/books"
@attribute [Authorize(BookStorePermissions.Books.Default)]
@using Acme.BookStore.Permissions
@using Microsoft.AspNetCore.Authorization
...
````
Adding this attribute prevents to enter this page if the current hasn't logged in or hasn't granted for the given permission. In case of attempt, the user is redirected to the login page.
### Show/Hide the Actions
The book management page has a *New Book* button and *Edit* and *Delete* actions for each book. We should hide these buttons/actions if the current user has not granted for the related permissions.
#### Inject the IAuthorizationService
Inject the `IAuthorizationService` into the `Books.razor`:
````csharp
@inject IAuthorizationService AuthorizationService
````
#### Get the Permissions On Initialization
Add the following code block to the end of the `Books.razor` file:
````csharp
@code
{
bool canCreateBook;
bool canEditBook;
bool canDeleteBook;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
canCreateBook =await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create);
canEditBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
}
}
````
We will use these `bool` fields to check the permissions.
> **Blazor Tip**: While adding the C# code into a `@code` block is fine for small code parts, it is suggested to use the code behind approach to develop a more maintainable code base when the code block becomes longer. We will use this approach for the authors part.
#### Hide the New Book Button
Wrap the *New Book* button by an `if` block as shown below:
````xml
@if (canCreateBook)
{
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
}
````
#### Hide the Edit/Delete Actions
As similar to the *New Book* button, we can use `if` blocks to conditionally show/hide the *Edit* and *Delete* actions:
````xml
@if (canEditBook)
{
<DropdownItem Clicked="() => OpenEditModalAsync(context.Id)">
@L["Edit"]
</DropdownItem>
}
@if (canDeleteBook)
{
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
@L["Delete"]
</DropdownItem>
}
````
#### About the Permission Caching
You can run and test the permissions. Remove a book related permission from the admin role to see the related button/action disappears from the UI.
However, ABP Framework caches the permissions of the current user in the client side. So, when you change a permission for yourself, you need to manually **refresh the page** to take the effect. If you don't refresh and try to use the prohibited action you get an HTTP 403 (forbidden) response from the server.
> Changing a permission for a role or user immediately available on the server side. So, this cache system doesn't cause any security problem.
### 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 in the `Acme.BookStore.Blazor` project, 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"
));
}
````
You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return values. The final `BookStoreMenuContributor` class should be the following:
````csharp
using System.Threading.Tasks;
using Acme.BookStore.Localization;
using Acme.BookStore.Permissions;
using Volo.Abp.UI.Navigation;
namespace Acme.BookStore.Blazor
{
public class BookStoreMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if(context.Menu.DisplayName != StandardMenus.Main)
{
return;
}
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);
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
));
}
}
}
}
````
{{end}}
## The Next Part
See the [next part](Part-6.md) of this tutorial.

35
docs/en/Tutorials/Part-6.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### 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:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Introduction
@ -242,16 +226,7 @@ This is a unique string represents the error code thrown by your application and
"BookStore:00001": "There is already an author with the same name: {name}"
````
Then open the `BookStoreDomainSharedModule` and add the following code block inside the `ConfigureServices` method:
````csharp
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("BookStore", typeof(BookStoreResource));
});
````
Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a nice error message on the UI.
Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a this message on the UI.
## IAuthorRepository

26
docs/en/Tutorials/Part-7.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### 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:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Introduction
@ -86,7 +70,7 @@ This is just like done for the `Book` entity before, so no need to explain again
## Create a new Database Migration
Open the **Package Manager Console** on Visual Studio and ensure that the **Default project** is `Acme.BookStore.EntityFrameworkCore.DbMigrations` in the Package Manager Console, as shown on the picture below. Also, set the `Acme.BookStore.Web` as the startup project (right click it on the solution explorer and click to "Set as Startup Project").
Open the **Package Manager Console** on Visual Studio and ensure that the **Default project** is `Acme.BookStore.EntityFrameworkCore.DbMigrations` in the Package Manager Console, as shown on the picture below. Also, set the `Acme.BookStore.Web` (or `Acme.BookStore.HttpApi.Host`, depending on your solution) as the **startup project** (right click it on the solution explorer and click to "Set as Startup Project").
Run the following command to create a new database migration:

26
docs/en/Tutorials/Part-8.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,9 +28,10 @@ This tutorial is organized as the following parts;
### 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:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Introduction
@ -318,7 +302,7 @@ public async Task UpdateAsync(Guid id, UpdateAuthorDto input)
{{if DB == "EF"}}
> **EF Core tip**: Entity Framework Core has a **change tracking** system and **automatically saves** any change to an entity at the end of the unit of work (You can simply think that the ABP Framework automatically calls `SaveChanges` at the end of the method). So, it will work as expected even if you don't call the `_authorRepository.UpdateAsync(...)` in the end of the method. If you don't consider to change the EF Core later, you can just remove this line.
> **EF Core Tip**: Entity Framework Core has a **change tracking** system and **automatically saves** any change to an entity at the end of the unit of work (You can simply think that the ABP Framework automatically calls `SaveChanges` at the end of the method). So, it will work as expected even if you don't call the `_authorRepository.UpdateAsync(...)` in the end of the method. If you don't consider to change the EF Core later, you can just remove this line.
{{end}}

377
docs/en/Tutorials/Part-9.md

@ -2,32 +2,15 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","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.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -45,18 +28,19 @@ This tutorial is organized as the following parts;
### 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:
This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared a few 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)
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Introduction
This part explains how to create a CRUD page for the `Author` entity introduced in previous parts.
This part explains how to create a CRUD page for the `Author` entity introduced in the previous parts.
{{if UI == "MVC"}}
## The Book List Page
## The Authors List Page
Create a new razor page, `Index.cshtml` under the `Pages/Authors` folder of the `Acme.BookStore.Web` project and change the content as given below.
@ -509,7 +493,7 @@ That's all! You can run the application and try to edit an author.
{{else if UI == "NG"}}
## The Author List Page, Create & Delete Authors
## The Author Management Page
Run the following command line to create a new module, named `AuthorModule` in the root folder of the angular application:
@ -848,6 +832,353 @@ That's all! This is a fully working CRUD page, you can create, edit and delete a
{{end}}
{{if UI == "Blazor"}}
## The Author Management Page
### Authors Razor Component
Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStore.Blazor` project with the following content:
````xml
@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@using Volo.Abp.ObjectMapping
@inject IAuthorAppService AuthorAppService
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@inject IUiMessageService UiMessageService
@inject IObjectMapper ObjectMapper
<Card>
<CardHeader>
<Row>
<Column ColumnSize="ColumnSize.Is6">
<h2>@L["Authors"]</h2>
</Column>
<Column ColumnSize="ColumnSize.Is6">
<Paragraph Alignment="TextAlignment.Right">
@if (CanCreateAuthor)
{
<Button Color="Color.Primary"
Clicked="OpenCreateAuthorModal">
@L["NewAuthor"]
</Button>
}
</Paragraph>
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="AuthorDto"
Data="AuthorList"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn Width="150px"
TItem="AuthorDto"
Field="@nameof(AuthorDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
@if (CanEditAuthor)
{
<DropdownItem Clicked="() => OpenEditAuthorModal(context)">
@L["Edit"]
</DropdownItem>
}
@if (CanDeleteAuthor)
{
<DropdownItem Clicked="() => DeleteAuthorAsync(context)">
@L["Delete"]
</DropdownItem>
}
</DropdownMenu>
</Dropdown>
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="AuthorDto"
Field="@nameof(AuthorDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="AuthorDto"
Field="@nameof(AuthorDto.BirthDate)"
Caption="@L["BirthDate"]">
<DisplayTemplate>
@context.BirthDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="CreateAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@L["NewAuthor"]</ModalTitle>
<CloseButton Clicked="CloseCreateAuthorModal" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="@NewAuthor.Name" />
</Field>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@NewAuthor.BirthDate" />
</Field>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-text="@NewAuthor.ShortBio" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Clicked="CreateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Modal @ref="EditAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<ModalHeader>
<ModalTitle>@EditingAuthor.Name</ModalTitle>
<CloseButton Clicked="CloseEditAuthorModal" />
</ModalHeader>
<ModalBody>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-text="@EditingAuthor.Name" />
</Field>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@EditingAuthor.BirthDate" />
</Field>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-text="@EditingAuthor.ShortBio" />
</Field>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Clicked="UpdateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</ModalContent>
</Modal>
````
* This code is similar to the `Books.razor`, except it doesn't inherit from the `BlazorisePageBase`, but uses its own implementation.
* Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../API/Dynamic-CSharp-API-Clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage.
* Injects the `IAuthorizationService` to check [permissions](../Authorization.md).
* Injects the `IObjectMapper` for [object to object mapping](../Object-To-Object-Mapping.md).
Create a new code behind file, `Authors.razor.cs`, under the `Pages` folder, with the following content:
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Blazorise;
using Blazorise.DataGrid;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Blazor.Pages
{
public partial class Authors
{
private IReadOnlyList<AuthorDto> AuthorList { get; set; }
private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
private int CurrentPage { get; set; }
private string CurrentSorting { get; set; }
private int TotalCount { get; set; }
private bool CanCreateAuthor { get; set; }
private bool CanEditAuthor { get; set; }
private bool CanDeleteAuthor { get; set; }
private CreateAuthorDto NewAuthor { get; set; }
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
private Modal CreateAuthorModal { get; set; }
private Modal EditAuthorModal { get; set; }
public Authors()
{
NewAuthor = new CreateAuthorDto();
EditingAuthor = new UpdateAuthorDto();
}
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
}
private async Task SetPermissionsAsync()
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
private async Task GetAuthorsAsync()
{
var result = await AuthorAppService.GetListAsync(
new GetAuthorListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting
}
);
AuthorList = result.Items;
TotalCount = (int)result.TotalCount;
}
private async Task OnDataGridReadAsync(DataGridReadDataEventArgs<AuthorDto> e)
{
CurrentSorting = e.Columns
.Where(c => c.Direction != SortDirection.None)
.Select(c => c.Field + (c.Direction == SortDirection.Descending ? " DESC" : ""))
.JoinAsString(",");
CurrentPage = e.Page - 1;
await GetAuthorsAsync();
StateHasChanged();
}
private void OpenCreateAuthorModal()
{
NewAuthor = new CreateAuthorDto();
CreateAuthorModal.Show();
}
private void CloseCreateAuthorModal()
{
CreateAuthorModal.Hide();
}
private void OpenEditAuthorModal(AuthorDto author)
{
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
EditAuthorModal.Show();
}
private async Task DeleteAuthorAsync(AuthorDto author)
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await UiMessageService.ConfirmAsync(confirmMessage))
{
return;
}
await AuthorAppService.DeleteAsync(author.Id);
await GetAuthorsAsync();
}
private void CloseEditAuthorModal()
{
EditAuthorModal.Hide();
}
private async Task CreateAuthorAsync()
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
}
private async Task UpdateAuthorAsync()
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
}
}
}
````
This class typically defines the properties and methods used by the `Authors.razor` page.
### Object Mapping
`Authors` class uses the `IObjectMapper` in the `OpenEditAuthorModal` method. So, we need to define this mapping.
Open the `BookStoreBlazorAutoMapperProfile.cs` in the `Acme.BookStore.Blazor` project and add the following mapping code in the constructor:
````csharp
CreateMap<AuthorDto, UpdateAuthorDto>();
````
You will need to declare a `using Acme.BookStore.Authors;` statement to the beginning of the file.
### Add to the Main Menu
Open the `BookStoreMenuContributor.cs` in the `Acme.BookStore.Blazor` project and add the following code to the end of the `ConfigureMainMenuAsync` method:
````csharp
if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Authors",
l["Menu:Authors"],
url: "/authors"
));
}
````
### Localizations
We should complete the localizations we've used above. Open the `en.json` file under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project and add the following entries:
````json
"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"
````
{{end}}
## The Next Part
See the [next part](Part-10.md) of this tutorial.

BIN
docs/en/Tutorials/images/blazor-add-book-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
docs/en/Tutorials/images/blazor-add-books-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list-with-authors.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Tutorials/images/blazor-menu-bookstore.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
docs/en/Tutorials/images/blazor-new-book-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Tutorials/images/book-create-modal-with-author.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

4
docs/en/UI/Angular/Environment.md

@ -54,7 +54,7 @@ Everything else will be sent to `"https://localhost:8080"`
* `rootNamespace` **(new)** : Root namespace of the related API. e.g. Acme.BookStore
## Application
## Application
```typescript
export interface Application {
@ -69,7 +69,7 @@ Everything else will be sent to `"https://localhost:8080"`
* `baseUrl`: [For detailed information](./Multi-Tenancy.md#domain-tenant-resolver)
## AuthConfig
## AuthConfig
For authentication, we use angular-oauth2-oidc. Please check their [docs](https://github.com/manfredsteyer/angular-oauth2-oidc) out

1
docs/en/docs-params.json

@ -5,6 +5,7 @@
"displayName": "UI",
"values": {
"MVC": "MVC / Razor Pages",
"Blazor": "Blazor",
"NG": "Angular"
}
},

13
docs/zh-Hans/Getting-Started.md

@ -153,6 +153,19 @@ abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构.
{{ if DB == "Mongo" }}
> [启动模板](Startup-templates/Index.md)默认在 `.MongoDB` 项目中**禁用**了工作单元事务. 如果你的MongoDB服务器支持事务,你可以手动启用工作单元的事务:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
});
```
{{ end }}
## 创建数据库
### 连接字符串

13
docs/zh-Hans/MongoDB.md

@ -233,6 +233,19 @@ public class BookService
> 重要:如果你想访问MongoDB API,你需要在你的项目中引用`Volo.Abp.MongoDB`.这会破坏封装,但在这种情况下,这就是你想要的.
#### 事务
MongoDB在4.0版本开始支持事务, ABP在3.2版本加入了对MongoDb事务的支持. 如果你升级到3.2版本,需要将[MongoDbSchemaMigrator](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MongoDbMyProjectNameDbSchemaMigrator.cs)添加到你的 `.MongoDB` 项目中.
[启动模板](Startup-templates/Index.md)默认在 `.MongoDB` 项目中**禁用**了工作单元事务. 如果你的MongoDB服务器支持事务,你可以手动启用工作单元的事务:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
});
```
#### 高级主题
##### 设置默认的仓储类

1
docs/zh-Hans/Multi-Tenancy.md

@ -305,6 +305,7 @@ Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前W
* **CurrentUserTenantResolveContributor**: 如果当前用户已登录,从当前用户的声明中获取租户Id. **出于安全考虑,应该始终将其做为第一个Contributor**.
* **QueryStringTenantResolveContributor**: 尝试从query string参数中获取当前租户,默认参数名为"__tenant".
* **FormTenantResolveContributor**: 尝试从form参数中获取当前租户,默认参数名为"__tenant".
* **RouteTenantResolveContributor**:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户.
* **HeaderTenantResolveContributor**: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant".
* **CookieTenantResolveContributor**: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant".

10
docs/zh-Hans/Tutorials/Part-1.md

@ -270,18 +270,8 @@ namespace Acme.BookStore
![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png)
{{if DB == "EF"}}
`.DbMigrator` 是一个控制台使用程序,可以在**开发**和**生产**环境**迁移数据库架构**和**初始化种子数据**.
{{end}}
{{if DB == "Mongo"}}
尽管MongoDB**不需要**数据库架构迁移,但仍建议运行该应用程序,因为它可以在数据库上播种初始数据. 可以在**开发**和**生产**环境中使用.
{{end}}
## 创建应用程序
应用程序层由两个分离的项目组成:

2
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
if (httpContext.Request == null || !httpContext.Request.Form.Any())
if (httpContext.Request == null || !httpContext.Request.HasFormContentType || !httpContext.Request.Form.Any())
{
return null;
}

10
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml

@ -9,11 +9,11 @@
</abp-component-demo-section>
<abp-component-demo-section title="Subtractive" view-path="@BordersDemoViewComponent.ViewPath">
<span abp-border="_0"></span>
<span abp-border="Top_0"></span>
<span abp-border="Right_0"></span>
<span abp-border="Bottom_0"></span>
<span abp-border="Left_0"></span>
<span class="border" abp-border="_0"></span>
<span class="border" abp-border="Top_0"></span>
<span class="border" abp-border="Right_0"></span>
<span class="border" abp-border="Bottom_0"></span>
<span class="border" abp-border="Left_0"></span>
</abp-component-demo-section>
<abp-component-demo-section title="Border color" view-path="@BordersDemoViewComponent.ViewPath">

79
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -15,19 +15,19 @@
return;
}
var getVisibilityValue = function (visibilityField, record) {
var getVisibilityValue = function (visibilityField, record, tableInstance) {
if (visibilityField === undefined) {
return true;
}
if (abp.utils.isFunction(visibilityField)) {
return visibilityField(record);
return visibilityField(record, tableInstance);
} else {
return visibilityField;
}
};
var _createDropdownItem = function (record, fieldItem) {
var _createDropdownItem = function (record, fieldItem, tableInstance) {
var $li = $('<li/>');
var $a = $('<a/>');
@ -50,14 +50,14 @@
if (!$(this).closest('li').hasClass('disabled')) {
if (fieldItem.confirmMessage) {
abp.message.confirm(fieldItem.confirmMessage({ record: record }))
abp.message.confirm(fieldItem.confirmMessage({ record: record, table: tableInstance }))
.done(function (accepted) {
if (accepted) {
fieldItem.action({ record: record });
fieldItem.action({ record: record, table: tableInstance });
}
});
} else {
fieldItem.action({ record: record });
fieldItem.action({ record: record, table: tableInstance });
}
}
});
@ -67,17 +67,60 @@
return $li;
};
var _createButtonDropdown = function (record, field) {
var _createButtonDropdown = function (record, field, tableInstance) {
if(field.items.length === 1) {
var firstItem = field.items[0];
if (!getVisibilityValue(firstItem.visible, record, tableInstance)) {
return "";
}
var $button = $('<button type="button" class="btn btn-primary"></button>');
if (firstItem.displayNameHtml) {
$button.html(firstItem.text);
} else {
if (firstItem.icon !== undefined && firstItem.icon) {
$button.append($("<i>").addClass("fa fa-" + firstItem.icon + " mr-1"));
} else if (firstItem.iconClass) {
$button.append($("<i>").addClass(firstItem.iconClass + " mr-1"));
}
$button.append(firstItem.text);
}
if (firstItem.enabled && !firstItem.enabled({ record: record, table: tableInstance })) {
$button.addClass('disabled');
}
if (firstItem.action) {
$button.click(function (e) {
e.preventDefault();
if (!$(this).hasClass('disabled')) {
if (firstItem.confirmMessage) {
abp.message.confirm(firstItem.confirmMessage({ record: record, table: tableInstance }))
.done(function (accepted) {
if (accepted) {
firstItem.action({ record: record, table: tableInstance });
}
});
} else {
firstItem.action({ record: record, table: tableInstance });
}
}
});
}
return $button;
}
var $container = $('<div/>')
.addClass('dropdown')
.addClass('action-button');
var $dropdownButton = $('<button/>');
if (field.icon !== undefined) {
if (field.icon) {
$dropdownButton.append($("<i>").addClass("fa fa-" + field.icon + " mr-1"));
}
if (field.icon !== undefined && field.icon) {
$dropdownButton.append($("<i>").addClass("fa fa-" + field.icon + " mr-1"));
} else if (field.iconClass) {
$dropdownButton.append($("<i>").addClass(field.iconClass + " mr-1"));
} else {
@ -105,14 +148,14 @@
for (var i = 0; i < field.items.length; i++) {
var fieldItem = field.items[i];
var isVisible = getVisibilityValue(fieldItem.visible, record);
var isVisible = getVisibilityValue(fieldItem.visible, record, tableInstance);
if (!isVisible) {
continue;
}
var $dropdownItem = _createDropdownItem(record, fieldItem);
var $dropdownItem = _createDropdownItem(record, fieldItem, tableInstance);
if (fieldItem.enabled && !fieldItem.enabled({ record: record })) {
if (fieldItem.enabled && !fieldItem.enabled({ record: record, table: tableInstance })) {
$dropdownItem.addClass('disabled');
}
@ -131,10 +174,10 @@
return $container;
};
var _createSingleButton = function (record, field) {
var _createSingleButton = function (record, field, tableInstance) {
$(field.element).data(record);
var isVisible = getVisibilityValue(field.visible, record);
var isVisible = getVisibilityValue(field.visible, record, tableInstance);
if (isVisible) {
return field.element;
@ -147,7 +190,7 @@
if (field.items && field.items.length > 0) {
return _createButtonDropdown(record, field, tableInstance);
} else if (field.element) {
var $singleActionButton = _createSingleButton(record, field);
var $singleActionButton = _createSingleButton(record, field, tableInstance);
if ($singleActionButton === "") {
return "";
}
@ -364,6 +407,8 @@
datatables.defaultConfigurations.scrollX = true;
datatables.defaultConfigurations.responsive = true;
datatables.defaultConfigurations.language = function () {
return {
info: localize("PagerInfo"),

4
framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs

@ -54,7 +54,7 @@ namespace Volo.Abp.BlobStoring.Aws
Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId));
Check.NotNullOrWhiteSpace(configuration.SecretAccessKey, nameof(configuration.SecretAccessKey));
return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey, configuration.Region);
return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey, region);
}
protected virtual AWSCredentials GetAwsCredentials(
@ -190,4 +190,4 @@ namespace Volo.Abp.BlobStoring.Aws
return temporaryCredentialsCache;
}
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs

@ -33,6 +33,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
private List<string> RemoveProject(List<string> solutionFileLines)
{
var projectKey = FindProjectKey(solutionFileLines);
if (projectKey == null)
{
return solutionFileLines;
}
var newSolutionFileLines = new List<string>();
var firstOccurence = true;
@ -67,7 +73,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
}
}
throw new ApplicationException($"The solution file '{_solutionFilePath}' does not contain a project '{_projectName}'");
return null;
}
}
}
}

14
framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs

@ -1,4 +1,5 @@
using System.Globalization;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace System
@ -9,7 +10,7 @@ namespace System
public static class AbpObjectExtensions
{
/// <summary>
/// Used to simplify and beautify casting an object to a type.
/// Used to simplify and beautify casting an object to a type.
/// </summary>
/// <typeparam name="T">Type to be casted</typeparam>
/// <param name="obj">Object to cast</param>
@ -29,6 +30,11 @@ namespace System
public static T To<T>(this object obj)
where T : struct
{
if (typeof(T) == typeof(Guid))
{
return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(obj.ToString());
}
return (T)Convert.ChangeType(obj, typeof(T), CultureInfo.InvariantCulture);
}
@ -42,7 +48,7 @@ namespace System
{
return list.Contains(item);
}
/// <summary>
/// Can be used to conditionally perform a function
/// on an object and return the modified or the original object.
@ -65,7 +71,7 @@ namespace System
return obj;
}
/// <summary>
/// Can be used to conditionally perform an action
/// on an object and return the original object.

3
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyRepository.cs

@ -2,12 +2,15 @@
using System.Linq;
using System.Linq.Expressions;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Linq;
namespace Volo.Abp.Domain.Repositories
{
public interface IReadOnlyRepository<TEntity> : IQueryable<TEntity>, IReadOnlyBasicRepository<TEntity>
where TEntity : class, IEntity
{
IAsyncQueryableExecuter AsyncExecuter { get; }
IQueryable<TEntity> WithDetails();
IQueryable<TEntity> WithDetails(params Expression<Func<TEntity, object>>[] propertySelectors);

441
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs

@ -0,0 +1,441 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories
{
public static class RepositoryAsyncExtensions
{
#region Contains
public static Task<bool> ContainsAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] T item,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.ContainsAsync(repository, item, cancellationToken);
}
#endregion
#region Any/All
public static Task<bool> AnyAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AnyAsync(repository, predicate, cancellationToken);
}
public static Task<bool> AllAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AllAsync(repository, predicate, cancellationToken);
}
#endregion
#region Count/LongCount
public static Task<int> CountAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.CountAsync(repository, cancellationToken);
}
public static Task<int> CountAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.CountAsync(repository, predicate, cancellationToken);
}
public static Task<long> LongCountAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LongCountAsync(repository, cancellationToken);
}
public static Task<long> LongCountAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LongCountAsync(repository, predicate, cancellationToken);
}
#endregion
#region First/FirstOrDefault
public static Task<T> FirstAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.FirstAsync(repository, cancellationToken);
}
public static Task<T> FirstAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.FirstAsync(repository, predicate, cancellationToken);
}
public static Task<T> FirstOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.FirstOrDefaultAsync(repository, cancellationToken);
}
public static Task<T> FirstOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.FirstOrDefaultAsync(repository, predicate, cancellationToken);
}
#endregion
#region Last/LastOrDefault
public static Task<T> LastAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LastAsync(repository, cancellationToken);
}
public static Task<T> LastAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LastAsync(repository, predicate, cancellationToken);
}
public static Task<T> LastOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LastOrDefaultAsync(repository, cancellationToken);
}
public static Task<T> LastOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.LastOrDefaultAsync(repository, predicate, cancellationToken);
}
#endregion
#region Single/SingleOrDefault
public static Task<T> SingleAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SingleAsync(repository, cancellationToken);
}
public static Task<T> SingleAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SingleAsync(repository, predicate, cancellationToken);
}
public static Task<T> SingleOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SingleOrDefaultAsync(repository, cancellationToken);
}
public static Task<T> SingleOrDefaultAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, bool>> predicate,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SingleOrDefaultAsync(repository, predicate, cancellationToken);
}
#endregion
#region Min
public static Task<T> MinAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.MinAsync(repository, cancellationToken);
}
public static Task<TResult> MinAsync<T, TResult>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, TResult>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.MinAsync(repository, selector, cancellationToken);
}
#endregion
#region Max
public static Task<T> MaxAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.MaxAsync(repository, cancellationToken);
}
public static Task<TResult> MaxAsync<T, TResult>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, TResult>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.MaxAsync(repository, selector, cancellationToken);
}
#endregion
#region Sum
public static Task<decimal> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, decimal>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<decimal?> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, decimal?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<int> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, int>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<int?> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, int?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<long> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, long>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<long?> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, long?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<double> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, double>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<double?> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, double?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<float> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, float>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
public static Task<float?> SumAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, float?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.SumAsync(repository, selector, cancellationToken);
}
#endregion
#region Average
public static Task<decimal> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, decimal>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<decimal?> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, decimal?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, int>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double?> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, int?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, long>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double?> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, long?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, double>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<double?> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, double?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
public static Task<float?> AverageAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
[NotNull] Expression<Func<T, float?>> selector,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.AverageAsync(repository, selector, cancellationToken);
}
#endregion
#region ToList/Array
public static Task<List<T>> ToListAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.ToListAsync(repository, cancellationToken);
}
public static Task<T[]> ToArrayAsync<T>(
[NotNull] this IReadOnlyRepository<T> repository,
CancellationToken cancellationToken = default)
where T : class, IEntity
{
return repository.AsyncExecuter.ToArrayAsync(repository, cancellationToken);
}
#endregion
}
}

9
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
@ -19,6 +20,8 @@ namespace Volo.Abp.Domain.Repositories
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public virtual Type ElementType => GetQueryable().ElementType;
@ -50,12 +53,12 @@ namespace Volo.Abp.Domain.Repositories
protected abstract IQueryable<TEntity> GetQueryable();
public abstract Task<TEntity> FindAsync(
Expression<Func<TEntity, bool>> predicate,
Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true,
CancellationToken cancellationToken = default);
public async Task<TEntity> GetAsync(
Expression<Func<TEntity, bool>> predicate,
Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -107,4 +110,4 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
}
}
}

17
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using System.Linq;
@ -22,6 +23,7 @@ using Volo.Abp.EntityFrameworkCore.EntityHistory;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.ValueConverters;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Reflection;
@ -339,7 +341,20 @@ namespace Volo.Abp.EntityFrameworkCore
{
if (TypeHelper.IsPrimitiveExtended(entryProperty.Metadata.ClrType, includeEnums: true))
{
entryProperty.CurrentValue = Convert.ChangeType(entityProperty, entryProperty.Metadata.ClrType, CultureInfo.InvariantCulture);
var conversionType = entryProperty.Metadata.ClrType;
if (TypeHelper.IsNullable(conversionType))
{
conversionType = conversionType.GetFirstGenericArgumentIfNullable();
}
if (conversionType == typeof(Guid))
{
entryProperty.CurrentValue = TypeDescriptor.GetConverter(conversionType).ConvertFromInvariantString(entityProperty.ToString());
}
else
{
entryProperty.CurrentValue = Convert.ChangeType(entityProperty, conversionType, CultureInfo.InvariantCulture);
}
}
}
}

21
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Localization;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Reflection;
@ -32,15 +34,26 @@ namespace Volo.Abp.Data
if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true))
{
return (TProperty)Convert.ChangeType(value, typeof(TProperty), CultureInfo.InvariantCulture);
var conversionType = typeof(TProperty);
if (TypeHelper.IsNullable(conversionType))
{
conversionType = conversionType.GetFirstGenericArgumentIfNullable();
}
if (conversionType == typeof(Guid))
{
return (TProperty)TypeDescriptor.GetConverter(conversionType).ConvertFromInvariantString(value.ToString());
}
return (TProperty)Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture);
}
throw new AbpException("GetProperty<TProperty> does not support non-primitive types. Use non-generic GetProperty method and handle type casting manually.");
}
public static TSource SetProperty<TSource>(
this TSource source,
string name,
this TSource source,
string name,
object value,
bool validate = true)
where TSource : IHasExtraProperties
@ -96,4 +109,4 @@ namespace Volo.Abp.Data
((IHasExtraProperties) source).SetDefaultsForExtraProperties(objectType);
}
}
}
}

2
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json

@ -3,7 +3,7 @@
"name": "asp.net",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.shared": "^3.1.2",
"@abp/aspnetcore.mvc.ui.theme.shared": "^3.2.0-rc.1",
"highlight.js": "^9.13.1"
},
"devDependencies": {}

46
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/luxon/abp.luxon.js

@ -1,46 +0,0 @@
var abp = abp || {};
(function () {
if (!luxon) {
throw "abp/luxon library requires the luxon library included to the page!";
}
/* TIMING *************************************************/
abp.timing = abp.timing || {};
var setObjectValue = function (obj, property, value) {
if (typeof property === "string") {
property = property.split('.');
}
if (property.length > 1) {
var p = property.shift();
setObjectValue(obj[p], property, value);
} else {
obj[property[0]] = value;
}
}
var getObjectValue = function (obj, property) {
return property.split('.').reduce((a, v) => a[v], obj)
}
abp.timing.convertFieldsToIsoDate = function (form, fields) {
for (var field of fields) {
var dateTime = luxon.DateTime
.fromFormat(
getObjectValue(form, field),
abp.localization.currentCulture.dateTimeFormat.shortDatePattern,
{locale: abp.localization.currentCulture.cultureName}
);
if (!dateTime.invalid) {
setObjectValue(form, field, dateTime.toFormat("yyyy-MM-dd HH:mm:ss"))
}
}
return form;
}
})(jQuery);

8258
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.js

File diff suppressed because it is too large

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.js.map

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.min.js

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/luxon/luxon.min.js.map

File diff suppressed because one or more lines are too long

223
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock

@ -2,30 +2,30 @@
# yarn lockfile v1
"@abp/aspnetcore.mvc.ui.theme.shared@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.1.2.tgz#88ea020b1d26f3e551ff81b96a848c700bed97ca"
integrity sha512-CjqinzcmES96dJ+IzEDSYO+uJE3Z3k3CZ5jGfgg97N0rYCTkt9LzYpuFHGeX4W+qI2qMurr5jstgsuLQY0cCzg==
dependencies:
"@abp/aspnetcore.mvc.ui" "~3.1.2"
"@abp/bootstrap" "~3.1.2"
"@abp/bootstrap-datepicker" "~3.1.2"
"@abp/datatables.net-bs4" "~3.1.2"
"@abp/font-awesome" "~3.1.2"
"@abp/jquery-form" "~3.1.2"
"@abp/jquery-validation-unobtrusive" "~3.1.2"
"@abp/lodash" "~3.1.2"
"@abp/luxon" "~3.1.2"
"@abp/malihu-custom-scrollbar-plugin" "~3.1.2"
"@abp/select2" "~3.1.2"
"@abp/sweetalert" "~3.1.2"
"@abp/timeago" "~3.1.2"
"@abp/toastr" "~3.1.2"
"@abp/aspnetcore.mvc.ui@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.1.2.tgz#4689ff7e4b722803f2133856f2fb39171bbfd752"
integrity sha512-jlBR+IgKuNGCerHbx6BEn2f1Xf9Vi9Dgge1/5VYzqu2MVk7dHRXHE7WYkZqpJuQKZtGMxF3c8ul2/u6uGlkmqw==
"@abp/aspnetcore.mvc.ui.theme.shared@^3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-3.2.0-rc.1.tgz#065737fbc22737557a5f5d0d945c0689546b5f07"
integrity sha512-oE90c8mnDq46+cg4bSU/GZiwMIVyBH4XA16kF8OwrL5BXGNY81UssJ1LJoxuUGOOPCl8cuWy0fnw9NvT3pH2tw==
dependencies:
"@abp/aspnetcore.mvc.ui" "~3.2.0-rc.1"
"@abp/bootstrap" "~3.2.0-rc.1"
"@abp/bootstrap-datepicker" "~3.2.0-rc.1"
"@abp/datatables.net-bs4" "~3.2.0-rc.1"
"@abp/font-awesome" "~3.2.0-rc.1"
"@abp/jquery-form" "~3.2.0-rc.1"
"@abp/jquery-validation-unobtrusive" "~3.2.0-rc.1"
"@abp/lodash" "~3.2.0-rc.1"
"@abp/luxon" "~3.2.0-rc.1"
"@abp/malihu-custom-scrollbar-plugin" "~3.2.0-rc.1"
"@abp/select2" "~3.2.0-rc.1"
"@abp/sweetalert" "~3.2.0-rc.1"
"@abp/timeago" "~3.2.0-rc.1"
"@abp/toastr" "~3.2.0-rc.1"
"@abp/aspnetcore.mvc.ui@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-3.2.0-rc.1.tgz#1a7be8ef2ba0eb71df33a3560eea7a631bd2bdcb"
integrity sha512-drLKIoH7C3I5Xw56M/cOmCTns/7PTwT0xnIdNpha6xicUFiDpHr8gtHHfK4iWCP48Wd2WErBDlv66umoc2tBtQ==
dependencies:
ansi-colors "^4.1.1"
extend-object "^1.0.0"
@ -34,144 +34,145 @@
path "^0.12.7"
rimraf "^3.0.2"
"@abp/bootstrap-datepicker@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.1.2.tgz#90ed84f4345bf8e47c0a05511fab9c63a5a3424c"
integrity sha512-sY9jXReDXCNzuSoNCiSkUD9cbdj9W/AR/glOsCb29zC7GX3XLEykuT42b8W6Kl6prS5NgfmXaX2pZOuyv0KBeQ==
"@abp/bootstrap-datepicker@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-3.2.0-rc.1.tgz#c08dea849c0c5cddcf24a83adafcf037f6971af1"
integrity sha512-u12GWi14QeFzs+LT3TVdv2JeqR79i/XkmYZETESGkI5kRKnpeqMJZ3sZ7igE/63rX9T2MF9T1RJ3+2FAsYi+ow==
dependencies:
bootstrap-datepicker "^1.9.0"
"@abp/bootstrap@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.1.2.tgz#d80e14d0cfa0b3213fafecf275520eddd9b846e2"
integrity sha512-neI766oMxwrwrsQc477koPXaT2/yy1mG4djGvnC+g2DgLeqxU7ye6dt6Lf/rKmQuZD9jtz28920wPN3N4N/HrQ==
"@abp/bootstrap@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-3.2.0-rc.1.tgz#d40eeebaf0fe16b98963f90b9bd985151f864f56"
integrity sha512-TYdMap1cYCmLjTAoZbOdgnpyi4ikak4cz0FvqCNyehS/Lflf+l+ljYEi/L2Uh7bj7va1cEEiooyeWFkvcVSbEw==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
bootstrap "^4.5.0"
bootstrap-v4-rtl "4.4.1-2"
"@abp/core@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.1.2.tgz#1d204b2f6eb3c04e036a30b416ecf77d5ce51a47"
integrity sha512-Tb/E8VHTESFoioj0gO1PB5AxZB5aIZSgAS4/V5GFne4Z399DPwkX8+XJIIRtQVzlFySAdoA37Emml/7eHCvMMQ==
"@abp/core@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/core/-/core-3.2.0-rc.1.tgz#51e12e59d0d8fca0e3ff95169f35e6a482b4f3cb"
integrity sha512-tgHm3pqnIboP6P/2IBb6IdBAiy32ZGlUgrX1Mwn64g3SKdQMtFU4pciE4Hm5fee/tJ6/UkhfRJOS8Xvrm6dihw==
dependencies:
"@abp/utils" "^3.1.2"
"@abp/utils" "^3.2.0-rc.1"
"@abp/datatables.net-bs4@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.1.2.tgz#23530f20d01776c34669c7d15df5ecd19beb6c95"
integrity sha512-dbJOTSkiyYHMW3Og9kQFqcSONNE+ElEHgfO/sfIT4Aj9hFpNHkGBTnaDTYXJ22ViFcSMWOTynvg6u+I3ZHe8qw==
"@abp/datatables.net-bs4@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs4/-/datatables.net-bs4-3.2.0-rc.1.tgz#b28c7197bb372e5033e46650dcaa65ea88e798b9"
integrity sha512-/l1EDTdQ66J/N+SnFxHT9fMzSKbOUhyRUG0DZX4numjZ5mKsrDDMLdCvlEdbXrPmAQUlmKLnCYA2YslVp/Aa+g==
dependencies:
"@abp/datatables.net" "~3.1.2"
"@abp/datatables.net" "~3.2.0-rc.1"
datatables.net-bs4 "^1.10.21"
"@abp/datatables.net@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.1.2.tgz#34a0718bdac6802336484c4011067cf665cdd413"
integrity sha512-TJWp/+sMZIQqEy1F7Zb4/kSvrUQoC3AfG5qrSFIRmKyZMEsuUzTLugtfYWUi0BKEHdc454ErWrGuD1MCMIzEsQ==
"@abp/datatables.net@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-3.2.0-rc.1.tgz#6324695b4f25e67f5f78b3df0fad941d9bb8631c"
integrity sha512-J5Oy7hUgR2Ni7UDjyATqnWyxMoYzUMUBt334g9s7FHH6mLp++FrMIM963x4U54LMxLvfV9o3ku30Aun21dSTuw==
dependencies:
"@abp/jquery" "~3.1.2"
"@abp/jquery" "~3.2.0-rc.1"
datatables.net "^1.10.21"
"@abp/font-awesome@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.1.2.tgz#1b14dc64cfe5f903bbcc25a4d2c88df54b561e87"
integrity sha512-33C0lAioBWcIs5wU+M7+SF06+Yvoq9BKVDjPx1R8TndphOfXU6IRneZvHDCLig76EY/BNcEYEREbv33Wqnie9Q==
"@abp/font-awesome@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-3.2.0-rc.1.tgz#f00c3dadc3a8ac3af45f3dc75e5f6a0354728216"
integrity sha512-2hhWHjDdmLp9uWPZqwzbDuEmoffJWtxVQ2WrZ/ooj19g0VQ0h2rfQWznLJCuh0ls9JGFLPxkYK6IhsRZmWbomw==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
"@fortawesome/fontawesome-free" "^5.13.0"
"@abp/jquery-form@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.1.2.tgz#8d516356e443e0ae8f1367ce3090e84a5afc2867"
integrity sha512-UCWZLnu7qETSeX69Wui0VtMpEiPrRv2+JsoJKsS3XcqXehNGJktMc5H5sNCDgPaQXzE5SY6hWnfLBj88014T6g==
"@abp/jquery-form@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-3.2.0-rc.1.tgz#85c0b84be31f6c68699b7463504c6fcfb7ce7a17"
integrity sha512-aug8wokVrFwRxwc3a01Y69+BcbzwOqWdXJPboA3cIiyfzUY3hxFqzW+Rssvb6Ax0mc9iHqmWf5+nMTgc5INfZQ==
dependencies:
"@abp/jquery" "~3.1.2"
"@abp/jquery" "~3.2.0-rc.1"
jquery-form "^4.3.0"
"@abp/jquery-validation-unobtrusive@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.1.2.tgz#64e6c2f287c21a4a569f4152936e84afa4a66c50"
integrity sha512-soZjLE2ygApecWs9DqKh7hRkf067esvLXSQOwUvw5WdeHOWccgAu/GxzAeFzwNA3CpfjFGoHqNNeSnETLLVEUQ==
"@abp/jquery-validation-unobtrusive@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.0-rc.1.tgz#b004d95b832fdddec2e6810899dcd198b6d9cdc4"
integrity sha512-XDWheFqVLVXVJr8Mi1xY+shdUmOdhTGOnTkUDW564YhOvDOijitbSG+5YAgQKCuamqbS5jDooiRtvTjo79e6Dw==
dependencies:
"@abp/jquery-validation" "~3.1.2"
"@abp/jquery-validation" "~3.2.0-rc.1"
jquery-validation-unobtrusive "^3.2.11"
"@abp/jquery-validation@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.1.2.tgz#a34fea85a67e2552d9ee278ecc22b5740bbcc5eb"
integrity sha512-RrzdY3nay9/OHpd58DyJ9oYnwr3++6gJoI135QMYz+otlJnUjrM6bFfqSeWQaetvbiCVy7nOVYVyqIFO7KgQUw==
"@abp/jquery-validation@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-3.2.0-rc.1.tgz#391c466fa1ef2477f42512d22e7ee2992b001a98"
integrity sha512-M4uh4YwMWKpBC5a4qJ221RMeo9MLTAhNFd+95d/GkXhNPTSxawFTM57VrOURYo0h1iFk0hmooDeZ2WofWQZTWw==
dependencies:
"@abp/jquery" "~3.1.2"
"@abp/jquery" "~3.2.0-rc.1"
jquery-validation "^1.19.2"
"@abp/jquery@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.1.2.tgz#f890d872d2d0287e7c195801f8c51c88c64602a0"
integrity sha512-Pk3Ybx/S88rFThDUlziiKIq7uKwiu2J1xYuzJTlP+xc1IgIcJNkBONl07KFdoHvctOtFtdf4mVN7SGYmNePK4g==
"@abp/jquery@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-3.2.0-rc.1.tgz#5ad6b59d532f95c5ba694e7b7f9b4f242f19cd33"
integrity sha512-TzDYmjGsB85fyol6k5eaneb6e2pcbBFePR8Ko9vuFlFqwmX/ZtPQhHHqNZ8vtOSQ61qTJFDlqj0fdoOXUq+J3w==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
jquery "~3.5.1"
"@abp/lodash@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.1.2.tgz#07ab3001e463f1c452865ff98e051251322c7061"
integrity sha512-0XMWCKQJyPVpn22OVEYlQR7+zEn1yz4sTb5ilUFhLOSTHXEOJXNVzxEYlGYCPUjxGZFvZH+vdeurSjXdIOOB5w==
"@abp/lodash@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-3.2.0-rc.1.tgz#17d8cf6dc11fcc41620abd68a0e00ee60003b71b"
integrity sha512-3OD5MfFIkos1CxMXVZkPWl72wffe6lRB0QLxRioLQs7YMgUXD8QqScSPmC7rcqa9DLi6+UyvoJ3patfr9BBoYQ==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
lodash "^4.17.15"
"@abp/luxon@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.1.2.tgz#ed3adfd1e7e46939c2ac456d44d9987b0d923334"
integrity sha512-fZj3yIT1H+I6vcRxMONw9VRYLWfvzdzLeDZ1xlkXsC4NqI7cK6vchYNTqZlTg4zH6DNYNg6+WOoxQ8ao8a00QA==
"@abp/luxon@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-3.2.0-rc.1.tgz#d35ae340dc8bf1bb571c058164a4d80280fd0038"
integrity sha512-qZELhRYoTmnmUdP9b5hfzNuMvXg7WVc3gGWErNcVceEL4wjA4DHnLZvEtUsbtBl3X1lKnY+d7yT2tb5uI0U6aA==
dependencies:
"@abp/core" "~3.2.0-rc.1"
luxon "^1.24.1"
"@abp/malihu-custom-scrollbar-plugin@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.1.2.tgz#3556912136a85289b0b237e5bb7368b08f5efac8"
integrity sha512-b0uU/PwNOQaAurEkEZ4e4103pGomeKduo3Rg0hsyN7eeUrTzNw2tq1AQlgGRxw2v1pzyOvzNd0v14jyERE947g==
"@abp/malihu-custom-scrollbar-plugin@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-3.2.0-rc.1.tgz#2de8f0e18c6f1244363d237d938f678c262eb830"
integrity sha512-JKZ2e8PGe4QMF2v/XCtw95GSKCa5bifNYi5ttDVfrjHn4tdxWvFYP14NiX05hF2l+aMcNM0rE7KTftV3dNwGuw==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
malihu-custom-scrollbar-plugin "^3.1.5"
"@abp/select2@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.1.2.tgz#05997301b2800249ed560c4d24c9bfcfee68f99a"
integrity sha512-PyIiCGkvRUEfSyVF4M7p4BzHgaUG7lysYPop0R1tGiJcEVSxkXvhCJnEODPYiqvVjdFFKVz8Ni1R+xpEIEL/2w==
"@abp/select2@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-3.2.0-rc.1.tgz#c5d0feabfb1b6c598b2eda5b1b1ab94b7afe5bfa"
integrity sha512-PmwUQLF1aekNDcsHG0SA5sXPZ/6kH+0/apy15OQL7M7QzMi6v6rKkGakZeKhwz3KSHRwRLe0tpWl5jrrA9xVig==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
select2 "^4.0.13"
"@abp/sweetalert@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.1.2.tgz#18b062b0d5c5d5c78b4f8d13186587ab87d46464"
integrity sha512-MJXLyrqPgxiy5NViyVvo6iasDoO3Jfdb3+4Jf+5PFlR7pZd1MmhmvNWn/rc5yFcCwOKvJkXVaWYaT9UsvZPxIA==
"@abp/sweetalert@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/sweetalert/-/sweetalert-3.2.0-rc.1.tgz#608f42ea97dca547b573370588c47a8fc69dcb07"
integrity sha512-U+gNuOdIRMNBB7fqKUHyDo0RCtV6WHXLLhMsP+aM/ELrd9PjvEY6Irb3RMToI2oqmym1yFqzs2gz0F7jUV6woQ==
dependencies:
"@abp/core" "~3.1.2"
"@abp/core" "~3.2.0-rc.1"
sweetalert "^2.1.2"
"@abp/timeago@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.1.2.tgz#120cdd99a9dde1fbd1276571fdc5f415eee2941e"
integrity sha512-C3d1RLMEUBctPA/ASpqt9+gumsziYu0De7PWdmCmkPC7q23JpZaOmvWffaTpP2QoInyQXUPo51obOmEaC23InQ==
"@abp/timeago@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-3.2.0-rc.1.tgz#311b9b987b3bd2a6363de2066093f48dc856041e"
integrity sha512-IVs+nXsWwLh4jildLKV5EvSYwQFfo3JQTmdmhwtMxXTjw7zDKlNRJirKv7CuS+a8Woprm0OoZsvjgLaD8xXOOg==
dependencies:
"@abp/jquery" "~3.1.2"
"@abp/jquery" "~3.2.0-rc.1"
timeago "^1.6.7"
"@abp/toastr@~3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.1.2.tgz#3f8838423559482b8791cd7ce5bb6ff3a8f3722a"
integrity sha512-Omb/7Mkv73hRunVfWbRJxlNoTC3cqKJACB+pAcB5rb+rCTiR3c9HaSf1C7ljlofpxY/AKMN+JfJZP9VmgH2e4g==
"@abp/toastr@~3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/toastr/-/toastr-3.2.0-rc.1.tgz#853911d4b7f9911a99ffe9ab203fd1e023440da2"
integrity sha512-A3FtRUyU0DEyAeyxFCvTiYU9wF0kGmBwd+uZVNM5heI0XFCvE1IOlmF32Pg1+lCboKSqm+SjA9eSWy7lpMcp/Q==
dependencies:
"@abp/jquery" "~3.1.2"
"@abp/jquery" "~3.2.0-rc.1"
toastr "^2.1.4"
"@abp/utils@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.1.2.tgz#e9bca8f6133dc2af587c45825e734d2accabcd96"
integrity sha512-wnbHGQDGkQYazez1YSXAZ0ZDbsIZZr2ZN8V4LtOSAfzmYhPhFZ5qftoM0xoxlbmsuLLX6QtiPvxU35kEmlZPyg==
"@abp/utils@^3.2.0-rc.1":
version "3.2.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-3.2.0-rc.1.tgz#2231c62512bfff3b130283d35ac4a6458cf7d070"
integrity sha512-Qk+ZFmskrCIY2L/C3Dw6WxC0XHHvKmoue508HBvszEZsIwhdw9QJefmOn+Um0tMHo0p9Bd7By80CNlMjG3CLeg==
dependencies:
just-compare "^1.3.0"

3
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json

@ -3,7 +3,8 @@
"name": "asp.net",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^3.1.2"
"@abp/aspnetcore.mvc.ui.theme.basic": "^3.2.0-rc.1",
"@abp/prismjs": "^3.2.0-rc.1"
},
"devDependencies": {}
}

46
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/abp/luxon/abp.luxon.js

@ -1,46 +0,0 @@
var abp = abp || {};
(function () {
if (!luxon) {
throw "abp/luxon library requires the luxon library included to the page!";
}
/* TIMING *************************************************/
abp.timing = abp.timing || {};
var setObjectValue = function (obj, property, value) {
if (typeof property === "string") {
property = property.split('.');
}
if (property.length > 1) {
var p = property.shift();
setObjectValue(obj[p], property, value);
} else {
obj[property[0]] = value;
}
}
var getObjectValue = function (obj, property) {
return property.split('.').reduce((a, v) => a[v], obj)
}
abp.timing.convertFieldsToIsoDate = function (form, fields) {
for (var field of fields) {
var dateTime = luxon.DateTime
.fromFormat(
getObjectValue(form, field),
abp.localization.currentCulture.dateTimeFormat.shortDatePattern,
{locale: abp.localization.currentCulture.cultureName}
);
if (!dateTime.invalid) {
setObjectValue(form, field, dateTime.toFormat("yyyy-MM-dd HH:mm:ss"))
}
}
return form;
}
})(jQuery);

973
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/clipboard/clipboard.js

@ -0,0 +1,973 @@
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["ClipboardJS"] = factory();
else
root["ClipboardJS"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 6);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
function select(element) {
var selectedText;
if (element.nodeName === 'SELECT') {
element.focus();
selectedText = element.value;
}
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
var isReadOnly = element.hasAttribute('readonly');
if (!isReadOnly) {
element.setAttribute('readonly', '');
}
element.select();
element.setSelectionRange(0, element.value.length);
if (!isReadOnly) {
element.removeAttribute('readonly');
}
selectedText = element.value;
}
else {
if (element.hasAttribute('contenteditable')) {
element.focus();
}
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
selectedText = selection.toString();
}
return selectedText;
}
module.exports = select;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
function E () {
// Keep this empty so it's easier to inherit from
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}
E.prototype = {
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
emit: function (name) {
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// Remove event from queue to prevent memory leak
// Suggested by https://github.com/lazd
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;
module.exports.TinyEmitter = E;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
var is = __webpack_require__(3);
var delegate = __webpack_require__(4);
/**
* Validates all params and calls the right
* listener function based on its target type.
*
* @param {String|HTMLElement|HTMLCollection|NodeList} target
* @param {String} type
* @param {Function} callback
* @return {Object}
*/
function listen(target, type, callback) {
if (!target && !type && !callback) {
throw new Error('Missing required arguments');
}
if (!is.string(type)) {
throw new TypeError('Second argument must be a String');
}
if (!is.fn(callback)) {
throw new TypeError('Third argument must be a Function');
}
if (is.node(target)) {
return listenNode(target, type, callback);
}
else if (is.nodeList(target)) {
return listenNodeList(target, type, callback);
}
else if (is.string(target)) {
return listenSelector(target, type, callback);
}
else {
throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
}
}
/**
* Adds an event listener to a HTML element
* and returns a remove listener function.
*
* @param {HTMLElement} node
* @param {String} type
* @param {Function} callback
* @return {Object}
*/
function listenNode(node, type, callback) {
node.addEventListener(type, callback);
return {
destroy: function() {
node.removeEventListener(type, callback);
}
}
}
/**
* Add an event listener to a list of HTML elements
* and returns a remove listener function.
*
* @param {NodeList|HTMLCollection} nodeList
* @param {String} type
* @param {Function} callback
* @return {Object}
*/
function listenNodeList(nodeList, type, callback) {
Array.prototype.forEach.call(nodeList, function(node) {
node.addEventListener(type, callback);
});
return {
destroy: function() {
Array.prototype.forEach.call(nodeList, function(node) {
node.removeEventListener(type, callback);
});
}
}
}
/**
* Add an event listener to a selector
* and returns a remove listener function.
*
* @param {String} selector
* @param {String} type
* @param {Function} callback
* @return {Object}
*/
function listenSelector(selector, type, callback) {
return delegate(document.body, selector, type, callback);
}
module.exports = listen;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
/**
* Check if argument is a HTML element.
*
* @param {Object} value
* @return {Boolean}
*/
exports.node = function(value) {
return value !== undefined
&& value instanceof HTMLElement
&& value.nodeType === 1;
};
/**
* Check if argument is a list of HTML elements.
*
* @param {Object} value
* @return {Boolean}
*/
exports.nodeList = function(value) {
var type = Object.prototype.toString.call(value);
return value !== undefined
&& (type === '[object NodeList]' || type === '[object HTMLCollection]')
&& ('length' in value)
&& (value.length === 0 || exports.node(value[0]));
};
/**
* Check if argument is a string.
*
* @param {Object} value
* @return {Boolean}
*/
exports.string = function(value) {
return typeof value === 'string'
|| value instanceof String;
};
/**
* Check if argument is a function.
*
* @param {Object} value
* @return {Boolean}
*/
exports.fn = function(value) {
var type = Object.prototype.toString.call(value);
return type === '[object Function]';
};
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
var closest = __webpack_require__(5);
/**
* Delegates event to a selector.
*
* @param {Element} element
* @param {String} selector
* @param {String} type
* @param {Function} callback
* @param {Boolean} useCapture
* @return {Object}
*/
function _delegate(element, selector, type, callback, useCapture) {
var listenerFn = listener.apply(this, arguments);
element.addEventListener(type, listenerFn, useCapture);
return {
destroy: function() {
element.removeEventListener(type, listenerFn, useCapture);
}
}
}
/**
* Delegates event to a selector.
*
* @param {Element|String|Array} [elements]
* @param {String} selector
* @param {String} type
* @param {Function} callback
* @param {Boolean} useCapture
* @return {Object}
*/
function delegate(elements, selector, type, callback, useCapture) {
// Handle the regular Element usage
if (typeof elements.addEventListener === 'function') {
return _delegate.apply(null, arguments);
}
// Handle Element-less usage, it defaults to global delegation
if (typeof type === 'function') {
// Use `document` as the first parameter, then apply arguments
// This is a short way to .unshift `arguments` without running into deoptimizations
return _delegate.bind(null, document).apply(null, arguments);
}
// Handle Selector-based usage
if (typeof elements === 'string') {
elements = document.querySelectorAll(elements);
}
// Handle Array-like based usage
return Array.prototype.map.call(elements, function (element) {
return _delegate(element, selector, type, callback, useCapture);
});
}
/**
* Finds closest match and invokes callback.
*
* @param {Element} element
* @param {String} selector
* @param {String} type
* @param {Function} callback
* @return {Function}
*/
function listener(element, selector, type, callback) {
return function(e) {
e.delegateTarget = closest(e.target, selector);
if (e.delegateTarget) {
callback.call(element, e);
}
}
}
module.exports = delegate;
/***/ }),
/* 5 */
/***/ (function(module, exports) {
var DOCUMENT_NODE_TYPE = 9;
/**
* A polyfill for Element.matches()
*/
if (typeof Element !== 'undefined' && !Element.prototype.matches) {
var proto = Element.prototype;
proto.matches = proto.matchesSelector ||
proto.mozMatchesSelector ||
proto.msMatchesSelector ||
proto.oMatchesSelector ||
proto.webkitMatchesSelector;
}
/**
* Finds the closest parent that matches a selector.
*
* @param {Element} element
* @param {String} selector
* @return {Function}
*/
function closest (element, selector) {
while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {
if (typeof element.matches === 'function' &&
element.matches(selector)) {
return element;
}
element = element.parentNode;
}
}
module.exports = closest;
/***/ }),
/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// EXTERNAL MODULE: ./node_modules/select/src/select.js
var src_select = __webpack_require__(0);
var select_default = /*#__PURE__*/__webpack_require__.n(src_select);
// CONCATENATED MODULE: ./src/clipboard-action.js
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Inner class which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
*/
var clipboard_action_ClipboardAction = function () {
/**
* @param {Object} options
*/
function ClipboardAction(options) {
_classCallCheck(this, ClipboardAction);
this.resolveOptions(options);
this.initSelection();
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
_createClass(ClipboardAction, [{
key: 'resolveOptions',
value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
}
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
}, {
key: 'initSelection',
value: function initSelection() {
if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
}
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
}, {
key: 'selectFake',
value: function selectFake() {
var _this = this;
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandlerCallback = function () {
return _this.removeFake();
};
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;
this.fakeElem = document.createElement('textarea');
// Prevent zooming on iOS
this.fakeElem.style.fontSize = '12pt';
// Reset box model
this.fakeElem.style.border = '0';
this.fakeElem.style.padding = '0';
this.fakeElem.style.margin = '0';
// Move element out of screen horizontally
this.fakeElem.style.position = 'absolute';
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
// Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = yPosition + 'px';
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select_default()(this.fakeElem);
this.copyText();
}
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
}, {
key: 'removeFake',
value: function removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
}
if (this.fakeElem) {
this.container.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
/**
* Selects the content from element passed on `target` property.
*/
}, {
key: 'selectTarget',
value: function selectTarget() {
this.selectedText = select_default()(this.target);
this.copyText();
}
/**
* Executes the copy operation based on the current selection.
*/
}, {
key: 'copyText',
value: function copyText() {
var succeeded = void 0;
try {
succeeded = document.execCommand(this.action);
} catch (err) {
succeeded = false;
}
this.handleResult(succeeded);
}
/**
* Fires an event based on the copy operation result.
* @param {Boolean} succeeded
*/
}, {
key: 'handleResult',
value: function handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
}, {
key: 'clearSelection',
value: function clearSelection() {
if (this.trigger) {
this.trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
}, {
key: 'destroy',
/**
* Destroy lifecycle.
*/
value: function destroy() {
this.removeFake();
}
}, {
key: 'action',
set: function set() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
/**
* Gets the `action` property.
* @return {String}
*/
,
get: function get() {
return this._action;
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
*/
}, {
key: 'target',
set: function set(target) {
if (target !== undefined) {
if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
}
this._target = target;
} else {
throw new Error('Invalid "target" value, use a valid Element');
}
}
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
,
get: function get() {
return this._target;
}
}]);
return ClipboardAction;
}();
/* harmony default export */ var clipboard_action = (clipboard_action_ClipboardAction);
// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js
var tiny_emitter = __webpack_require__(1);
var tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);
// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js
var listen = __webpack_require__(2);
var listen_default = /*#__PURE__*/__webpack_require__.n(listen);
// CONCATENATED MODULE: ./src/clipboard.js
var clipboard_typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var clipboard_createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
var clipboard_Clipboard = function (_Emitter) {
_inherits(Clipboard, _Emitter);
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
function Clipboard(trigger, options) {
clipboard_classCallCheck(this, Clipboard);
var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
_this.resolveOptions(options);
_this.listenClick(trigger);
return _this;
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
clipboard_createClass(Clipboard, [{
key: 'resolveOptions',
value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;
}
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
}, {
key: 'listenClick',
value: function listenClick(trigger) {
var _this2 = this;
this.listener = listen_default()(trigger, 'click', function (e) {
return _this2.onClick(e);
});
}
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
}, {
key: 'onClick',
value: function onClick(e) {
var trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new clipboard_action({
action: this.action(trigger),
target: this.target(trigger),
text: this.text(trigger),
container: this.container,
trigger: trigger,
emitter: this
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
}, {
key: 'defaultAction',
value: function defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
}, {
key: 'defaultTarget',
value: function defaultTarget(trigger) {
var selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
*/
}, {
key: 'defaultText',
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
value: function defaultText(trigger) {
return getAttributeValue('text', trigger);
}
/**
* Destroy lifecycle.
*/
}, {
key: 'destroy',
value: function destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
}
}], [{
key: 'isSupported',
value: function isSupported() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];
var actions = typeof action === 'string' ? [action] : action;
var support = !!document.queryCommandSupported;
actions.forEach(function (action) {
support = support && !!document.queryCommandSupported(action);
});
return support;
}
}]);
return Clipboard;
}(tiny_emitter_default.a);
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
var attribute = 'data-clipboard-' + suffix;
if (!element.hasAttribute(attribute)) {
return;
}
return element.getAttribute(attribute);
}
/* harmony default export */ var clipboard = __webpack_exports__["default"] = (clipboard_Clipboard);
/***/ })
/******/ ])["default"];
});

7
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/clipboard/clipboard.min.js

File diff suppressed because one or more lines are too long

8258
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.js

File diff suppressed because it is too large

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.js.map

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.min.js

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/luxon/luxon.min.js.map

File diff suppressed because one or more lines are too long

2174
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/CHANGELOG.md

File diff suppressed because it is too large

32
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/README.md

@ -0,0 +1,32 @@
# [Prism](https://prismjs.com/)
[![Build Status](https://travis-ci.org/PrismJS/prism.svg?branch=master)](https://travis-ci.org/PrismJS/prism)
[![npm](https://img.shields.io/npm/dw/prismjs.svg)](https://www.npmjs.com/package/prismjs)
Prism is a lightweight, robust, elegant syntax highlighting library. It's a spin-off project from [Dabblet](https://dabblet.com/).
You can learn more on [prismjs.com](https://prismjs.com/).
[Why another syntax highlighter?](https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/#more-1841)
[More themes for Prism!](https://github.com/PrismJS/prism-themes)
## Contribute to Prism!
Prism depends on community contributions to expand and cover a wider array of use cases. If you like it, considering giving back by sending a pull request. Here are a few tips:
- Read the [documentation](https://prismjs.com/extending.html). Prism was designed to be extensible.
- Do not edit `prism.js`, it’s just the version of Prism used by the Prism website and is built automatically. Limit your changes to the unminified files in the `components/` folder. `prism.js` and all minified files are also generated automatically by our build system.
- The build system uses [gulp](https://github.com/gulpjs/gulp) to minify the files and build `prism.js`. With all of Prism's dependencies installed, you just need to run the command `npm run build`.
- Please follow the code conventions used in the files already. For example, I use [tabs for indentation and spaces for alignment](http://lea.verou.me/2012/01/why-tabs-are-clearly-superior/). Opening braces are on the same line, closing braces on their own line regardless of construct. There is a space before the opening brace. etc etc.
- Please try to err towards more smaller PRs rather than few huge PRs. If a PR includes changes I want to merge and changes I don't, handling it becomes difficult.
- My time is very limited these days, so it might take a long time to review longer PRs (short ones are usually merged very quickly), especially those modifying the Prism Core. This doesn't mean your PR is rejected.
- If you contribute a new language definition, you will be responsible for handling bug reports about that language definition.
- If you [add a new language definition](https://prismjs.com/extending.html#creating-a-new-language-definition) or plugin, you need to add it to `components.json` as well and rebuild Prism by running `npm run build`, so that it becomes available to the download build page. For new languages, please also add a few [tests](https://prismjs.com/test-suite.html) and an example in the `examples/` folder.
- Go to [prism-themes](https://github.com/PrismJS/prism-themes) if you want to add a new theme.
Thank you so much for contributing!!
## Translations
* [![中文说明](http://awesomes.oss-cn-beijing.aliyuncs.com/readme.png)](https://www.awesomes.cn/repo/PrismJS/prism)

2
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components.js

File diff suppressed because one or more lines are too long

1386
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components.json

File diff suppressed because it is too large

56
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/index.js

@ -0,0 +1,56 @@
const components = require('../components.js');
const getLoader = require('../dependencies');
/**
* The set of all languages which have been loaded using the below function.
*
* @type {Set<string>}
*/
const loadedLanguages = new Set();
/**
* Loads the given languages and adds them to the current Prism instance.
*
* If no languages are provided, __all__ Prism languages will be loaded.
*
* @param {string|string[]} [languages]
* @returns {void}
*/
function loadLanguages(languages) {
if (languages === undefined) {
languages = Object.keys(components.languages).filter(l => l != 'meta');
} else if (!Array.isArray(languages)) {
languages = [languages];
}
// the user might have loaded languages via some other way or used `prism.js` which already includes some
// we don't need to validate the ids because `getLoader` will ignore invalid ones
const loaded = [...loadedLanguages, ...Object.keys(Prism.languages)];
getLoader(components, languages, loaded).load(lang => {
if (!(lang in components.languages)) {
if (!loadLanguages.silent) {
console.warn('Language does not exist: ' + lang);
}
return;
}
const pathToLanguage = './prism-' + lang;
// remove from require cache and from Prism
delete require.cache[require.resolve(pathToLanguage)];
delete Prism.languages[lang];
require(pathToLanguage);
loadedLanguages.add(lang);
});
}
/**
* Set this to `true` to prevent all warning messages `loadLanguages` logs.
*/
loadLanguages.silent = false;
module.exports = loadLanguages;

48
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abap.js

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abap.min.js

File diff suppressed because one or more lines are too long

54
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abnf.js

@ -0,0 +1,54 @@
(function (Prism) {
var coreRules = '(?:ALPHA|BIT|CHAR|CR|CRLF|CTL|DIGIT|DQUOTE|HEXDIG|HTAB|LF|LWSP|OCTET|SP|VCHAR|WSP)';
Prism.languages.abnf = {
'comment': /;.*/,
'string': {
pattern: /(?:%[is])?"[^"\n\r]*"/,
greedy: true,
inside: {
'punctuation': /^%[is]/
}
},
'range': {
pattern: /%(?:b[01]+-[01]+|d\d+-\d+|x[A-F\d]+-[A-F\d]+)/i,
alias: 'number'
},
'terminal': {
pattern: /%(?:b[01]+(?:\.[01]+)*|d\d+(?:\.\d+)*|x[A-F\d]+(?:\.[A-F\d]+)*)/i,
alias: 'number'
},
'repetition': {
pattern: /(^|[^\w-])(?:\d*\*\d*|\d+)/,
lookbehind: true,
alias: 'operator'
},
'definition': {
pattern: /(^[ \t]*)(?:[a-z][\w-]*|<[^>\r\n]*>)(?=\s*=)/m,
lookbehind: true,
alias: 'keyword',
inside: {
'punctuation': /<|>/
}
},
'core-rule': {
pattern: RegExp('(?:(^|[^<\\w-])' + coreRules + '|<' + coreRules + '>)(?![\\w-])', 'i'),
lookbehind: true,
alias: ['rule', 'constant'],
inside: {
'punctuation': /<|>/
}
},
'rule': {
pattern: /(^|[^<\w-])[a-z][\w-]*|<[^>\r\n]*>/i,
lookbehind: true,
inside: {
'punctuation': /<|>/
}
},
'operator': /=\/?|\//,
'punctuation': /[()\[\]]/
};
})(Prism);

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-abnf.min.js

@ -0,0 +1 @@
!function(n){var i="(?:ALPHA|BIT|CHAR|CR|CRLF|CTL|DIGIT|DQUOTE|HEXDIG|HTAB|LF|LWSP|OCTET|SP|VCHAR|WSP)";Prism.languages.abnf={comment:/;.*/,string:{pattern:/(?:%[is])?"[^"\n\r]*"/,greedy:!0,inside:{punctuation:/^%[is]/}},range:{pattern:/%(?:b[01]+-[01]+|d\d+-\d+|x[A-F\d]+-[A-F\d]+)/i,alias:"number"},terminal:{pattern:/%(?:b[01]+(?:\.[01]+)*|d\d+(?:\.\d+)*|x[A-F\d]+(?:\.[A-F\d]+)*)/i,alias:"number"},repetition:{pattern:/(^|[^\w-])(?:\d*\*\d*|\d+)/,lookbehind:!0,alias:"operator"},definition:{pattern:/(^[ \t]*)(?:[a-z][\w-]*|<[^>\r\n]*>)(?=\s*=)/m,lookbehind:!0,alias:"keyword",inside:{punctuation:/<|>/}},"core-rule":{pattern:RegExp("(?:(^|[^<\\w-])"+i+"|<"+i+">)(?![\\w-])","i"),lookbehind:!0,alias:["rule","constant"],inside:{punctuation:/<|>/}},rule:{pattern:/(^|[^<\w-])[a-z][\w-]*|<[^>\r\n]*>/i,lookbehind:!0,inside:{punctuation:/<|>/}},operator:/=\/?|\//,punctuation:/[()\[\]]/}}();

15
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-actionscript.js

@ -0,0 +1,15 @@
Prism.languages.actionscript = Prism.languages.extend('javascript', {
'keyword': /\b(?:as|break|case|catch|class|const|default|delete|do|else|extends|finally|for|function|if|implements|import|in|instanceof|interface|internal|is|native|new|null|package|private|protected|public|return|super|switch|this|throw|try|typeof|use|var|void|while|with|dynamic|each|final|get|include|namespace|native|override|set|static)\b/,
'operator': /\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/
});
Prism.languages.actionscript['class-name'].alias = 'function';
if (Prism.languages.markup) {
Prism.languages.insertBefore('actionscript', 'string', {
'xml': {
pattern: /(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,
lookbehind: true,
inside: Prism.languages.markup
}
});
}

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-actionscript.min.js

@ -0,0 +1 @@
Prism.languages.actionscript=Prism.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|else|extends|finally|for|function|if|implements|import|in|instanceof|interface|internal|is|native|new|null|package|private|protected|public|return|super|switch|this|throw|try|typeof|use|var|void|while|with|dynamic|each|final|get|include|namespace|native|override|set|static)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/}),Prism.languages.actionscript["class-name"].alias="function",Prism.languages.markup&&Prism.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:Prism.languages.markup}});

19
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-ada.js

@ -0,0 +1,19 @@
Prism.languages.ada = {
'comment': /--.*/,
'string': /"(?:""|[^"\r\f\n])*"/i,
'number': [
{
pattern: /\b\d(?:_?\d)*#[\dA-F](?:_?[\dA-F])*(?:\.[\dA-F](?:_?[\dA-F])*)?#(?:E[+-]?\d(?:_?\d)*)?/i
},
{
pattern: /\b\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:E[+-]?\d(?:_?\d)*)?\b/i
}
],
'attr-name': /\b'\w+/i,
'keyword': /\b(?:abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case|constant|declare|delay|delta|digits|do|else|new|return|elsif|end|entry|exception|exit|for|function|generic|goto|if|in|interface|is|limited|loop|mod|not|null|of|others|out|overriding|package|pragma|private|procedure|protected|raise|range|record|rem|renames|requeue|reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until|use|when|while|with|xor)\b/i,
'boolean': /\b(?:true|false)\b/i,
'operator': /<[=>]?|>=?|=>?|:=|\/=?|\*\*?|[&+-]/,
'punctuation': /\.\.?|[,;():]/,
'char': /'.'/,
'variable': /\b[a-z](?:[_a-z\d])*\b/i
};

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-ada.min.js

@ -0,0 +1 @@
Prism.languages.ada={comment:/--.*/,string:/"(?:""|[^"\r\f\n])*"/i,number:[{pattern:/\b\d(?:_?\d)*#[\dA-F](?:_?[\dA-F])*(?:\.[\dA-F](?:_?[\dA-F])*)?#(?:E[+-]?\d(?:_?\d)*)?/i},{pattern:/\b\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:E[+-]?\d(?:_?\d)*)?\b/i}],"attr-name":/\b'\w+/i,keyword:/\b(?:abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case|constant|declare|delay|delta|digits|do|else|new|return|elsif|end|entry|exception|exit|for|function|generic|goto|if|in|interface|is|limited|loop|mod|not|null|of|others|out|overriding|package|pragma|private|procedure|protected|raise|range|record|rem|renames|requeue|reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until|use|when|while|with|xor)\b/i,boolean:/\b(?:true|false)\b/i,operator:/<[=>]?|>=?|=>?|:=|\/=?|\*\*?|[&+-]/,punctuation:/\.\.?|[,;():]/,char:/'.'/,variable:/\b[a-z](?:[_a-z\d])*\b/i};

24
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-agda.js

@ -0,0 +1,24 @@
(function (Prism) {
Prism.languages.agda = {
'comment': /\{-[\s\S]*?(?:-\}|$)|--.*/,
'string': {
pattern: /"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,
greedy: true,
},
'punctuation': /[(){}⦃⦄.;@]/,
'class-name': {
pattern: /((?:data|record) +)\S+/,
lookbehind: true,
},
'function': {
pattern: /(^[ \t]*)[^:\r\n]+?(?=:)/m,
lookbehind: true,
},
'operator': {
pattern: /(^\s*|\s)(?:[=|:∀→λ\\?_]|->)(?=\s)/,
lookbehind: true,
},
'keyword': /\b(?:Set|abstract|constructor|data|eta-equality|field|forall|forall|hiding|import|in|inductive|infix|infixl|infixr|instance|let|macro|module|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|public|quote|quoteContext|quoteGoal|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)\b/,
};
}(Prism));

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-agda.min.js

@ -0,0 +1 @@
Prism.languages.agda={comment:/\{-[\s\S]*?(?:-\}|$)|--.*/,string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},punctuation:/[(){}⦃⦄.;@]/,"class-name":{pattern:/((?:data|record) +)\S+/,lookbehind:!0},function:{pattern:/(^[ \t]*)[^:\r\n]+?(?=:)/m,lookbehind:!0},operator:{pattern:/(^\s*|\s)(?:[=|:∀→λ\\?_]|->)(?=\s)/,lookbehind:!0},keyword:/\b(?:Set|abstract|constructor|data|eta-equality|field|forall|forall|hiding|import|in|inductive|infix|infixl|infixr|instance|let|macro|module|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|public|quote|quoteContext|quoteGoal|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)\b/};

25
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-al.js

@ -0,0 +1,25 @@
// based on https://github.com/microsoft/AL/blob/master/grammar/alsyntax.tmlanguage
Prism.languages.al = {
'comment': /\/\/.*|\/\*[\s\S]*?\*\//,
'string': {
pattern: /'(?:''|[^'\r\n])*'(?!')|"(?:""|[^"\r\n])*"(?!")/,
greedy: true
},
'function': {
pattern: /(\b(?:event|procedure|trigger)\s+|(?:^|[^.])\.\s*)[a-z_]\w*(?=\s*\()/i,
lookbehind: true
},
'keyword': [
// keywords
/\b(?:array|asserterror|begin|break|case|do|downto|else|end|event|exit|for|foreach|function|if|implements|in|indataset|interface|internal|local|of|procedure|program|protected|repeat|runonclient|securityfiltering|suppressdispose|temporary|then|to|trigger|until|var|while|with|withevents)\b/i,
// objects and metadata that are used like keywords
/\b(?:action|actions|addafter|addbefore|addfirst|addlast|area|assembly|chartpart|codeunit|column|controladdin|cuegroup|customizes|dataitem|dataset|dotnet|elements|enum|enumextension|extends|field|fieldattribute|fieldelement|fieldgroup|fieldgroups|fields|filter|fixed|grid|group|key|keys|label|labels|layout|modify|moveafter|movebefore|movefirst|movelast|page|pagecustomization|pageextension|part|profile|query|repeater|report|requestpage|schema|separator|systempart|table|tableelement|tableextension|textattribute|textelement|type|usercontrol|value|xmlport)\b/i
],
'number': /\b(?:0x[\da-f]+|(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(?:F|U(?:LL?)?|LL?)?\b/i,
'boolean': /\b(?:false|true)\b/i,
'variable': /\b(?:Curr(?:FieldNo|Page|Report)|RequestOptionsPage|x?Rec)\b/,
'class-name': /\b(?:automation|biginteger|bigtext|blob|boolean|byte|char|clienttype|code|completiontriggererrorlevel|connectiontype|database|dataclassification|datascope|date|dateformula|datetime|decimal|defaultlayout|dialog|dictionary|dotnetassembly|dotnettypedeclaration|duration|errorinfo|errortype|executioncontext|executionmode|fieldclass|fieldref|fieldtype|file|filterpagebuilder|guid|httpclient|httpcontent|httpheaders|httprequestmessage|httpresponsemessage|instream|integer|joker|jsonarray|jsonobject|jsontoken|jsonvalue|keyref|list|moduledependencyinfo|moduleinfo|none|notification|notificationscope|objecttype|option|outstream|pageresult|record|recordid|recordref|reportformat|securityfilter|sessionsettings|tableconnectiontype|tablefilter|testaction|testfield|testfilterfield|testpage|testpermissions|testrequestpage|text|textbuilder|textconst|textencoding|time|transactionmodel|transactiontype|variant|verbosity|version|view|views|webserviceactioncontext|webserviceactionresultcode|xmlattribute|xmlattributecollection|xmlcdata|xmlcomment|xmldeclaration|xmldocument|xmldocumenttype|xmlelement|xmlnamespacemanager|xmlnametable|xmlnode|xmlnodelist|xmlprocessinginstruction|xmlreadoptions|xmltext|xmlwriteoptions)\b/i,
'operator': /\.\.|:[=:]|[-+*/]=?|<>|[<>]=?|=|\b(?:and|div|mod|not|or|xor)\b/i,
'punctuation': /[()\[\]{}:.;,]/
};

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-al.min.js

@ -0,0 +1 @@
Prism.languages.al={comment:/\/\/.*|\/\*[\s\S]*?\*\//,string:{pattern:/'(?:''|[^'\r\n])*'(?!')|"(?:""|[^"\r\n])*"(?!")/,greedy:!0},function:{pattern:/(\b(?:event|procedure|trigger)\s+|(?:^|[^.])\.\s*)[a-z_]\w*(?=\s*\()/i,lookbehind:!0},keyword:[/\b(?:array|asserterror|begin|break|case|do|downto|else|end|event|exit|for|foreach|function|if|implements|in|indataset|interface|internal|local|of|procedure|program|protected|repeat|runonclient|securityfiltering|suppressdispose|temporary|then|to|trigger|until|var|while|with|withevents)\b/i,/\b(?:action|actions|addafter|addbefore|addfirst|addlast|area|assembly|chartpart|codeunit|column|controladdin|cuegroup|customizes|dataitem|dataset|dotnet|elements|enum|enumextension|extends|field|fieldattribute|fieldelement|fieldgroup|fieldgroups|fields|filter|fixed|grid|group|key|keys|label|labels|layout|modify|moveafter|movebefore|movefirst|movelast|page|pagecustomization|pageextension|part|profile|query|repeater|report|requestpage|schema|separator|systempart|table|tableelement|tableextension|textattribute|textelement|type|usercontrol|value|xmlport)\b/i],number:/\b(?:0x[\da-f]+|(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(?:F|U(?:LL?)?|LL?)?\b/i,boolean:/\b(?:false|true)\b/i,variable:/\b(?:Curr(?:FieldNo|Page|Report)|RequestOptionsPage|x?Rec)\b/,"class-name":/\b(?:automation|biginteger|bigtext|blob|boolean|byte|char|clienttype|code|completiontriggererrorlevel|connectiontype|database|dataclassification|datascope|date|dateformula|datetime|decimal|defaultlayout|dialog|dictionary|dotnetassembly|dotnettypedeclaration|duration|errorinfo|errortype|executioncontext|executionmode|fieldclass|fieldref|fieldtype|file|filterpagebuilder|guid|httpclient|httpcontent|httpheaders|httprequestmessage|httpresponsemessage|instream|integer|joker|jsonarray|jsonobject|jsontoken|jsonvalue|keyref|list|moduledependencyinfo|moduleinfo|none|notification|notificationscope|objecttype|option|outstream|pageresult|record|recordid|recordref|reportformat|securityfilter|sessionsettings|tableconnectiontype|tablefilter|testaction|testfield|testfilterfield|testpage|testpermissions|testrequestpage|text|textbuilder|textconst|textencoding|time|transactionmodel|transactiontype|variant|verbosity|version|view|views|webserviceactioncontext|webserviceactionresultcode|xmlattribute|xmlattributecollection|xmlcdata|xmlcomment|xmldeclaration|xmldocument|xmldocumenttype|xmlelement|xmlnamespacemanager|xmlnametable|xmlnode|xmlnodelist|xmlprocessinginstruction|xmlreadoptions|xmltext|xmlwriteoptions)\b/i,operator:/\.\.|:[=:]|[-+*/]=?|<>|[<>]=?|=|\b(?:and|div|mod|not|or|xor)\b/i,punctuation:/[()\[\]{}:.;,]/};

65
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-antlr4.js

@ -0,0 +1,65 @@
Prism.languages.antlr4 = {
'comment': /\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,
'string': {
pattern: /'(?:\\.|[^\\'\r\n])*'/,
greedy: true
},
'character-class': {
pattern: /\[(?:\\.|[^\\\]\r\n])*\]/,
greedy: true,
alias: 'regex',
inside: {
'range': {
pattern: /([^[]|(?:^|[^\\])(?:\\\\)*\\\[)-(?!\])/,
lookbehind: true,
alias: 'punctuation'
},
'escape': /\\(?:u(?:[a-fA-F\d]{4}|\{[a-fA-F\d]+\})|[pP]\{[=\w-]+\}|[^\r\nupP])/,
'punctuation': /[\[\]]/
}
},
'action': {
pattern: /\{(?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*\}/,
greedy: true,
inside: {
'content': {
// this might be C, C++, Python, Java, C#, or any other language ANTLR4 compiles to
pattern: /(\{)[\s\S]+(?=\})/,
lookbehind: true
},
'punctuation': /[{}]/
}
},
'command': {
pattern: /(->\s*)(?:\s*(?:,\s*)?\b[a-z]\w*(?:\s*\([^()\r\n]*\))?)+(?=\s*;)/i,
lookbehind: true,
inside: {
'function': /\b\w+(?=\s*(?:[,(]|$))/,
'punctuation': /[,()]/
}
},
'annotation': {
pattern: /@\w+(?:::\w+)*/,
alias: 'keyword'
},
'label': {
pattern: /#[ \t]*\w+/,
alias: 'punctuation'
},
'keyword': /\b(?:catch|channels|finally|fragment|grammar|import|lexer|locals|mode|options|parser|returns|throws|tokens)\b/,
'definition': [
{
pattern: /\b[a-z]\w*(?=\s*:)/,
alias: ['rule', 'class-name']
},
{
pattern: /\b[A-Z]\w*(?=\s*:)/,
alias: ['token', 'constant']
},
],
'constant': /\b[A-Z][A-Z_]*\b/,
'operator': /\.\.|->|[|~]|[*+?]\??/,
'punctuation': /[;:()=]/
};
Prism.languages.g4 = Prism.languages.antlr4;

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-antlr4.min.js

@ -0,0 +1 @@
Prism.languages.antlr4={comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,string:{pattern:/'(?:\\.|[^\\'\r\n])*'/,greedy:!0},"character-class":{pattern:/\[(?:\\.|[^\\\]\r\n])*\]/,greedy:!0,alias:"regex",inside:{range:{pattern:/([^[]|(?:^|[^\\])(?:\\\\)*\\\[)-(?!\])/,lookbehind:!0,alias:"punctuation"},escape:/\\(?:u(?:[a-fA-F\d]{4}|\{[a-fA-F\d]+\})|[pP]\{[=\w-]+\}|[^\r\nupP])/,punctuation:/[\[\]]/}},action:{pattern:/\{(?:[^{}]|\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})*\}/,greedy:!0,inside:{content:{pattern:/(\{)[\s\S]+(?=\})/,lookbehind:!0},punctuation:/[{}]/}},command:{pattern:/(->\s*)(?:\s*(?:,\s*)?\b[a-z]\w*(?:\s*\([^()\r\n]*\))?)+(?=\s*;)/i,lookbehind:!0,inside:{function:/\b\w+(?=\s*(?:[,(]|$))/,punctuation:/[,()]/}},annotation:{pattern:/@\w+(?:::\w+)*/,alias:"keyword"},label:{pattern:/#[ \t]*\w+/,alias:"punctuation"},keyword:/\b(?:catch|channels|finally|fragment|grammar|import|lexer|locals|mode|options|parser|returns|throws|tokens)\b/,definition:[{pattern:/\b[a-z]\w*(?=\s*:)/,alias:["rule","class-name"]},{pattern:/\b[A-Z]\w*(?=\s*:)/,alias:["token","constant"]}],constant:/\b[A-Z][A-Z_]*\b/,operator:/\.\.|->|[|~]|[*+?]\??/,punctuation:/[;:()=]/},Prism.languages.g4=Prism.languages.antlr4;

47
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apacheconf.js

File diff suppressed because one or more lines are too long

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apacheconf.min.js

File diff suppressed because one or more lines are too long

32
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apl.js

@ -0,0 +1,32 @@
Prism.languages.apl = {
'comment': /(?:⍝|#[! ]).*$/m,
'string': {
pattern: /'(?:[^'\r\n]|'')*'/,
greedy: true
},
'number': /¯?(?:\d*\.?\b\d+(?:e[+¯]?\d+)?|¯|∞)(?:j¯?(?:\d*\.?\d+(?:e[+¯]?\d+)?|¯|∞))?/i,
'statement': /:[A-Z][a-z][A-Za-z]*\b/,
'system-function': {
pattern: /⎕[A-Z]+/i,
alias: 'function'
},
'constant': /[⍬⌾#⎕⍞]/,
'function': /[-+×÷⌈⌊∣|⍳⍸?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⊆⊇⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⍯↗¤→]/,
'monadic-operator': {
pattern: /[\\\/⌿⍀¨⍨⌶&∥]/,
alias: 'operator'
},
'dyadic-operator': {
pattern: /[.⍣⍠⍤∘⌸@⌺⍥]/,
alias: 'operator'
},
'assignment': {
pattern: /←/,
alias: 'keyword'
},
'punctuation': /[\[;\]()◇⋄]/,
'dfn': {
pattern: /[{}⍺⍵⍶⍹∇⍫:]/,
alias: 'builtin'
}
};

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-apl.min.js

@ -0,0 +1 @@
Prism.languages.apl={comment:/(?:⍝|#[! ]).*$/m,string:{pattern:/'(?:[^'\r\n]|'')*'/,greedy:!0},number:/¯?(?:\d*\.?\b\d+(?:e[+¯]?\d+)?|¯|∞)(?:j¯?(?:\d*\.?\d+(?:e[+¯]?\d+)?|¯|∞))?/i,statement:/:[A-Z][a-z][A-Za-z]*\b/,"system-function":{pattern:/⎕[A-Z]+/i,alias:"function"},constant:/[⍬⌾#⎕⍞]/,function:/[-+×÷⌈⌊∣|⍳⍸?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⊆⊇⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⍯↗¤→]/,"monadic-operator":{pattern:/[\\\/⌿⍀¨⍨⌶&∥]/,alias:"operator"},"dyadic-operator":{pattern:/[.⍣⍠⍤∘⌸@⌺⍥]/,alias:"operator"},assignment:{pattern:/←/,alias:"keyword"},punctuation:/[\[;\]()◇⋄]/,dfn:{pattern:/[{}⍺⍵⍶⍹∇⍫:]/,alias:"builtin"}};

20
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-applescript.js

@ -0,0 +1,20 @@
Prism.languages.applescript = {
'comment': [
// Allow one level of nesting
/\(\*(?:\(\*(?:[^*]|\*(?!\)))*\*\)|(?!\(\*)[\s\S])*?\*\)/,
/--.+/,
/#.+/
],
'string': /"(?:\\.|[^"\\\r\n])*"/,
'number': /(?:\b\d+\.?\d*|\B\.\d+)(?:e-?\d+)?\b/i,
'operator': [
/[&=≠≤≥*+\-\/÷^]|[<>]=?/,
/\b(?:(?:start|begin|end)s? with|(?:(?:does not|doesn't) contain|contains?)|(?:is|isn't|is not) (?:in|contained by)|(?:(?:is|isn't|is not) )?(?:greater|less) than(?: or equal)?(?: to)?|(?:(?:does not|doesn't) come|comes) (?:before|after)|(?:is|isn't|is not) equal(?: to)?|(?:(?:does not|doesn't) equal|equals|equal to|isn't|is not)|(?:a )?(?:ref(?: to)?|reference to)|(?:and|or|div|mod|as|not))\b/
],
'keyword': /\b(?:about|above|after|against|apart from|around|aside from|at|back|before|beginning|behind|below|beneath|beside|between|but|by|considering|continue|copy|does|eighth|else|end|equal|error|every|exit|false|fifth|first|for|fourth|from|front|get|given|global|if|ignoring|in|instead of|into|is|it|its|last|local|me|middle|my|ninth|of|on|onto|out of|over|prop|property|put|repeat|return|returning|second|set|seventh|since|sixth|some|tell|tenth|that|the|then|third|through|thru|timeout|times|to|transaction|true|try|until|where|while|whose|with|without)\b/,
'class': {
pattern: /\b(?:alias|application|boolean|class|constant|date|file|integer|list|number|POSIX file|real|record|reference|RGB color|script|text|centimetres|centimeters|feet|inches|kilometres|kilometers|metres|meters|miles|yards|square feet|square kilometres|square kilometers|square metres|square meters|square miles|square yards|cubic centimetres|cubic centimeters|cubic feet|cubic inches|cubic metres|cubic meters|cubic yards|gallons|litres|liters|quarts|grams|kilograms|ounces|pounds|degrees Celsius|degrees Fahrenheit|degrees Kelvin)\b/,
alias: 'builtin'
},
'punctuation': /[{}():,¬«»《》]/
};

1
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/prismjs/components/prism-applescript.min.js

@ -0,0 +1 @@
Prism.languages.applescript={comment:[/\(\*(?:\(\*(?:[^*]|\*(?!\)))*\*\)|(?!\(\*)[\s\S])*?\*\)/,/--.+/,/#.+/],string:/"(?:\\.|[^"\\\r\n])*"/,number:/(?:\b\d+\.?\d*|\B\.\d+)(?:e-?\d+)?\b/i,operator:[/[&=≠≤≥*+\-\/÷^]|[<>]=?/,/\b(?:(?:start|begin|end)s? with|(?:(?:does not|doesn't) contain|contains?)|(?:is|isn't|is not) (?:in|contained by)|(?:(?:is|isn't|is not) )?(?:greater|less) than(?: or equal)?(?: to)?|(?:(?:does not|doesn't) come|comes) (?:before|after)|(?:is|isn't|is not) equal(?: to)?|(?:(?:does not|doesn't) equal|equals|equal to|isn't|is not)|(?:a )?(?:ref(?: to)?|reference to)|(?:and|or|div|mod|as|not))\b/],keyword:/\b(?:about|above|after|against|apart from|around|aside from|at|back|before|beginning|behind|below|beneath|beside|between|but|by|considering|continue|copy|does|eighth|else|end|equal|error|every|exit|false|fifth|first|for|fourth|from|front|get|given|global|if|ignoring|in|instead of|into|is|it|its|last|local|me|middle|my|ninth|of|on|onto|out of|over|prop|property|put|repeat|return|returning|second|set|seventh|since|sixth|some|tell|tenth|that|the|then|third|through|thru|timeout|times|to|transaction|true|try|until|where|while|whose|with|without)\b/,class:{pattern:/\b(?:alias|application|boolean|class|constant|date|file|integer|list|number|POSIX file|real|record|reference|RGB color|script|text|centimetres|centimeters|feet|inches|kilometres|kilometers|metres|meters|miles|yards|square feet|square kilometres|square kilometers|square metres|square meters|square miles|square yards|cubic centimetres|cubic centimeters|cubic feet|cubic inches|cubic metres|cubic meters|cubic yards|gallons|litres|liters|quarts|grams|kilograms|ounces|pounds|degrees Celsius|degrees Fahrenheit|degrees Kelvin)\b/,alias:"builtin"},punctuation:/[{}():,¬«»《》]/};

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save