@ -0,0 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<configuration> |
|||
<packageSources> |
|||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> |
|||
<add key="BlazoriseMyGet" value="https://www.myget.org/F/blazorise/api/v3/index.json" /> |
|||
</packageSources> |
|||
</configuration> |
|||
@ -0,0 +1,271 @@ |
|||
# ABP Framework 3.3 RC Has Been Published |
|||
|
|||
We have released the [ABP Framework](https://abp.io/) (and the [ABP Commercial](https://commercial.abp.io/)) `3.3.0-rc.1` today. This blog post introduces the new features and important changes in the new version. |
|||
|
|||
## Get Started with the 3.3 RC.1 |
|||
|
|||
If you want to try the version `3.3.0-rc.1` today, follow the steps below; |
|||
|
|||
1) **Upgrade** the ABP CLI to the version `3.3.0-rc.1` using a command line terminal: |
|||
|
|||
````bash |
|||
dotnet tool update Volo.Abp.Cli -g --version 3.3.0-rc.1 |
|||
```` |
|||
|
|||
**or install** if you haven't installed before: |
|||
|
|||
````bash |
|||
dotnet tool install Volo.Abp.Cli -g --version 3.3.0-rc.1 |
|||
```` |
|||
|
|||
2) Create a **new application** with the `--preview` option: |
|||
|
|||
````bash |
|||
abp new BookStore --preview |
|||
```` |
|||
|
|||
See the [ABP CLI documentation](https://docs.abp.io/en/abp/3.3/CLI) for all the available options. |
|||
|
|||
> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the Preview checkbox. |
|||
|
|||
## What's new with the ABP Framework 3.3 |
|||
|
|||
### The Blazor UI |
|||
|
|||
We had released an experimental early preview version of the Blazor UI with the [previous version](https://blog.abp.io/abp/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI). In this version, we've completed most of the fundamental infrastructure features and the application modules (like identity and tenant management). |
|||
|
|||
It currently has almost the same functionalities as the other UI types (Angular & MVC / Razor Pages). |
|||
|
|||
**Example screenshot**: User management page of the Blazor UI |
|||
|
|||
 |
|||
|
|||
> We've adapted the [Lepton Theme](https://commercial.abp.io/themes) for the ABP Commercial, see the related section below. |
|||
|
|||
We are still working on the fundamentals. So, the next version may introduce breaking changes of the Blazor UI. We will work hard to keep them with the minimal effect on your application code. |
|||
|
|||
#### Breaking Changes on the Blazor UI |
|||
|
|||
There are some breaking changes with the Blazor UI. If you've built an application and upgrade it, your application might not properly work. See [the migration guide](https://docs.abp.io/en/abp/3.3/Migration-Guides/BlazorUI-3_3) for the changes you need to do after upgrading your application. |
|||
|
|||
### Automatic Validation for AntiForgery Token for HTTP APIs |
|||
|
|||
Starting with the version 3.3, all your HTTP API endpoints are **automatically protected** against CSRF attacks, unless you disable it for your application. So, no configuration needed, just upgrade the ABP Framework. |
|||
|
|||
[See the documentation](https://docs.abp.io/en/abp/3.3/CSRF-Anti-Forgery) to if you want to understand why you need it and how ABP Framework solves the problem. |
|||
|
|||
### Rebus Integration Package for the Distributed Event Bus |
|||
|
|||
[Rebus](https://github.com/rebus-org/Rebus) describes itself as "Simple and lean service bus implementation for .NET". There are a lot of integration packages like RabbitMQ and Azure Service Bus for the Rebus. The new [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) package allows you to use the Rebus as the [distributed event bus](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) for the ABP Framework. |
|||
|
|||
See [the documentation](https://docs.abp.io/en/abp/3.3/Distributed-Event-Bus-Rebus-Integration) to learn how to use Rebus with the ABP Framework. |
|||
|
|||
### Async Repository LINQ Extension Methods |
|||
|
|||
You have a problem when you want to use **Async LINQ Extension Methods** (e.g. `FirstOrDefaultAsync(...)`) in your **domain** and **application** layers. These async methods are **not included in the standard LINQ extension methods**. Those are defined by the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) NuGet package (see [the code](https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs)). To be able to use these `async` methods, you need to reference to the `Microsoft.EntityFrameworkCore` package. |
|||
|
|||
If you don't want to depend on the EF Core in your business layer, then ABP Framework provides the `IAsyncQueryableExecuter` service to execute your queries asynchronously without depending on the EF Core package. You can see [the documentation](https://docs.abp.io/en/abp/latest/Repositories#option-3-iasyncqueryableexecuter) to get more information about this service. |
|||
|
|||
ABP Framework version 3.3 takes this one step further and allows you to directly execute the async LINQ extension methods on the `IRepository` interface. |
|||
|
|||
**Example: Use `CountAsync` and `FirstOrDefaultAsync` methods on the repositories** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace MyCompanyName.MyProjectName |
|||
{ |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
private readonly IRepository<Book, Guid> _bookRepository; |
|||
|
|||
public BookAppService(IRepository<Book, Guid> bookRepository) |
|||
{ |
|||
_bookRepository = bookRepository; |
|||
} |
|||
|
|||
public async Task DemoAsync() |
|||
{ |
|||
var countAll = await _bookRepository |
|||
.CountAsync(); |
|||
|
|||
var count = await _bookRepository |
|||
.CountAsync(x => x.Name.Contains("A")); |
|||
|
|||
var book1984 = await _bookRepository |
|||
.FirstOrDefaultAsync(x => x.Name == "1984"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
All the standard LINQ methods are supported: *AllAsync, AnyAsync, AverageAsync, ContainsAsync, CountAsync, FirstAsync, FirstOrDefaultAsync, LastAsync, LastOrDefaultAsync, LongCountAsync, MaxAsync, MinAsync, SingleAsync, SingleOrDefaultAsync, SumAsync, ToArrayAsync, ToListAsync*. |
|||
|
|||
This approach still has a limitation. You need to execute the extension method directly on the repository object. For example, the below usage is **not supported**: |
|||
|
|||
````csharp |
|||
var count = await _bookRepository.Where(x => x.Name.Contains("A")).CountAsync(); |
|||
```` |
|||
|
|||
This is because the object returned from the `Where` method is not a repository object, it is a standard `IQueryable`. In such cases, you can still use the `IAsyncQueryableExecuter`: |
|||
|
|||
````csharp |
|||
var count = await AsyncExecuter.CountAsync( |
|||
_bookRepository.Where(x => x.Name.Contains("A")) |
|||
); |
|||
```` |
|||
|
|||
`AsyncExecuter` has all the standard extension methods, so you don't have any restriction here. See [the repository documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) for all the options you have. |
|||
|
|||
> ABP Framework does its best to not depend on the EF Core and still be able to use the async LINQ extension methods. However, there is no problem to depend on the EF Core for your application, you can add the `Microsoft.EntityFrameworkCore` NuGet package and use the native methods. |
|||
|
|||
### Stream Support for the Application Service Methods |
|||
|
|||
[Application services](https://docs.abp.io/en/abp/latest/Application-Services) are consumed by clients and the parameters and return values (typically [Data Transfer Objects](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)). In case of the client is a remote application, then these objects should be serialized & deserialized. |
|||
|
|||
Until the version 3.3, we hadn't suggest to use the `Stream` in the application service contracts, since it is not serializable/deserializable. However, with the version 3.3, ABP Framework properly supports this scenario by introducing the new `IRemoteStreamContent` interface. |
|||
|
|||
Example: An application service that can get or return streams |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace MyProject.Test |
|||
{ |
|||
public interface ITestAppService : IApplicationService |
|||
{ |
|||
Task Upload(Guid id, IRemoteStreamContent streamContent); |
|||
Task<IRemoteStreamContent> Download(Guid id); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
The implementation can be as shown below: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace MyProject.Test |
|||
{ |
|||
public class TestAppService : ApplicationService, ITestAppService |
|||
{ |
|||
public Task<IRemoteStreamContent> Download(Guid id) |
|||
{ |
|||
var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.OpenOrCreate); |
|||
return Task.FromResult( |
|||
(IRemoteStreamContent) new RemoteStreamContent(fs) { |
|||
ContentType = "application/octet-stream" |
|||
} |
|||
); |
|||
} |
|||
|
|||
public async Task Upload(Guid id, IRemoteStreamContent streamContent) |
|||
{ |
|||
using (var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.Create)) |
|||
{ |
|||
await streamContent.GetStream().CopyToAsync(fs); |
|||
await fs.FlushAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> This is just a demo code. Do it better in your production code :) |
|||
|
|||
Thanks to [@alexandru-bagu](https://github.com/alexandru-bagu) for the great contribution! |
|||
|
|||
### Other Changes |
|||
|
|||
* Upgraded all the .NET Core / ASP.NET Core related packages to the version 3.1.8. If you have additional dependencies to the .NET Core / ASP.NET Core related packages, we suggest you to updates your packages to the version 3.1.8 to have the latest bug and security fixes published by Microsoft. |
|||
* The blogging module now uses the [BLOB Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) system to store images & files of the blog posts. If you are using this module, then you need to manually migrate the local files to the BLOB Storing system after the upgrade. |
|||
* The Angular UI is now redirecting to the profile management page of the MVC UI instead of using its own UI, if you've configured the authorization code flow (which is default since the version 3.2.0). |
|||
|
|||
## What's new with the ABP Commercial 3.3 |
|||
|
|||
### The Blazor UI |
|||
|
|||
We have good news for the ABP Commercial Blazor UI too. We have implemented the [Lepton Theme](https://commercial.abp.io/themes) integration, so it is now available with the Blazor UI. Also, implemented most of the fundamental [modules](https://commercial.abp.io/modules). |
|||
|
|||
**A screenshot from the ABP Commercial startup template with the Blazor UI** |
|||
|
|||
 |
|||
|
|||
There are still missing features and modules. However, we are working on it to have a more complete version in the next release. |
|||
|
|||
#### Breaking Changes on the Blazor UI |
|||
|
|||
There are some breaking changes with the Blazor UI. If you've built an application and upgrade it, your application might not properly work. See the [ABP Commercial Blazor UI v 3.3 Migration Guide](https://docs.abp.io/en/commercial/3.3/migration-guides/blazor-ui-3_3) for the changes you need to do after upgrading your application. |
|||
|
|||
#### Known Issues |
|||
|
|||
When you create a new project, profile management doesn't work, you get an exception because it can't find the `/libs/cropperjs/css/cropper.min.css` file. To fix the issue; |
|||
|
|||
* Add `"@volo/account": "^3.3.0-rc.1"` to the `package.json` in the `.Host` project. |
|||
* Run `yarn` (or `npm install`), then `gulp` on a command line terminal in the root folder of the `.Host` project. |
|||
|
|||
### Multi-Tenant Social Logins |
|||
|
|||
[Account module](https://commercial.abp.io/modules/Volo.Account.Pro) now supports to manage the social/external logins in the UI. You can **enable/disable** and **set options** in the settings page. It also supports to use **different credentials for the tenants** and it is also **configured on the runtime**. |
|||
|
|||
 |
|||
|
|||
### Linked Accounts |
|||
|
|||
Linked user system allows you to link other accounts (including account in a different tenant) with your account, so you can switch between different accounts with a single-click. It is practical since you no longer need to logout and login again with entering the credentials of the target account. |
|||
|
|||
To manage the linked accounts, go to the profile management page from the user menu; |
|||
|
|||
 |
|||
|
|||
### Paypal & Stripe Integrations |
|||
|
|||
The [Payment Module](https://commercial.abp.io/modules/Volo.Payment) was supporting PayU and 2Checkout providers until the version 3.3. It's now integrated to PayPal and Stripe. See the [technical documentation](https://docs.abp.io/en/commercial/latest/modules/payment) to learn how to use it. |
|||
|
|||
### ABP Suite Improvements |
|||
|
|||
We've done a lot of small improvements for the [ABP Suite](https://commercial.abp.io/tools/suite). Some of the enhancements are; |
|||
|
|||
* Show the previously installed modules as *installed* on the module list. |
|||
* Switch between the latest stable, the latest [preview](https://docs.abp.io/en/abp/latest/Previews) and the latest [nightly build](https://docs.abp.io/en/abp/latest/Nightly-Builds) versions of the ABP related packages. |
|||
* Moved the file that stores the *previously created entities* into the solution folder to allow you to store it in your source control system. |
|||
|
|||
### Others |
|||
|
|||
* Added an option to the Account Module to show reCAPTCHA on the login & the registration forms. |
|||
|
|||
Besides the new features introduced in this post, we've done a lot of small other enhancements and bug fixes to provide a better development experience and increase the developer productivity. |
|||
|
|||
## New Articles |
|||
|
|||
The core ABP Framework team & the community continue to publish new articles on the [ABP Community](https://community.abp.io/) web site. The recently published articles are; |
|||
|
|||
* [Replacing Email Templates and Sending Emails](https://community.abp.io/articles/replacing-email-templates-and-sending-emails-jkeb8zzh) (by [@EngincanV](https://community.abp.io/members/EngincanV)) |
|||
* [How to Add Custom Properties to the User Entity](https://community.abp.io/articles/how-to-add-custom-property-to-the-user-entity-6ggxiddr) (by [@berkansasmaz](https://community.abp.io/members/berkansasmaz)) |
|||
* [Using the AdminLTE Theme with the ABP Framework MVC / Razor Pages UI](https://community.abp.io/articles/using-the-adminlte-theme-with-the-abp-framework-mvc-razor-pages-ui-gssbhb7m) (by [@mucahiddanis](https://community.abp.io/members/mucahiddanis)) |
|||
* [Using DevExtreme Angular Components With the ABP Framework](https://community.abp.io/articles/using-devextreme-angular-components-with-the-abp-framework-x5nyvj3i) (by [@bunyamin](https://community.abp.io/members/bunyamin)) |
|||
|
|||
It is appreciated if you want to [submit an article](https://community.abp.io/articles/submit) related to the ABP Framework. |
|||
|
|||
## About the Next Release |
|||
|
|||
The next version will be `4.0.0`. We are releasing a major version, since we will move the ABP Framework to .NET 5.0. We see that for most of the applications this will not be a breaking change and we hope you easily upgrade to it. |
|||
|
|||
The planned 4.0.0-rc.1 (Release Candidate) version date is **November 11**, just after the Microsoft releases the .NET 5.0 final. The planned 4.0.0 final release date is **November 26**. |
|||
|
|||
Follow the [GitHub milestones](https://github.com/abpframework/abp/milestones) for all the planned ABP Framework version release dates. |
|||
|
|||
## Feedback |
|||
|
|||
Please check out the ABP Framework 3.3.0 RC and [provide feedback](https://github.com/abpframework/abp/issues/new) to help us to release a more stable version. The planned release date for the [3.3.0 final](https://github.com/abpframework/abp/milestone/44) version is October 27th. |
|||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 199 KiB |
@ -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,477 @@ |
|||
# Replacing Email Templates and Sending Emails |
|||
|
|||
## Introduction |
|||
|
|||
Hi, in this step by step article, we will send an email by using standard email template and then we will replace the standard email template with our new created template, thanks to [Text Templating System](https://docs.abp.io/en/abp/latest/Text-Templating#replacing-the-existing-templates) and [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). Let's start by explaining what these systems do. |
|||
|
|||
* ABP framework provides a strong and flexible [Text Templating System](https://docs.abp.io/en/abp/latest/Text-Templating). So, we can use the text templating system to create dynamic email contents on a template and a model. |
|||
|
|||
* In this article, we will use `StandardEmailTemplates.Message` as standard email template. Then we will create a new template and replace the standard email template with our new template by using [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). |
|||
|
|||
* The `Virtual File System` makes it possible to manage files that do not physically exist on the file system. That means we can override `StandardEmailTemplates.Message` template by changing it's path with our new template's path. |
|||
|
|||
## Creating the Solution |
|||
|
|||
> ABP Framework offers startup templates to get into the business faster. |
|||
|
|||
In this article, I will create a new startup template and perform the operations on this template. But if you already have a project you don't need to create a new startup template, you can implement the following steps to your existing project. (These steps can be applied to any project. (MVC, Angular etc.)) |
|||
|
|||
> If you have already a project you can skip this section. |
|||
|
|||
Before starting to development, we will create a solution named `TemplateReplace` (or whatever you want). We can create a new startup template by using [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) : |
|||
|
|||
````bash |
|||
abp new TemplateReplace |
|||
```` |
|||
|
|||
Our project boilerplate will be ready after the download is finished. Then, open the solution in the Visual Studio (or your favorite IDE). |
|||
|
|||
Run the `TemplateReplace.DbMigrator` application as below to create the database and seed initial data (which creates the admin user, admin role, permissions etc.). |
|||
|
|||
 |
|||
|
|||
* Right click to `TemplateReplace.DbMigrator` and choose the `Debug`. |
|||
|
|||
 |
|||
|
|||
* After that, click the `Start new instance` option to start the database migrations. |
|||
|
|||
 |
|||
|
|||
Then we can run the `TemplateReplace.Web` project to see our application working. |
|||
|
|||
> _Default login credentials for admin: username is **admin** and password is **1q2w3E\***_ |
|||
|
|||
## Starting the Development |
|||
|
|||
First thing we need to do is, creating a email service to sending emails. ABP Framework provides `IEmailSender` service that is used to send emails. |
|||
|
|||
### Step - 1 |
|||
|
|||
Create an `Emailing` folder in the `TemplateReplace.Domain` project and add a class named `EmailService` inside of it. |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.Emailing.Templates; |
|||
using Volo.Abp.TextTemplating; |
|||
|
|||
namespace TemplateReplace.Emailing |
|||
{ |
|||
public class EmailService : ITransientDependency |
|||
{ |
|||
private readonly IEmailSender _emailSender; |
|||
private readonly ITemplateRenderer _templateRenderer; |
|||
|
|||
public EmailService(IEmailSender emailSender, ITemplateRenderer templateRenderer) |
|||
{ |
|||
_emailSender = emailSender; |
|||
_templateRenderer = templateRenderer; |
|||
} |
|||
|
|||
public async Task SendAsync(string targetEmail) |
|||
{ |
|||
var emailBody = await _templateRenderer.RenderAsync( |
|||
StandardEmailTemplates.Message, |
|||
new |
|||
{ |
|||
message = "ABP Framework provides IEmailSender service that is used to send emails." |
|||
} |
|||
); |
|||
|
|||
await _emailSender.SendAsync( |
|||
targetEmail, |
|||
"Subject", |
|||
emailBody |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* To create an email content, we need to inject `ITemplateRenderer` and use the `RenderAsync` method to render a template. |
|||
|
|||
* We've used `StandardEmailTemplates.Message` as standart email template. This provides us a standard and simple message template to send mails. |
|||
|
|||
* The resulting email body should be like shown below: |
|||
|
|||
```html |
|||
<!DOCTYPE html> |
|||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
</head> |
|||
<body> |
|||
ABP Framework provides IEmailSender service that is used to send emails. |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
### Step - 2 (Configuring Email Settings) |
|||
|
|||
* Now, we need to configure some email settings by following [settings documentation](https://docs.abp.io/en/abp/latest/Settings#setting-values-in-the-application-configuration). For achieve this, open the `appsettings.json` file under `TemplateReplace.Web` and configure your email settings in **settings** section like below. |
|||
|
|||
 |
|||
|
|||
* Here, I used Google's SMTP settings to send emails via Gmail. You can change these setting values by your need. |
|||
|
|||
> **Note:** If you want to use Google's SMTP server settings and send emails via Gmail, you should confirm [this](https://myaccount.google.com/u/0/lesssecureapps). |
|||
|
|||
### Step - 3 |
|||
|
|||
* After that we need to open `TemplateReplaceDomainModule.cs` file and change its contents as below to sending real-time emails. |
|||
|
|||
```csharp |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using TemplateReplace.MultiTenancy; |
|||
using Volo.Abp.AuditLogging; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.FeatureManagement; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.IdentityServer; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.PermissionManagement.Identity; |
|||
using Volo.Abp.PermissionManagement.IdentityServer; |
|||
using Volo.Abp.SettingManagement; |
|||
using Volo.Abp.TenantManagement; |
|||
|
|||
namespace TemplateReplace |
|||
{ |
|||
[DependsOn( |
|||
typeof(TemplateReplaceDomainSharedModule), |
|||
typeof(AbpAuditLoggingDomainModule), |
|||
typeof(AbpBackgroundJobsDomainModule), |
|||
typeof(AbpFeatureManagementDomainModule), |
|||
typeof(AbpIdentityDomainModule), |
|||
typeof(AbpPermissionManagementDomainIdentityModule), |
|||
typeof(AbpIdentityServerDomainModule), |
|||
typeof(AbpPermissionManagementDomainIdentityServerModule), |
|||
typeof(AbpSettingManagementDomainModule), |
|||
typeof(AbpTenantManagementDomainModule), |
|||
typeof(AbpEmailingModule) |
|||
)] |
|||
public class TemplateReplaceDomainModule : AbpModule |
|||
{ |
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var settingManager = context.ServiceProvider.GetService<SettingManager>(); |
|||
//encrypts the password on set and decrypts on get |
|||
settingManager.SetGlobalAsync(EmailSettingNames.Smtp.Password, "your_password"); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = MultiTenancyConsts.IsEnabled; |
|||
}); |
|||
|
|||
// #if DEBUG |
|||
// context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>()); |
|||
// #endif |
|||
} |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
* `NullEmailSender` is a built-in class that implements the `IEmailSender`, but writes email contents to the standard log system, rather than actually sending the emails. This class can be useful especially in development time where you generally don't want to send real emails. Therefore ABP framework defined this by default. But in our case we want to send real emails, so we must remove these lines or we must take it to the comment line. |
|||
|
|||
* `Abp.Mailing.Smtp.Password` must be an encrypted value. Therefore we used `SettingManager` in here to set the password. It internally **encrypts** the values on set and **decrypts** on get. |
|||
|
|||
* After all these steps, whenever we want to send an email, we can do it by using our `EmailService` class. We can inject this class and invoke the `SendAsync` method to sending email where its needed. |
|||
|
|||
After sending the email we should see the template like below. |
|||
|
|||
 |
|||
|
|||
### Step - 4 (Defining New Template) |
|||
|
|||
* So far we've sent mail by using standard email template of ABP. But we may want to replace the email template with the new one. We can achieve this by following the `Text Templating` [documentation](https://docs.abp.io/en/abp/latest/Text-Templating#replacing-the-existing-templates). |
|||
|
|||
* In this article, I will create a email template by using free template generator named **Bee**. You can reach the free templates from [here](https://beefree.io/templates/free/). |
|||
|
|||
* When we find a template for our purpose, we can hover the link and click the **get started** button to edit the template. (I chose a template named "gdpr".) |
|||
|
|||
* Here, you can edit your template as below. (You can delete or add sections, edit texts, and so on.) |
|||
|
|||
 |
|||
|
|||
> **Note:** After editing our template, we need to export it to reach our created template's content. You can see the **export** button top-right of the template editing page. |
|||
|
|||
* After choosing and editing our free template, we can create a new **email template** in our project. For this, create a folder named `Templates` under `Emailing` folder in `TemplateReplace.Domain` and add `EmailTemplate.tpl` file inside of it. And copy-paste the below content or your template's content. |
|||
|
|||
```tpl |
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"> |
|||
<head> |
|||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> |
|||
<meta content="width=device-width" name="viewport"/> |
|||
<meta content="IE=edge" http-equiv="X-UA-Compatible"/> |
|||
<style type="text/css"> |
|||
body { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
table, |
|||
td, |
|||
tr { |
|||
vertical-align: top; |
|||
border-collapse: collapse; |
|||
} |
|||
* { |
|||
line-height: inherit; |
|||
} |
|||
a[x-apple-data-detectors=true] { |
|||
color: inherit !important; |
|||
text-decoration: none !important; |
|||
} |
|||
</style> |
|||
<style id="media-query" type="text/css"> |
|||
@media (max-width: 670px) { |
|||
.block-grid, |
|||
.col { |
|||
min-width: 320px !important; |
|||
max-width: 100% !important; |
|||
display: block !important; |
|||
} |
|||
.block-grid { |
|||
width: 100% !important; |
|||
} |
|||
.col { |
|||
width: 100% !important; |
|||
} |
|||
.col>div { |
|||
margin: 0 auto; |
|||
} |
|||
img.fullwidth, |
|||
img.fullwidthOnMobile { |
|||
max-width: 100% !important; |
|||
} |
|||
.no-stack .col { |
|||
min-width: 0 !important; |
|||
display: table-cell !important; |
|||
} |
|||
.no-stack.two-up .col { |
|||
width: 50% !important; |
|||
} |
|||
.no-stack .col.num2 { |
|||
width: 16.6% !important; |
|||
} |
|||
.no-stack .col.num3 { |
|||
width: 25% !important; |
|||
} |
|||
.no-stack .col.num4 { |
|||
width: 33% !important; |
|||
} |
|||
.no-stack .col.num5 { |
|||
width: 41.6% !important; |
|||
} |
|||
.no-stack .col.num6 { |
|||
width: 50% !important; |
|||
} |
|||
.no-stack .col.num7 { |
|||
width: 58.3% !important; |
|||
} |
|||
.no-stack .col.num8 { |
|||
width: 66.6% !important; |
|||
} |
|||
.no-stack .col.num9 { |
|||
width: 75% !important; |
|||
} |
|||
.no-stack .col.num10 { |
|||
width: 83.3% !important; |
|||
} |
|||
.video-block { |
|||
max-width: none !important; |
|||
} |
|||
.mobile_hide { |
|||
min-height: 0px; |
|||
max-height: 0px; |
|||
max-width: 0px; |
|||
display: none; |
|||
overflow: hidden; |
|||
font-size: 0px; |
|||
} |
|||
.desktop_hide { |
|||
display: block !important; |
|||
max-height: none !important; |
|||
} |
|||
} |
|||
</style> |
|||
</head> |
|||
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #3d1554;"> |
|||
<table bgcolor="#3d1554" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" style="table-layout: fixed; vertical-align: top; min-width: 320px; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #3d1554; width: 100%;" valign="top" width="100%"> |
|||
<tbody> |
|||
<tr style="vertical-align: top;" valign="top"> |
|||
<td style="word-break: break-word; vertical-align: top;" valign="top"> |
|||
<div style="background-color:transparent;overflow:hidden"> |
|||
<div class="block-grid" style="min-width: 320px; max-width: 650px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; Margin: 0 auto; width: 100%; background-color: transparent;"> |
|||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:transparent;"> |
|||
<div class="col num12" style="min-width: 320px; max-width: 650px; display: table-cell; vertical-align: top; width: 650px;"> |
|||
<div style="width:100% !important;"> |
|||
<div style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:35px; padding-bottom:0px; padding-right: 0px; padding-left: 0px;"> |
|||
<div align="center" class="img-container center autowidth" style="padding-right: 0px;padding-left: 0px;"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div style="background-color:transparent;overflow:hidden"> |
|||
<div class="block-grid" style="min-width: 320px; max-width: 650px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; Margin: 0 auto; width: 100%; background-color: transparent;"> |
|||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:transparent;"> |
|||
<div class="col num12" style="min-width: 320px; max-width: 650px; display: table-cell; vertical-align: top; width: 642px;"> |
|||
<div style="width:100% !important;"> |
|||
<div style="border-top:0px solid transparent; border-left:4px solid #57366E; border-bottom:0px solid transparent; border-right:4px solid #57366E; padding-top:55px; padding-bottom:60px; padding-right: 0px; padding-left: 0px;"> |
|||
<div style="color:#fbd711;font-family:Poppins, Arial, Helvetica, sans-serif;line-height:1.2;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> |
|||
<div style="line-height: 1.2; font-size: 12px; color: #fbd711; font-family: Poppins, Arial, Helvetica, sans-serif; mso-line-height-alt: 14px;"> |
|||
<p style="font-size: 14px; line-height: 1.2; word-break: break-word; text-align: center; mso-line-height-alt: 17px; margin: 0;"><strong><span style="font-size: 30px;">ABP Community </span></strong></p> |
|||
</div> |
|||
</div> |
|||
<div style="color:#ffffff;font-family:Poppins, Arial, Helvetica, sans-serif;line-height:1.8;padding-top:10px;padding-right:50px;padding-bottom:10px;padding-left:50px;"> |
|||
<div style="line-height: 1.8; font-size: 12px; color: #ffffff; font-family: Poppins, Arial, Helvetica, sans-serif; mso-line-height-alt: 22px;"> |
|||
<p style="line-height: 1.8; word-break: break-word; font-size: 14px; mso-line-height-alt: 25px; margin: 0;"><span style="font-size: 14px;">Share your experiences with the ABP Framework!</span><br/><span style="font-size: 14px;">ABP is an open source and community driven project. This guide is aims to help anyone wants to contribute to the project.</span></p> |
|||
<p style="line-height: 1.8; word-break: break-word; font-size: 14px; mso-line-height-alt: 25px; margin: 0;"><span style="font-size: 14px;">If you want to write articles or "how to" guides related to the ABP Framework and ASP.NET Core, please submit your article to the community.abp.io web site.</span></p> |
|||
</div> |
|||
</div> |
|||
<div align="center" class="button-container" style="padding-top:12px;padding-right:10px;padding-bottom:12px;padding-left:10px;"> |
|||
<a href="http://www.example.com/" style="-webkit-text-size-adjust: none; text-decoration: none; display: inline-block; color: #000000; background-color: #fbd711; border-radius: 30px; -webkit-border-radius: 30px; -moz-border-radius: 30px; width: auto; width: auto; border-top: 1px solid #fbd711; border-right: 1px solid #fbd711; border-bottom: 1px solid #fbd711; border-left: 1px solid #fbd711; padding-top: 10px; padding-bottom: 10px; font-family: Poppins, Arial, Helvetica, sans-serif; text-align: center; mso-border-alt: none; word-break: keep-all;" target="_blank"><span style="padding-left:45px;padding-right:45px;font-size:18px;display:inline-block;"><span style="font-size: 16px; line-height: 2; word-break: break-word; mso-line-height-alt: 32px;"><span data-mce-style="font-size: 18px; line-height: 36px;" style="font-size: 18px; line-height: 36px;"><strong>Contribute</strong></span></span></span></a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
* Then we need to make the template file as "Embedded Resource". We can do this as below. |
|||
|
|||
* First right click to **EmailTemplate.tpl** and choose `Properties`. |
|||
|
|||
 |
|||
|
|||
* Then be sure about build action is **Embedded resource**. |
|||
|
|||
 |
|||
|
|||
### Step - 4 (Replacing the Email Template) |
|||
|
|||
* To replace the current email template with our new email template, we need to override it. To achieve this, create a class named `EmailTemplateDefinitionProvider` under `Emailing` folder in `TemplateReplace.Domain` and fill it with the below content. |
|||
|
|||
```csharp |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Emailing.Templates; |
|||
using Volo.Abp.TextTemplating; |
|||
|
|||
namespace TemplateReplace.Emailing |
|||
{ |
|||
public class EmailTemplateDefinitionProvider : TemplateDefinitionProvider, ITransientDependency |
|||
{ |
|||
public override void Define(ITemplateDefinitionContext context) |
|||
{ |
|||
var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Message); |
|||
|
|||
emailLayoutTemplate |
|||
.WithVirtualFilePath( |
|||
"/Emailing/Templates/EmailTemplate.tpl", |
|||
isInlineLocalized: true |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* In here we've created a template definition provider class that gets the email layout template and change the virtual file path for the template. |
|||
|
|||
* This approach allows us to locate templates in any folder instead of the folder defined by the depended module. For more detail, check the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). |
|||
|
|||
### Step - 5 |
|||
|
|||
* Lastly, we need to configure the [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). To do this open your `TemplateReplaceDomainModule.cs` in `TemplateReplace.Domain` and update the content as below. |
|||
|
|||
```csharp |
|||
using TemplateReplace.MultiTenancy; |
|||
using Volo.Abp.AuditLogging; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Emailing; |
|||
using Volo.Abp.FeatureManagement; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.IdentityServer; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.PermissionManagement.Identity; |
|||
using Volo.Abp.PermissionManagement.IdentityServer; |
|||
using Volo.Abp.SettingManagement; |
|||
using Volo.Abp.TenantManagement; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace TemplateReplace |
|||
{ |
|||
[DependsOn( |
|||
typeof(TemplateReplaceDomainSharedModule), |
|||
typeof(AbpAuditLoggingDomainModule), |
|||
typeof(AbpBackgroundJobsDomainModule), |
|||
typeof(AbpFeatureManagementDomainModule), |
|||
typeof(AbpIdentityDomainModule), |
|||
typeof(AbpPermissionManagementDomainIdentityModule), |
|||
typeof(AbpIdentityServerDomainModule), |
|||
typeof(AbpPermissionManagementDomainIdentityServerModule), |
|||
typeof(AbpSettingManagementDomainModule), |
|||
typeof(AbpTenantManagementDomainModule), |
|||
typeof(AbpEmailingModule) |
|||
)] |
|||
public class TemplateReplaceDomainModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = MultiTenancyConsts.IsEnabled; |
|||
}); |
|||
|
|||
//Add this configuration |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<TemplateReplaceDomainModule>(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
* And now when we send a new email, we should see our newly defined template as the message like below. |
|||
|
|||
 |
|||
|
|||
## Text Template Management |
|||
|
|||
* Generally, more than one e-mail is required in applications. We create email templates for **"password changes"** or **"welcome"** etc in our applications. In such cases, it is necessary to create different templates for each mail. ABP Commercial allows us to perform these operations on UI in a simple way. Text Template Management provides UI to easily create and manage email templates. |
|||
|
|||
 |
|||
|
|||
* ABP Commercial's [Text Template Management](https://commercial.abp.io/modules/Volo.TextTemplateManagement) module is really fascinating. It makes it super easy to stores and edits template contents. We can list all templates on a page, editing them, localizing them, and so on. |
|||
|
|||
 |
|||
|
|||
* ABP Commercial's text template management module, allows us to modify a template through the UI. |
|||
|
|||
* I highly recommend you to [check it out](https://commercial.abp.io/modules/Volo.TextTemplateManagement). |
|||
|
|||
## References |
|||
|
|||
* [Text Templating](https://docs.abp.io/en/abp/latest/Text-Templating) |
|||
* [Emailing](https://docs.abp.io/en/abp/latest/Emailing) |
|||
* [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System) |
|||
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 91 KiB |
@ -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 |
@ -0,0 +1,65 @@ |
|||
# Distributed Event Bus Rebus Integration |
|||
|
|||
> This document explains **how to configure the [Rebus](http://mookid.dk/category/rebus/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system |
|||
|
|||
## Installation |
|||
|
|||
Use the ABP CLI to add [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) NuGet package to your project: |
|||
|
|||
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. |
|||
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Rebus` package. |
|||
* Run `abp add-package Volo.Abp.EventBus.Rebus` command. |
|||
|
|||
If you want to do it manually, install the [Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusRebusModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. |
|||
|
|||
## Configuration |
|||
|
|||
You can configure using the standard [configuration system](Configuration.md), like using the [options](Options.md) classes. |
|||
|
|||
### The Options Classes |
|||
|
|||
`AbpRebusEventBusOptions` classe can be used to configure the event bus options for the Rebus. |
|||
|
|||
You can configure this options inside the `PreConfigureServices` of your [module](Module-Development-Basics.md). |
|||
|
|||
**Example: Minimize configuration** |
|||
|
|||
```csharp |
|||
PreConfigure<AbpRebusEventBusOptions>(options => |
|||
{ |
|||
options.InputQueueName = "eventbus"; |
|||
}); |
|||
``` |
|||
|
|||
Rebus has many options, you can use the `Configurer` property of `AbpRebusEventBusOptions` class to configure. |
|||
|
|||
Default events are **stored in memory**. See the [rebus document](https://github.com/rebus-org/Rebus/wiki/Transport) for more details. |
|||
|
|||
**Example: Configure the store** |
|||
|
|||
````csharp |
|||
PreConfigure<AbpRebusEventBusOptions>(options => |
|||
{ |
|||
options.InputQueueName = "eventbus"; |
|||
options.Configurer = rebusConfigurer => |
|||
{ |
|||
rebusConfigurer.Transport(t => t.UseMsmq("eventbus")); |
|||
rebusConfigurer.Subscriptions(s => s.UseJsonFile(@"subscriptions.json")); |
|||
}; |
|||
}); |
|||
```` |
|||
|
|||
You can use the `Publish` properpty of `AbpRebusEventBusOptions` class to change the publishing method |
|||
|
|||
**Example: Configure the event publishing** |
|||
|
|||
````csharp |
|||
PreConfigure<AbpRebusEventBusOptions>(options => |
|||
{ |
|||
options.InputQueueName = "eventbus"; |
|||
options.Publish = async (bus, type, data) => |
|||
{ |
|||
await bus.Publish(data); |
|||
}; |
|||
}); |
|||
```` |
|||
@ -1,3 +0,0 @@ |
|||
## Dynamic JavaScript API Clients |
|||
|
|||
TODO |
|||
@ -0,0 +1,13 @@ |
|||
# Migration Guide for the Blazor UI from the v3.2 to the v3.3 |
|||
|
|||
## Startup Template Changes |
|||
|
|||
* Remove `Volo.Abp.Account.Blazor` NuGet package from your `.Blazor.csproj` and add `Volo.Abp.TenantManagement.Blazor` NuGet package. |
|||
* Remove the ``typeof(AbpAccountBlazorModule)`` from the dependency list of *YourProjectBlazorModule* class and add the `typeof(AbpTenantManagementBlazorModule)`. |
|||
* Add `@using Volo.Abp.BlazoriseUI` and `@using Volo.Abp.BlazoriseUI.Components` into the `_Imports.razor` file. |
|||
* Remove the `div` with `id="blazor-error-ui"` (with its contents) from the `wwwroot/index.html ` file, since the ABP Framework now shows error messages as a better message box. |
|||
|
|||
## BlazoriseCrudPageBase to AbpCrudPageBase |
|||
|
|||
Renamed `BlazoriseCrudPageBase` to `AbpCrudPageBase`. Just update the usages. It also has some changes, you may need to update method calls/usages manually. |
|||
|
|||
|
After Width: | Height: | Size: 51 KiB |
@ -0,0 +1,3 @@ |
|||
# Angular UI: Settings |
|||
|
|||
> This document explains how to get setting values in an Angular application. See the [settings document](../../Settings.md) to learn the setting system. |
|||
@ -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,24 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Auth API |
|||
|
|||
Auth API allows you to check permissions (policies) for the current user in the client side. In this way, you can conditionally show/hide UI parts or perform your client side logic based on the current permissions. |
|||
|
|||
> This document only explains the JavaScript API. See the [authorization document](../../../Authorization.md) to understand the ABP authorization & permission system. |
|||
|
|||
## Basic Usage |
|||
|
|||
`abp.auth.isGranted(...)` function is used to check if a permission/policy has granted or not: |
|||
|
|||
````js |
|||
if (abp.auth.isGranted('DeleteUsers')) { |
|||
//TODO: Delete the user |
|||
} else { |
|||
alert("You don't have permission to delete a user!"); |
|||
} |
|||
```` |
|||
|
|||
## Other Fields & Functions |
|||
|
|||
* ` abp.auth.isAnyGranted(...)`: Gets one or more permission/policy names and returns `true` if at least one of them has granted. |
|||
* `abp.auth.areAllGranted(...)`: Gets one or more permission/policy names and returns `true` if all of them of them have granted. |
|||
* `abp.auth.policies`: This is an object where its keys are the permission/policy names. You can find all permission/policy names here. |
|||
* `abp.auth.grantedPolicies`: This is an object where its keys are the permission/policy names. You can find the granted permission/policy names here. |
|||
@ -0,0 +1,56 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript UI Block/Busy API |
|||
|
|||
UI Block API disables (blocks) the page or a part of the page. |
|||
|
|||
## Basic Usage |
|||
|
|||
**Example: Block (disable) the complete page** |
|||
|
|||
````js |
|||
abp.ui.block(); |
|||
```` |
|||
|
|||
**Example: Block (disable) an HTML element** |
|||
|
|||
````js |
|||
abp.ui.block('#MyContainer'); |
|||
```` |
|||
|
|||
**Example: Enables the previously blocked element or page:** |
|||
|
|||
````js |
|||
abp.ui.unblock(); |
|||
```` |
|||
|
|||
## Options |
|||
|
|||
`abp.ui.block()` method can get an options object which may contain the following fields: |
|||
|
|||
* `elm`: An optional selector to find the element to be blocked (e.g. `#MyContainerId`). If not provided, the entire page is blocked. The selector can also be directly passed to the `block()` method as shown above. |
|||
* `busy`: Set to `true` to show a progress indicator on the blocked area. |
|||
* `promise`: A promise object with `always` or `finally` callbacks. This can be helpful if you want to automatically unblock the blocked area when a deferred operation completes. |
|||
|
|||
**Example: Block an element with busy indicator** |
|||
|
|||
````js |
|||
abp.ui.block({ |
|||
elm: '#MySection', |
|||
busy: true |
|||
}); |
|||
```` |
|||
|
|||
The resulting UI will look like below: |
|||
|
|||
 |
|||
|
|||
## setBusy |
|||
|
|||
`abp.ui.setBusy(...)` and `abp.ui.clearBusy()` are shortcut functions if you want to use the block with `busy` option. |
|||
|
|||
**Example: Block with busy** |
|||
|
|||
````js |
|||
abp.ui.setBusy('#MySection'); |
|||
```` |
|||
|
|||
Then you can use `abp.ui.clearBusy();` to re-enable the busy area/page. |
|||
@ -0,0 +1,49 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript CurrentUser API |
|||
|
|||
`abp.currentUser` is an object that contains information about the current user of the application. |
|||
|
|||
> This document only explains the JavaScript API. See the [CurrentUser document](../../../CurrentUser.md) to get information about the current user in the server side. |
|||
|
|||
## Authenticated User |
|||
|
|||
If the user was authenticated, this object will be something like below: |
|||
|
|||
````js |
|||
{ |
|||
isAuthenticated: true, |
|||
id: "34f1f4a7-13cc-4b91-84d1-b91c87afa95f", |
|||
tenantId: null, |
|||
userName: "john", |
|||
name: "John", |
|||
surName: "Nash", |
|||
email: "john.nash@abp.io", |
|||
emailVerified: true, |
|||
phoneNumber: null, |
|||
phoneNumberVerified: false, |
|||
roles: ["moderator","supporter"] |
|||
} |
|||
```` |
|||
|
|||
So, `abp.currentUser.userName` returns `john` in this case. |
|||
|
|||
## Anonymous User |
|||
|
|||
If the user was not authenticated, this object will be something like below: |
|||
|
|||
````js |
|||
{ |
|||
isAuthenticated: false, |
|||
id: null, |
|||
tenantId: null, |
|||
userName: null, |
|||
name: null, |
|||
surName: null, |
|||
email: null, |
|||
emailVerified: false, |
|||
phoneNumber: null, |
|||
phoneNumberVerified: false, |
|||
roles: [] |
|||
} |
|||
```` |
|||
|
|||
You can check `abp.currentUser.isAuthenticated` to understand if the use was authenticated or not. |
|||
@ -0,0 +1,117 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript DOM API |
|||
|
|||
`abp.dom` (Document Object Model) provides events that you can subscribe to get notified when elements dynamically added to and removed from the page (DOM). |
|||
|
|||
It is especially helpful if you want to initialize the new loaded elements. This is generally needed when you dynamically add elements to DOM (for example, get some HTML elements via AJAX) after page initialization. |
|||
|
|||
> ABP uses the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to observe the changes made on the DOM. |
|||
|
|||
## Node Events |
|||
|
|||
### onNodeAdded |
|||
|
|||
This event is triggered when an element is added to the DOM. Example: |
|||
|
|||
````js |
|||
abp.dom.onNodeAdded(function(args){ |
|||
console.log(args.$el); |
|||
}); |
|||
```` |
|||
|
|||
`args` object has the following fields; |
|||
|
|||
* `$el`: The JQuery selection to get the new element inserted to the DOM. |
|||
|
|||
### onNodeRemoved |
|||
|
|||
This event is triggered when an element is removed from the DOM. Example: |
|||
|
|||
````js |
|||
abp.dom.onNodeRemoved(function(args){ |
|||
console.log(args.$el); |
|||
}); |
|||
```` |
|||
|
|||
`args` object has the following fields; |
|||
|
|||
* `$el`: The JQuery selection to get the element removed from the DOM. |
|||
|
|||
## Pre-Build Initializers |
|||
|
|||
ABP Framework is using the DOM events to initialize some kind of HTML elements when they are added to the DOM after than the page was already initialized. |
|||
|
|||
> Note that the same initializers also work if these elements were already included in the initial DOM. So, whether they are initially or lazy loaded, they work as expected. |
|||
|
|||
### Form Initializer |
|||
|
|||
The Form initializer (defined as `abp.dom.initializers.initializeForms`) initializes the lazy loaded forms; |
|||
|
|||
* Automatically enabled the `unobtrusive` validation on the form. |
|||
* Can automatically show a confirmation message when you submit the form. To enable this feature, just add `data-confirm` attribute with a message (like `data-confirm="Are you sure?"`) to the `form` element. |
|||
* If the `form` element has `data-ajaxForm="true"` attribute, then automatically calls the `.abpAjaxForm()` on the `form` element, to make the form posted via AJAX. |
|||
|
|||
See the [Forms & Validation](../Forms-Validation.md) document for more. |
|||
|
|||
### Script Initializer |
|||
|
|||
Script initializer (`abp.dom.initializers.initializeScript`) can execute a JavaScript code for a DOM element. |
|||
|
|||
**Example: Lazy load a component and execute some code when the element has loaded** |
|||
|
|||
Assume that you've a container to load the element inside: |
|||
|
|||
````html |
|||
<div id="LazyComponent"></div> |
|||
```` |
|||
|
|||
And this is the component that will be loaded via AJAX from the server and inserted into the container: |
|||
|
|||
````html |
|||
<div data-script-class="MyCustomClass"> |
|||
<p>Sample message</p> |
|||
</div> |
|||
```` |
|||
|
|||
`data-script-class="MyCustomClass"` indicates the JavaScript class that will be used to perform some logic on this element: |
|||
|
|||
`MyCustomClass` is a global object defined as shown below: |
|||
|
|||
````js |
|||
MyCustomClass = function(){ |
|||
|
|||
function initDom($el){ |
|||
$el.css('color', 'red'); |
|||
} |
|||
|
|||
return { |
|||
initDom: initDom |
|||
} |
|||
}; |
|||
```` |
|||
|
|||
`initDom` is the function that is called by the ABP Framework. The `$el` argument is the loaded HTML element as a JQuery selection. |
|||
|
|||
Finally, you can load the component inside the container after an AJAX call: |
|||
|
|||
````js |
|||
$(function () { |
|||
setTimeout(function(){ |
|||
$.get('/get-my-element').then(function(response){ |
|||
$('#LazyComponent').html(response); |
|||
}); |
|||
}, 2000); |
|||
}); |
|||
```` |
|||
|
|||
Script Initialization system is especially helpful if you don't know how and when the component will be loaded into the DOM. This can be possible if you've developed a reusable UI component in a library and you want the application developer shouldn't care how to initialize the component in different use cases. |
|||
|
|||
> Script initialization doesn't work if the component was loaded in the initial DOM. In this case, you are responsible to initialize it. |
|||
|
|||
### Other Initializers |
|||
|
|||
The following Bootstrap components and libraries are automatically initialized when they are added to the DOM: |
|||
|
|||
* Tooltip |
|||
* Popover |
|||
* Timeage |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: Dynamic JavaScript Client Proxies |
|||
|
|||
TODO |
|||
@ -0,0 +1,88 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Events API |
|||
|
|||
`abp.event` object is a simple service that is used to publish and subscribe to global events **in the browser**. |
|||
|
|||
> This API is not related to server side local or distributed events. It works in the browser boundaries to make the UI components (code parts) communicate in a loosely coupled way. |
|||
|
|||
## Basic Usage |
|||
|
|||
### Publishing Events |
|||
|
|||
Use `abp.event.trigger` to publish events. |
|||
|
|||
**Example: Publish a *Basket Updated* event** |
|||
|
|||
````js |
|||
abp.event.trigger('basketUpdated'); |
|||
```` |
|||
|
|||
This will trigger all the subscribed callbacks. |
|||
|
|||
### Subscribing to the Events |
|||
|
|||
Use `abp.event.on` to subscribe to events. |
|||
|
|||
**Example: Consume the *Basket Updated* event** |
|||
|
|||
````js |
|||
abp.event.on('basketUpdated', function() { |
|||
console.log('Handled the basketUpdated event...'); |
|||
}); |
|||
```` |
|||
|
|||
You start to get events after you subscribe to the event. |
|||
|
|||
### Unsubscribing from the Events |
|||
|
|||
If you need to unsubscribe from a pre-subscribed event, you can use the `abp.event.off(eventName, callback)` function. In this case, you have the callback as a separate function declaration. |
|||
|
|||
**Example: Subscribe & Unsubscribe** |
|||
|
|||
````js |
|||
function onBasketUpdated() { |
|||
console.log('Handled the basketUpdated event...'); |
|||
} |
|||
|
|||
//Subscribe |
|||
abp.event.on('basketUpdated', onBasketUpdated); |
|||
|
|||
//Unsubscribe |
|||
abp.event.off('basketUpdated', onBasketUpdated); |
|||
```` |
|||
|
|||
You don't get events after you unsubscribe from the event. |
|||
|
|||
## Event Arguments |
|||
|
|||
You can pass arguments (of any count) to the `trigger` method and get them in the subscription callback. |
|||
|
|||
**Example: Add the basket as the event argument** |
|||
|
|||
````js |
|||
//Subscribe to the event |
|||
abp.event.on('basketUpdated', function(basket) { |
|||
console.log('The new basket object: '); |
|||
console.log(basket); |
|||
}); |
|||
|
|||
//Trigger the event |
|||
abp.event.trigger('basketUpdated', { |
|||
items: [ |
|||
{ |
|||
"productId": "123", |
|||
"count": 2 |
|||
}, |
|||
{ |
|||
"productId": "832", |
|||
"count": 1 |
|||
} |
|||
] |
|||
}); |
|||
```` |
|||
|
|||
### Multiple Arguments |
|||
|
|||
If you want to pass multiple arguments, you can pass like `abp.event.on('basketUpdated', arg0, arg1, agr2)`. Then you can add the same argument list to the callback function on the subscriber side. |
|||
|
|||
> **Tip:** Alternatively, you can send a single object that has a separate field for each argument. This makes easier to extend/change the event arguments in the future without breaking the subscribers. |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Features API |
|||
|
|||
`abp.features` API allows you to check features or get the values of the features on the client side. You can read the current value of a feature in the client side only if it is allowed by the feature definition (on the server side). |
|||
|
|||
> This document only explains the JavaScript API. See the [Features](../../../Features.md) document to understand the ABP Features system. |
|||
|
|||
## Basic Usage |
|||
|
|||
`abp.features.values` can be used to access to the all feature values. |
|||
|
|||
````js |
|||
var excelExportFeatureValue = abp.features.values["ExportingToExcel"]; |
|||
```` |
|||
|
|||
Then you can check the value of the feature to perform your logic. |
|||
@ -1,23 +1,19 @@ |
|||
# 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.auth |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
* [AJAX](Ajax.md) |
|||
* [Auth](Auth.md) |
|||
* [CurrentUser](CurrentUser.md) |
|||
* [DOM](DOM.md) |
|||
* [Events](Events.md) |
|||
* [Features](Features.md) |
|||
* [Localization](Localization.md) |
|||
* [Logging](Logging.md) |
|||
* [ResourceLoader](ResourceLoader.md) |
|||
* [Settings](Settings.md) |
|||
* [UI Block/Busy](Block-Busy.md) |
|||
* [UI Message](Message.md) |
|||
* [UI Notification](Notify.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 |
|||
} |
|||
] |
|||
```` |
|||
|
|||
@ -0,0 +1,50 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Logging API |
|||
|
|||
`abp.log` API is used to write simple logs in the client side. |
|||
|
|||
> The logs are written to console, using the `console.log`, by default. |
|||
|
|||
> This document is for simple client side logging. See the [Logging](../../../Logging.md) document for server side logging system. |
|||
|
|||
## Basic Usage |
|||
|
|||
Use one of the `abp.log.xxx(...)` methods based on the severity of your log message. |
|||
|
|||
````js |
|||
abp.log.debug("Some debug log here..."); //Logging a simple debug message |
|||
abp.log.info({ name: "john", age: 42 }); //Logging an object as an information log |
|||
abp.log.warn("A warning message"); //Logging a warning message |
|||
abp.log.error('An error happens...'); //Error message |
|||
abp.log.fatal('Network connection has gone away!'); //Fatal error |
|||
```` |
|||
|
|||
## Log Levels |
|||
|
|||
There are 5 levels for a log message: |
|||
|
|||
* DEBUG = 1 |
|||
* INFO = 2 |
|||
* WARN = 3 |
|||
* ERROR = 4 |
|||
* FATAL = 5 |
|||
|
|||
These are defined in the `abp.log.levels` object (like `abp.log.levels.WARN`). |
|||
|
|||
### Changing the Current Log Level |
|||
|
|||
You can control the log level as shown below: |
|||
|
|||
````js |
|||
abp.log.level = abp.log.levels.WARN; |
|||
```` |
|||
|
|||
Default log level is `DEBUG`. |
|||
|
|||
### Logging with Specifying the Level |
|||
|
|||
Instead of calling `abp.log.info(...)` function, you can use the `abp.log.log` by specifying the log level as a parameter: |
|||
|
|||
````js |
|||
abp.log.log("log message...", abp.log.levels.INFO); |
|||
```` |
|||
|
|||
@ -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. |
|||
@ -0,0 +1,40 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Resource Loader API |
|||
|
|||
`abp.ResourceLoader` is a service that can load a JavaScript or CSS file on demand. It guarantees to load the file only once even if you request multiple times. |
|||
|
|||
## Loading Script Files |
|||
|
|||
`abp.ResourceLoader.loadScript(...)` function **loads** a JavaScript file from the server and **executes** it. |
|||
|
|||
**Example: Load a JavaScript file** |
|||
|
|||
````js |
|||
abp.ResourceLoader.loadScript('/Pages/my-script.js'); |
|||
```` |
|||
|
|||
### Parameters |
|||
|
|||
`loadScript` function can get three parameters; |
|||
|
|||
* `url` (required, `string`): The URL of the script file to be loaded. |
|||
* `loadCallback` (optional, `function`): A callback function that is called once the script is loaded & executed. In this callback you can safely use the code in the script file. This callback is called even if the file was loaded before. |
|||
* `failCallback` (optional, `function`): A callback function that is called if loading the script fails. |
|||
|
|||
**Example: Provide the `loadCallback` argument** |
|||
|
|||
````js |
|||
abp.ResourceLoader.loadScript('/Pages/my-script.js', function() { |
|||
console.log('successfully loaded :)'); |
|||
}); |
|||
```` |
|||
|
|||
## Loading Style Files |
|||
|
|||
`abp.ResourceLoader.loadStyle(...)` function adds a `link` element to the `head` of the document for the given URL, so the CSS file is automatically loaded by the browser. |
|||
|
|||
**Example: Load a CSS file** |
|||
|
|||
````js |
|||
abp.ResourceLoader.loadStyle('/Pages/my-styles.css'); |
|||
```` |
|||
|
|||
@ -0,0 +1,33 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: JavaScript Setting API |
|||
|
|||
Localization API allows you to get the values of the settings on the client side. You can read the current value of a setting in the client side only if it is allowed by the setting definition (on the server side). |
|||
|
|||
> This document only explains the JavaScript API. See the [settings document](../../../Settings.md) to understand the ABP setting system. |
|||
|
|||
## Basic Usage |
|||
|
|||
````js |
|||
//Gets a value as string. |
|||
var language = abp.setting.get('Abp.Localization.DefaultLanguage'); |
|||
|
|||
//Gets an integer value. |
|||
var requiredLength = abp.setting.getInt('Abp.Identity.Password.RequiredLength'); |
|||
|
|||
//Gets a boolean value. |
|||
var requireDigit = abp.setting.getBoolean('Abp.Identity.Password.RequireDigit'); |
|||
```` |
|||
|
|||
## All Values |
|||
|
|||
`abp.setting.values` can be used to obtain all the setting values as an object where the object properties are setting names and property values are the setting values. |
|||
|
|||
An example value of this object is shown below: |
|||
|
|||
````js |
|||
{ |
|||
Abp.Localization.DefaultLanguage: "en", |
|||
Abp.Timing.TimeZone: "UTC", |
|||
... |
|||
} |
|||
```` |
|||
|
|||
@ -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,483 @@ |
|||
# ASP.NET Core MVC / Razor Pages UI: Modals |
|||
|
|||
While you can continue to use the standard [Bootstrap way](https://getbootstrap.com/docs/4.5/components/modal/) to create, open and manage modals in your applications, ABP Framework provides a **flexible** way to manage modals by **automating common tasks** for you. |
|||
|
|||
**Example: A modal dialog to create a new role entity** |
|||
|
|||
 |
|||
|
|||
ABP Framework provides the following benefits for such a modal with a form inside it; |
|||
|
|||
* **Lazy loads** the modal HTML into the page and **removes** it from the DOM once its closed. This makes easy to consume a reusable modal dialog. Also, every time you open the modal, it will be a fresh new modal, so you don't have to deal with resetting the modal content. |
|||
* **Auto-focuses** the first input of the form once the modal has been opened. |
|||
* Automatically determines the **form** inside a modal and posts the form via **AJAX** instead of normal page post. |
|||
* Automatically checks if the form inside the modal **has changed, but not saved**. It warns the user in this case. |
|||
* Automatically **disables the modal buttons** (save & cancel) until the AJAX operation completes. |
|||
* Makes it easy to register a **JavaScript object that is initialized** once the modal has loaded. |
|||
|
|||
So, it makes you write less code when you deal with the modals, especially the modals with a form inside. |
|||
|
|||
## Basic Usage |
|||
|
|||
### Creating a Modal as a Razor Page |
|||
|
|||
To demonstrate the usage, we are creating a simple Razor Page, named `ProductInfoModal.cshtml`, under the `/Pages/Products` folder: |
|||
|
|||
 |
|||
|
|||
**ProductInfoModal.cshtml Content:** |
|||
|
|||
````html |
|||
@page |
|||
@model MyProject.Web.Pages.Products.ProductInfoModalModel |
|||
@{ |
|||
Layout = null; |
|||
} |
|||
<abp-modal> |
|||
<abp-modal-header title="Product Information"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<h3>@Model.ProductName</h3> |
|||
<div> |
|||
<img src="@Model.ProductImageUrl" /> |
|||
</div> |
|||
<p> |
|||
@Model.ProductDescription |
|||
</p> |
|||
<p> |
|||
<small><i>Reference: https://acme.com/catalog/</i></small> |
|||
</p> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="Close"></abp-modal-footer> |
|||
</abp-modal> |
|||
```` |
|||
|
|||
* This page sets the `Layout` to `null` since we will show this as a modal. So, no need to wrap with a layout. |
|||
* It uses [abp-modal tag helper](Tag-Helpers/Modals.md) to simplify creating the modal HTML code. You can use the standard Bootstrap modal code if you prefer it. |
|||
|
|||
**ProductInfoModalModel.cshtml.cs Content:** |
|||
|
|||
```csharp |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace MyProject.Web.Pages.Products |
|||
{ |
|||
public class ProductInfoModalModel : AbpPageModel |
|||
{ |
|||
public string ProductName { get; set; } |
|||
|
|||
public string ProductDescription { get; set; } |
|||
|
|||
public string ProductImageUrl { get; set; } |
|||
|
|||
public void OnGet() |
|||
{ |
|||
ProductName = "Acme Indestructo Steel Ball"; |
|||
ProductDescription = "The ACME Indestructo Steel Ball is completely indestructible, there is nothing that can destroy it!"; |
|||
ProductImageUrl = "https://acme.com/catalog/acmeindestructo.jpg"; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
You can surely get the product info from a database or API. We are setting the properties hard-coded for the sake of simplicity, |
|||
|
|||
### Defining the Modal Manager |
|||
|
|||
Once you have a modal, you can open it in any page using some simple **JavaScript** code. |
|||
|
|||
First, create an `abp.ModalManager` object by setting the `viewUrl`, in the JavaScript file of the page that will use the modal: |
|||
|
|||
````js |
|||
var productInfoModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductInfoModal' |
|||
}); |
|||
```` |
|||
|
|||
> If you only need to specify the `viewUrl`, you can directly pass it to the `ModalManager` constructor, as a shortcut. Example: `new abp.ModalManager('/Products/ProductInfoModal');` |
|||
|
|||
### Opening the Modal |
|||
|
|||
Then open the modal whenever you need: |
|||
|
|||
````js |
|||
productInfoModal.open(); |
|||
```` |
|||
|
|||
You typically want to open the modal when something happens; For example, when the user clicks a button: |
|||
|
|||
````js |
|||
$('#OpenProductInfoModal').click(function(){ |
|||
productInfoModal.open(); |
|||
}); |
|||
```` |
|||
|
|||
The resulting modal will be like that: |
|||
|
|||
 |
|||
|
|||
#### Opening the Modal with Arguments |
|||
|
|||
When you call the `open()` method, `ModalManager` loads the modal HTML by requesting it from the `viewUrl`. You can pass some **query string parameters** to this URL when you open the modal. |
|||
|
|||
**Example: Pass the product id while opening the modal** |
|||
|
|||
````js |
|||
productInfoModal.open({ |
|||
productId: 42 |
|||
}); |
|||
```` |
|||
|
|||
You can add a `productId` parameter to the get method: |
|||
|
|||
````csharp |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace MyProject.Web.Pages.Products |
|||
{ |
|||
public class ProductInfoModalModel : AbpPageModel |
|||
{ |
|||
//... |
|||
|
|||
public async Task OnGetAsync(int productId) //Add productId parameter |
|||
{ |
|||
//TODO: Get the product with database with the given productId |
|||
//... |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this way, you can use the `productId` to query the product from a data source. |
|||
|
|||
## Modals with Forms |
|||
|
|||
`abp.ModalManager` handles various common tasks (described in the introduction) when you want to use a form inside the modal. |
|||
|
|||
### Example Modal with a Form |
|||
|
|||
This section shows an example form to create a new product. |
|||
|
|||
#### Creating the Razor Page |
|||
|
|||
For this example, creating a new Razor Page, named `ProductCreateModal.cshtml`, under the `/Pages/Products` folder: |
|||
|
|||
 |
|||
|
|||
**ProductCreateModal.cshtml Content:** |
|||
|
|||
````html |
|||
@page |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal |
|||
@model MyProject.Web.Pages.Products.ProductCreateModalModel |
|||
@{ |
|||
Layout = null; |
|||
} |
|||
<form method="post" action="@Url.Page("/Products/ProductCreateModal")"> |
|||
<abp-modal> |
|||
<abp-modal-header title="Create New Product"></abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-input asp-for="Product.Name"/> |
|||
<abp-input asp-for="Product.Description"/> |
|||
<abp-input asp-for="Product.ReleaseDate"/> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@AbpModalButtons.Save | @AbpModalButtons.Cancel"></abp-modal-footer> |
|||
</abp-modal> |
|||
</form> |
|||
```` |
|||
|
|||
* The `abp-modal` has been wrapped by the `form`. This is needed to place the `Save` and the `Cancel` buttons into the form. In this way, the `Save` button acts as the `submit` button for the `form`. |
|||
* Used the [abp-input tag helpers](Tag-Helpers/Form-Elements.md) to simplify to create the form elements. Otherwise, you need to write more HTML. |
|||
|
|||
**ProductCreateModal.cshtml.cs Content:** |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace MyProject.Web.Pages.Products |
|||
{ |
|||
public class ProductCreateModalModel : AbpPageModel |
|||
{ |
|||
[BindProperty] |
|||
public PoductCreationDto Product { get; set; } |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
//TODO: Get logic, if available |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
//TODO: Save the Product... |
|||
|
|||
return NoContent(); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
* This is a simple `PageModal` class. The `[BindProperty]` make the form binding to the model when you post (submit) the form; The standard ASP.NET Core system. |
|||
* `OnPostAsync` returns `NoContent` (this method is defined by the base `AbpPageModel` class). Because we don't need to a return value in the client side, after the form post operation. |
|||
|
|||
**PoductCreationDto:** |
|||
|
|||
`ProductCreateModalModel` uses a `PoductCreationDto` class defined as shown below: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; |
|||
|
|||
namespace MyProject.Web.Pages.Products |
|||
{ |
|||
public class PoductCreationDto |
|||
{ |
|||
[Required] |
|||
[StringLength(128)] |
|||
public string Name { get; set; } |
|||
|
|||
[TextArea(Rows = 4)] |
|||
[StringLength(2000)] |
|||
public string Description { get; set; } |
|||
|
|||
[DataType(DataType.Date)] |
|||
public DateTime ReleaseDate { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `abp-input` Tag Helper can understand the data annotation attributes and uses them to shape and validate the form elements. See the [abp-input tag helpers](Tag-Helpers/Form-Elements.md) document to learn more. |
|||
|
|||
#### Defining the Modal Manager |
|||
|
|||
Again, create an `abp.ModalManager` object by setting the `viewUrl`, in the JavaScript file of the page that will use the modal: |
|||
|
|||
````js |
|||
var productCreateModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductCreateModal' |
|||
}); |
|||
```` |
|||
|
|||
#### Opening the Modal |
|||
|
|||
Then open the modal whenever you need: |
|||
|
|||
````js |
|||
productCreateModal.open(); |
|||
```` |
|||
|
|||
You typically want to open the modal when something happens; For example, when the user clicks a button: |
|||
|
|||
````js |
|||
$('#OpenProductCreateModal').click(function(){ |
|||
productCreateModal.open(); |
|||
}); |
|||
```` |
|||
|
|||
So, the complete code will be something like that (assuming you have a `button` with `id` is `OpenProductCreateModal` on the view side): |
|||
|
|||
```js |
|||
$(function () { |
|||
|
|||
var productCreateModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductCreateModal' |
|||
}); |
|||
|
|||
$('#OpenProductCreateModal').click(function () { |
|||
productCreateModal.open(); |
|||
}); |
|||
|
|||
}); |
|||
``` |
|||
|
|||
The resulting modal will be like that: |
|||
|
|||
 |
|||
|
|||
#### Saving the Modal |
|||
|
|||
When you click to the `Save` button, the form is posted to the server. If the server returns a **success response**, then the `onResult` event is triggered with some arguments including the server response and the modal is automatically closed. |
|||
|
|||
An example callback that logs the arguments passed to the `onResult` method: |
|||
|
|||
````js |
|||
productCreateModal.onResult(function(){ |
|||
console.log(arguments); |
|||
}); |
|||
```` |
|||
|
|||
If the server returns a failed response, it shows the error message returned from the server and keeps the modal open. |
|||
|
|||
> See the *Modal Manager Reference* section below for other modal events. |
|||
|
|||
#### Canceling the Modal |
|||
|
|||
If you click to the Cancel button with some changes made but not saved, you get such a warning message: |
|||
|
|||
 |
|||
|
|||
If you don't want such a check & message, you can add `data-check-form-on-close="false"` attribute to your `form` element. Example: |
|||
|
|||
````html |
|||
<form method="post" |
|||
action="@Url.Page("/Products/ProductCreateModal")" |
|||
data-check-form-on-close="false"> |
|||
```` |
|||
|
|||
### Form Validation |
|||
|
|||
`ModalManager` automatically triggers the form validation when you click to the `Save` button or hit the `Enter` key on the form: |
|||
|
|||
 |
|||
|
|||
See the [Forms & Validation document](Forms-Validation.md) to learn more about the validation. |
|||
|
|||
## Modals with Script Files |
|||
|
|||
You may need to perform some logic for your modal. To do that, create a JavaScript file like below: |
|||
|
|||
````js |
|||
abp.modals.ProductInfo = function () { |
|||
|
|||
function initModal(modalManager, args) { |
|||
var $modal = modalManager.getModal(); |
|||
var $form = modalManager.getForm(); |
|||
|
|||
$modal.find('h3').css('color', 'red'); |
|||
|
|||
console.log('initialized the modal...'); |
|||
}; |
|||
|
|||
return { |
|||
initModal: initModal |
|||
}; |
|||
}; |
|||
```` |
|||
|
|||
* This code simply adds a `ProductInfo` class into the `abp.modals` namespace. The `ProductInfo` class exposes a single public function: `initModal`. |
|||
* `initModal` method is called by the `ModalManager` once the modal HTML is inserted to DOM and ready for the initialization logic. |
|||
* `modalManager` parameter is the `ModalManager` object related to this modal instance. So, you can use any function on it in your code. See the *ModalManager Reference* section. |
|||
|
|||
Then include this file to the page that you use the modal: |
|||
|
|||
````html |
|||
<abp-script src="/Pages/Products/ProductInfoModal.js"/> |
|||
<abp-script src="/Pages/Products/Index.js"/> |
|||
```` |
|||
|
|||
* We've use the `abp-script` Tag Helper here. See the [Bundling & Minification](Bundling-Minification.md) document if you want to understand it. You can use the standard `script` tag. It doesn't matter for this case. |
|||
|
|||
Finally, set the `modalClass` option while creating the `ModalManager` instance: |
|||
|
|||
````js |
|||
var productInfoModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductInfoModal', |
|||
modalClass: 'ProductInfo' //Matches to the abp.modals.ProductInfo |
|||
}); |
|||
```` |
|||
|
|||
### Lazy Loading the Script File |
|||
|
|||
Instead of adding the `ProductInfoModal.js` to the page you use the modal, you can configure it to lazy load the script file when the first time the modal is opened. |
|||
|
|||
Example: |
|||
|
|||
````js |
|||
var productInfoModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductInfoModal', |
|||
scriptUrl: '/Pages/Products/ProductInfoModal.js', //Lazy Load URL |
|||
modalClass: 'ProductInfo' |
|||
}); |
|||
```` |
|||
|
|||
* `scriptUrl` is used to set the URL to load the script file of the modal. |
|||
* In this case, you no longer need to include the `ProductInfoModal.js` to the page. It will be loaded on demand. |
|||
|
|||
#### Tip: Bundling & Minification |
|||
|
|||
While lazy loading seems cool at the beginning, it requires an additional call to the server when you first open the modal. |
|||
|
|||
Instead, you can use the [Bundling & Minification](Bundling-Minification.md) system to create a bundle (that is a single and minified file on production) for all the used script files for a page: |
|||
|
|||
````html |
|||
<abp-script-bundle> |
|||
<abp-script src="/Pages/Products/ProductInfoModal.js"/> |
|||
<abp-script src="/Pages/Products/Index.js"/> |
|||
</abp-script-bundle> |
|||
```` |
|||
|
|||
This is efficient if the script file is not large and frequently opened while users use the page. |
|||
|
|||
Alternatively, you can define the `abp.modals.ProductInfo` class in the page's main JavaScript file if the modal is only and always used in the same page. In this case, you don't need to another external script file at all. |
|||
|
|||
## ModalManager Reference |
|||
|
|||
### Options |
|||
|
|||
Options can be passed when you create a new `ModalManager` object: |
|||
|
|||
````js |
|||
var productInfoModal = new abp.ModalManager({ |
|||
viewUrl: '/Products/ProductInfoModal', |
|||
//...other options |
|||
}); |
|||
```` |
|||
|
|||
Here, the list of all available options; |
|||
|
|||
* `viewUrl` (required, `string`): The URL to lazy load the HTML of the modal. |
|||
* `scriptUrl` (optional, `string`): A URL to lazy load a JavaScript file. It is loaded only once, when the modal first opened. |
|||
* `modalClass` (optional, `string`): A JavaScript class defined in the `abp.modals` namespace that can be used to execute code related to the modal. |
|||
|
|||
### Functions |
|||
|
|||
When you create a new `ModalManager` object, you can use its functions to perform operations on the modal. Example: |
|||
|
|||
````js |
|||
var myModal = new abp.ModalManager({ |
|||
//...options |
|||
}); |
|||
|
|||
//Open the modal |
|||
myModal.open(); |
|||
|
|||
//Close the modal |
|||
myModal.close(); |
|||
```` |
|||
|
|||
Here, the list of all available functions of the `ModalManager` object; |
|||
|
|||
* `open([args])`: Opens the modal dialog. It can get an `args` object that is converted to query string while getting the `viewUrl` from the server. For example, if `args` is `{ productId: 42 }`, then the `ModalManager` passes `?productId=42` to the end of the `viewUrl` while loading the view from the server. |
|||
* `reopen()`: Opens the modal with the latest provided `args` for the `open()` method. So, it is a shortcut if you want to re-open the modal with the same `args`. |
|||
* `close()`: Closes the modal. The modal HTML is automatically removed from DOM once it has been closed. |
|||
* `getModalId()`: Gets the `id` attribute of the container that contains the view returned from the server. This is a unique id per modal and it doesn't change after you create the `ModalManager`. |
|||
* `getModal()`: Returns the modal wrapper DOM element (the HTML element with the `modal` CSS class) as a JQuery selection, so you can perform any JQuery method on it. |
|||
* `getForm()`: Returns the `form` HTML element as a JQuery selection, so you can perform any JQuery method on it. It returns `null` if the modal has no form inside it. |
|||
* `getArgs()` Gets the latest arguments object provided while opening the modal. |
|||
* `getOptions()`: Gets the options object passed to the `ModalManager` constructor. |
|||
* `setResult(...)`: Triggers the `onResult` event with the provided arguments. You can pass zero or more arguments those are directly passed to the `onResult` event. This function is generally called by the modal script to notify the page that uses the modal. |
|||
|
|||
### Events |
|||
|
|||
When you create a new `ModalManager` object, you can use its functions register to events of the modal. Examples: |
|||
|
|||
````js |
|||
var myModal = new abp.ModalManager({ |
|||
//...options |
|||
}); |
|||
|
|||
myModal.onOpen(function () { |
|||
console.log('opened the modal...'); |
|||
}); |
|||
|
|||
myModal.onClose(function () { |
|||
console.log('closed the modal...'); |
|||
}); |
|||
```` |
|||
|
|||
Here, the list of all available functions to register to events of the `ModalManager` object; |
|||
|
|||
* `onOpen(callback)`: Registers a callback function to get notified once the modal is opened. It is triggered when the modal is completely visible on the UI. |
|||
* `onClose(callback)`: Registers a callback function to get notified once the modal is closed. It is triggered when the modal is completely invisible on the UI. |
|||
* `onResult(callback)`: Registers a callback function that is triggered when the ``setResult(...)` method is called. All the parameters sent to the `setResult` method is passed to the callback. |
|||
@ -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. |
|||
@ -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. |
|||
@ -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. |
|||
@ -0,0 +1,3 @@ |
|||
# Blazor UI: Settings |
|||
|
|||
Blazor applications can reuse the same `ISettingProvider` service that is explained in the [settings document](../../Settings.md). |
|||
|
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 |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 31 KiB |