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. |
[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md) |
||||
|
|
||||
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. |
|
||||
@ -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: |
[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md) |
||||
|
|
||||
* 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) |
|
||||
@ -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. |
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md) |
||||
|
|
||||
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) |
|
||||
|
|||||
@ -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. |
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md) |
||||
|
|
||||
## 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). |
|
||||
|
|||||
@ -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 |
This document has moved. |
||||
|
|
||||
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 |
|
||||
|
|
||||
|
[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. |
[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md) |
||||
* 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). |
|
||||
|
|||||
@ -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