mirror of https://github.com/abpframework/abp.git
40 changed files with 1793 additions and 1388 deletions
@ -0,0 +1,140 @@ |
|||
# Auto API Controllers |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
@ -0,0 +1,165 @@ |
|||
# Dynamic C# API Clients |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "RemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
@ -0,0 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
|
|||
TODO |
|||
@ -0,0 +1,24 @@ |
|||
# 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. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* 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 |
|||
|
|||
@ -1,140 +1,3 @@ |
|||
# Auto API Controllers |
|||
This document has moved. |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md) |
|||
@ -1,352 +1,4 @@ |
|||
|
|||
# ASP.NET Core MVC Bundling & Minification |
|||
This document has moved. |
|||
|
|||
There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are: |
|||
|
|||
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/). |
|||
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins. |
|||
|
|||
ABP offers a simple, dynamic, powerful, modular and built-in way. |
|||
|
|||
## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package |
|||
|
|||
> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Razor Bundling Tag Helpers |
|||
|
|||
The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example: |
|||
|
|||
````html |
|||
<abp-style-bundle name="MyGlobalBundle"> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*: |
|||
|
|||
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. |
|||
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). |
|||
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). |
|||
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). |
|||
|
|||
### Importing The Bundling Tag Helpers |
|||
|
|||
> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually. |
|||
|
|||
In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page: |
|||
|
|||
```` |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
### Unnamed Bundles |
|||
|
|||
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example: |
|||
|
|||
````html |
|||
<abp-style-bundle> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
@if (ViewBag.IncludeCustomStyles != false) |
|||
{ |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
} |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not). |
|||
|
|||
Advantages of **unnamed** bundles: |
|||
|
|||
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions. |
|||
|
|||
Advantages of **named** bundles: |
|||
|
|||
* Other **modules can contribute** to the bundle by its name (see the sections below). |
|||
|
|||
### Single File |
|||
|
|||
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example: |
|||
|
|||
````xml |
|||
<abp-script src="/scripts/my-script.js" /> |
|||
```` |
|||
|
|||
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too. |
|||
|
|||
## Bundling Options |
|||
|
|||
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. |
|||
|
|||
### Creating A New Bundle |
|||
|
|||
Example usage: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Add("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/libs/jquery/jquery.js", |
|||
"/libs/bootstrap/js/bootstrap.js", |
|||
"/libs/toastr/toastr.min.js", |
|||
"/scripts/my-global-scripts.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`). |
|||
|
|||
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example: |
|||
|
|||
````html |
|||
<abp-script-bundle name="MyGlobalBundle" /> |
|||
```` |
|||
|
|||
This time, no file defined in the tag helper definition because the bundle files are defined by the code. |
|||
|
|||
### Configuring An Existing Bundle |
|||
|
|||
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyWebModule))] |
|||
public class MyWebExtensionModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/scripts/my-extension-script.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper. |
|||
|
|||
## Bundle Contributors |
|||
|
|||
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases. |
|||
|
|||
An example bundle contributor that replaces bootstrap.css with a customized version: |
|||
|
|||
````C# |
|||
public class MyExtensionGlobalStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.ReplaceOne( |
|||
"/libs/bootstrap/css/bootstrap.css", |
|||
"/styles/extensions/bootstrap-customized.css" |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can use this contributor as like below: |
|||
|
|||
````C# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
> You can also add contributors while creating a new bundle. |
|||
|
|||
Contributors can also be used in the bundle tag helpers. Example: |
|||
|
|||
````xml |
|||
<abp-style-bundle> |
|||
<abp-style type="@typeof(BootstrapStyleContributor)" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle. |
|||
|
|||
### Contributor Dependencies |
|||
|
|||
A bundle contributor can have one or more dependencies to other contributors. |
|||
Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications. |
|||
|
|||
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules. |
|||
|
|||
### Contributor Extensions |
|||
|
|||
In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used. |
|||
|
|||
The example below adds some styles for prism.js library: |
|||
|
|||
````csharp |
|||
public class MyPrismjsStyleExtension : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`. |
|||
|
|||
````csharp |
|||
Configure<BundleContributorOptions>(options => |
|||
{ |
|||
options |
|||
.Extensions<PrismjsStyleBundleContributor>() |
|||
.Add<MyPrismjsStyleExtension>(); |
|||
}); |
|||
```` |
|||
|
|||
Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added. |
|||
|
|||
### Accessing to the IServiceProvider |
|||
|
|||
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method. |
|||
|
|||
### Standard Package Contributors |
|||
|
|||
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package. |
|||
|
|||
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself. |
|||
|
|||
````C# |
|||
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Using the built-in contributors for standard packages; |
|||
|
|||
* Prevents you typing **the invalid resource paths**. |
|||
* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it). |
|||
* Prevents multiple modules adding the **duplicate files**. |
|||
* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary). |
|||
|
|||
#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package |
|||
|
|||
> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. |
|||
To install it to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages |
|||
```` |
|||
|
|||
Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### Bundle Inheritance |
|||
|
|||
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle. |
|||
Example: |
|||
|
|||
````c# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.StyleBundles |
|||
.Add("MyTheme.MyGlobalBundle", bundle => { |
|||
bundle |
|||
.AddBaseBundles("MyGlobalBundle") //Can add multiple |
|||
.AddFiles( |
|||
"/styles/mytheme-global-styles.css" |
|||
); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
## Themes |
|||
|
|||
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. |
|||
|
|||
## Best Practices & Suggestions |
|||
|
|||
It's suggested to define multiple bundles for an application, each one is used for different purposes. |
|||
|
|||
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. |
|||
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. |
|||
* **Module bundles**: For shared resources among the pages of an individual module. |
|||
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. |
|||
|
|||
Establish a balance between performance, network bandwidth usage and count of many bundles. |
|||
|
|||
## See Also |
|||
|
|||
* [Client Side Package Management](Client-Side-Package-Management.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md) |
|||
@ -1,116 +1,4 @@ |
|||
|
|||
## ASP.NET Core MVC Client Side Package Management |
|||
This document has moved. |
|||
|
|||
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. |
|||
|
|||
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. |
|||
|
|||
Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. |
|||
|
|||
### @ABP NPM Packages |
|||
|
|||
ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. |
|||
|
|||
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. |
|||
|
|||
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs). |
|||
|
|||
The benefit of a **standard package** is: |
|||
|
|||
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. |
|||
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more. |
|||
|
|||
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: |
|||
|
|||
```` |
|||
{ |
|||
... |
|||
"dependencies": { |
|||
"@abp/bootstrap": "^1.0.0" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It's suggested to depend on a standard package instead of directly depending on a third-party package. |
|||
|
|||
#### Package Installation |
|||
|
|||
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: |
|||
|
|||
```` |
|||
yarn |
|||
```` |
|||
|
|||
Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before. |
|||
|
|||
#### Package Contribution |
|||
|
|||
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: |
|||
|
|||
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). |
|||
* It should be the **latest stable** version of the package. |
|||
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. |
|||
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. |
|||
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. |
|||
|
|||
See current standard packages for examples. |
|||
|
|||
### Mapping The Library Resources |
|||
|
|||
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. |
|||
|
|||
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. |
|||
|
|||
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. |
|||
|
|||
The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. |
|||
|
|||
#### Resource Mapping Definition File |
|||
|
|||
A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: |
|||
|
|||
````js |
|||
module.exports = { |
|||
aliases: { |
|||
"@node_modules": "./node_modules", |
|||
"@libs": "./wwwroot/libs" |
|||
}, |
|||
clean: [ |
|||
"@libs" |
|||
], |
|||
mappings: { |
|||
|
|||
} |
|||
} |
|||
```` |
|||
|
|||
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. |
|||
* **clean** section is a list of folders to clean before copying the files. |
|||
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. |
|||
|
|||
An example mapping configuration is shown below: |
|||
|
|||
````js |
|||
mappings: { |
|||
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", |
|||
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/", |
|||
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/" |
|||
} |
|||
```` |
|||
|
|||
#### Using The Gulp |
|||
|
|||
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line: |
|||
|
|||
```` |
|||
gulp |
|||
```` |
|||
|
|||
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file. |
|||
|
|||
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively). |
|||
|
|||
#### See Also |
|||
|
|||
* [Bundling & Minification](Bundling-Minification.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md) |
|||
|
|||
@ -1,165 +1,3 @@ |
|||
# Dynamic C# API Clients |
|||
This document has moved. |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "RemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md) |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
This document has moved. |
|||
|
|||
TODO |
|||
[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md) |
|||
@ -1,24 +1,3 @@ |
|||
# 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. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* 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 |
|||
This document has moved. |
|||
|
|||
[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md) |
|||
@ -1,3 +1,3 @@ |
|||
## Dynamic Forms |
|||
This document has moved. |
|||
|
|||
This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now. |
|||
[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) |
|||
@ -1,3 +1,3 @@ |
|||
## ABP Tag Helpers |
|||
This document has moved. |
|||
|
|||
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
|||
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md) |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
# Theming |
|||
|
|||
TODO |
|||
This document has moved. |
|||
|
|||
[Click to navigate to Theming document](../UI/AspNetCore/Theming.md) |
|||
@ -1,505 +1,4 @@ |
|||
# Widgets |
|||
|
|||
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to; |
|||
This document has moved. |
|||
|
|||
* Have **scripts & styles** dependencies for your widget. |
|||
* Create **dashboards** with widgets used inside. |
|||
* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. |
|||
* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. |
|||
|
|||
## Basic Widget Definition |
|||
|
|||
### Create a View Component |
|||
|
|||
As the first step, create a new regular ASP.NET Core View Component: |
|||
|
|||
 |
|||
|
|||
**MySimpleWidgetViewComponent.cs**: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties. |
|||
|
|||
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages. |
|||
|
|||
**Default.cshtml**: |
|||
|
|||
```xml |
|||
<div class="my-simple-widget"> |
|||
<h2>My Simple Widget</h2> |
|||
<p>This is a simple widget!</p> |
|||
</div> |
|||
``` |
|||
|
|||
### Define the Widget |
|||
|
|||
Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Rendering a Widget |
|||
|
|||
Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("MySimpleWidget") |
|||
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) |
|||
```` |
|||
|
|||
First approach uses the widget name while second approach uses the view component type. |
|||
|
|||
### Widgets with Arguments |
|||
|
|||
ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service. |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget |
|||
{ |
|||
[Widget] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IDashboardAppService _dashboardAppService; |
|||
|
|||
public CountersWidgetViewComponent(IDashboardAppService dashboardAppService) |
|||
{ |
|||
_dashboardAppService = dashboardAppService; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync( |
|||
DateTime startDate, DateTime endDate) |
|||
{ |
|||
var result = await _dashboardAppService.GetCountersWidgetAsync( |
|||
new CountersWidgetInputDto |
|||
{ |
|||
StartDate = startDate, |
|||
EndDate = endDate |
|||
} |
|||
); |
|||
|
|||
return View(result); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Now, you need to pass an anonymous object to pass arguments as shown below: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("CountersWidget", new |
|||
{ |
|||
startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)), |
|||
endDate = DateTime.Now |
|||
}) |
|||
```` |
|||
|
|||
## Widget Name |
|||
|
|||
Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name. |
|||
|
|||
To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
[ViewComponent(Name = "MyCustomNamedWidget")] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/Pages/Components/MySimpleWidget/Default.cshtml"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP will respect to the custom name by handling the widget. |
|||
|
|||
> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example. |
|||
|
|||
### Display Name |
|||
|
|||
You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute: |
|||
|
|||
````csharp |
|||
using DashboardDemo.Localization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
DisplayName = "MySimpleWidgetDisplayName", //Localization key |
|||
DisplayNameResource = typeof(DashboardDemoResource) //localization resource |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See [the localization document](../Localization.md) to learn about localization resources and keys. |
|||
|
|||
## Style & Script Dependencies |
|||
|
|||
There are some challenges when your widget has script and style files; |
|||
|
|||
* Any page uses the widget should also include the **its script & styles** files into the page. |
|||
* The page should also care about **depended libraries/files** of the widget. |
|||
|
|||
ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it. |
|||
|
|||
### Defining as Simple File Paths |
|||
|
|||
The example widget below adds a style and a script file: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" }, |
|||
ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md). |
|||
|
|||
### Defining Bundle Contributors |
|||
|
|||
All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors. |
|||
|
|||
The sample code below does the same with the code above, but defines and uses bundle contributors: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) }, |
|||
ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css"); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies. |
|||
|
|||
See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system. |
|||
|
|||
## RefreshUrl |
|||
|
|||
A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below). |
|||
|
|||
````csharp |
|||
[Widget(RefreshUrl = "Widgets/Counters")] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it: |
|||
|
|||
````csharp |
|||
[Route("Widgets")] |
|||
public class CountersWidgetController : AbpController |
|||
{ |
|||
[HttpGet] |
|||
[Route("Counters")] |
|||
public IActionResult Counters(DateTime startDate, DateTime endDate) |
|||
{ |
|||
return ViewComponent("CountersWidget", new {startDate, endDate}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`Widgets/Counters` route matches to the `RefreshUrl` declared before. |
|||
|
|||
> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section). |
|||
|
|||
## JavaScript API |
|||
|
|||
A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets. |
|||
|
|||
### WidgetManager |
|||
|
|||
`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below: |
|||
|
|||
````js |
|||
$(function() { |
|||
var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea'); |
|||
}) |
|||
```` |
|||
|
|||
`MyDashboardWidgetsArea` may contain one or more widgets inside. |
|||
|
|||
> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready. |
|||
|
|||
#### WidgetManager.init() |
|||
|
|||
`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below) |
|||
|
|||
```js |
|||
myWidgetManager.init(); |
|||
``` |
|||
|
|||
#### WidgetManager.refresh() |
|||
|
|||
`refresh` method refreshes all widgets related to this `WidgetManager`: |
|||
|
|||
```` |
|||
myWidgetManager.refresh(); |
|||
```` |
|||
|
|||
#### WidgetManager Options |
|||
|
|||
WidgetManager has some additional options. |
|||
|
|||
##### Filter Form |
|||
|
|||
If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example: |
|||
|
|||
````xml |
|||
<form method="get" id="MyDashboardFilterForm"> |
|||
...form elements |
|||
</form> |
|||
|
|||
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm"> |
|||
...widgets |
|||
</div> |
|||
```` |
|||
|
|||
`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter. |
|||
|
|||
Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterForm: '#MyDashboardFilterForm' |
|||
}); |
|||
```` |
|||
|
|||
##### Filter Callback |
|||
|
|||
You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterCallback: function() { |
|||
return $('#MyDashboardFilterForm').serializeFormToObject(); |
|||
} |
|||
}); |
|||
```` |
|||
|
|||
This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example: |
|||
|
|||
````js |
|||
filterCallback: function() { |
|||
return { |
|||
'startDate': $('#StartDateInput').val(), |
|||
'endDate': $('#EndDateInput').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
The returning filters are passed to all widgets on `init` and `refresh`. |
|||
|
|||
### Widget JavaScript API |
|||
|
|||
A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget. |
|||
|
|||
````js |
|||
(function () { |
|||
abp.widgets.NewUserStatisticWidget = function ($wrapper) { |
|||
|
|||
var getFilters = function () { |
|||
return { |
|||
... |
|||
}; |
|||
} |
|||
|
|||
var refresh = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
var init = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
return { |
|||
getFilters: getFilters, |
|||
init: init, |
|||
refresh: refresh |
|||
}; |
|||
}; |
|||
})(); |
|||
```` |
|||
|
|||
`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional. |
|||
|
|||
#### getFilters |
|||
|
|||
If the widget has internal custom filters, this function should return the filter object. Example: |
|||
|
|||
````js |
|||
var getFilters = function() { |
|||
return { |
|||
frequency: $wrapper.find('.frequency-filter option:selected').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
This method is used by the `WidgetManager` while building filters. |
|||
|
|||
#### init |
|||
|
|||
Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option. |
|||
|
|||
#### refresh |
|||
|
|||
Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called. |
|||
|
|||
## Authorization |
|||
|
|||
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute: |
|||
|
|||
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application). |
|||
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget(RequiredPolicies = new[] { "MyPolicyName" })] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## WidgetOptions |
|||
|
|||
As alternative to the `Widget` attribute, you can use the `AbpWidgetOptions` to configure widgets: |
|||
|
|||
```csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets.Add<MySimpleWidgetViewComponent>(); |
|||
}); |
|||
``` |
|||
|
|||
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget: |
|||
|
|||
````csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets |
|||
.Add<MySimpleWidgetViewComponent>() |
|||
.WithStyles("/Pages/Components/MySimpleWidget/Default.css"); |
|||
}); |
|||
```` |
|||
|
|||
> Tip: `AbpWidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`. |
|||
|
|||
## See Also |
|||
|
|||
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). |
|||
[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md) |
|||
|
|||
@ -0,0 +1,43 @@ |
|||
## Creating a Settings Tab |
|||
|
|||
There are several settings tabs from different modules. You can add custom settings tabs to your project in 3 steps. |
|||
|
|||
1. Create a Component |
|||
|
|||
```js |
|||
import { Select } from '@ngxs/store'; |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-your-custom-settings', |
|||
template: ` |
|||
your-custom-settings works! mySetting: {%{{{ mySetting$ | async }}}%} |
|||
`, |
|||
}) |
|||
export class YourCustomSettingsComponent { |
|||
@Select(ConfigState.getSetting('MyProjectName.MySetting1')) // Gets a setting. MyProjectName.MySetting1 is a setting key. |
|||
mySetting$: Observable<string>; // The selected setting is set to the mySetting variable as Observable. |
|||
} |
|||
``` |
|||
|
|||
2. Add the `YourCustomSettingsComponent` to `declarations` and the `entryComponents` arrays in the `AppModule`. |
|||
|
|||
3. Open the `app.component.ts` and add the below content to the `ngOnInit` |
|||
|
|||
```js |
|||
import { addSettingTab } from '@abp/ng.theme.shared'; |
|||
// ... |
|||
|
|||
ngOnInit() { |
|||
addSettingTab({ |
|||
component: YourCustomSettingsComponent, |
|||
name: 'Type here the setting tab title (you can type a localization key, e.g: AbpAccount::Login', |
|||
order: 4, |
|||
requiredPolicy: 'type here a policy key' |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
Open the `setting-management` page to see the changes: |
|||
|
|||
 |
|||
@ -0,0 +1,48 @@ |
|||
# Component Replacement |
|||
|
|||
You can replace some ABP components with your custom components. |
|||
|
|||
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_. |
|||
|
|||
## How to Replace a Component |
|||
|
|||
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. |
|||
|
|||
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below: |
|||
|
|||
```js |
|||
import { ..., AddReplaceableComponent } from '@abp/ng.core'; |
|||
export class AppComponent { |
|||
constructor(..., private store: Store) {} |
|||
|
|||
ngOnInit() { |
|||
this.store.dispatch( |
|||
new AddReplaceableComponent({ |
|||
component: YourNewRoleComponent, |
|||
key: 'Identity.RolesComponent', |
|||
}), |
|||
); |
|||
//... |
|||
} |
|||
} |
|||
``` |
|||
|
|||
 |
|||
|
|||
## Available Replaceable Components |
|||
|
|||
| Component key | Description | |
|||
|----------------------------------------------------|-------------------------| |
|||
| Account.LoginComponent | Login page | |
|||
| Account.RegisterComponent | Register page | |
|||
| Account.ManageProfileComponent | Manage Profile page | |
|||
| Account.AuthWrapperComponent | This component wraps register and login pages | |
|||
| Account.ChangePasswordComponent | Change password form | |
|||
| Account.PersonalSettingsComponent | Personal settings form | |
|||
| Account.TenantBoxComponentInputs | Tenant changing box | |
|||
| FeatureManagement.FeatureManagementComponent | Features modal | |
|||
| Identity.UsersComponent | Users page | |
|||
| Identity.RolesComponent | Roles page | |
|||
| PermissionManagement.PermissionManagementComponent | Permissions modal | |
|||
| SettingManagement.SettingManagementComponent | Setting Management page | |
|||
| TenantManagement.TenantsComponent | Tenants page | |
|||
@ -0,0 +1,136 @@ |
|||
# Localization |
|||
|
|||
Before you read about _the Localization Pipe_ and _the Localization Service_, you should know about localization keys. |
|||
|
|||
The Localization key format consists of 2 sections which are **Resource Name** and **Key**. |
|||
`ResourceName::Key` |
|||
|
|||
> If you do not specify the resource name, it will be `defaultResourceName` which is declared in `environment.ts` |
|||
|
|||
```js |
|||
const environment = { |
|||
//... |
|||
localization: { |
|||
defaultResourceName: 'MyProjectName', |
|||
}, |
|||
}; |
|||
``` |
|||
|
|||
So these two are the same: |
|||
|
|||
```html |
|||
<h1>{%{{{ '::Key' | abpLocalization }}}%}</h1> |
|||
|
|||
<h1>{%{{{ 'MyProjectName::Key' | abpLocalization }}}%}</h1> |
|||
``` |
|||
|
|||
## Using the Localization Pipe |
|||
|
|||
You can use the `abpLocalization` pipe to get localized text as in this example: |
|||
|
|||
```html |
|||
<h1>{%{{{ 'Resource::Key' | abpLocalization }}}%}</h1> |
|||
``` |
|||
|
|||
The pipe will replace the key with the localized text. |
|||
|
|||
You can also specify a default value as shown below: |
|||
|
|||
```html |
|||
<h1>{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } | abpLocalization }}}%}</h1> |
|||
``` |
|||
|
|||
To use interpolation, you must give the values for interpolation as pipe parameters, for example: |
|||
|
|||
Localization data is stored in key-value pairs: |
|||
|
|||
```js |
|||
{ |
|||
//... |
|||
AbpAccount: { // AbpAccount is the resource name |
|||
Key: "Value", |
|||
PagerInfo: "Showing {0} to {1} of {2} entries" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
So we can use this key like this: |
|||
|
|||
```html |
|||
<h1>{%{{{ 'AbpAccount::PagerInfo' | abpLocalization:'20':'30':'50' }}}%}</h1> |
|||
|
|||
<!-- Output: Showing 20 to 30 of 50 entries --> |
|||
``` |
|||
|
|||
### Using the Localization Service |
|||
|
|||
First of all you should import the `LocalizationService` from **@abp/ng.core** |
|||
|
|||
```js |
|||
import { LocalizationService } from '@abp/ng.core'; |
|||
|
|||
class MyClass { |
|||
constructor(private localizationService: LocalizationService) {} |
|||
} |
|||
``` |
|||
|
|||
After that, you are able to use localization service. |
|||
|
|||
> You can add interpolation parameters as arguments to `instant()` and `get()` methods. |
|||
|
|||
```js |
|||
this.localizationService.instant('AbpIdentity::UserDeletionConfirmation', 'John'); |
|||
|
|||
// with fallback value |
|||
this.localizationService.instant( |
|||
{ key: 'AbpIdentity::UserDeletionConfirmation', defaultValue: 'Default Value' }, |
|||
'John', |
|||
); |
|||
|
|||
// Output |
|||
// User 'John' will be deleted. Do you confirm that? |
|||
``` |
|||
|
|||
To get a localized text as [_Observable_](https://rxjs.dev/guide/observable) use `get` method instead of `instant`: |
|||
|
|||
```js |
|||
this.localizationService.get('Resource::Key'); |
|||
|
|||
// with fallback value |
|||
this.localizationService.get({ key: 'Resource::Key', defaultValue: 'Default Value' }); |
|||
``` |
|||
|
|||
### Using the Config State |
|||
|
|||
In order to you `getLocalization` method you should import ConfigState. |
|||
|
|||
```js |
|||
import { ConfigState } from '@abp/ng.core'; |
|||
``` |
|||
|
|||
Then you can use it as followed: |
|||
|
|||
```js |
|||
this.store.selectSnapshot(ConfigState.getLocalization('ResourceName::Key')); |
|||
``` |
|||
|
|||
`getLocalization` method can be used with both `localization key` and [`LocalizationWithDefault`](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L34) interface. |
|||
|
|||
```js |
|||
this.store.selectSnapshot( |
|||
ConfigState.getLocalization( |
|||
{ |
|||
key: 'AbpIdentity::UserDeletionConfirmation', |
|||
defaultValue: 'Default Value', |
|||
}, |
|||
'John', |
|||
), |
|||
); |
|||
``` |
|||
|
|||
Localization resources are stored in the `localization` property of `ConfigState`. |
|||
|
|||
|
|||
## See Also |
|||
|
|||
* [Localization in ASP.NET Core](../../Localization.md) |
|||
@ -0,0 +1,79 @@ |
|||
# Permission Management |
|||
|
|||
A permission is a simple policy that is granted or prohibited for a particular user, role or client. You can read more about [authorization in ABP](../../Authorization.md) document. |
|||
|
|||
You can get permission of authenticated user using `getGrantedPolicy` selector of `ConfigState`. |
|||
|
|||
You can get permission as boolean value from store: |
|||
|
|||
```js |
|||
import { Store } from '@ngxs/store'; |
|||
import { ConfigState } from '../states'; |
|||
|
|||
export class YourComponent { |
|||
constructor(private store: Store) {} |
|||
|
|||
ngOnInit(): void { |
|||
const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create')); |
|||
} |
|||
|
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
Or you can get it via `ConfigStateService`: |
|||
|
|||
```js |
|||
import { ConfigStateService } from '../services/config-state.service'; |
|||
|
|||
export class YourComponent { |
|||
constructor(private configStateService: ConfigStateService) {} |
|||
|
|||
ngOnInit(): void { |
|||
const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create'); |
|||
} |
|||
|
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
## Permission Directive |
|||
|
|||
You can use the `PermissionDirective` to manage visibility of a DOM Element accordingly to user's permission. |
|||
|
|||
```html |
|||
<div *abpPermission="AbpIdentity.Roles"> |
|||
This content is only visible if the user has 'AbpIdentity.Roles' permission. |
|||
</div> |
|||
``` |
|||
|
|||
As shown above you can remove elements from DOM with `abpPermission` structural directive. |
|||
|
|||
The directive can also be used as an attribute directive but we recommend to you to use it as a structural directive. |
|||
|
|||
## Permission Guard |
|||
|
|||
You can use `PermissionGuard` if you want to control authenticated user's permission to access to the route during navigation. |
|||
|
|||
Add `requiredPolicy` to the `routes` property in your routing module. |
|||
|
|||
```js |
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'path', |
|||
component: YourComponent, |
|||
canActivate: [PermissionGuard], |
|||
data: { |
|||
routes: { |
|||
requiredPolicy: 'AbpIdentity.Roles.Create', |
|||
}, |
|||
}, |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
Granted Policies are stored in the `auth` property of `ConfigState`. |
|||
|
|||
## What's Next? |
|||
|
|||
* [Component Replacement](./Component-Replacement.md) |
|||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 30 KiB |
@ -0,0 +1,352 @@ |
|||
|
|||
# ASP.NET Core MVC Bundling & Minification |
|||
|
|||
There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are: |
|||
|
|||
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/). |
|||
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins. |
|||
|
|||
ABP offers a simple, dynamic, powerful, modular and built-in way. |
|||
|
|||
## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package |
|||
|
|||
> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Razor Bundling Tag Helpers |
|||
|
|||
The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example: |
|||
|
|||
````html |
|||
<abp-style-bundle name="MyGlobalBundle"> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*: |
|||
|
|||
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. |
|||
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). |
|||
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). |
|||
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). |
|||
|
|||
### Importing The Bundling Tag Helpers |
|||
|
|||
> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually. |
|||
|
|||
In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page: |
|||
|
|||
```` |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
### Unnamed Bundles |
|||
|
|||
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example: |
|||
|
|||
````html |
|||
<abp-style-bundle> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
@if (ViewBag.IncludeCustomStyles != false) |
|||
{ |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
} |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not). |
|||
|
|||
Advantages of **unnamed** bundles: |
|||
|
|||
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions. |
|||
|
|||
Advantages of **named** bundles: |
|||
|
|||
* Other **modules can contribute** to the bundle by its name (see the sections below). |
|||
|
|||
### Single File |
|||
|
|||
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example: |
|||
|
|||
````xml |
|||
<abp-script src="/scripts/my-script.js" /> |
|||
```` |
|||
|
|||
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too. |
|||
|
|||
## Bundling Options |
|||
|
|||
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. |
|||
|
|||
### Creating A New Bundle |
|||
|
|||
Example usage: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Add("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/libs/jquery/jquery.js", |
|||
"/libs/bootstrap/js/bootstrap.js", |
|||
"/libs/toastr/toastr.min.js", |
|||
"/scripts/my-global-scripts.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`). |
|||
|
|||
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example: |
|||
|
|||
````html |
|||
<abp-script-bundle name="MyGlobalBundle" /> |
|||
```` |
|||
|
|||
This time, no file defined in the tag helper definition because the bundle files are defined by the code. |
|||
|
|||
### Configuring An Existing Bundle |
|||
|
|||
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyWebModule))] |
|||
public class MyWebExtensionModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/scripts/my-extension-script.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper. |
|||
|
|||
## Bundle Contributors |
|||
|
|||
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases. |
|||
|
|||
An example bundle contributor that replaces bootstrap.css with a customized version: |
|||
|
|||
````C# |
|||
public class MyExtensionGlobalStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.ReplaceOne( |
|||
"/libs/bootstrap/css/bootstrap.css", |
|||
"/styles/extensions/bootstrap-customized.css" |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can use this contributor as like below: |
|||
|
|||
````C# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
> You can also add contributors while creating a new bundle. |
|||
|
|||
Contributors can also be used in the bundle tag helpers. Example: |
|||
|
|||
````xml |
|||
<abp-style-bundle> |
|||
<abp-style type="@typeof(BootstrapStyleContributor)" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle. |
|||
|
|||
### Contributor Dependencies |
|||
|
|||
A bundle contributor can have one or more dependencies to other contributors. |
|||
Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications. |
|||
|
|||
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules. |
|||
|
|||
### Contributor Extensions |
|||
|
|||
In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used. |
|||
|
|||
The example below adds some styles for prism.js library: |
|||
|
|||
````csharp |
|||
public class MyPrismjsStyleExtension : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`. |
|||
|
|||
````csharp |
|||
Configure<BundleContributorOptions>(options => |
|||
{ |
|||
options |
|||
.Extensions<PrismjsStyleBundleContributor>() |
|||
.Add<MyPrismjsStyleExtension>(); |
|||
}); |
|||
```` |
|||
|
|||
Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added. |
|||
|
|||
### Accessing to the IServiceProvider |
|||
|
|||
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method. |
|||
|
|||
### Standard Package Contributors |
|||
|
|||
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package. |
|||
|
|||
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself. |
|||
|
|||
````C# |
|||
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Using the built-in contributors for standard packages; |
|||
|
|||
* Prevents you typing **the invalid resource paths**. |
|||
* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it). |
|||
* Prevents multiple modules adding the **duplicate files**. |
|||
* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary). |
|||
|
|||
#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package |
|||
|
|||
> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. |
|||
To install it to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages |
|||
```` |
|||
|
|||
Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### Bundle Inheritance |
|||
|
|||
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle. |
|||
Example: |
|||
|
|||
````c# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.StyleBundles |
|||
.Add("MyTheme.MyGlobalBundle", bundle => { |
|||
bundle |
|||
.AddBaseBundles("MyGlobalBundle") //Can add multiple |
|||
.AddFiles( |
|||
"/styles/mytheme-global-styles.css" |
|||
); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
## Themes |
|||
|
|||
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. |
|||
|
|||
## Best Practices & Suggestions |
|||
|
|||
It's suggested to define multiple bundles for an application, each one is used for different purposes. |
|||
|
|||
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. |
|||
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. |
|||
* **Module bundles**: For shared resources among the pages of an individual module. |
|||
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. |
|||
|
|||
Establish a balance between performance, network bandwidth usage and count of many bundles. |
|||
|
|||
## See Also |
|||
|
|||
* [Client Side Package Management](Client-Side-Package-Management.md) |
|||
* [Theming](Theming.md) |
|||
@ -0,0 +1,116 @@ |
|||
|
|||
## ASP.NET Core MVC Client Side Package Management |
|||
|
|||
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. |
|||
|
|||
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. |
|||
|
|||
Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. |
|||
|
|||
### @ABP NPM Packages |
|||
|
|||
ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. |
|||
|
|||
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. |
|||
|
|||
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs). |
|||
|
|||
The benefit of a **standard package** is: |
|||
|
|||
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. |
|||
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more. |
|||
|
|||
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: |
|||
|
|||
```` |
|||
{ |
|||
... |
|||
"dependencies": { |
|||
"@abp/bootstrap": "^1.0.0" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It's suggested to depend on a standard package instead of directly depending on a third-party package. |
|||
|
|||
#### Package Installation |
|||
|
|||
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: |
|||
|
|||
```` |
|||
yarn |
|||
```` |
|||
|
|||
Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before. |
|||
|
|||
#### Package Contribution |
|||
|
|||
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: |
|||
|
|||
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). |
|||
* It should be the **latest stable** version of the package. |
|||
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. |
|||
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. |
|||
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. |
|||
|
|||
See current standard packages for examples. |
|||
|
|||
### Mapping The Library Resources |
|||
|
|||
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. |
|||
|
|||
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. |
|||
|
|||
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. |
|||
|
|||
The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. |
|||
|
|||
#### Resource Mapping Definition File |
|||
|
|||
A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: |
|||
|
|||
````js |
|||
module.exports = { |
|||
aliases: { |
|||
"@node_modules": "./node_modules", |
|||
"@libs": "./wwwroot/libs" |
|||
}, |
|||
clean: [ |
|||
"@libs" |
|||
], |
|||
mappings: { |
|||
|
|||
} |
|||
} |
|||
```` |
|||
|
|||
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. |
|||
* **clean** section is a list of folders to clean before copying the files. |
|||
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. |
|||
|
|||
An example mapping configuration is shown below: |
|||
|
|||
````js |
|||
mappings: { |
|||
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", |
|||
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/", |
|||
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/" |
|||
} |
|||
```` |
|||
|
|||
#### Using The Gulp |
|||
|
|||
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line: |
|||
|
|||
```` |
|||
gulp |
|||
```` |
|||
|
|||
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file. |
|||
|
|||
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively). |
|||
|
|||
#### See Also |
|||
|
|||
* [Bundling & Minification](Bundling-Minification.md) |
|||
* [Theming](Theming.md) |
|||
@ -0,0 +1,3 @@ |
|||
## Dynamic Forms |
|||
|
|||
This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now. |
|||
@ -0,0 +1,3 @@ |
|||
## ABP Tag Helpers |
|||
|
|||
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,3 @@ |
|||
# Theming |
|||
|
|||
TODO |
|||
@ -0,0 +1,505 @@ |
|||
# Widgets |
|||
|
|||
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to; |
|||
|
|||
* Have **scripts & styles** dependencies for your widget. |
|||
* Create **dashboards** with widgets used inside. |
|||
* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. |
|||
* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. |
|||
|
|||
## Basic Widget Definition |
|||
|
|||
### Create a View Component |
|||
|
|||
As the first step, create a new regular ASP.NET Core View Component: |
|||
|
|||
 |
|||
|
|||
**MySimpleWidgetViewComponent.cs**: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties. |
|||
|
|||
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages. |
|||
|
|||
**Default.cshtml**: |
|||
|
|||
```xml |
|||
<div class="my-simple-widget"> |
|||
<h2>My Simple Widget</h2> |
|||
<p>This is a simple widget!</p> |
|||
</div> |
|||
``` |
|||
|
|||
### Define the Widget |
|||
|
|||
Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Rendering a Widget |
|||
|
|||
Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("MySimpleWidget") |
|||
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) |
|||
```` |
|||
|
|||
First approach uses the widget name while second approach uses the view component type. |
|||
|
|||
### Widgets with Arguments |
|||
|
|||
ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service. |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget |
|||
{ |
|||
[Widget] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IDashboardAppService _dashboardAppService; |
|||
|
|||
public CountersWidgetViewComponent(IDashboardAppService dashboardAppService) |
|||
{ |
|||
_dashboardAppService = dashboardAppService; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync( |
|||
DateTime startDate, DateTime endDate) |
|||
{ |
|||
var result = await _dashboardAppService.GetCountersWidgetAsync( |
|||
new CountersWidgetInputDto |
|||
{ |
|||
StartDate = startDate, |
|||
EndDate = endDate |
|||
} |
|||
); |
|||
|
|||
return View(result); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Now, you need to pass an anonymous object to pass arguments as shown below: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("CountersWidget", new |
|||
{ |
|||
startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)), |
|||
endDate = DateTime.Now |
|||
}) |
|||
```` |
|||
|
|||
## Widget Name |
|||
|
|||
Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name. |
|||
|
|||
To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
[ViewComponent(Name = "MyCustomNamedWidget")] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/Pages/Components/MySimpleWidget/Default.cshtml"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP will respect to the custom name by handling the widget. |
|||
|
|||
> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example. |
|||
|
|||
### Display Name |
|||
|
|||
You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute: |
|||
|
|||
````csharp |
|||
using DashboardDemo.Localization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
DisplayName = "MySimpleWidgetDisplayName", //Localization key |
|||
DisplayNameResource = typeof(DashboardDemoResource) //localization resource |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See [the localization document](../Localization.md) to learn about localization resources and keys. |
|||
|
|||
## Style & Script Dependencies |
|||
|
|||
There are some challenges when your widget has script and style files; |
|||
|
|||
* Any page uses the widget should also include the **its script & styles** files into the page. |
|||
* The page should also care about **depended libraries/files** of the widget. |
|||
|
|||
ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it. |
|||
|
|||
### Defining as Simple File Paths |
|||
|
|||
The example widget below adds a style and a script file: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" }, |
|||
ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md). |
|||
|
|||
### Defining Bundle Contributors |
|||
|
|||
All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors. |
|||
|
|||
The sample code below does the same with the code above, but defines and uses bundle contributors: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) }, |
|||
ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css"); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies. |
|||
|
|||
See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system. |
|||
|
|||
## RefreshUrl |
|||
|
|||
A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below). |
|||
|
|||
````csharp |
|||
[Widget(RefreshUrl = "Widgets/Counters")] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it: |
|||
|
|||
````csharp |
|||
[Route("Widgets")] |
|||
public class CountersWidgetController : AbpController |
|||
{ |
|||
[HttpGet] |
|||
[Route("Counters")] |
|||
public IActionResult Counters(DateTime startDate, DateTime endDate) |
|||
{ |
|||
return ViewComponent("CountersWidget", new {startDate, endDate}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`Widgets/Counters` route matches to the `RefreshUrl` declared before. |
|||
|
|||
> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section). |
|||
|
|||
## JavaScript API |
|||
|
|||
A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets. |
|||
|
|||
### WidgetManager |
|||
|
|||
`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below: |
|||
|
|||
````js |
|||
$(function() { |
|||
var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea'); |
|||
}) |
|||
```` |
|||
|
|||
`MyDashboardWidgetsArea` may contain one or more widgets inside. |
|||
|
|||
> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready. |
|||
|
|||
#### WidgetManager.init() |
|||
|
|||
`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below) |
|||
|
|||
```js |
|||
myWidgetManager.init(); |
|||
``` |
|||
|
|||
#### WidgetManager.refresh() |
|||
|
|||
`refresh` method refreshes all widgets related to this `WidgetManager`: |
|||
|
|||
```` |
|||
myWidgetManager.refresh(); |
|||
```` |
|||
|
|||
#### WidgetManager Options |
|||
|
|||
WidgetManager has some additional options. |
|||
|
|||
##### Filter Form |
|||
|
|||
If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example: |
|||
|
|||
````xml |
|||
<form method="get" id="MyDashboardFilterForm"> |
|||
...form elements |
|||
</form> |
|||
|
|||
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm"> |
|||
...widgets |
|||
</div> |
|||
```` |
|||
|
|||
`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter. |
|||
|
|||
Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterForm: '#MyDashboardFilterForm' |
|||
}); |
|||
```` |
|||
|
|||
##### Filter Callback |
|||
|
|||
You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterCallback: function() { |
|||
return $('#MyDashboardFilterForm').serializeFormToObject(); |
|||
} |
|||
}); |
|||
```` |
|||
|
|||
This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example: |
|||
|
|||
````js |
|||
filterCallback: function() { |
|||
return { |
|||
'startDate': $('#StartDateInput').val(), |
|||
'endDate': $('#EndDateInput').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
The returning filters are passed to all widgets on `init` and `refresh`. |
|||
|
|||
### Widget JavaScript API |
|||
|
|||
A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget. |
|||
|
|||
````js |
|||
(function () { |
|||
abp.widgets.NewUserStatisticWidget = function ($wrapper) { |
|||
|
|||
var getFilters = function () { |
|||
return { |
|||
... |
|||
}; |
|||
} |
|||
|
|||
var refresh = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
var init = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
return { |
|||
getFilters: getFilters, |
|||
init: init, |
|||
refresh: refresh |
|||
}; |
|||
}; |
|||
})(); |
|||
```` |
|||
|
|||
`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional. |
|||
|
|||
#### getFilters |
|||
|
|||
If the widget has internal custom filters, this function should return the filter object. Example: |
|||
|
|||
````js |
|||
var getFilters = function() { |
|||
return { |
|||
frequency: $wrapper.find('.frequency-filter option:selected').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
This method is used by the `WidgetManager` while building filters. |
|||
|
|||
#### init |
|||
|
|||
Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option. |
|||
|
|||
#### refresh |
|||
|
|||
Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called. |
|||
|
|||
## Authorization |
|||
|
|||
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute: |
|||
|
|||
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application). |
|||
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget(RequiredPolicies = new[] { "MyPolicyName" })] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## WidgetOptions |
|||
|
|||
As alternative to the `Widget` attribute, you can use the `AbpWidgetOptions` to configure widgets: |
|||
|
|||
```csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets.Add<MySimpleWidgetViewComponent>(); |
|||
}); |
|||
``` |
|||
|
|||
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget: |
|||
|
|||
````csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets |
|||
.Add<MySimpleWidgetViewComponent>() |
|||
.WithStyles("/Pages/Components/MySimpleWidget/Default.css"); |
|||
}); |
|||
```` |
|||
|
|||
> Tip: `AbpWidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`. |
|||
|
|||
## See Also |
|||
|
|||
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). |
|||
Loading…
Reference in new issue