Browse Source

Merge branch 'dev' into maliming/net5

pull/5700/head
maliming 6 years ago
parent
commit
5374a6818d
  1. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  2. 5
      docs/en/Best-Practices/Application-Services.md
  3. 119
      docs/en/CSRF-Anti-Forgery.md
  4. 160
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md
  5. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png
  6. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png
  7. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png
  8. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png
  9. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png
  10. BIN
      docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png
  11. 35
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  12. 12
      docs/en/Entity-Framework-Core-SQLite.md
  13. 3
      docs/en/JavaScript/Dynamic-JavaScript-API-Clients.md
  14. 99
      docs/en/Localization.md
  15. 2
      docs/en/Redis-Cache.md
  16. 11
      docs/en/UI/Angular/Localization.md
  17. 96
      docs/en/UI/Angular/Toaster-Service.md
  18. 3
      docs/en/UI/AspNetCore/Client-Side-Package-Management.md
  19. 4
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  20. 150
      docs/en/UI/AspNetCore/JavaScript-API/Ajax.md
  21. 3
      docs/en/UI/AspNetCore/JavaScript-API/Dynamic-JavaScript-Client-Proxies.md
  22. 12
      docs/en/UI/AspNetCore/JavaScript-API/Index.md
  23. 143
      docs/en/UI/AspNetCore/JavaScript-API/Localization.md
  24. 3
      docs/en/UI/AspNetCore/JavaScript-API/Logging.md
  25. 128
      docs/en/UI/AspNetCore/JavaScript-API/Message.md
  26. 45
      docs/en/UI/AspNetCore/JavaScript-API/Notify.md
  27. 106
      docs/en/UI/AspNetCore/Layout-Hooks.md
  28. 10
      docs/en/UI/AspNetCore/Tag-Helpers/Badges.md
  29. 18
      docs/en/UI/AspNetCore/Tag-Helpers/Blockquote.md
  30. 6
      docs/en/UI/AspNetCore/Tag-Helpers/Borders.md
  31. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md
  32. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md
  33. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md
  34. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Collapse.md
  35. 14
      docs/en/UI/AspNetCore/Tag-Helpers/Figure.md
  36. 9
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  37. 4
      docs/en/UI/AspNetCore/Tag-Helpers/Navs.md
  38. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md
  39. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Tables.md
  40. 3
      docs/en/UI/Blazor/Localization.md
  41. 35
      docs/en/docs-nav.json
  42. BIN
      docs/en/images/ajax-error.png
  43. BIN
      docs/en/images/js-message-confirm.png
  44. BIN
      docs/en/images/js-message-error.png
  45. BIN
      docs/en/images/js-message-success.png
  46. BIN
      docs/en/images/js-notify-success.png
  47. 2
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  48. 28
      framework/Volo.Abp.sln
  49. 6
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor
  50. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor
  51. 1
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj
  52. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpBlazorClientHttpMessageHandler.cs
  53. 21
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs
  54. 3
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs
  55. 34
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiMessageService.cs
  56. 14
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs
  57. 35
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageEventArgs.cs
  58. 53
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageOptions.cs
  59. 17
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs
  60. 14
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageType.cs
  61. 48
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs
  62. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperServiceBase.cs
  63. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs
  64. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/es.json
  65. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml
  66. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml
  67. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml
  68. 6
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  69. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  70. 35
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryCookieNameProvider.cs
  71. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryManagerAspNetCoreExtensions.cs
  72. 66
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryOptions.cs
  73. 37
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAutoValidateAntiforgeryTokenAttribute.cs
  74. 65
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAutoValidateAntiforgeryTokenAuthorizationFilter.cs
  75. 37
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpValidateAntiForgeryTokenAttribute.cs
  76. 81
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpValidateAntiforgeryTokenAuthorizationFilter.cs
  77. 16
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AspNetCoreAbpAntiForgeryManager.cs
  78. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/IAbpAntiForgeryManager.cs
  79. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
  80. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
  81. 20
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
  82. 25
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs
  83. 28
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs
  84. 30
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs
  85. 33
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  86. 1
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
  87. 4
      framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
  88. 96
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs
  89. 15
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs
  90. 48
      framework/src/Volo.Abp.BlazoriseUI/Components/UiMessageAlert.razor
  91. 140
      framework/src/Volo.Abp.BlazoriseUI/Components/UiMessageAlert.razor.cs
  92. 2
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  93. 2
      framework/src/Volo.Abp.BlazoriseUI/_Imports.razor
  94. 20
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs
  95. 1
      framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
  96. 14
      framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
  97. 26
      framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs
  98. 16
      framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs
  99. 13
      framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs
  100. 23
      framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -83,6 +83,7 @@
"LatestBlogPost": "Latest Blog Post",
"Edit": "Edit",
"ProfileImageChange": "Change the profile image",
"BlogItemErrorMessage": "Could not get the latest blog post details from ABP."
"BlogItemErrorMessage": "Could not get the latest blog post details from ABP.",
"PlannedReleaseDate": "Planned release date"
}
}

5
docs/en/Best-Practices/Application-Services.md

@ -213,6 +213,11 @@ This method votes a question and returns the current score of the question.
* **Do** always get all the related entities from repositories to perform the operations on them.
* **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update.
#### Handle files
* **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`.
* **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method.
#### Using Other Application Services
* **Do not** use other application services of the same module/application. Instead;

119
docs/en/CSRF-Anti-Forgery.md

@ -0,0 +1,119 @@
# CSRF/XSRF & Anti Forgery System
"*Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s web browser to perform an unwanted action on a trusted site for which the user is currently authenticated*" ([OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet)).
**ABP Framework completely automates CSRF preventing** and works out of the box without any configuration. Read this documentation only if you want to understand it better or need to customize.
## The Problem
ASP.NET Core [provides infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery) to prevent CSRF attacks by providing a system to **generate** and **validate antiforgery tokens**. However, the standard implementation has a few drawbacks;
Antiforgery token validation is only **enabled for razor pages by default** and not enabled for **HTTP APIs**. You need to enable it yourself for the Controllers. You can use the `[ValidateAntiForgeryToken]` attribute for a specific API Controller/Action or the `[AutoValidateAntiforgeryToken]` attribute to prevent attacks globally.
Once you enable it;
* You need to manually add an HTTP header, named `RequestVerificationToken` to every **AJAX request** made in your application. You should care about obtaining the token, saving in the client side and adding to the HTTP header on every HTTP request.
* All your clients, including **non-browser clients**, should care about obtaining and sending the antiforgery token in every request. In fact, non-browser clients has no CSRF risk and should not care about this.
Especially, the second point is a pain for your clients and unnecessarily consumes your server resources.
> You can read more about the ASP.NET Core antiforgery system in its own [documentation](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery).
## The Solution
ABP Framework provides `[AbpValidateAntiForgeryToken]` and `[AbpAutoValidateAntiforgeryToken]` attributes, just like the attributes explained above. `[AbpAutoValidateAntiforgeryToken]` is already added to the global filters, so you should do nothing to enable it for your application.
ABP Framework also automates the following infrastructure;
* Server side sets a **special cookie**, named `XSRF-TOKEN` by default, that is used make the antiforgery token value available to the browser. This is **done automatically** (by the [application configuration](Application-Configuration.md) endpoint). Nothing to do in the client side.
* In the client side, it reads the token from the cookie and sends it in the **HTTP header** (named `RequestVerificationToken` by default). This is implemented for all the supported UI types.
* Server side validates the antiforgery token **only for same and cross site requests** made by the browser. It bypasses the validation for non-browser clients.
That's all. The systems works smoothly.
## Configuration / Customization
### AbpAntiForgeryOptions
`AbpAntiForgeryOptions` is the main [options class](Options.md) to configure the ABP Antiforgery system. It has the following properties;
* `TokenCookie`: Can be used to configure the cookie details. This cookie is used to store the antiforgery token value in the client side, so clients can read it and sends the value as the HTTP header. Default cookie name is `XSRF-TOKEN`, expiration time is 10 years (yes, ten years! It should be a value longer than the authentication cookie max life time, for the security).
* `AuthCookieSchemaName`: The name of the authentication cookie used by your application. Default value is `Identity.Application` (which becomes `AspNetCore.Identity.Application` on runtime). The default value properly works with the ABP startup templates. **If you change the authentication cookie name, you also must change this.**
* `AutoValidate`: The single point to enable/disable the ABP automatic antiforgery validation system. Default value is `true`.
* `AutoValidateFilter`: A predicate that gets a type and returns a boolean. ABP uses this predicate to check a controller type. If it returns false for a controller type, the controller is excluded from the automatic antiforgery token validation.
* `AutoValidateIgnoredHttpMethods`: A list of HTTP Methods to ignore on automatic antiforgery validation. Default value: "GET", "HEAD", "TRACE", "OPTIONS". These HTTP Methods are safe to skip antiforgery validation since they don't change the application state.
If you need to change these options, do it in the `ConfigureServices` method of your [module](Module-Development-Basics.md).
**Example: Configuring the AbpAntiForgeryOptions**
```csharp
Configure<AbpAntiForgeryOptions>(options =>
{
options.TokenCookie.Expiration = TimeSpan.FromDays(365);
options.AutoValidateIgnoredHttpMethods.Remove("GET");
options.AutoValidateFilter =
type => !type.Namespace.StartsWith("MyProject.MyIgnoredNamespace");
});
```
This configuration;
* Sets the antiforgery token expiration time to ~1 year.
* Enables antiforgery token validation for GET requests too.
* Ignores the controller types in the specified namespace.
### AntiforgeryOptions
`AntiforgeryOptions` is the standard [options class](Options.md) of the ASP.NET Core. **You can find all the information about this class in its [own documentation](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery)**.
`HeaderName` option is especially important for the ABP Framework point of view. Default value of this value is `RequestVerificationToken` and the clients uses this name while sending the token value in the header. So, if you change this option, you should also arrange your clients to align the change. If you don't have a good reason, leave it as default.
### AbpValidateAntiForgeryToken Attribute
If you disable the automatic validation or want to perform the validation for an endpoint that is not validated by default (for example, an endpoint with HTTP GET Method), you can use the `[AbpValidateAntiForgeryToken]` attribute for a **controller type or method** (action).
**Example: Add `[AbpValidateAntiForgeryToken]` to a HTTP GET method**
```csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
namespace MyCompanyName.MyProjectName.Controllers
{
[Route("api/products")]
public class ProductController : AbpController
{
[HttpGet]
[AbpValidateAntiForgeryToken]
public async Task GetAsync()
{
//TODO: ...
}
}
}
```
### Angular UI
Angular supports CSRF Token out of box, but the token header name is `X-XSRF-TOKEN`. Since ABP Framework follows the ASP.NET Core conventions, it changes this value to `RequestVerificationToken` in the core package.
You don't need to make anything unless you need to change the `AntiforgeryOptions.HeaderName` as explained before. If you change it, remember to change the header name for the Angular application too. To do that, add an import declaration for the `HttpClientXsrfModule` into your root module.
**Example: Change the header name to *MyCustomHeaderName***
```typescript
@NgModule({
// ...
imports: [
//...
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'MyCustomHeaderName'
})
],
})
export class AppModule {}
```

160
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md

@ -0,0 +1,160 @@
# How to Add Custom Properties to the User Entity
## Introduction
In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework.
You can see the screenshots below which we will reach at the end of the article.
![custom-identity-user-list](./custom-identity-user-list.png)
![new-user](./new-user.png)
## Preparing the Project
### Startup template and the initial run
Abp Framework offers startup templates to get into the work faster. We can create a new startup template using Abp CLI:
`abp new CustomizeUserDemo`
> In this article, I will go through the MVC application, but it will work also in the [Angular](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No) application.
After the download is finished, we can run **CustomizeUserDemo.DbMigrator** project to create the database migrations and seed the initial data (admin user, role, etc). Then we can run `CustomizeUserDemo.Web` to see that our application is working.
> Default admin username is **admin** and password is **1q2w3E\***
![initial-project](./initial-project.png)
In this article, we will go through a scenario together and find the solutions to our questions through this scenario. However, since the scenario is not a real-life scenario, it may be strange, please don't get too about this issue :)
## Step-1
Add two new properties to the `AppUser` in the Users folder of the **CustomizeUserDemo.Domain** project as follows:
```csharp
public string Title { get; protected set; }
public int Reputation { get; protected set; }
```
## Step-2
Create the Users folder in the **CustomizeUserDemo.Domain.Shared** project, create the class `UserConsts` inside the folder and update the class you created as below:
```csharp
public static class UserConsts
{
public const string TitlePropertyName = "Title";
public const string ReputationPropertyName = "Reputation";
public const int MaxTitleLength = 64;
public const double MaxReputationValue = 1_000;
public const double MinReputationValue = 1;
}
```
## Step-3
Update the `CustomizeUserDemoEfCoreEntityExtensionMappings` class in the **CustomizeUserDemo.EntityFramework** project in the EntityFrameworkCore folder as below:
```csharp
public static class CustomizeUserDemoEfCoreEntityExtensionMappings
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
CustomizeUserDemoGlobalFeatureConfigurator.Configure();
CustomizeUserDemoModuleExtensionConfigurator.Configure();
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
nameof(AppUser.Title),
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.IsRequired();
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength);
}
).MapEfCoreProperty<IdentityUser, int>(
nameof(AppUser.Reputation),
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasDefaultValue(UserConsts.MinReputationValue);
}
);
});
}
}
```
This class can be used to map these extra properties to table fields in the database. Please read [this](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities) article to improve your understanding of what we are doing.
So far, we have added our extra features to the `User` entity and matched these features with the `ef core`.
Now we need to add migration to see what has changed in our database. This for, open the Package Manager Console (PMC) under the menu Tools > NuGet Package Manager.
![nuget-package-manager](./nuget-package-manager.png)
Select the **CustomizeUserDemo.EntityFramework.DbMigrations** as the **default project** and execute the following command:
```bash
Add-Migration "Updated-User-Entity"
```
![added-new-migration](./added-new-migration.png)
This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore.DbMigrations** project.
> If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration).
Finally, run the **CustomizeUserDemo.DbMigrator** project to update the database.
When we updated the database, you can see that the `Title` and `Reputation` columns are added to the `Users` table.
![user-table](./user-table.png)
## Step-4
Open the `CustomizeUserDemoModuleExtensionConfigurator` in the **CustomizeUserDemo.Domain.Shared** project, and change the contents of the `ConfigureExtraProperties` method as shown below:
```csharp
private static void ConfigureExtraProperties()
{
ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>(
UserConsts.TitlePropertyName,
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(UserConsts.MaxTitleLength)
);
}
);
user.AddOrUpdateProperty<int>(
UserConsts.ReputationPropertyName,
options =>
{
options.DefaultValue = UserConsts.MinReputationValue;
options.Attributes.Add(
new RangeAttribute(UserConsts.MinReputationValue, UserConsts.MaxReputationValue)
);
}
);
});
});
}
```
That's it. Now let's run the application and look at the Identity user page. You can also try to edit and recreate a record if you want, it will work even though we haven't done anything extra. Here is the magic code behind ABP framework.
If there is a situation you want to add, you can click the contribute button or make a comment. Also, if you like the article, don't forget to share it :)
Happy coding :)

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/added-new-migration.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/custom-identity-user-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/new-user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/nuget-package-manager.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/user-table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

35
docs/en/Customizing-Application-Modules-Overriding-Services.md

@ -162,6 +162,41 @@ This example class inherits from the `IdentityUserManager` [domain service](Doma
Check the [localization system](Localization.md) to learn how to localize the error messages.
### Example: Overriding a Controller
````csharp
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.Account;
using Volo.Abp.DependencyInjection;
namespace MyProject.Controllers
{
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AccountController))]
public class MyAccountController : AccountController
{
public MyAccountController(IAccountAppService accountAppService)
: base(accountAppService)
{
}
public override async Task SendPasswordResetCodeAsync(
SendPasswordResetCodeDto input)
{
Logger.LogInformation("Your custom logic...");
await base.SendPasswordResetCodeAsync(input);
}
}
}
````
This example replaces the `AccountController` (An API Controller defined in the [Account Module](Modules/Account.md)) and overrides the `SendPasswordResetCodeAsync` method.
**`[ExposeServices(typeof(AccountController))]` is essential** here since it registers this controller for the `AccountController` in the dependency injection system. `[Dependency(ReplaceServices = true)]` is also recommended to clear the old registration (even the ASP.NET Core DI system selects the last registered one).
### Overriding Other Classes
Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above.

12
docs/en/Entity-Framework-Core-SQLite.md

@ -23,6 +23,16 @@ Find `UseSqlServer()` calls in your solution, replace with `UseSqlite()`. Check
SQLite connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/sqlite/ ) for details of SQLite connection string options.
An example connection string is
```
{
"ConnectionStrings": {
"Default": "Filename=./MySQLiteDBFile.sqlite"
}
}
```
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
## Re-Generate the Migrations
@ -38,4 +48,4 @@ Run the `.DbMigrator` project to create the database and seed the initial data.
## Run the Application
It is ready. Just run the application and enjoy coding.
It is ready. Just run the application and enjoy coding.

3
docs/en/JavaScript/Dynamic-JavaScript-API-Clients.md

@ -1,3 +0,0 @@
## Dynamic JavaScript API Clients
TODO

99
docs/en/Localization.md

@ -100,8 +100,6 @@ Configure<AbpLocalizationOptions>(options =>
> The [application startup template](Startup-Templates/Application.md) sets `DefaultResourceType` to the localization resource of the application.
See the *Client Side* section below for a use case.
### Short Localization Resource Name
Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example:
@ -156,13 +154,13 @@ services.Configure<AbpLocalizationOptions>(options =>
* If an extension file defines the same localized string, it overrides the string.
## Getting Localized Texts
## Getting the Localized Texts
### Server Side
Getting the localized text is pretty standard.
Getting the localized text on the server side is pretty standard.
### Simplest Usage In A Class
#### Simplest Usage In A Class
Just inject the `IStringLocalizer<TResource>` service and use it like shown below:
````C#
public class MyService
@ -183,9 +181,13 @@ public class MyService
##### Format Arguments
Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`
Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`.
> Refer to the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) for details about using the localization.
#### Simplest Usage In A Razor View/Page
### Using In A Razor View/Page
Use `IHtmlLocalizer<T>` in razor views/pages;
````c#
@inject IHtmlLocalizer<TestResource> Localizer
@ -193,54 +195,59 @@ Format arguments can be passed after the localization key. If your message is `H
<h1>@Localizer["HelloWorld"]</h1>
````
Refer to the [Microsoft's localization documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) for details about using localization on the server side.
### Client Side
ABP provides JavaScript services to use the same localized texts in the client side.
#### getResource
`abp.localization.getResource` function is used to get a localization resource:
````js
var testResource = abp.localization.getResource('Test');
````
Then you can localize a string based on this resource:
### Special Base Classes
````js
var str = testResource('HelloWorld');
````
Some ABP Framework base classes provide a `L` property to use the localizer even easier.
#### localize
**Example: Localize a text in an application service method**
`abp.localization.localize` function is a shortcut where you can both specify the text name and the resource name:
```csharp
using System.Threading.Tasks;
using MyProject.Localization;
using Volo.Abp.Application.Services;
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
namespace MyProject
{
public class TestAppService : ApplicationService
{
public TestAppService()
{
LocalizationResource = typeof(MyProjectResource);
}
`HelloWorld` is the text to localize, where `Test` is the localization resource name here.
public async Task DoIt()
{
var str = L["HelloWorld"];
}
}
}
```
If you don't specify the localization resource name, it uses the default localization resource defined on the `AbpLocalizationOptions` (see the *Default Resource* section above). Example:
When you set the `LocalizationResource` in the constructor, the `ApplicationService` class uses that resource type when you use the `L` property, just like in the `DoIt()` method.
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
Setting `LocalizationResource` in every application service can be tedious. You can create an abstract base application service class, set it there and derive your application services from that base class. This is already implemented when you create a new project with the [startup templates](Startup-Templates/Application.md). So, you can simply inherit from the base class directly use the `L` property:
##### Format Arguments
```csharp
using System.Threading.Tasks;
If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples:
namespace MyProject
{
public class TestAppService : MyProjectAppService
{
public async Task DoIt()
{
var str = L["HelloWorld"];
}
}
}
```
````js
var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
````
The `L` property is also available for some other base classes like `AbpController` and `AbpPageModel`.
Both of the samples above produce the output `Hello John, welcome!`.
## The Client Side
## See Also
See the following documents to learn how to reuse the same localization texts in the JavaScript side;
* [Localization in Angular UI](UI/Angular/Localization.md)
* [Forms & Validation](UI/AspNetCore/Forms-Validation.md) for the ASP.NET Core MVC / Razor Pages UI
* [Localization for the MVC / Razor Pages UI](UI/AspNetCore/JavaScript-API/Localization.md)
* [Localization for the Blazor UI](UI/Blazor/Localization.md)
* [Localization for the Angular UI](UI/Angular/Localization.md)

2
docs/en/Redis-Cache.md

@ -25,9 +25,11 @@ Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [config
````js
"Redis": {
"IsEnabled": "true",
"Configuration": "127.0.0.1"
}
````
The setting `IsEnabled` is optional and will be considered `true` if it is not set.
Alternatively you can configure the standard [RedisCacheOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.stackexchangeredis.rediscacheoptions) [options](Options.md) class in the `ConfigureServices` method of your [module](Module-Development-Basics.md):

11
docs/en/UI/Angular/Localization.md

@ -219,7 +219,18 @@ If you see an error like this, you should pass the `cultureNameLocaleFileMap` pr
See [all locale files in Angular](https://github.com/angular/angular/tree/master/packages/common/locales).
## Adding new culture
```js
//app.module.ts
import { storeLocaleData } from '@abp/ng.core';
import(
/* webpackChunkName: "_locale-your-locale-js"*/
/* webpackMode: "eager" */
'@angular/common/locales/your-locale.js'
).then(m => storeLocaleData(m.default, 'your-locale'));
```
## See Also
* [Localization in ASP.NET Core](../../Localization.md)

96
docs/en/UI/Angular/Toaster-Service.md

@ -85,7 +85,103 @@ The all open toasts can be removed manually via the `clear` method:
```js
this.toaster.clear();
```
## Replacing ToasterService with 3rd party toaster libraries
If you want the ABP Framework to utilize 3rd party libraries for the toasters instead of the built-in one, you can provide a service that implements `Toaster.Service` interface, and provide it as follows (ngx-toastr library used in example):
> You can use *LocalizationService* for toaster messages translations.
```js
// your-custom-toaster.service.ts
import { Injectable } from '@angular/core';
import { Config, LocalizationService } from '@abp/ng.core';
import { Toaster } from '@abp/ng.theme.shared';
import { ToastrService } from 'ngx-toastr';
@Injectable()
export class CustomToasterService implements Toaster.Service {
constructor(private toastr: ToastrService, private localizationService: LocalizationService) {}
error(
message: Config.LocalizationParam,
title?: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
) {
return this.show(message, title, 'error', options);
}
clear(): void {
this.toastr.clear();
}
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam | undefined,
options: Partial<Toaster.ToastOptions> | undefined,
): Toaster.ToasterId {
return this.show(message, title, 'info', options);
}
remove(id: number): void {
this.toastr.remove(id);
}
show(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
severity: Toaster.Severity,
options: Partial<Toaster.ToastOptions>,
): Toaster.ToasterId {
const translatedMessage = this.localizationService.instant(message);
const translatedTitle = this.localizationService.instant(title);
const toasterOptions = {
positionClass: 'toast-bottom-right',
tapToDismiss: options.tapToDismiss,
...(options.sticky && {
extendedTimeOut: 0,
timeOut: 0,
}),
};
const activeToast = this.toastr.show(
translatedMessage,
translatedTitle,
toasterOptions,
`toast-${severity}`,
);
return activeToast.toastId;
}
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam | undefined,
options: Partial<Toaster.ToastOptions> | undefined,
): Toaster.ToasterId {
return this.show(message, title, 'success', options);
}
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam | undefined,
options: Partial<Toaster.ToastOptions> | undefined,
): Toaster.ToasterId {
return this.show(message, title, 'warning', options);
}
}
```
```js
// app.module.ts
import { ToasterService } from '@abp/ng.theme.shared';
@NgModule({
providers: [
// ...
{
provide: ToasterService,
useClass: CustomToasterService,
},
]
})
```
## API
### success

3
docs/en/UI/AspNetCore/Client-Side-Package-Management.md

@ -94,7 +94,8 @@ An example mapping configuration is shown below:
mappings: {
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/",
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/"
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/",
"@node_modules/bootstrap-v4-rtl/dist/**/*": "@libs/bootstrap-v4-rtl/dist/"
}
````

4
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -376,7 +376,7 @@ Assume that you need to add the Google Analytics script to the layout (that will
![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png)
**NotificationViewComponent.cs**
**GoogleAnalyticsViewComponent.cs**
````csharp
public class GoogleAnalyticsViewComponent : AbpViewComponent
@ -441,7 +441,7 @@ Layout system allows themes to define standard, named layouts and allows any pag
* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default.
* "**Empty**": Empty and minimal layout.
These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are standard layout names and implemented by all the themes out of the box.
These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are the standard layout names and implemented by all the themes out of the box.
#### Layout Location

150
docs/en/UI/AspNetCore/JavaScript-API/Ajax.md

@ -0,0 +1,150 @@
# ASP.NET Core MVC / Razor Pages UI JavaScript AJAX API
`abp.ajax` API provides a convenient way of performing AJAX calls to the server. It internally uses JQuery's `$.ajax`, but automates some common tasks for you;
* Automatically **handles & localize the errors** and informs the user (using the [abp.message](Message.md)). So you typically don't care about errors.
* Automatically adds **anti forgery** token to the HTTP header to satisfy CSRF protection validation on the server side.
* Automatically sets **default options** and allows to configure the defaults in a single place.
* Can **block** a UI part (or the full page) during the AJAX operation.
* Allows to fully customize any AJAX call, by using the standard `$.ajax` **options**.
> While `abp.ajax` makes the AJAX call pretty easier, you typically will use the [Dynamic JavaScript Client Proxy](Dynamic-JavaScript-Client-Proxies.md) system to perform calls to your server side HTTP APIs. `abp.ajax` can be used when you need to perform low level AJAX operations.
## Basic Usage
`abp.ajax` accepts an options object that is accepted by the standard [$.ajax](https://api.jquery.com/jquery.ajax/#jQuery-ajax-settings). All the standard options are valid. It returns a [promise](https://api.jquery.com/category/deferred-object/) as the return value.
**Example: Get the list of users**
````js
abp.ajax({
type: 'GET',
url: '/api/identity/users'
}).then(function(result){
console.log(result);
});
````
This command logs the list of users to the console, if you've **logged in** to the application and have [permission](../../../Authorization.md) for the user management page of the [Identity Module](../../../Modules/Identity.md).
## Error Handling
The example AJAX call above shows an **error message** if you haven't login to the application or you don't have the necessary permissions to perform this request:
![ajax-error](../../../images/ajax-error.png)
All kinds of errors are automatically handled by `abp.ajax`, unless you want to disable it.
### Standard Error Response
`abp.ajax` is compatible with the [exception handling system](../../../Exception-Handling.md) of the ABP Framework and it properly handles the standard error format returned from the server. A typical error message is a JSON as like below:
````json
{
"error": {
"code": "App:010042",
"message": "This topic is locked and can not add a new message",
"details": "A more detailed info about the error..."
}
}
````
The error message is directly shown to the user, using the `message` and `details` properties.
### Non-Standard Error Response & HTTP Status Codes
It also handles errors even if the standard error format was not sent by the server. This can be case if you bypass the ABP exception handling system and manually build the HTTP response on the server. In that case, **HTTP status codes** are considered.
The following HTTP Status Codes are pre-defined;
* **401**: Shows an error message like "*You should be authenticated (sign in) in order to perform this operation*". When the users click the OK button, they are redirected to the home page of the application to make them login again.
* **403**: Shows an error message like "*You are not allowed to perform this operation*".
* **404**: Shows an error message like "*The resource requested could not found on the server*".
* **Others**: Shows a generic error message like "*An error has occurred. Error detail not sent by server*".
All these messages are localized based on the current user's language.
### Manually Handling the Errors
Since `abp.ajax` returns a promise, you can always chain a `.cactch(...)` call to register a callback that is executed if the AJAX request fails.
**Example: Show an alert if the AJAX request fails**
````js
abp.ajax({
type: 'GET',
url: '/api/identity/users'
}).then(function(result){
console.log(result);
}).catch(function(){
alert("request failed :(");
});
````
While your callback is fired, ABP still handles the error itself. If you want to disable automatic error handling, pass `abpHandleError: false` the the `abp.ajax` options.
**Example: Disable the auto error handling**
````js
abp.ajax({
type: 'GET',
url: '/api/identity/users',
abpHandleError: false //DISABLE AUTO ERROR HANDLING
}).then(function(result){
console.log(result);
}).catch(function(){
alert("request failed :(");
});
````
If you set `abpHandleError: false` and don't catch the error yourself, then the error will be hidden and the request silently fails. `abp.ajax` still logs the error to the browser console (see the *Configuration* section to override it).
## Configuration
`abp.ajax` has a **global configuration** that you can customize based on your requirements.
### Default AJAX Options
`abp.ajax.defaultOpts` object is used to configure default options used while performing an AJAX call, unless you override them. Default value of this object is shown below:
````js
{
dataType: 'json',
type: 'POST',
contentType: 'application/json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}
````
So, if you want to change the default request type, you can do it as shown below:
````js
abp.ajax.defaultOpts.type = 'GET';
````
Write this code before all of your JavaScript code. You typically want to place such a configuration into a separate JavaScript file and add it to the layout using the global [bundle](../Bundling-Minification.md).
### Log/Show Errors
The following functions can be overridden to customize the logging and showing the error messages:
* `abp.ajax.logError` function logs errors using the [abp.log.error(...)](Logging.md) by default.
* `abp.ajax.showError` function shows the error message using the [abp.message.error(...)](Message.md) by default.
* `abp.ajax.handleErrorStatusCode` handles different HTTP status codes and shows different messages based on the code.
* `abp.ajax.handleAbpErrorResponse` handles the errors sent with the standard ABP error format.
* `abp.ajax.handleNonAbpErrorResponse` handles the non-standard error responses.
* `abp.ajax.handleUnAuthorizedRequest` handles responses with `401` status code and redirect users to the home page of the application.
**Example: Override the `logError` function**
````js
abp.ajax.logError = function(error) {
//...
}
````
### Other Options
* `abp.ajax.ajaxSendHandler` function is used to intercept the AJAX requests and add antiforgery token to the HTTP header. Note that this works for all AJAX requests, even if you don't use the `abp.ajax`.

3
docs/en/UI/AspNetCore/JavaScript-API/Dynamic-JavaScript-Client-Proxies.md

@ -0,0 +1,3 @@
# ASP.NET Core MVC / Razor Pages UI: Dynamic JavaScript Client Proxies
TODO

12
docs/en/UI/AspNetCore/JavaScript-API/Index.md

@ -1,23 +1,23 @@
# JavaScript API
ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side.
ABP provides a set of JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform common application requirements easily in the client side and integrate to the server side.
## APIs
* abp.ajax
* [abp.ajax](Ajax.md)
* abp.auth
* abp.currentUser
* abp.dom
* abp.event
* abp.features
* abp.localization
* [abp.localization](Localization.md)
* abp.log
* [abp.message](Message.md)
* abp.ModalManager
* abp.notify
* [abp.notify](Notify.md)
* abp.security
* abp.setting
* abp.ui
* abp.utils
* abp.ResourceLoader
* abp.WidgetManager
* Other APIs
* abp.WidgetManager

143
docs/en/UI/AspNetCore/JavaScript-API/Localization.md

@ -0,0 +1,143 @@
# ASP.NET Core MVC / Razor Pages UI: JavaScript Localization API
Localization API allows you to reuse the server side localization resources in the client side.
> This document only explains the JavaScript API. See the [localization document](../../../Localization.md) to understand the ABP localization system.
## Basic Usage
`abp.localization.getResource(...)` function is used to get a localization resource:
````js
var testResource = abp.localization.getResource('Test');
````
Then you can localize a string based on this resource:
````js
var str = testResource('HelloWorld');
````
`abp.localization.localize(...)` function is a shortcut where you can both specify the text name and the resource name:
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
`HelloWorld` is the text to localize, where `Test` is the localization resource name here.
### Fallback Logic
If given texts was not localized, localization method returns the given key as the localization result.
### Default Localization Resource
If you don't specify the localization resource name, it uses the **default localization resource** defined on the `AbpLocalizationOptions` (see the [localization document](../../../Localization.md)).
**Example: Using the default localization resource**
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
### Format Arguments
If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples:
````js
var testSource = abp.localization.getResource('Test');
var str1 = testSource('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWelcomeMessage', 'Test', 'John');
````
Assuming the `HelloWelcomeMessage` is localized as `Hello {0}, welcome!`, both of the samples above produce the output `Hello John, welcome!`.
## Other Properties & Methods
### abp.localization.values
`abp.localization.values` property stores all the localization resources, keys and their values.
### abp.localization.isLocalized
Returns a boolean indicating that if the given text was localized or not.
**Example**
````js
abp.localization.isLocalized('ProductName', 'MyResource');
````
Returns `true` if the `ProductName` text was localized for the `MyResource` resource. Otherwise, returns `false`. You can leave the resource name empty to use the default localization resource.
### abp.localization.defaultResourceName
`abp.localization.defaultResourceName` can be set to change the default localization resource. You normally don't set this since the ABP Framework automatically sets is based on the server side configuration.
### abp.localization.currentCulture
`abp.localization.currentCulture` returns an object to get information about the **currently selected language**.
An example value of this object is shown below:
````js
{
"displayName": "English",
"englishName": "English",
"threeLetterIsoLanguageName": "eng",
"twoLetterIsoLanguageName": "en",
"isRightToLeft": false,
"cultureName": "en",
"name": "en",
"nativeName": "English",
"dateTimeFormat": {
"calendarAlgorithmType": "SolarCalendar",
"dateTimeFormatLong": "dddd, MMMM d, yyyy",
"shortDatePattern": "M/d/yyyy",
"fullDateTimePattern": "dddd, MMMM d, yyyy h:mm:ss tt",
"dateSeparator": "/",
"shortTimePattern": "h:mm tt",
"longTimePattern": "h:mm:ss tt"
}
}
````
### abp.localization.languages
Used to get list of all **available languages** in the application. An example value of this object is shown below:
````js
[
{
"cultureName": "en",
"uiCultureName": "en",
"displayName": "English",
"flagIcon": null
},
{
"cultureName": "fr",
"uiCultureName": "fr",
"displayName": "Français",
"flagIcon": null
},
{
"cultureName": "pt-BR",
"uiCultureName": "pt-BR",
"displayName": "Português",
"flagIcon": null
},
{
"cultureName": "tr",
"uiCultureName": "tr",
"displayName": "Türkçe",
"flagIcon": null
},
{
"cultureName": "zh-Hans",
"uiCultureName": "zh-Hans",
"displayName": "简体中文",
"flagIcon": null
}
]
````

3
docs/en/UI/AspNetCore/JavaScript-API/Logging.md

@ -0,0 +1,3 @@
# ASP.NET Core MVC / Razor Pages UI: JavaScript Logging API
TODO

128
docs/en/UI/AspNetCore/JavaScript-API/Message.md

@ -0,0 +1,128 @@
# ASP.NET Core MVC / Razor Pages UI: JavaScript Message API
Message API is used to show nice looking messages to the user as a blocking dialog. Message API is an abstraction provided by the ABP Framework and implemented using the [SweetAlert2](https://sweetalert2.github.io/) library by default.
## Quick Example
Use `abp.message.success(...)` function to show a success message:
````js
abp.message.success('Your changes have been successfully saved!', 'Congratulations');
````
It will show a dialog on the UI:
![js-message-success](../../../images/js-message-success.png)
## Informative Messages
There are four types of informative message functions:
* `abp.message.info(...)`
* `abp.message.success(...)`
* `abp.message.warn(...)`
* `abp.message.error(...)`
All these methods get two parameters:
* `message`: The message (`string`) to be shown.
* `title`: An optional (`string`) title.
**Example: Show an error message**
````js
abp.message.error('Your credit card number is not valid!');
````
![js-message-error](../../../images/js-message-error.png)
## Confirmation Message
`abp.message.confirm(...)` function can be used to get a confirmation from the user.
**Example**
Use the following code to get a confirmation result from the user:
````js
abp.message.confirm('Are you sure to delete the "admin" role?')
.then(function(confirmed){
if(confirmed){
console.log('TODO: deleting the role...');
}
});
````
The resulting UI will be like shown below:
![js-message-confirm](../../../images/js-message-confirm.png)
If user has clicked the `Yes` button, the `confirmed` argument in the `then` callback function will be `true`.
> "*Are you sure?*" is the default title (localized based on the current language) and you can override it.
### The Return Value
The return value of the `abp.message.confirm(...)` function is a promise, so you can chain a `then` callback as shown above.
### Parameters
`abp.message.confirm(...)` function has the following parameters:
* `message`: A message (string) to show to the user.
* `titleOrCallback` (optional): A title or a callback function. If you supply a string, it is shown as the title. If you supply a callback function (that gets a `bool` parameter) then it's called with the result.
* `callback` (optional): If you've passes a title to the second parameter, you can pass your callback function as the 3rd parameter.
Passing a callback function is an alternative to the `then` callback shown above.
**Example: Providing all the parameters and getting result with the callback function**
````js
abp.message.confirm(
'Are you sure to delete the "admin" role?',
'Be careful!',
function(confirmed){
if(confirmed){
console.log('TODO: deleting the role...');
}
});
````
## SweetAlert Configuration
The Message API is implemented using the [SweetAlert2](https://sweetalert2.github.io/) library by default. If you want to change its configuration, you can set the options in the `abp.libs.sweetAlert.config` object. The default configuration object is shown below:
````js
{
'default': {
},
info: {
icon: 'info'
},
success: {
icon: 'success'
},
warn: {
icon: 'warning'
},
error: {
icon: 'error'
},
confirm: {
icon: 'warning',
title: 'Are you sure?',
buttons: ['Cancel', 'Yes']
}
}
````
> "Are you sure?", "Cancel" and "Yes" texts are automatically localized based on the current language.
So, if you want to set the `warn` icon, you can set it like:
````js
abp.libs.sweetAlert.config.warn.icon = 'error';
````
See the [SweetAlert document](https://sweetalert2.github.io/) for all the configuration options.

45
docs/en/UI/AspNetCore/JavaScript-API/Notify.md

@ -0,0 +1,45 @@
# ASP.NET Core MVC / Razor Pages UI: JavaScript Notify API
Notify API is used to show toast style, auto disappearing UI notifications to the end user. It is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default.
## Quick Example
Use `abp.notify.success(...)` function to show a success message:
````js
abp.notify.success(
'The product "Acme Atom Re-Arranger" has been successfully deleted.',
'Deleted the Product'
);
````
A notification message is shown at the bottom right of the page:
![js-message-success](../../../images/js-notify-success.png)
## Notification Types
There are four types of pre-defined notifications;
* `abp.notify.success(...)`
* `abp.notify.info(...)`
* `abp.notify.warn(...)`
* `abp.notify.error(...)`
All of the methods above gets the following parameters;
* `message`: A message (`string`) to show to the user.
* `title`: An optional title (`string`).
* `options`: Additional options to be passed to the underlying library, to the Toastr by default.
## Toastr Configuration
The notification API is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. You can see its own configuration options.
**Example: Show toast messages on the top right of the page**
````js
toastr.options.positionClass = 'toast-top-right';
````
> ABP sets this option to `toast-bottom-right` by default. You can override it just as shown above.

106
docs/en/UI/AspNetCore/Layout-Hooks.md

@ -1,3 +1,105 @@
# Layout Hooks
# ASP.NET Core MVC / Razor Pages Layout Hooks
TODO
ABP Framework theming system places the page layout into the [theme](Theming.md) NuGet packages. That means the final application doesn't include a `Layout.cshtml`, so you can't directly change the layout code to customize it.
You copy the theme code into your solution. In this case you are completely free to customize it. However, then you won't be able to get automatic updates of the theme (by upgrading the theme NuGet package).
ABP Framework provides different ways of [customizing the UI](Customization-User-Interface.md).
The **Layout Hook System** allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Finally, you can add a **view component** into a hook point.
## Example: Add Google Analytics Script
Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project:
![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class GoogleAnalyticsViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
}
}
````
**Default.cshtml**
````html
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxxx-1', 'auto');
ga('send', 'pageview');
</script>
````
Change `UA-xxxxxx-1` with your own code.
You can then add this component to any of the hook points in the `ConfigureServices` of your module:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last, //The hook name
typeof(GoogleAnalyticsViewComponent) //The component to add
);
});
````
Now, the GA code will be inserted in the `head` of the page as the last item.
### Specifying the Layout
The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last,
typeof(GoogleAnalyticsViewComponent),
layout: StandardLayouts.Application //Set the layout to add
);
});
````
See the *Layouts* section below to learn more about the layout system.
## Layout Hook Points
There are some pre-defined layout hook points. The `LayoutHooks.Head.Last` used above was one of them. The standard hook points are;
* `LayoutHooks.Head.First`: Used to add a component as the first item in the HTML head tag.
* `LayoutHooks.Head.Last`: Used to add a component as the last item in the HTML head tag.
* `LayoutHooks.Body.First`: Used to add a component as the first item in the HTML body tag.
* `LayoutHooks.Body.Last`: Used to add a component as the last item in the HTML body tag.
* `LayoutHooks.PageContent.First`: Used to add a component just before the page content (the `@RenderBody()` in the layout).
* `LayoutHooks.PageContent.Last`: Used to add a component just after the page content (the `@RenderBody()` in the layout).
> You (or the modules you are using) can add **multiple items to the same hook point**. All of them will be added to the layout by the order they were added.
## Layouts
Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts:
* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc.
* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default.
* "**Empty**": Empty and minimal layout.
These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are the standard layout names and implemented by all the themes out of the box.
### Layout Location
You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary.
## See Also
* [Customizing the User Interface](Customization-User-Interface.md)

10
docs/en/UI/AspNetCore/Tag-Helpers/Badges.md

@ -2,11 +2,11 @@
## Introduction
`abp-badge` and `abp-badge-pill` are abp tags for badges.
`abp-badge` and `abp-badge-pill` are ABP Tag Helper attributes for `a` and `span` html tags.
Basic usage:
````csharp
````html
<span abp-badge="Primary">Primary</span>
<a abp-badge="Info" href="#">Info</a>
<a abp-badge-pill="Danger" href="#">Danger</a>
@ -22,8 +22,7 @@ See the [badges demo page](https://bootstrap-taghelpers.abp.io/Components/Badges
* Indicates the type of the badge. Should be one of the following values:
* `_` (default value)
* `Default` (default value)
* `Default`
* `Primary`
* `Secondary`
* `Success`
@ -35,6 +34,7 @@ See the [badges demo page](https://bootstrap-taghelpers.abp.io/Components/Badges
Example:
````csharp
````html
<span abp-badge-pill="Danger">Danger</span>
````

18
docs/en/UI/AspNetCore/Tag-Helpers/Blockquote.md

@ -0,0 +1,18 @@
# Blockquote
`abp-blockquote` is the main container for blockquote items.
Basic usages:
````html
<abp-blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
</abp-blockquote>
<abp-blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
<footer>Someone famous in Source Title</footer>
</abp-blockquote>
````
It adds `blockquote` class to main container, also adds `blockquote-footer` class to inner `footer` element and `mb-0` class to inner `p` element.

6
docs/en/UI/AspNetCore/Tag-Helpers/Borders.md

@ -2,11 +2,11 @@
## Introduction
`abp-border` is a main element for border styling.
`abp-border` is ABP Tag Helper attribute for border styling.
Basic usage:
````csharp
````html
<span abp-border="Default"></span>
<span abp-border="Top"></span>
<span abp-border="Right"></span>
@ -124,3 +124,5 @@ A value indicates type, position and the color of the border. Should be one of t
* `Bottom_Light_0`
* `Bottom_Dark_0`
* `Bottom_White_0`
(Values with `_0` at the end is for [Subtractive](https://getbootstrap.com/docs/4.0/utilities/borders/#subtractive) usages)

2
docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md

@ -6,7 +6,7 @@
Basic usage:
````csharp
````html
<abp-breadcrumb>
<abp-breadcrumb-item href="#" title="Home" />
<abp-breadcrumb-item href="#" title="Library"/>

2
docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md

@ -6,7 +6,7 @@
Basic usage:
````csharp
````html
<abp-button-group>
<abp-button button-type="Secondary">Left</abp-button>
<abp-button button-type="Secondary">Middle</abp-button>

2
docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md

@ -6,7 +6,7 @@
Basic usage:
````csharp
````html
<abp-carousel>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>

2
docs/en/UI/AspNetCore/Tag-Helpers/Collapse.md

@ -6,7 +6,7 @@
Basic usage:
````xml
````html
<abp-button button-type="Primary" abp-collapse-id="collapseExample" text="Button with data-target" />
<a abp-button="Primary" abp-collapse-id="collapseExample"> Link with href </a>

14
docs/en/UI/AspNetCore/Tag-Helpers/Figure.md

@ -0,0 +1,14 @@
# Figures
`abp-figure` is the main container for bootstrap figure items.
Basic usage:
````html
<abp-figure>
<abp-image src="..." class="img-fluid rounded" alt="A generic square placeholder image with rounded corners in a figure.">
<abp-figcaption class="text-right">A caption for the above image.</abp-figcaption>
</abp-figure>
````
It adds `figure` class to main container, also adds `figure-img` class to inner `abp-image` element and `figure-caption` class to inner `abp-figcaption` element.

9
docs/en/UI/AspNetCore/Tag-Helpers/Index.md

@ -13,19 +13,26 @@ ABP Framework also adds some **useful features** to the standard bootstrap compo
Here, the list of components those are wrapped by the ABP Framework:
* [Alerts](Alerts.md)
* [Badges](Badges.md))
* [Blockquote](Blockquote.md)
* [Borders](Borders.md)
* [Breadcrumb](Breadcrumb.md)
* [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Carousel](Carousel.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [Figures](Figure.md)
* [Grids](Grids.md)
* [List Groups](List-Groups.md)
* [Modals](Modals.md)
* [Navigation](Navs.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tables](Tables.md)
* [Tabs](Tabs.md)
* [Tooltips](Tooltips.md)
* ...
> Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples.

4
docs/en/UI/AspNetCore/Tag-Helpers/Navs.md

@ -6,7 +6,7 @@
Basic usage:
````csharp
````html
<abp-nav nav-style="Pill" align="Center">
<abp-nav-item>
<a abp-nav-link active="true" href="#">Active</a>
@ -78,7 +78,7 @@ See the [navs demo page](https://bootstrap-taghelpers.abp.io/Components/Navs) to
Example:
````csharp
````html
<abp-nav-bar size="Lg" navbar-style="Dark_Warning">
<a abp-navbar-brand href="#">Navbar</a>
<abp-navbar-toggle>

2
docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md

@ -12,7 +12,7 @@ Basic usage:
Model:
````xml
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination;

2
docs/en/UI/AspNetCore/Tag-Helpers/Tables.md

@ -6,7 +6,7 @@
Basic usage:
````csharp
````html
<abp-table hoverable-rows="true" responsive-sm="true">
<thead>
<tr>

3
docs/en/UI/Blazor/Localization.md

@ -0,0 +1,3 @@
# Blazor UI: Localization
Blazor applications can reuse the same `IStringLocalizer<T>` service that is explained in the [localization document](../../Localization.md). All the localization resources and texts available in the server side are usable in the Blazor application.

35
docs/en/docs-nav.json

@ -99,11 +99,15 @@
"path": "CLI.md"
},
{
"text": "Authentication",
"text": "Authentication & Security",
"items": [
{
"text": "Social/External Logins",
"path": "Authentication/Social-External-Logins.md"
},
{
"text": "CSRF/XSRF & Anti Forgery",
"path": "CSRF-Anti-Forgery.md"
}
]
},
@ -432,10 +436,39 @@
"text": "Widgets",
"path": "UI/AspNetCore/Widgets.md"
},
{
"text": "Layout Hooks",
"path": "UI/AspNetCore/Layout-Hooks.md"
},
{
"text": "Theming",
"path": "UI/AspNetCore/Theming.md"
},
{
"text": "JavaScript API",
"items": [
{
"text": "Overall",
"path": "UI/AspNetCore/JavaScript-API/Index.md"
},
{
"text": "Localization",
"path": "UI/AspNetCore/JavaScript-API/Localization.md"
},
{
"text": "AJAX",
"path": "UI/AspNetCore/JavaScript-API/Ajax.md"
},
{
"text": "Message",
"path": "UI/AspNetCore/JavaScript-API/Message.md"
},
{
"text": "Notify",
"path": "UI/AspNetCore/JavaScript-API/Notify.md"
}
]
},
{
"text": "Customize/Extend the UI",
"path": "UI/AspNetCore/Customization-User-Interface.md"

BIN
docs/en/images/ajax-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/images/js-message-confirm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/images/js-message-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/images/js-message-success.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/images/js-notify-success.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

2
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -70,7 +70,7 @@ namespace Acme.BookStore.Web.Pages.Identity.Users
在这种情况下;
1. 像上面描述过的那术重写C#页面模型类,但不需要替换已存在的页面模型类.
1. 像上面描述过的那样重写C#页面模型类,但不需要替换已存在的页面模型类.
2. 像上面描述过的那样重写Razor页面,并且更改@model指向新的页面模型
#### 示例

28
framework/Volo.Abp.sln

@ -319,33 +319,33 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun.Tests", "test\Volo.Abp.BlobStoring.Aliyun.Tests\Volo.Abp.BlobStoring.Aliyun.Tests.csproj", "{8E49687A-E69F-49F2-8DB0-428D0883A937}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws", "src\Volo.Abp.BlobStoring.Aws\Volo.Abp.BlobStoring.Aws.csproj", "{50968CDE-1029-4051-B2E5-B69D0ECF2A18}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aws", "src\Volo.Abp.BlobStoring.Aws\Volo.Abp.BlobStoring.Aws.csproj", "{50968CDE-1029-4051-B2E5-B69D0ECF2A18}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Kafka", "src\Volo.Abp.Kafka\Volo.Abp.Kafka.csproj", "{2A864049-9CD5-4493-8CDB-C408474D43D4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Kafka", "src\Volo.Abp.Kafka\Volo.Abp.Kafka.csproj", "{2A864049-9CD5-4493-8CDB-C408474D43D4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EventBus.Kafka", "src\Volo.Abp.EventBus.Kafka\Volo.Abp.EventBus.Kafka.csproj", "{C1D891B0-AE83-42CB-987D-425A2787DE78}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EventBus.Kafka", "src\Volo.Abp.EventBus.Kafka\Volo.Abp.EventBus.Kafka.csproj", "{C1D891B0-AE83-42CB-987D-425A2787DE78}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures", "src\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj", "{04F44063-C952-403A-815F-EFB778BDA125}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.GlobalFeatures", "src\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj", "{04F44063-C952-403A-815F-EFB778BDA125}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures.Tests", "test\Volo.Abp.GlobalFeatures.Tests\Volo.Abp.GlobalFeatures.Tests.csproj", "{231F1581-AA21-44C3-BF27-51EB3AD5355C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.GlobalFeatures.Tests", "test\Volo.Abp.GlobalFeatures.Tests\Volo.Abp.GlobalFeatures.Tests.csproj", "{231F1581-AA21-44C3-BF27-51EB3AD5355C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.IdentityModel.WebAssembly", "src\Volo.Abp.Http.Client.IdentityModel.WebAssembly\Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj", "{3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.WebAssembly", "src\Volo.Abp.Http.Client.IdentityModel.WebAssembly\Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj", "{3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Client.Common", "src\Volo.Abp.AspNetCore.Mvc.Client.Common\Volo.Abp.AspNetCore.Mvc.Client.Common.csproj", "{8A22D962-016E-474A-8BB7-F831F0ABF3AC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Client.Common", "src\Volo.Abp.AspNetCore.Mvc.Client.Common\Volo.Abp.AspNetCore.Mvc.Client.Common.csproj", "{8A22D962-016E-474A-8BB7-F831F0ABF3AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly", "src\Volo.Abp.AspNetCore.Components.WebAssembly\Volo.Abp.AspNetCore.Components.WebAssembly.csproj", "{E1A62D10-F2FB-4040-BD60-11A3934058DF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.WebAssembly", "src\Volo.Abp.AspNetCore.Components.WebAssembly\Volo.Abp.AspNetCore.Components.WebAssembly.csproj", "{E1A62D10-F2FB-4040-BD60-11A3934058DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlazoriseUI", "src\Volo.Abp.BlazoriseUI\Volo.Abp.BlazoriseUI.csproj", "{4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlazoriseUI", "src\Volo.Abp.BlazoriseUI\Volo.Abp.BlazoriseUI.csproj", "{4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme", "src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj", "{ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme", "src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj", "{ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj", "{29CA7471-4E3E-4E75-8B33-001DDF682F01}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj", "{29CA7471-4E3E-4E75-8B33-001DDF682F01}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Autofac.WebAssembly", "src\Volo.Abp.Autofac.WebAssembly\Volo.Abp.Autofac.WebAssembly.csproj", "{37F89B0B-1C6B-426F-A5EE-676D1956D9E9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Autofac.WebAssembly", "src\Volo.Abp.Autofac.WebAssembly\Volo.Abp.Autofac.WebAssembly.csproj", "{37F89B0B-1C6B-426F-A5EE-676D1956D9E9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj", "{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj", "{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ExceptionHandling", "src\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj", "{B9D1ADCB-D552-4626-A1F1-78FF72C1E822}"
EndProject

6
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor

@ -11,12 +11,14 @@
</button>
<div class="collapse navbar-collapse" id="main-navbar-collapse">
<ul class="navbar-nav mx-auto">
<NavMenu/>
<NavMenu />
</ul>
<NavToolbar/>
<NavToolbar />
</div>
</div>
</nav>
<div class="container">
@Body
<UiMessageAlert />
</div>

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor

@ -8,3 +8,5 @@
@using Volo.Abp.AspNetCore.Components.WebAssembly
@using Blazorise
@using Blazorise.DataGrid
@using Volo.Abp.BlazoriseUI;
@using Volo.Abp.BlazoriseUI.Components;

1
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj

@ -22,6 +22,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.0-rc.1.*" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpBlazorClientHttpMessageHandler.cs

@ -17,7 +17,7 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly
_jsRuntime = jsRuntime;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await SetLanguageAsync(request, cancellationToken);

21
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs

@ -1,17 +1,18 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public interface IUiMessageService
{
Task InfoAsync(string message, string title = null);
Task SuccessAsync(string message, string title = null);
Task WarnAsync(string message, string title = null);
Task ErrorAsync(string message, string title = null);
Task<bool> ConfirmAsync(string message, string title = null);
Task InfoAsync(string message, string title = null, Action<UiMessageOptions> options = null);
Task SuccessAsync(string message, string title = null, Action<UiMessageOptions> options = null);
Task WarnAsync(string message, string title = null, Action<UiMessageOptions> options = null);
Task ErrorAsync(string message, string title = null, Action<UiMessageOptions> options = null);
Task<bool> ConfirmAsync(string message, string title = null, Action<UiMessageOptions> options = null);
}
}

3
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs

@ -5,5 +5,8 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly
public interface IUiNotificationService
{
Task Info(string message);
Task Success(string message);
Task Warn(string message);
Task Error(string message);
}
}

34
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiMessageService.cs

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public class NullUiMessageService : IUiMessageService, ITransientDependency
{
public Task InfoAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return Task.CompletedTask;
}
public Task SuccessAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return Task.CompletedTask;
}
public Task WarnAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return Task.CompletedTask;
}
public Task ErrorAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return Task.CompletedTask;
}
public Task<bool> ConfirmAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return Task.FromResult(true);
}
}
}

14
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs

@ -9,5 +9,19 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
return Task.CompletedTask;
}
public Task Success(string message)
{
return Task.CompletedTask;
}
public Task Warn(string message)
{
return Task.CompletedTask;
}
public Task Error(string message)
{
return Task.CompletedTask;
}
}
}

35
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageEventArgs.cs

@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
public class UiMessageEventArgs : EventArgs
{
public UiMessageEventArgs(UiMessageType messageType, string message, string title, UiMessageOptions options)
{
MessageType = messageType;
Message = message;
Title = title;
Options = options;
}
public UiMessageEventArgs(UiMessageType messageType, string message, string title, UiMessageOptions options, TaskCompletionSource<bool> callback)
{
MessageType = messageType;
Message = message;
Title = title;
Options = options;
Callback = callback;
}
public UiMessageType MessageType { get; set; }
public string Message { get; }
public string Title { get; }
public UiMessageOptions Options { get; }
public TaskCompletionSource<bool> Callback { get; }
}
}

53
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageOptions.cs

@ -0,0 +1,53 @@
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
/// <summary>
/// Options to override message dialog appearance.
/// </summary>
public class UiMessageOptions
{
/// <summary>
/// If true, the message dialogue will be centered on the screen.
/// </summary>
public bool CenterMessage { get; set; }
/// <summary>
/// If true, the message dialogue will show the large icon for the current message type.
/// </summary>
public bool ShowMessageIcon { get; set; }
/// <summary>
/// Overrides the build-in message icon.
/// </summary>
public object MessageIcon { get; set; }
/// <summary>
/// Custom text for the Ok button.
/// </summary>
public string OkButtonText { get; set; }
/// <summary>
/// Custom icon for the Ok button.
/// </summary>
public object OkButtonIcon { get; set; }
/// <summary>
/// Custom text for the Confirmation button.
/// </summary>
public string ConfirmButtonText { get; set; }
/// <summary>
/// Custom icon for the Confirmation button.
/// </summary>
public object ConfirmButtonIcon { get; set; }
/// <summary>
/// Custom text for the Cancel button.
/// </summary>
public string CancelButtonText { get; set; }
/// <summary>
/// Custom icon for the Cancel button.
/// </summary>
public object CancelButtonIcon { get; set; }
}
}

17
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Volo.Abp.DependencyInjection;
@ -14,27 +15,27 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly
JsRuntime = jsRuntime;
}
public async Task InfoAsync(string message, string title = null)
public async Task InfoAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
await JsRuntime.InvokeVoidAsync("alert", message);
}
public async Task SuccessAsync(string message, string title = null)
public async Task SuccessAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
await JsRuntime.InvokeVoidAsync("alert", message);
}
public async Task WarnAsync(string message, string title = null)
public async Task WarnAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
await JsRuntime.InvokeVoidAsync("alert", message);
}
public async Task ErrorAsync(string message, string title = null)
public async Task ErrorAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
await JsRuntime.InvokeVoidAsync("alert", message);
}
public async Task<bool> ConfirmAsync(string message, string title = null)
public async Task<bool> ConfirmAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
return await JsRuntime.InvokeAsync<bool>("confirm", message);
}

14
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageType.cs

@ -0,0 +1,14 @@
namespace Volo.Abp.AspNetCore.Components.WebAssembly
{
/// <summary>
/// Defines the possible ui message types with predefined actions.
/// </summary>
public enum UiMessageType
{
Info,
Success,
Warning,
Error,
Confirmation,
}
}

48
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs

@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
@ -8,59 +7,24 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class MultiTenancyMiddleware : IMiddleware, ITransientDependency
{
private readonly ITenantResolver _tenantResolver;
private readonly ITenantStore _tenantStore;
private readonly ITenantConfigurationProvider _tenantConfigurationProvider;
private readonly ICurrentTenant _currentTenant;
private readonly ITenantResolveResultAccessor _tenantResolveResultAccessor;
public MultiTenancyMiddleware(
ITenantResolver tenantResolver,
ITenantStore tenantStore,
ICurrentTenant currentTenant,
ITenantResolveResultAccessor tenantResolveResultAccessor)
ITenantConfigurationProvider tenantConfigurationProvider,
ICurrentTenant currentTenant)
{
_tenantResolver = tenantResolver;
_tenantStore = tenantStore;
_tenantConfigurationProvider = tenantConfigurationProvider;
_currentTenant = currentTenant;
_tenantResolveResultAccessor = tenantResolveResultAccessor;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var resolveResult = _tenantResolver.ResolveTenantIdOrName();
_tenantResolveResultAccessor.Result = resolveResult;
TenantConfiguration tenant = null;
if (resolveResult.TenantIdOrName != null)
{
tenant = await FindTenantAsync(resolveResult.TenantIdOrName);
if (tenant == null)
{
throw new BusinessException(
code: "Volo.AbpIo.MultiTenancy:010001",
message: "Tenant not found!",
details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName
);
}
}
var tenant = await _tenantConfigurationProvider.GetAsync(saveResolveResult: true);
using (_currentTenant.Change(tenant?.Id, tenant?.Name))
{
await next(context);
}
}
private async Task<TenantConfiguration> FindTenantAsync(string tenantIdOrName)
{
if (Guid.TryParse(tenantIdOrName, out var parsedTenantId))
{
return await _tenantStore.FindAsync(parsedTenantId);
}
else
{
return await _tenantStore.FindAsync(tenantIdOrName);
}
}
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonTagHelperServiceBase.cs

@ -47,6 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
var icon = new TagBuilder("i");
icon.AddCssClass(GetIconClass(context, output));
output.Content.AppendHtml(icon);
output.Content.Append(" ");
}
protected virtual string GetIconClass(TagHelperContext context, TagHelperOutput output)
@ -80,4 +81,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button
}
}
}
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs

@ -45,12 +45,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
sb.AppendLine(GetSaveButton());
break;
case AbpModalButtons.Save | AbpModalButtons.Cancel:
sb.AppendLine(GetSaveButton());
sb.AppendLine(GetCancelButton());
sb.AppendLine(GetSaveButton());
break;
case AbpModalButtons.Save | AbpModalButtons.Close:
sb.AppendLine(GetSaveButton());
sb.AppendLine(GetCloseButton());
sb.AppendLine(GetSaveButton());
break;
}
@ -72,6 +72,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
element.AddCssClass("btn-primary");
element.Attributes.Add("data-busy-text", _localizer["SavingWithThreeDot"]);
element.InnerHtml.AppendHtml(icon);
element.InnerHtml.AppendHtml(" ");
element.InnerHtml.AppendHtml(span);
return element.ToHtmlString();

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/es.json

@ -0,0 +1,12 @@
{
"culture": "es",
"texts": {
"GivenTenantIsNotAvailable": "El inquilino {0} no está disponible",
"Tenant": "Inquilino",
"Switch": "cambiar",
"Name": "Nombre",
"SwitchTenant": "Cambiar inquilino",
"SwitchTenantHint": "Deje en blanco el campo nombre para cambiar el lado del host.",
"NotSelected": "No seleccionado"
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml

@ -13,7 +13,6 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.MultiTenancy
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IOptions<AbpMultiTenancyOptions> MultiTenancyOptions
@inject ICurrentTenant CurrentTenant
@ -22,7 +21,6 @@
@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options
var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty;
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml

@ -9,12 +9,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IPageLayout PageLayout
@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options
var pageTitle = ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title; //TODO: Discard to get from Title

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml

@ -8,12 +8,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
@using Volo.Abp.Localization
@inject IAbpAntiForgeryManager AbpAntiForgeryManager
@inject IBrandingProvider BrandingProvider
@inject IPageLayout PageLayout
@{
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options
var pageTitle = ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title; //TODO: Discard to get from Title

6
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DataAnnotations;
@ -94,7 +95,10 @@ namespace Volo.Abp.AspNetCore.Mvc
}
});
var mvcCoreBuilder = context.Services.AddMvcCore();
var mvcCoreBuilder = context.Services.AddMvcCore(options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
});
context.Services.ExecutePreConfiguredActions(mvcCoreBuilder);
var abpMvcDataAnnotationsLocalizationOptions = context.Services

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Auditing;
using Volo.Abp.AspNetCore.Mvc.Content;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
@ -21,6 +22,13 @@ namespace Volo.Abp.AspNetCore.Mvc
AddPageFilters(options);
AddModelBinders(options);
AddMetadataProviders(options, services);
AddFormatters(options);
}
private static void AddFormatters(MvcOptions options)
{
options.InputFormatters.Insert(0, new RemoteStreamContentInputFormatter());
options.OutputFormatters.Insert(0, new RemoteStreamContentOutputFormatter());
}
private static void AddConventions(MvcOptions options, IServiceCollection services)

35
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryCookieNameProvider.cs

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAntiForgeryCookieNameProvider : ITransientDependency
{
private readonly IOptionsSnapshot<CookieAuthenticationOptions> _namedOptionsAccessor;
private readonly AbpAntiForgeryOptions _abpAntiForgeryOptions;
public AbpAntiForgeryCookieNameProvider(
IOptionsSnapshot<CookieAuthenticationOptions> namedOptionsAccessor,
IOptions<AbpAntiForgeryOptions> abpAntiForgeryOptions)
{
_namedOptionsAccessor = namedOptionsAccessor;
_abpAntiForgeryOptions = abpAntiForgeryOptions.Value;
}
public virtual string GetAuthCookieNameOrNull()
{
if (_abpAntiForgeryOptions.AuthCookieSchemaName == null)
{
return null;
}
return _namedOptionsAccessor.Get(_abpAntiForgeryOptions.AuthCookieSchemaName)?.Cookie?.Name;
}
public virtual string GetAntiForgeryCookieNameOrNull()
{
return _abpAntiForgeryOptions.TokenCookie.Name;
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryManagerAspNetCoreExtensions.cs

@ -1,10 +0,0 @@
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public static class AbpAntiForgeryManagerAspNetCoreExtensions
{
public static void SetCookie(this IAbpAntiForgeryManager manager)
{
manager.HttpContext.Response.Cookies.Append(manager.Options.TokenCookieName, manager.GenerateToken());
}
}
}

66
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAntiForgeryOptions.cs

@ -1,23 +1,69 @@
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAntiForgeryOptions
{
/// <summary>
/// Get/sets cookie name to transfer Anti Forgery token between server and client.
/// Default value: "XSRF-TOKEN".
/// Use to set the cookie options to transfer Anti Forgery token between server and client.
/// Default name of the cookie: "XSRF-TOKEN".
/// </summary>
public string TokenCookieName { get; set; }
public CookieBuilder TokenCookie { get; }
/// <summary>
/// Get/sets header name to transfer Anti Forgery token from client to the server.
/// Default value: "X-XSRF-TOKEN".
/// Used to find auth cookie when validating Anti Forgery token.
/// Default value: "Identity.Application".
/// </summary>
public string TokenHeaderName { get; set; }
public string AuthCookieSchemaName { get; set; }
/// <summary>
/// Default value: true.
/// </summary>
public bool AutoValidate { get; set; } = true;
/// <summary>
/// A predicate to filter types to auto-validate.
/// Return true to select the type to validate.
/// Default: returns true for all given types.
/// </summary>
[NotNull]
public Predicate<Type> AutoValidateFilter
{
get => _autoValidateFilter;
set => _autoValidateFilter = Check.NotNull(value, nameof(value));
}
private Predicate<Type> _autoValidateFilter;
/// <summary>
/// Default methods: "GET", "HEAD", "TRACE", "OPTIONS".
/// </summary>
[NotNull]
public HashSet<string> AutoValidateIgnoredHttpMethods
{
get => _autoValidateIgnoredHttpMethods;
set => _autoValidateIgnoredHttpMethods = Check.NotNull(value, nameof(value));
}
private HashSet<string> _autoValidateIgnoredHttpMethods;
public AbpAntiForgeryOptions()
{
TokenCookieName = "XSRF-TOKEN";
TokenHeaderName = "X-XSRF-TOKEN";
AutoValidateFilter = type => true;
TokenCookie = new CookieBuilder
{
Name = "XSRF-TOKEN",
HttpOnly = false,
IsEssential = true,
SameSite = SameSiteMode.None,
Expiration = TimeSpan.FromDays(3650) //10 years!
};
AuthCookieSchemaName = "Identity.Application";
AutoValidateIgnoredHttpMethods = new HashSet<string> {"GET", "HEAD", "TRACE", "OPTIONS"};
}
}
}
}

37
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAutoValidateAntiforgeryTokenAttribute.cs

@ -0,0 +1,37 @@
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AbpAutoValidateAntiforgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in a sequence determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// The default Order for this attribute is 1000 because it must run after any filter which does authentication
/// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
/// </para>
/// <para>
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
/// </para>
/// </remarks>
public int Order { get; set; } = 1000;
/// <inheritdoc />
public bool IsReusable => true;
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<AbpAutoValidateAntiforgeryTokenAuthorizationFilter>();
}
}
}

65
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAutoValidateAntiforgeryTokenAuthorizationFilter.cs

@ -0,0 +1,65 @@
using System;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpAutoValidateAntiforgeryTokenAuthorizationFilter : AbpValidateAntiforgeryTokenAuthorizationFilter, ITransientDependency
{
private readonly AbpAntiForgeryOptions _options;
public AbpAutoValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
AbpAntiForgeryCookieNameProvider antiForgeryCookieNameProvider,
IOptions<AbpAntiForgeryOptions> options,
ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> logger)
: base(
antiforgery,
antiForgeryCookieNameProvider,
logger)
{
_options = options.Value;
}
protected override bool ShouldValidate(AuthorizationFilterContext context)
{
if (!_options.AutoValidate)
{
return false;
}
if(context.ActionDescriptor.IsControllerAction())
{
var controllerType = context.ActionDescriptor
.AsControllerActionDescriptor()
.ControllerTypeInfo
.AsType();
if (!_options.AutoValidateFilter(controllerType))
{
return false;
}
}
if (IsIgnoredHttpMethod(context))
{
return false;
}
return base.ShouldValidate(context);
}
protected virtual bool IsIgnoredHttpMethod(AuthorizationFilterContext context)
{
return context.HttpContext
.Request
.Method
.ToUpperInvariant()
.IsIn(_options.AutoValidateIgnoredHttpMethods);
}
}
}

37
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpValidateAntiForgeryTokenAttribute.cs

@ -0,0 +1,37 @@
using System;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AbpValidateAntiForgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// The default Order for this attribute is 1000 because it must run after any filter which does authentication
/// or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400).
/// </para>
/// <para>
/// Look at <see cref="IOrderedFilter.Order"/> for more detailed info.
/// </para>
/// </remarks>
public int Order { get; set; } = 1000;
/// <inheritdoc />
public bool IsReusable => true;
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<AbpValidateAntiforgeryTokenAuthorizationFilter>();
}
}
}

81
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpValidateAntiforgeryTokenAuthorizationFilter.cs

@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AbpValidateAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy, ITransientDependency
{
private IAntiforgery _antiforgery;
private readonly AbpAntiForgeryCookieNameProvider _antiForgeryCookieNameProvider;
private readonly ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> _logger;
public AbpValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
AbpAntiForgeryCookieNameProvider antiForgeryCookieNameProvider,
ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> logger)
{
_antiforgery = antiforgery;
_logger = logger;
_antiForgeryCookieNameProvider = antiForgeryCookieNameProvider;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.IsEffectivePolicy<IAntiforgeryPolicy>(this))
{
_logger.LogInformation("Skipping the execution of current filter as its not the most effective filter implementing the policy " + typeof(IAntiforgeryPolicy));
return;
}
if (ShouldValidate(context))
{
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AntiforgeryValidationException exception)
{
_logger.LogError(exception.Message, exception);
context.Result = new AntiforgeryValidationFailedResult();
}
}
}
protected virtual bool ShouldValidate(AuthorizationFilterContext context)
{
var authCookieName = _antiForgeryCookieNameProvider.GetAuthCookieNameOrNull();
//Always perform antiforgery validation when request contains authentication cookie
if (authCookieName != null &&
context.HttpContext.Request.Cookies.ContainsKey(authCookieName))
{
return true;
}
var antiForgeryCookieName = _antiForgeryCookieNameProvider.GetAntiForgeryCookieNameOrNull();
//No need to validate if antiforgery cookie is not sent.
//That means the request is sent from a non-browser client.
//See https://github.com/aspnet/Antiforgery/issues/115
if (antiForgeryCookieName != null &&
!context.HttpContext.Request.Cookies.ContainsKey(antiForgeryCookieName))
{
return false;
}
// Anything else requires a token.
return true;
}
}
}

16
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AspNetCoreAbpAntiForgeryManager.cs

@ -7,9 +7,9 @@ namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public class AspNetCoreAbpAntiForgeryManager : IAbpAntiForgeryManager, ITransientDependency
{
public AbpAntiForgeryOptions Options { get; }
protected AbpAntiForgeryOptions Options { get; }
public HttpContext HttpContext => _httpContextAccessor.HttpContext;
protected HttpContext HttpContext => _httpContextAccessor.HttpContext;
private readonly IAntiforgery _antiforgery;
private readonly IHttpContextAccessor _httpContextAccessor;
@ -24,14 +24,18 @@ namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
Options = options.Value;
}
public void SetCookie()
public virtual void SetCookie()
{
HttpContext.Response.Cookies.Append(Options.TokenCookieName, GenerateToken());
HttpContext.Response.Cookies.Append(
Options.TokenCookie.Name,
GenerateToken(),
Options.TokenCookie.Build(HttpContext)
);
}
public string GenerateToken()
public virtual string GenerateToken()
{
return _antiforgery.GetAndStoreTokens(_httpContextAccessor.HttpContext).RequestToken;
}
}
}
}

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/IAbpAntiForgeryManager.cs

@ -1,13 +1,7 @@
using Microsoft.AspNetCore.Http;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery
{
public interface IAbpAntiForgeryManager
{
AbpAntiForgeryOptions Options { get; }
HttpContext HttpContext { get; }
void SetCookie();
string GenerateToken();

9
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
@ -9,17 +10,21 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public class AbpApplicationConfigurationController : AbpController, IAbpApplicationConfigurationAppService
{
private readonly IAbpApplicationConfigurationAppService _applicationConfigurationAppService;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationController(
IAbpApplicationConfigurationAppService applicationConfigurationAppService)
IAbpApplicationConfigurationAppService applicationConfigurationAppService,
IAbpAntiForgeryManager antiForgeryManager)
{
_applicationConfigurationAppService = applicationConfigurationAppService;
_antiForgeryManager = antiForgeryManager;
}
[HttpGet]
public async Task<ApplicationConfigurationDto> GetAsync()
{
_antiForgeryManager.SetCookie();
return await _applicationConfigurationAppService.GetAsync();
}
}
}
}

8
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.Auditing;
using Volo.Abp.Http;
using Volo.Abp.Json;
@ -20,17 +21,20 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
private readonly IJsonSerializer _jsonSerializer;
private readonly AbpAspNetCoreMvcOptions _options;
private readonly IJavascriptMinifier _javascriptMinifier;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationScriptController(
IAbpApplicationConfigurationAppService configurationAppService,
IJsonSerializer jsonSerializer,
IOptions<AbpAspNetCoreMvcOptions> options,
IJavascriptMinifier javascriptMinifier)
IJavascriptMinifier javascriptMinifier,
IAbpAntiForgeryManager antiForgeryManager)
{
_configurationAppService = configurationAppService;
_jsonSerializer = jsonSerializer;
_options = options.Value;
_javascriptMinifier = javascriptMinifier;
_antiForgeryManager = antiForgeryManager;
}
[HttpGet]
@ -39,6 +43,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
var script = CreateAbpExtendScript(await _configurationAppService.GetAsync());
_antiForgeryManager.SetCookie();
return Content(
_options.MinifyGeneratedScript == true
? _javascriptMinifier.Minify(script)

20
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs

@ -282,9 +282,20 @@ namespace Volo.Abp.AspNetCore.Mvc
return;
}
var parameterDescriptionNames = apiDescription
.ParameterDescriptions
.Select(p => p.Name)
.ToArray();
var methodParameterNames = method
.GetParameters()
.Where(IsNotFromServicesParameter)
.Select(GetMethodParamName)
.ToArray();
var matchedMethodParamNames = ArrayMatcher.Match(
apiDescription.ParameterDescriptions.Select(p => p.Name).ToArray(),
method.GetParameters().Select(GetMethodParamName).ToArray()
parameterDescriptionNames,
methodParameterNames
);
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; i++)
@ -310,6 +321,11 @@ namespace Volo.Abp.AspNetCore.Mvc
}
}
private static bool IsNotFromServicesParameter(ParameterInfo parameterInfo)
{
return !parameterInfo.IsDefined(typeof(FromServicesAttribute), true);
}
public string GetMethodParamName(ParameterInfo parameterInfo)
{
var modelNameProvider = parameterInfo.GetCustomAttributes()

25
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs

@ -0,0 +1,25 @@
using System.IO;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
internal class InternalRemoteStreamContent : IRemoteStreamContent
{
private readonly HttpContext _httpContext;
public InternalRemoteStreamContent(HttpContext httpContext)
{
_httpContext = httpContext;
}
public string ContentType => _httpContext.Request.ContentType;
public long? ContentLength => _httpContext.Request.ContentLength;
public Stream GetStream()
{
return _httpContext.Request.Body;
}
}
}

28
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs

@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
public class RemoteStreamContentInputFormatter : InputFormatter
{
public RemoteStreamContentInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
}
protected override bool CanReadType(Type type)
{
return typeof(IRemoteStreamContent) == type;
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
return InputFormatterResult.SuccessAsync(
new InternalRemoteStreamContent(context.HttpContext)
);
}
}
}

30
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs

@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
public class RemoteStreamContentOutputFormatter : OutputFormatter
{
public RemoteStreamContentOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
}
protected override bool CanWriteType(Type type)
{
return typeof(IRemoteStreamContent).IsAssignableFrom(type);
}
public async override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var remoteStream = (IRemoteStreamContent)context.Object;
using (var stream = remoteStream.GetStream())
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
}
}
}
}

33
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -7,24 +7,30 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.Conventions
{
public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
{
public ILogger<AbpServiceConvention> Logger { get; set; }
private readonly AbpAspNetCoreMvcOptions _options;
public AbpServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options)
public AbpServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options)
{
_options = options.Value;
Logger = NullLogger<AbpServiceConvention>.Instance;
}
public void Apply(ApplicationModel application)
@ -34,9 +40,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
protected virtual void ApplyForControllers(ApplicationModel application)
{
RemoveDuplicateControllers(application);
foreach (var controller in application.Controllers)
{
var controllerType = controller.ControllerType.AsType();
var configuration = GetControllerSettingOrNull(controllerType);
//TODO: We can remove different behaviour for ImplementsRemoteServiceInterface. If there is a configuration, then it should be applied!
@ -59,6 +68,26 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
}
}
protected virtual void RemoveDuplicateControllers(ApplicationModel application)
{
var derivedControllerModels = new List<ControllerModel>();
foreach (var controllerModel in application.Controllers)
{
var baseControllerTypes = controllerModel.ControllerType
.GetBaseClasses(typeof(Controller), includeObject: false)
.Where(t => !t.IsAbstract)
.ToArray();
if (baseControllerTypes.Length > 0)
{
derivedControllerModels.Add(controllerModel);
Logger.LogInformation($"Removing the controller {controllerModel.ControllerType.AssemblyQualifiedName} from the application model since it replaces the controller(s): {baseControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")}");
}
}
application.Controllers.RemoveAll(derivedControllerModels);
}
protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)
{
ConfigureApiExplorer(controller);

1
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Nito.AsyncEx;

4
framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs

@ -225,7 +225,7 @@ namespace Volo.Abp.BlazoriseUI
{
if (reference == null)
{
reference = (TRef) ScopedServices.GetRequiredService(serviceType);
reference = (TRef)ScopedServices.GetRequiredService(serviceType);
}
return reference;
@ -266,7 +266,7 @@ namespace Volo.Abp.BlazoriseUI
var input = await CreateGetListInputAsync();
var result = await AppService.GetListAsync(input);
Entities = MapToListViewModel(result.Items);
TotalCount = (int?) result.TotalCount;
TotalCount = (int?)result.TotalCount;
}
private IReadOnlyList<TListViewModel> MapToListViewModel(IReadOnlyList<TGetListOutputDto> dtos)

96
framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiMessageService.cs

@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.Components.WebAssembly;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlazoriseUI
{
[Dependency(ReplaceServices = true)]
public class BlazoriseUiMessageService : IUiMessageService, IScopedDependency
{
/// <summary>
/// An event raised after the message is received. Used to notify the message dialog.
/// </summary>
public event EventHandler<UiMessageEventArgs> MessageReceived;
private readonly IStringLocalizer<AbpUiResource> localizer;
public ILogger<BlazoriseUiMessageService> Logger { get; set; }
public BlazoriseUiMessageService(
IStringLocalizer<AbpUiResource> localizer)
{
this.localizer = localizer;
Logger = NullLogger<BlazoriseUiMessageService>.Instance;
}
public Task InfoAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
var uiMessageOptions = CreateDefaultOptions();
options?.Invoke(uiMessageOptions);
MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Info, message, title, uiMessageOptions));
return Task.CompletedTask;
}
public Task SuccessAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
var uiMessageOptions = CreateDefaultOptions();
options?.Invoke(uiMessageOptions);
MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Success, message, title, uiMessageOptions));
return Task.CompletedTask;
}
public Task WarnAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
var uiMessageOptions = CreateDefaultOptions();
options?.Invoke(uiMessageOptions);
MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Warning, message, title, uiMessageOptions));
return Task.CompletedTask;
}
public Task ErrorAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
var uiMessageOptions = CreateDefaultOptions();
options?.Invoke(uiMessageOptions);
MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Error, message, title, uiMessageOptions));
return Task.CompletedTask;
}
public async Task<bool> ConfirmAsync(string message, string title = null, Action<UiMessageOptions> options = null)
{
var uiMessageOptions = CreateDefaultOptions();
options?.Invoke(uiMessageOptions);
var callback = new TaskCompletionSource<bool>();
MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Confirmation, message, title, uiMessageOptions));
return await callback.Task;
}
protected virtual UiMessageOptions CreateDefaultOptions()
{
return new UiMessageOptions
{
CenterMessage = true,
ShowMessageIcon = true,
OkButtonText = localizer["Ok"],
CancelButtonText = localizer["Cancel"],
ConfirmButtonText = localizer["Yes"],
};
}
}
}

15
framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs

@ -21,5 +21,20 @@ namespace Volo.Abp.BlazoriseUI
Logger.LogInformation(message);
return Task.CompletedTask;
}
public Task Success(string message)
{
return Task.CompletedTask;
}
public Task Warn(string message)
{
return Task.CompletedTask;
}
public Task Error(string message)
{
return Task.CompletedTask;
}
}
}

48
framework/src/Volo.Abp.BlazoriseUI/Components/UiMessageAlert.razor

@ -0,0 +1,48 @@
<Modal @ref="@ModalRef">
<ModalBackdrop />
<ModalContent Centered="@CenterMessage">
<ModalHeader>
<ModalTitle>
@Title
</ModalTitle>
</ModalHeader>
<ModalBody>
@if ( ShowMessageIcon )
{
<DisplayHeading Size="DisplayHeadingSize.Is2" Style="text-align:center;">
<Icon Name="@MessageIcon" Style="@MessageIconStyle" />
</DisplayHeading>
}
@Message
</ModalBody>
<ModalFooter>
@if ( IsConfirmation )
{
<Button Color="Color.Danger" Padding="Padding.Is2.OnX" Margin="Margin.IsAuto.FromRight" Clicked="@OnCancelClicked">
@if ( Options?.CancelButtonIcon != null )
{
<Icon Name="@Options.CancelButtonIcon" Margin="Margin.Is2.FromRight" />
}
@CancelButtonText
</Button>
<Button Color="Color.Primary" Padding="Padding.Is2.OnX" Clicked="@OnConfirmClicked">
@if ( Options?.ConfirmButtonIcon != null )
{
<Icon Name="@Options.ConfirmButtonIcon" Margin="Margin.Is2.FromRight" />
}
@ConfirmButtonText
</Button>
}
else
{
<Button Color="Color.Primary" Padding="Padding.Is2.OnX" Clicked="@OnOkClicked">
@if ( Options?.OkButtonIcon != null )
{
<Icon Name="@Options.OkButtonIcon" Margin="Margin.Is2.FromRight" />
}
@OkButtonText
</Button>
}
</ModalFooter>
</ModalContent>
</Modal>

140
framework/src/Volo.Abp.BlazoriseUI/Components/UiMessageAlert.razor.cs

@ -0,0 +1,140 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Blazorise;
using Microsoft.AspNetCore.Components;
using Volo.Abp.AspNetCore.Components.WebAssembly;
namespace Volo.Abp.BlazoriseUI.Components
{
public partial class UiMessageAlert : ComponentBase, IDisposable
{
protected Modal ModalRef { get; set; }
protected virtual bool IsConfirmation
=> MessageType == UiMessageType.Confirmation;
protected virtual bool CenterMessage
=> Options?.CenterMessage ?? true;
protected virtual bool ShowMessageIcon
=> Options?.ShowMessageIcon ?? true;
protected virtual object MessageIcon => Options?.MessageIcon ?? MessageType switch
{
UiMessageType.Info => IconName.Info,
UiMessageType.Success => IconName.Check,
UiMessageType.Warning => IconName.Exclamation,
UiMessageType.Error => IconName.Times,
UiMessageType.Confirmation => IconName.QuestionCircle,
_ => null,
};
protected virtual string MessageIconColor => MessageType switch
{
// gets the color in the order of importance: Blazorise > Bootstrap > fallback color
UiMessageType.Info => "var(--b-theme-info, var(--info, #17a2b8))",
UiMessageType.Success => "var(--b-theme-success, var(--success, #28a745))",
UiMessageType.Warning => "var(--b-theme-warning, var(--warning, #ffc107))",
UiMessageType.Error => "var(--b-theme-danger, var(--danger, #dc3545))",
UiMessageType.Confirmation => "var(--b-theme-secondary, var(--secondary, #6c757d))",
_ => null,
};
protected virtual string MessageIconStyle
{
get
{
var sb = new StringBuilder();
sb.Append($"color:{MessageIconColor}");
return sb.ToString();
}
}
protected virtual string OkButtonText
=> Options?.OkButtonText ?? "OK";
protected virtual string ConfirmButtonText
=> Options?.ConfirmButtonText ?? "Confirm";
protected virtual string CancelButtonText
=> Options?.CancelButtonText ?? "Cancel";
[Parameter] public UiMessageType MessageType { get; set; }
[Parameter] public string Title { get; set; }
[Parameter] public string Message { get; set; }
[Parameter] public TaskCompletionSource<bool> Callback { get; set; }
[Parameter] public UiMessageOptions Options { get; set; }
[Parameter] public EventCallback Okayed { get; set; }
[Parameter] public EventCallback Confirmed { get; set; }
[Parameter] public EventCallback Canceled { get; set; }
[Inject] protected BlazoriseUiMessageService UiMessageService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
UiMessageService.MessageReceived += OnMessageReceived;
}
private void OnMessageReceived(object sender, UiMessageEventArgs e)
{
MessageType = e.MessageType;
Message = e.Message;
Title = e.Title;
Options = e.Options;
Callback = e.Callback;
ModalRef.Show();
}
public void Dispose()
{
if (UiMessageService != null)
{
UiMessageService.MessageReceived -= OnMessageReceived;
}
}
protected Task OnOkClicked()
{
ModalRef.Hide();
return Okayed.InvokeAsync(null);
}
protected Task OnConfirmClicked()
{
ModalRef.Hide();
if (IsConfirmation && Callback != null)
{
Callback.SetResult(true);
}
return Confirmed.InvokeAsync(null);
}
protected Task OnCancelClicked()
{
ModalRef.Hide();
if (IsConfirmation && Callback != null)
{
Callback.SetResult(false);
}
return Canceled.InvokeAsync(null);
}
}
}

2
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />

2
framework/src/Volo.Abp.BlazoriseUI/_Imports.razor

@ -0,0 +1,2 @@
@using Microsoft.AspNetCore.Components.Web
@using Blazorise

20
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs

@ -14,17 +14,21 @@ namespace Volo.Abp.Caching.StackExchangeRedis
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
context.Services.AddStackExchangeRedisCache(options =>
var redisEnabled = configuration["Redis:IsEnabled"];
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
{
var redisConfiguration = configuration["Redis:Configuration"];
if (!redisConfiguration.IsNullOrEmpty())
context.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration["Redis:Configuration"];
}
});
var redisConfiguration = configuration["Redis:Configuration"];
if (!redisConfiguration.IsNullOrEmpty())
{
options.Configuration = redisConfiguration;
}
});
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, AbpRedisCache>());
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, AbpRedisCache>());
}
}
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj

@ -14,7 +14,6 @@
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="HtmlAgilityPack.NetCore" Version="1.5.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="NuGet.Versioning" Version="5.6.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />

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

@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
@ -49,6 +50,17 @@ namespace System
return list.Contains(item);
}
/// <summary>
/// Check if an item is in the given enumerable.
/// </summary>
/// <param name="item">Item to check</param>
/// <param name="items">Items</param>
/// <typeparam name="T">Type of the items</typeparam>
public static bool IsIn<T>(this T item, IEnumerable<T> items)
{
return items.Contains(item);
}
/// <summary>
/// Can be used to conditionally perform a function
/// on an object and return the modified or the original object.

26
framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs

@ -55,14 +55,28 @@ namespace System
return types.ToArray();
}
/// <summary>
/// Gets all base classes of this type.
/// </summary>
/// <param name="type">The type to get its base classes.</param>
/// <param name="stoppingType">A type to stop going to the deeper base classes. This type will be be included in the returned array</param>
/// <param name="includeObject">True, to include the standard <see cref="object"/> type in the returned array.</param>
public static Type[] GetBaseClasses([NotNull] this Type type, Type stoppingType, bool includeObject = true)
{
Check.NotNull(type, nameof(type));
var types = new List<Type>();
AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
return types.ToArray();
}
private static void AddTypeAndBaseTypesRecursively(
[NotNull] List<Type> types,
[CanBeNull] Type type,
bool includeObject)
[CanBeNull] Type type,
bool includeObject,
[CanBeNull] Type stoppingType = null)
{
Check.NotNull(types, nameof(types));
if (type == null)
if (type == null || type == stoppingType)
{
return;
}
@ -72,7 +86,7 @@ namespace System
return;
}
AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject);
AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
types.Add(type);
}
}

16
framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs

@ -105,5 +105,19 @@ namespace System.Collections.Generic
return items;
}
/// <summary>
/// Removes all items from the collection those satisfy the given <paramref name="predicate"/>.
/// </summary>
/// <typeparam name="T">Type of the items in the collection</typeparam>
/// <param name="source">The collection</param>
/// <param name="items">Items to be removed from the list</param>
public static void RemoveAll<T>([NotNull] this ICollection<T> source, IEnumerable<T> items)
{
foreach (var item in items)
{
source.Remove(item);
}
}
}
}
}

13
framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs

@ -0,0 +1,13 @@
using System.IO;
namespace Volo.Abp.Content
{
public interface IRemoteStreamContent
{
string ContentType { get; }
long? ContentLength { get; }
Stream GetStream();
}
}

23
framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs

@ -0,0 +1,23 @@
using System.IO;
namespace Volo.Abp.Content
{
public class RemoteStreamContent : IRemoteStreamContent
{
private readonly Stream _stream;
public RemoteStreamContent(Stream stream)
{
_stream = stream;
}
public virtual string ContentType { get; set; }
public virtual long? ContentLength => _stream.Length;
public virtual Stream GetStream()
{
return _stream;
}
}
}

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

Loading…
Cancel
Save