@ -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 {} |
||||
|
``` |
||||
@ -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. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 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\*** |
||||
|
|
||||
|
 |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Select the **CustomizeUserDemo.EntityFramework.DbMigrations** as the **default project** and execute the following command: |
||||
|
|
||||
|
```bash |
||||
|
Add-Migration "Updated-User-Entity" |
||||
|
``` |
||||
|
|
||||
|
 |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 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 :) |
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 193 KiB |
@ -1,3 +0,0 @@ |
|||||
## Dynamic JavaScript API Clients |
|
||||
|
|
||||
TODO |
|
||||
@ -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: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
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`. |
||||
@ -0,0 +1,3 @@ |
|||||
|
# ASP.NET Core MVC / Razor Pages UI: Dynamic JavaScript Client Proxies |
||||
|
|
||||
|
TODO |
||||
@ -1,23 +1,23 @@ |
|||||
# JavaScript API |
# 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 |
## APIs |
||||
|
|
||||
* abp.ajax |
* [abp.ajax](Ajax.md) |
||||
* abp.auth |
* abp.auth |
||||
* abp.currentUser |
* abp.currentUser |
||||
* abp.dom |
* abp.dom |
||||
* abp.event |
* abp.event |
||||
* abp.features |
* abp.features |
||||
* abp.localization |
* [abp.localization](Localization.md) |
||||
* abp.log |
* abp.log |
||||
|
* [abp.message](Message.md) |
||||
* abp.ModalManager |
* abp.ModalManager |
||||
* abp.notify |
* [abp.notify](Notify.md) |
||||
* abp.security |
* abp.security |
||||
* abp.setting |
* abp.setting |
||||
* abp.ui |
* abp.ui |
||||
* abp.utils |
* abp.utils |
||||
* abp.ResourceLoader |
* abp.ResourceLoader |
||||
* abp.WidgetManager |
* abp.WidgetManager |
||||
* Other APIs |
|
||||
@ -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 |
||||
|
} |
||||
|
] |
||||
|
```` |
||||
|
|
||||
@ -0,0 +1,3 @@ |
|||||
|
# ASP.NET Core MVC / Razor Pages UI: JavaScript Logging API |
||||
|
|
||||
|
TODO |
||||
@ -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: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 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!'); |
||||
|
```` |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 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: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
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. |
||||
|
|
||||
@ -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: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 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. |
||||
@ -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: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
**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) |
||||
@ -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. |
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 17 KiB |
@ -1,17 +1,18 @@ |
|||||
using System.Threading.Tasks; |
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Components.WebAssembly |
namespace Volo.Abp.AspNetCore.Components.WebAssembly |
||||
{ |
{ |
||||
public interface IUiMessageService |
public interface IUiMessageService |
||||
{ |
{ |
||||
Task InfoAsync(string message, string title = null); |
Task InfoAsync(string message, string title = null, Action<UiMessageOptions> options = null); |
||||
|
|
||||
Task SuccessAsync(string message, string title = null); |
Task SuccessAsync(string message, string title = null, Action<UiMessageOptions> options = null); |
||||
|
|
||||
Task WarnAsync(string message, string title = null); |
Task WarnAsync(string message, string title = null, Action<UiMessageOptions> options = null); |
||||
|
|
||||
Task ErrorAsync(string message, string title = null); |
Task ErrorAsync(string message, string title = null, Action<UiMessageOptions> options = null); |
||||
|
|
||||
Task<bool> ConfirmAsync(string message, string title = null); |
Task<bool> ConfirmAsync(string message, string title = null, Action<UiMessageOptions> options = null); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
} |
||||
|
} |
||||
@ -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" |
||||
|
} |
||||
|
} |
||||
@ -1,14 +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.TokenCookie.Name, |
|
||||
manager.GenerateToken(), |
|
||||
manager.Options.TokenCookie.Build(manager.HttpContext) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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"], |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
@using Microsoft.AspNetCore.Components.Web |
||||
|
@using Blazorise |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "es", |
||||
|
"texts": { |
||||
|
"MaxResultCountExceededExceptionMessage": "{0} no puede tener más de {1}! Incremente {2}.{3} en el lado del servidor para obtener más resultados." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"culture": "es", |
||||
|
"texts": { |
||||
|
"DisplayName:Abp.Ldap.ServerHost": "Server host", |
||||
|
"Description:Abp.Ldap.ServerHost": "Servidor host", |
||||
|
|
||||
|
"DisplayName:Abp.Ldap.ServerPort": "Server port", |
||||
|
"Description:Abp.Ldap.ServerPort": "Puerto del servidor host", |
||||
|
|
||||
|
"DisplayName:Abp.Ldap.BaseDc": "Base domain component", |
||||
|
"Description:Abp.Ldap.BaseDc": "Dominio base", |
||||
|
|
||||
|
"DisplayName:Abp.Ldap.UserName": "Username", |
||||
|
"Description:Abp.Ldap.UserName": "Nombre de usuario", |
||||
|
|
||||
|
"DisplayName:Abp.Ldap.Password": "Password", |
||||
|
"Description:Abp.Ldap.Password": "Contraseña" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "es", |
||||
|
"texts": { |
||||
|
"DisplayName:Abp.Timing.Timezone": "Zona horaria", |
||||
|
"Description:Abp.Timing.Timezone": "Zona horaria de la Aplicacion" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"culture": "es", |
||||
|
"texts": { |
||||
|
"BirthDate": "Fecha de nacimiento", |
||||
|
"Value1": "Valor uno" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"culture": "es", |
||||
|
"texts": { |
||||
|
"hello": "Hola" |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,10 @@ |
|||||
namespace Volo.Blogging.Comments.Dtos |
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Volo.Blogging.Comments.Dtos |
||||
{ |
{ |
||||
public class UpdateCommentDto |
public class UpdateCommentDto |
||||
{ |
{ |
||||
|
[Required] |
||||
public string Text { get; set; } |
public string Text { get; set; } |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,11 +1,13 @@ |
|||||
using System; |
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
namespace Volo.Blogging.Posts |
namespace Volo.Blogging.Posts |
||||
{ |
{ |
||||
public class GetPostInput |
public class GetPostInput |
||||
{ |
{ |
||||
|
[Required] |
||||
public string Url { get; set; } |
public string Url { get; set; } |
||||
|
|
||||
public Guid BlogId { get; set; } |
public Guid BlogId { get; set; } |
||||
} |
} |
||||
} |
} |
||||
|
|||||