@ -0,0 +1,140 @@ |
|||
# Auto API Controllers |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
@ -0,0 +1,165 @@ |
|||
# Dynamic C# API Clients |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "RemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
@ -0,0 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
|
|||
TODO |
|||
@ -0,0 +1,24 @@ |
|||
# JavaScript API |
|||
|
|||
ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
|
|||
@ -1,140 +1,3 @@ |
|||
# Auto API Controllers |
|||
This document has moved. |
|||
|
|||
Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. |
|||
|
|||
ABP can **automagically** configure your application services as API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible to fully customize it. |
|||
|
|||
## Configuration |
|||
|
|||
Basic configuration is simple. Just configure `AbpAspNetCoreMvcOptions` and use `ConventionalControllers.Create` method as shown below: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example code configures all the application services in the assembly containing the class `BookStoreApplicationModule`. The figure below shows the resulting API on the [Swagger UI](https://swagger.io/tools/swagger-ui/). |
|||
|
|||
 |
|||
|
|||
### Examples |
|||
|
|||
Some example method names and the corresponding routes calculated by convention: |
|||
|
|||
| Service Method Name | HTTP Method | Route | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| GetAsync(Guid id) | GET | /api/app/book/{id} | |
|||
| GetListAsync() | GET | /api/app/book | |
|||
| CreateAsync(CreateBookDto input) | POST | /api/app/book | |
|||
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} | |
|||
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} | |
|||
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors | |
|||
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor | |
|||
|
|||
### HTTP Method |
|||
|
|||
ABP uses a naming convention while determining the HTTP method for a service method (action): |
|||
|
|||
- **Get**: Used if the method name starts with 'GetList', 'GetAll' or 'Get'. |
|||
- **Put**: Used if the method name starts with 'Put' or 'Update'. |
|||
- **Delete**: Used if the method name starts with 'Delete' or 'Remove'. |
|||
- **Post**: Used if the method name starts with 'Create', 'Add', 'Insert' or 'Post'. |
|||
- **Patch**: Used if the method name starts with 'Patch'. |
|||
- Otherwise, **Post** is used **by default**. |
|||
|
|||
If you need to customize HTTP method for a particular method, then you can use one of the standard ASP.NET Core attributes ([HttpPost], [HttpGet], [HttpPut]... etc.). This requires to add [Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core) nuget package to your project that contains the service. |
|||
|
|||
### Route |
|||
|
|||
Route is calculated based on some conventions: |
|||
|
|||
* It always starts with '**/api**'. |
|||
* Continues with a **route path**. Default value is '**/app**' and can be configured as like below: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Then the route for getting a book will be '**/api/volosoft/book-store/book/{id}**'. This sample uses two-level root path, but you generally use a single level depth. |
|||
|
|||
* Continues with the **normalized controller/service name**. Normalization removes 'AppService', 'ApplicationService' and 'Service' postfixes and converts it to **camelCase**. If your application service class name is 'BookAppService' then it becomes only '/book'. |
|||
* If you want to customize naming, then set the `UrlControllerNameNormalizer` option. It's a func delegate which allows you to determine the name per controller/service. |
|||
* If the method has an '**id**' parameter then it adds '**/{id}**' ro the route. |
|||
* Then it adds the action name if necessary. Action name is obtained from the method name on the service and normalized by; |
|||
* Removing '**Async**' postfix. If the method name is 'GetPhonesAsync' then it becomes 'GetPhones'. |
|||
* Removing **HTTP method prefix**. 'GetList', 'GetAll', 'Get', 'Put', 'Update', 'Delete', 'Remove', 'Create', 'Add', 'Insert', 'Post' and 'Patch' prefixes are removed based on the selected HTTP method. So, 'GetPhones' becomes 'Phones' since 'Get' prefix is a duplicate for a GET request. |
|||
* Converting the result to **camelCase**. |
|||
* If the resulting action name is **empty** then it's not added to the route. If it's not empty, it's added to the route (like '/phones'). For 'GetAllAsync' method name it will be empty, for 'GetPhonesAsync' method name it will be 'phones'. |
|||
* Normalization can be customized by setting the `UrlActionNameNormalizer` option. It's an action delegate that is called for every method. |
|||
* If there is another parameter with 'Id' postfix, then it's also added to the route as the final route segment (like '/phoneId'). |
|||
|
|||
## Service Selection |
|||
|
|||
Creating conventional HTTP API controllers are not unique to application services actually. |
|||
|
|||
### IRemoteService Interface |
|||
|
|||
If a class implements the `IRemoteService` interface then it's automatically selected to be a conventional API controller. Since application services inherently implement it, they are considered as natural API controllers. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService` attribute can be used to mark a class as a remote service or disable for a particular class that inherently implements the `IRemoteService` interface. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate Option |
|||
|
|||
You can further filter classes to become an API controller by providing the `TypePredicate` option: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
Instead of returning `true` for every type, you can check it and return `false` if you don't want to expose this type as an API controller. |
|||
|
|||
## API Explorer |
|||
|
|||
API Exploring a service that makes possible to investigate API structure by the clients. Swagger uses it to create a documentation and test UI for an endpoint. |
|||
|
|||
API Explorer is automatically enabled for conventional HTTP API controllers by default. Use `RemoteService` attribute to control it per class or method level. Example: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. |
|||
[Click to navigate to Auto API Controllers document](../API/Auto-API-Controllers.md) |
|||
@ -1,352 +1,4 @@ |
|||
|
|||
# ASP.NET Core MVC Bundling & Minification |
|||
This document has moved. |
|||
|
|||
There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are: |
|||
|
|||
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/). |
|||
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins. |
|||
|
|||
ABP offers a simple, dynamic, powerful, modular and built-in way. |
|||
|
|||
## Volo.Abp.AspNetCore.Mvc.UI.Bundling Package |
|||
|
|||
> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Razor Bundling Tag Helpers |
|||
|
|||
The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example: |
|||
|
|||
````html |
|||
<abp-style-bundle name="MyGlobalBundle"> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*: |
|||
|
|||
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. |
|||
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). |
|||
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). |
|||
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). |
|||
|
|||
### Importing The Bundling Tag Helpers |
|||
|
|||
> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually. |
|||
|
|||
In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page: |
|||
|
|||
```` |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
### Unnamed Bundles |
|||
|
|||
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example: |
|||
|
|||
````html |
|||
<abp-style-bundle> |
|||
<abp-style src="/libs/bootstrap/css/bootstrap.css" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
@if (ViewBag.IncludeCustomStyles != false) |
|||
{ |
|||
<abp-style src="/styles/my-global-style.css" /> |
|||
} |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not). |
|||
|
|||
Advantages of **unnamed** bundles: |
|||
|
|||
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions. |
|||
|
|||
Advantages of **named** bundles: |
|||
|
|||
* Other **modules can contribute** to the bundle by its name (see the sections below). |
|||
|
|||
### Single File |
|||
|
|||
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example: |
|||
|
|||
````xml |
|||
<abp-script src="/scripts/my-script.js" /> |
|||
```` |
|||
|
|||
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too. |
|||
|
|||
## Bundling Options |
|||
|
|||
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. |
|||
|
|||
### Creating A New Bundle |
|||
|
|||
Example usage: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Add("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/libs/jquery/jquery.js", |
|||
"/libs/bootstrap/js/bootstrap.js", |
|||
"/libs/toastr/toastr.min.js", |
|||
"/scripts/my-global-scripts.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`). |
|||
|
|||
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example: |
|||
|
|||
````html |
|||
<abp-script-bundle name="MyGlobalBundle" /> |
|||
```` |
|||
|
|||
This time, no file defined in the tag helper definition because the bundle files are defined by the code. |
|||
|
|||
### Configuring An Existing Bundle |
|||
|
|||
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyWebModule))] |
|||
public class MyWebExtensionModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/scripts/my-extension-script.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper. |
|||
|
|||
## Bundle Contributors |
|||
|
|||
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases. |
|||
|
|||
An example bundle contributor that replaces bootstrap.css with a customized version: |
|||
|
|||
````C# |
|||
public class MyExtensionGlobalStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.ReplaceOne( |
|||
"/libs/bootstrap/css/bootstrap.css", |
|||
"/styles/extensions/bootstrap-customized.css" |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can use this contributor as like below: |
|||
|
|||
````C# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
> You can also add contributors while creating a new bundle. |
|||
|
|||
Contributors can also be used in the bundle tag helpers. Example: |
|||
|
|||
````xml |
|||
<abp-style-bundle> |
|||
<abp-style type="@typeof(BootstrapStyleContributor)" /> |
|||
<abp-style src="/libs/font-awesome/css/font-awesome.css" /> |
|||
<abp-style src="/libs/toastr/toastr.css" /> |
|||
</abp-style-bundle> |
|||
```` |
|||
|
|||
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle. |
|||
|
|||
### Contributor Dependencies |
|||
|
|||
A bundle contributor can have one or more dependencies to other contributors. |
|||
Example: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications. |
|||
|
|||
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules. |
|||
|
|||
### Contributor Extensions |
|||
|
|||
In some advanced scenarios, you may want to do some additional configuration whenever a bundle contributor is used. Contributor extensions works seamlessly when the extended contributor is used. |
|||
|
|||
The example below adds some styles for prism.js library: |
|||
|
|||
````csharp |
|||
public class MyPrismjsStyleExtension : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`. |
|||
|
|||
````csharp |
|||
Configure<BundleContributorOptions>(options => |
|||
{ |
|||
options |
|||
.Extensions<PrismjsStyleBundleContributor>() |
|||
.Add<MyPrismjsStyleExtension>(); |
|||
}); |
|||
```` |
|||
|
|||
Whenever `PrismjsStyleBundleContributor` is added into a bundle, `MyPrismjsStyleExtension` will also be automatically added. |
|||
|
|||
### Accessing to the IServiceProvider |
|||
|
|||
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method. |
|||
|
|||
### Standard Package Contributors |
|||
|
|||
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package. |
|||
|
|||
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself. |
|||
|
|||
````C# |
|||
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
Using the built-in contributors for standard packages; |
|||
|
|||
* Prevents you typing **the invalid resource paths**. |
|||
* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it). |
|||
* Prevents multiple modules adding the **duplicate files**. |
|||
* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary). |
|||
|
|||
#### Volo.Abp.AspNetCore.Mvc.UI.Packages Package |
|||
|
|||
> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. |
|||
|
|||
Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. |
|||
To install it to your project: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages |
|||
```` |
|||
|
|||
Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### Bundle Inheritance |
|||
|
|||
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle. |
|||
Example: |
|||
|
|||
````c# |
|||
services.Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options |
|||
.StyleBundles |
|||
.Add("MyTheme.MyGlobalBundle", bundle => { |
|||
bundle |
|||
.AddBaseBundles("MyGlobalBundle") //Can add multiple |
|||
.AddFiles( |
|||
"/styles/mytheme-global-styles.css" |
|||
); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
## Themes |
|||
|
|||
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. |
|||
|
|||
## Best Practices & Suggestions |
|||
|
|||
It's suggested to define multiple bundles for an application, each one is used for different purposes. |
|||
|
|||
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. |
|||
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. |
|||
* **Module bundles**: For shared resources among the pages of an individual module. |
|||
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. |
|||
|
|||
Establish a balance between performance, network bandwidth usage and count of many bundles. |
|||
|
|||
## See Also |
|||
|
|||
* [Client Side Package Management](Client-Side-Package-Management.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Bundling & Minification document](../UI/AspNetCore/Bundling-Minification.md) |
|||
@ -1,116 +1,4 @@ |
|||
|
|||
## ASP.NET Core MVC Client Side Package Management |
|||
This document has moved. |
|||
|
|||
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. |
|||
|
|||
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. |
|||
|
|||
Finally, we suggest the [**Yarn**](https://classic.yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. |
|||
|
|||
### @ABP NPM Packages |
|||
|
|||
ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. |
|||
|
|||
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. |
|||
|
|||
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs). |
|||
|
|||
The benefit of a **standard package** is: |
|||
|
|||
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. |
|||
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more. |
|||
|
|||
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: |
|||
|
|||
```` |
|||
{ |
|||
... |
|||
"dependencies": { |
|||
"@abp/bootstrap": "^1.0.0" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It's suggested to depend on a standard package instead of directly depending on a third-party package. |
|||
|
|||
#### Package Installation |
|||
|
|||
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: |
|||
|
|||
```` |
|||
yarn |
|||
```` |
|||
|
|||
Alternatively, you can use `npm install` but [Yarn](https://classic.yarnpkg.com/) is suggested as mentioned before. |
|||
|
|||
#### Package Contribution |
|||
|
|||
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: |
|||
|
|||
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). |
|||
* It should be the **latest stable** version of the package. |
|||
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. |
|||
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. |
|||
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. |
|||
|
|||
See current standard packages for examples. |
|||
|
|||
### Mapping The Library Resources |
|||
|
|||
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. |
|||
|
|||
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. |
|||
|
|||
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. |
|||
|
|||
The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. |
|||
|
|||
#### Resource Mapping Definition File |
|||
|
|||
A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: |
|||
|
|||
````js |
|||
module.exports = { |
|||
aliases: { |
|||
"@node_modules": "./node_modules", |
|||
"@libs": "./wwwroot/libs" |
|||
}, |
|||
clean: [ |
|||
"@libs" |
|||
], |
|||
mappings: { |
|||
|
|||
} |
|||
} |
|||
```` |
|||
|
|||
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. |
|||
* **clean** section is a list of folders to clean before copying the files. |
|||
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. |
|||
|
|||
An example mapping configuration is shown below: |
|||
|
|||
````js |
|||
mappings: { |
|||
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", |
|||
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/", |
|||
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/" |
|||
} |
|||
```` |
|||
|
|||
#### Using The Gulp |
|||
|
|||
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line: |
|||
|
|||
```` |
|||
gulp |
|||
```` |
|||
|
|||
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file. |
|||
|
|||
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively). |
|||
|
|||
#### See Also |
|||
|
|||
* [Bundling & Minification](Bundling-Minification.md) |
|||
* [Theming](Theming.md) |
|||
[Click to navigate to ASP.NET Core MVC Client Side Package Management document](../UI/AspNetCore/Client-Side-Package-Management.md) |
|||
|
|||
@ -1,165 +1,3 @@ |
|||
# Dynamic C# API Clients |
|||
This document has moved. |
|||
|
|||
ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. |
|||
|
|||
## Service Interface |
|||
|
|||
Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. |
|||
|
|||
Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. |
|||
|
|||
## Client Proxy Generation |
|||
|
|||
First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
Then add `AbpHttpClientModule` dependency to your module: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //add the dependency |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
Now, it's ready to create the client proxies. Example: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //used to create client proxies |
|||
typeof(BookStoreApplicationModule) //contains the application service interfaces |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//Create dynamic client proxies |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. |
|||
|
|||
### Endpoint Configuration |
|||
|
|||
`RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See the "RemoteServiceOptions" section below for more detailed configuration. |
|||
|
|||
## Usage |
|||
|
|||
It's straightforward to use. Just inject the service interface in the client application code: |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IBookAppService _bookService; |
|||
|
|||
public MyService(IBookAppService bookService) |
|||
{ |
|||
_bookService = bookService; |
|||
} |
|||
|
|||
public async Task DoIt() |
|||
{ |
|||
var books = await _bookService.GetListAsync(); |
|||
foreach (var book in books) |
|||
{ |
|||
Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This sample injects the `IBookAppService` service interface defined above. The dynamic client proxy implementation makes an HTTP call whenever a service method is called by the client. |
|||
|
|||
### IHttpClientProxy Interface |
|||
|
|||
While you can inject `IBookAppService` like above to use the client proxy, you could inject `IHttpClientProxy<IBookAppService>` for a more explicit usage. In this case you will use the `Service` property of the `IHttpClientProxy<T>` interface. |
|||
|
|||
## Configuration |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### Multiple Remote Service Endpoints |
|||
|
|||
The examples above have configured the "Default" remote service endpoint. You may have different endpoints for different services (as like in a microservice approach where each microservice has different endpoints). In this case, you can add other endpoints to your configuration file: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies` method can get an additional parameter for the remote service name. Example: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName` parameter matches the service endpoint configured via `AbpRemoteServiceOptions`. If the `BookStore` endpoint is not defined then it fallbacks to the `Default` endpoint. |
|||
|
|||
### As Default Services |
|||
|
|||
When you create a service proxy for `IBookAppService`, you can directly inject the `IBookAppService` to use the proxy client (as shown in the usage section). You can pass `asDefaultServices: false` to the `AddHttpClientProxies` method to disable this feature. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. |
|||
|
|||
> If you disable `asDefaultServices`, you can only use `IHttpClientProxy<T>` interface to use the client proxies (see the related section above). |
|||
[Click to navigate to Dynamic C# API Clients document](../API/Dynamic-CSharp-API-Clients.md) |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
This document has moved. |
|||
|
|||
TODO |
|||
[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md) |
|||
@ -1,24 +1,3 @@ |
|||
# JavaScript API |
|||
|
|||
ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages applications. They can be used to perform some common application requirements in the client side. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth](Auth.md) |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
This document has moved. |
|||
|
|||
[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md) |
|||
@ -1,94 +0,0 @@ |
|||
# Buttons |
|||
|
|||
ABP framework has a special Tag Helper to create bootstrap button easily. |
|||
|
|||
`<abp-button>` |
|||
|
|||
## Attributes |
|||
|
|||
`<abp-button>` has 7 different attribute. |
|||
|
|||
* [`button-type`](#button-type) |
|||
* [`size`](#size) |
|||
* [`busy-text`](#busy-text) |
|||
* [`text`](#text) |
|||
* [`icon`](#icon) |
|||
* [`disabled`](#disabled) |
|||
* [`icon-type`](#icon-type) |
|||
|
|||
### `button-type` |
|||
|
|||
`button-type` is a selectable parameter. It's default value is `Default`. |
|||
|
|||
`<abp-button button-type="Primary">Button</abp-button>` |
|||
|
|||
You can choose one of the button type listed below. |
|||
|
|||
* `Default` |
|||
* `Primary` |
|||
* `Secondary` |
|||
* `Success` |
|||
* `Danger` |
|||
* `Warning` |
|||
* `Info` |
|||
* `Light` |
|||
* `Dark` |
|||
* `Outline_Primary` |
|||
* `Outline_Secondary` |
|||
* `Outline_Success` |
|||
* `Outline_Danger` |
|||
* `Outline_Warning` |
|||
* `Outline_Info` |
|||
* `Outline_Light` |
|||
* `Outline_Dark` |
|||
* `Link` |
|||
|
|||
### `size` |
|||
|
|||
`size` is a selectable parameter. It's default value is `Default`. |
|||
|
|||
`<abp-button size="Default">Button</abp-button>` |
|||
|
|||
You can choose one of the size type listed below. |
|||
|
|||
* `Default` |
|||
* `Small` |
|||
* `Medium` |
|||
* `Large` |
|||
* `Block` |
|||
* `Block_Small` |
|||
* `Block_Medium` |
|||
* `Block_Large` |
|||
|
|||
### `busy-text` |
|||
|
|||
`busy-text` is a string parameter. It shows the text while the button is busy. |
|||
|
|||
### `text` |
|||
|
|||
`text` is a string parameter that displaying on button. |
|||
|
|||
### `icon` |
|||
|
|||
`icon` is a string parameter. It is depending to [`icon-type`](#`icon-type`). For default, we use [Font Awesome](https://fontawesome.com/) for icons. To use it, you need to set `icon` parameter as a icon name. |
|||
|
|||
##### Example |
|||
|
|||
[fa-address-card](https://fontawesome.com/icons/address-card):  |
|||
|
|||
`<abp-button icon="address-card" text="Address" />` |
|||
|
|||
> Don't forget: You dont need to write prefix! It will add automatically "fa" prefix for [Font Awesome](https://fontawesome.com/) icons while you did not change `icon-type`. |
|||
|
|||
### `disabled` |
|||
|
|||
`disabled` is a boolean parameter. If you set it `true`, your button will be disabled. |
|||
|
|||
### `icon-type` |
|||
|
|||
`icon-type` is a selectable parameter. It's default value is `FontAwesome`. You can create your own icon type provider and change it. |
|||
|
|||
You can choose one of the icon type listed below. |
|||
|
|||
* `FontAwesome` |
|||
* `Other` |
|||
@ -1,3 +1,3 @@ |
|||
## Dynamic Forms |
|||
This document has moved. |
|||
|
|||
This is not documented yet. You can see a [demo](http://bootstrap-taghelpers.abp.io/Components/DynamicForms) for now. |
|||
[Click to navigate to Dynamic Forms document](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) |
|||
@ -1,3 +1,3 @@ |
|||
## ABP Tag Helpers |
|||
This document has moved. |
|||
|
|||
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
|||
[Click to navigate to ABP Tag Helpers document](../../UI/AspNetCore/Tag-Helpers/Index.md) |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
# Theming |
|||
|
|||
TODO |
|||
This document has moved. |
|||
|
|||
[Click to navigate to Theming document](../UI/AspNetCore/Theming.md) |
|||
@ -1,505 +1,4 @@ |
|||
# Widgets |
|||
|
|||
ABP provides a model and infrastructure to create **reusable widgets**. Widget system is an extension to [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components). Widgets are especially useful when you want to; |
|||
This document has moved. |
|||
|
|||
* Have **scripts & styles** dependencies for your widget. |
|||
* Create **dashboards** with widgets used inside. |
|||
* Define widgets in reusable **[modules](../Module-Development-Basics.md)**. |
|||
* Co-operate widgets with **[authorization](../Authorization.md)** and **[bundling](Bundling-Minification.md)** systems. |
|||
|
|||
## Basic Widget Definition |
|||
|
|||
### Create a View Component |
|||
|
|||
As the first step, create a new regular ASP.NET Core View Component: |
|||
|
|||
 |
|||
|
|||
**MySimpleWidgetViewComponent.cs**: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Inheriting from `AbpViewComponent` is not required. You could inherit from ASP.NET Core's standard `ViewComponent`. `AbpViewComponent` only defines some base useful properties. |
|||
|
|||
You can inject a service and use in the `Invoke` method to get some data from the service. You may need to make Invoke method async, like `public async Task<IViewComponentResult> InvokeAsync()`. See [ASP.NET Core's ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components) document fore all different usages. |
|||
|
|||
**Default.cshtml**: |
|||
|
|||
```xml |
|||
<div class="my-simple-widget"> |
|||
<h2>My Simple Widget</h2> |
|||
<p>This is a simple widget!</p> |
|||
</div> |
|||
``` |
|||
|
|||
### Define the Widget |
|||
|
|||
Add a `Widget` attribute to the `MySimpleWidgetViewComponent` class to mark this view component as a widget: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Rendering a Widget |
|||
|
|||
Rendering a widget is pretty standard. Use the `Component.InvokeAsync` method in a razor view/page as you do for any view component. Examples: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("MySimpleWidget") |
|||
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) |
|||
```` |
|||
|
|||
First approach uses the widget name while second approach uses the view component type. |
|||
|
|||
### Widgets with Arguments |
|||
|
|||
ASP.NET Core's view component system allows you to accept arguments for view components. The sample view component below accepts `startDate` and `endDate` and uses these arguments to retrieve data from a service. |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Shared.Components.CountersWidget |
|||
{ |
|||
[Widget] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
private readonly IDashboardAppService _dashboardAppService; |
|||
|
|||
public CountersWidgetViewComponent(IDashboardAppService dashboardAppService) |
|||
{ |
|||
_dashboardAppService = dashboardAppService; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync( |
|||
DateTime startDate, DateTime endDate) |
|||
{ |
|||
var result = await _dashboardAppService.GetCountersWidgetAsync( |
|||
new CountersWidgetInputDto |
|||
{ |
|||
StartDate = startDate, |
|||
EndDate = endDate |
|||
} |
|||
); |
|||
|
|||
return View(result); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Now, you need to pass an anonymous object to pass arguments as shown below: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("CountersWidget", new |
|||
{ |
|||
startDate = DateTime.Now.Subtract(TimeSpan.FromDays(7)), |
|||
endDate = DateTime.Now |
|||
}) |
|||
```` |
|||
|
|||
## Widget Name |
|||
|
|||
Default name of the view components are calculated based on the name of the view component type. If your view component type is `MySimpleWidgetViewComponent` then the widget name will be `MySimpleWidget` (removes `ViewComponent` postfix). This is how ASP.NET Core calculates a view component's name. |
|||
|
|||
To customize widget's name, just use the standard `ViewComponent` attribute of ASP.NET Core: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget] |
|||
[ViewComponent(Name = "MyCustomNamedWidget")] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/Pages/Components/MySimpleWidget/Default.cshtml"); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP will respect to the custom name by handling the widget. |
|||
|
|||
> If the view component name and the folder name of the view component don't match, you may need to manually write the view path as done in this example. |
|||
|
|||
### Display Name |
|||
|
|||
You can also define a human-readable, localizable display name for the widget. This display name then can be used on the UI when needed. Display name is optional and can be defined using properties of the `Widget` attribute: |
|||
|
|||
````csharp |
|||
using DashboardDemo.Localization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
DisplayName = "MySimpleWidgetDisplayName", //Localization key |
|||
DisplayNameResource = typeof(DashboardDemoResource) //localization resource |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
See [the localization document](../Localization.md) to learn about localization resources and keys. |
|||
|
|||
## Style & Script Dependencies |
|||
|
|||
There are some challenges when your widget has script and style files; |
|||
|
|||
* Any page uses the widget should also include the **its script & styles** files into the page. |
|||
* The page should also care about **depended libraries/files** of the widget. |
|||
|
|||
ABP solves these issues when you properly relate the resources with the widget. You don't care about dependencies of the widget while using it. |
|||
|
|||
### Defining as Simple File Paths |
|||
|
|||
The example widget below adds a style and a script file: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleFiles = new[] { "/Pages/Components/MySimpleWidget/Default.css" }, |
|||
ScriptFiles = new[] { "/Pages/Components/MySimpleWidget/Default.js" } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP takes account these dependencies and properly adds to the view/page when you use the widget. Style/script files can be **physical or virtual**. It is completely integrated to the [Virtual File System](../Virtual-File-System.md). |
|||
|
|||
### Defining Bundle Contributors |
|||
|
|||
All resources for used widgets in a page are added as a **bundle** (bundled & minified in production if you don't configure otherwise). In addition to adding a simple file, you can take full power of the bundle contributors. |
|||
|
|||
The sample code below does the same with the code above, but defines and uses bundle contributors: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget( |
|||
StyleTypes = new []{ typeof(MySimpleWidgetStyleBundleContributor) }, |
|||
ScriptTypes = new[]{ typeof(MySimpleWidgetScriptBundleContributor) } |
|||
)] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.css"); |
|||
} |
|||
} |
|||
|
|||
public class MySimpleWidgetScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files |
|||
.AddIfNotContains("/Pages/Components/MySimpleWidget/Default.js"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
Bundle contribution system is very powerful. If your widget uses a JavaScript library to render a chart, then you can declare it as a dependency, so the JavaScript library is automatically added to the page if it wasn't added before. In this way, the page using your widget doesn't care about the dependencies. |
|||
|
|||
See the [bundling & minification](Bundling-Minification.md) documentation for more information about that system. |
|||
|
|||
## RefreshUrl |
|||
|
|||
A widget may design a `RefreshUrl` that is used whenever the widget needs to be refreshed. If it is defined, the widget is re-rendered on the server side on every refresh (see the refresh `method` of the `WidgetManager` below). |
|||
|
|||
````csharp |
|||
[Widget(RefreshUrl = "Widgets/Counters")] |
|||
public class CountersWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
Once you define a `RefreshUrl` for your widget, you need to provide an endpoint to render and return it: |
|||
|
|||
````csharp |
|||
[Route("Widgets")] |
|||
public class CountersWidgetController : AbpController |
|||
{ |
|||
[HttpGet] |
|||
[Route("Counters")] |
|||
public IActionResult Counters(DateTime startDate, DateTime endDate) |
|||
{ |
|||
return ViewComponent("CountersWidget", new {startDate, endDate}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`Widgets/Counters` route matches to the `RefreshUrl` declared before. |
|||
|
|||
> A widget supposed to be refreshed in two ways: In the first way, when you use a `RefreshUrl`, it re-rendered on the server and replaced by the HTML returned from server. In the second way the widget gets data (generally a JSON object) from server and refreshes itself in the client side (see the refresh method in the Widget JavaScript API section). |
|||
|
|||
## JavaScript API |
|||
|
|||
A widget may need to be rendered and refreshed in the client side. In such cases, you can use ABP's `WidgetManager` and define APIs for your widgets. |
|||
|
|||
### WidgetManager |
|||
|
|||
`WidgetManager` is used to initialize and refresh one or more widgets. Create a new `WidgetManager` as shown below: |
|||
|
|||
````js |
|||
$(function() { |
|||
var myWidgetManager = new abp.WidgetManager('#MyDashboardWidgetsArea'); |
|||
}) |
|||
```` |
|||
|
|||
`MyDashboardWidgetsArea` may contain one or more widgets inside. |
|||
|
|||
> Using the `WidgetManager` inside document.ready (like above) is a good practice since its functions use the DOM and need DOM to be ready. |
|||
|
|||
#### WidgetManager.init() |
|||
|
|||
`init` simply initializes the `WidgetManager` and calls `init` methods of the related widgets if they define (see Widget JavaScript API section below) |
|||
|
|||
```js |
|||
myWidgetManager.init(); |
|||
``` |
|||
|
|||
#### WidgetManager.refresh() |
|||
|
|||
`refresh` method refreshes all widgets related to this `WidgetManager`: |
|||
|
|||
```` |
|||
myWidgetManager.refresh(); |
|||
```` |
|||
|
|||
#### WidgetManager Options |
|||
|
|||
WidgetManager has some additional options. |
|||
|
|||
##### Filter Form |
|||
|
|||
If your widgets require parameters/filters then you will generally have a form to filter the widgets. In such cases, you can create a form that has some form elements and a dashboard area with some widgets inside. Example: |
|||
|
|||
````xml |
|||
<form method="get" id="MyDashboardFilterForm"> |
|||
...form elements |
|||
</form> |
|||
|
|||
<div id="MyDashboardWidgetsArea" data-widget-filter="#MyDashboardFilterForm"> |
|||
...widgets |
|||
</div> |
|||
```` |
|||
|
|||
`data-widget-filter` attribute relates the form with the widgets. Whenever the form is submitted, all the widgets are automatically refreshed with the form fields as the filter. |
|||
|
|||
Instead of the `data-widget-filter` attribute, you can use the `filterForm` parameter of the `WidgetManager` constructor. Example: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterForm: '#MyDashboardFilterForm' |
|||
}); |
|||
```` |
|||
|
|||
##### Filter Callback |
|||
|
|||
You may want to have a better control to provide filters while initializing and refreshing the widgets. In this case, you can use the `filterCallback` option: |
|||
|
|||
````js |
|||
var myWidgetManager = new abp.WidgetManager({ |
|||
wrapper: '#MyDashboardWidgetsArea', |
|||
filterCallback: function() { |
|||
return $('#MyDashboardFilterForm').serializeFormToObject(); |
|||
} |
|||
}); |
|||
```` |
|||
|
|||
This example shows the default implementation of the `filterCallback`. You can return any JavaScript object with fields. Example: |
|||
|
|||
````js |
|||
filterCallback: function() { |
|||
return { |
|||
'startDate': $('#StartDateInput').val(), |
|||
'endDate': $('#EndDateInput').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
The returning filters are passed to all widgets on `init` and `refresh`. |
|||
|
|||
### Widget JavaScript API |
|||
|
|||
A widget can define a JavaScript API that is invoked by the `WidgetManager` when needed. The code sample below can be used to start to define an API for a widget. |
|||
|
|||
````js |
|||
(function () { |
|||
abp.widgets.NewUserStatisticWidget = function ($wrapper) { |
|||
|
|||
var getFilters = function () { |
|||
return { |
|||
... |
|||
}; |
|||
} |
|||
|
|||
var refresh = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
var init = function (filters) { |
|||
... |
|||
}; |
|||
|
|||
return { |
|||
getFilters: getFilters, |
|||
init: init, |
|||
refresh: refresh |
|||
}; |
|||
}; |
|||
})(); |
|||
```` |
|||
|
|||
`NewUserStatisticWidget` is the name of the widget here. It should match the widget name defined in the server side. All of the functions are optional. |
|||
|
|||
#### getFilters |
|||
|
|||
If the widget has internal custom filters, this function should return the filter object. Example: |
|||
|
|||
````js |
|||
var getFilters = function() { |
|||
return { |
|||
frequency: $wrapper.find('.frequency-filter option:selected').val() |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
This method is used by the `WidgetManager` while building filters. |
|||
|
|||
#### init |
|||
|
|||
Used to initialize the widget when needed. It has a filter argument that can be used while getting data from server. `init` method is used when `WidgetManager.init()` function is called. It is also called if your widget requires a full re-load on refresh. See the `RefreshUrl` widget option. |
|||
|
|||
#### refresh |
|||
|
|||
Used to refresh the widget when needed. It has a filter argument that can be used while getting data from server. `refresh` method is used whenever `WidgetManager.refresh()` function is called. |
|||
|
|||
## Authorization |
|||
|
|||
Some widgets may need to be available only for authenticated or authorized users. In this case, use the following properties of the `Widget` attribute: |
|||
|
|||
* `RequiresAuthentication` (`bool`): Set to true to make this widget usable only for authentication users (user have logged in to the application). |
|||
* `RequiredPolicies` (`List<string>`): A list of policy names to authorize the user. See [the authorization document](../Authorization.md) for more info about policies. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Widgets; |
|||
|
|||
namespace DashboardDemo.Web.Pages.Components.MySimpleWidget |
|||
{ |
|||
[Widget(RequiredPolicies = new[] { "MyPolicyName" })] |
|||
public class MySimpleWidgetViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## WidgetOptions |
|||
|
|||
As alternative to the `Widget` attribute, you can use the `AbpWidgetOptions` to configure widgets: |
|||
|
|||
```csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets.Add<MySimpleWidgetViewComponent>(); |
|||
}); |
|||
``` |
|||
|
|||
Write this into the `ConfigureServices` method of your [module](../Module-Development-Basics.md). All the configuration done with the `Widget` attribute is also possible with the `AbpWidgetOptions`. Example configuration that adds a style for the widget: |
|||
|
|||
````csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets |
|||
.Add<MySimpleWidgetViewComponent>() |
|||
.WithStyles("/Pages/Components/MySimpleWidget/Default.css"); |
|||
}); |
|||
```` |
|||
|
|||
> Tip: `AbpWidgetOptions` can also be used to get an existing widget and change its configuration. This is especially useful if you want to modify the configuration of a widget inside a module used by your application. Use `options.Widgets.Find` to get an existing `WidgetDefinition`. |
|||
|
|||
## See Also |
|||
|
|||
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). |
|||
[Click to navigate to Widgets document](../UI/AspNetCore/Widgets.md) |
|||
|
|||
@ -0,0 +1,149 @@ |
|||
# Customizing the Application Modules: Extending Entities |
|||
|
|||
In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible. |
|||
|
|||
## Extra Properties |
|||
|
|||
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
//SET AN EXTRA PROPERTY |
|||
var user = await _identityUserRepository.GetAsync(userId); |
|||
user.SetProperty("Title", "My custom title value!"); |
|||
await _identityUserRepository.UpdateAsync(user); |
|||
|
|||
//GET AN EXTRA PROPERTY |
|||
var user = await _identityUserRepository.GetAsync(userId); |
|||
return user.GetProperty<string>("Title"); |
|||
```` |
|||
|
|||
This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here). |
|||
|
|||
Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document. |
|||
|
|||
See the [entities document](Entities.md) for more about the extra properties system. |
|||
|
|||
> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below. |
|||
|
|||
## Creating a New Entity Maps to the Same Database Table/Collection |
|||
|
|||
While using the extra properties approach is **easy to use** and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md). |
|||
|
|||
Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database). |
|||
|
|||
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems. |
|||
|
|||
## Creating a New Entity with Its Own Database Table/Collection |
|||
|
|||
Mapping your entity to an **existing table** of a depended module has a few disadvantages; |
|||
|
|||
* You deal with the **database migration structure** for EF Core. While it is possible, you should extra care about the migration code especially when you want to add **relations** between entities. |
|||
* Your application database and the module database will be the **same physical database**. Normally, a module database can be separated if needed, but using the same table restricts it. |
|||
|
|||
If you want to **loose couple** your entity with the entity defined by the module, you can create your own database table/collection and map your entity to your own table in your own database. |
|||
|
|||
In this case, you need to deal with the **synchronization problems**, especially if you want to **duplicate** some properties/fields of the related entity. There are a few solutions; |
|||
|
|||
* If you are building a **monolithic** application (or managing your entity and the related module entity within the same process), you can use the [local event bus](Local-Event-Bus.md) to listen changes. |
|||
* If you are building a **distributed** system where the module entity is managed (created/updated/deleted) on a different process/service than your entity is managed, then you can subscribe to the [distributed event bus](Distributed-Event-Bus.md) for change events. |
|||
|
|||
Once you handle the event, you can update your own entity in your own database. |
|||
|
|||
### Subscribing to Local Events |
|||
|
|||
[Local Event Bus](Local-Event-Bus.md) system is a way to publish and subscribe to events occurring in the same application. |
|||
|
|||
Assume that you want to get informed when a `IdentityUser` entity changes (created, updated or deleted). You can create a class that implements the `ILocalEventHandler<EntityChangedEventData<IdentityUser>>` interface. |
|||
|
|||
````csharp |
|||
public class MyLocalIdentityUserChangeEventHandler : |
|||
ILocalEventHandler<EntityChangedEventData<IdentityUser>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityChangedEventData<IdentityUser> eventData) |
|||
{ |
|||
var userId = eventData.Entity.Id; |
|||
var userName = eventData.Entity.UserName; |
|||
|
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `EntityChangedEventData<T>` covers create, update and delete events for the given entity. If you need, you can subscribe to create, update and delete events individually (in the same class or different classes). |
|||
* This code will be executed **out of the local transaction**, because it listens the `EntityChanged` event. You can subscribe to the `EntityChangingEventData<T>` to perform your event handler in **the same local (in-process) transaction** if the current [unit of work](Unit-Of-Work.md) is transactional. |
|||
|
|||
> Reminder: This approach needs to change the `IdentityUser` entity in the same process contains the handler class. It perfectly works even for a clustered environment (when multiple instances of the same application are running on multiple servers). |
|||
|
|||
### Subscribing to Distributed Events |
|||
|
|||
[Distributed Event Bus](Distributed-Event-Bus.md) system is a way to publish an event in one application and receive the event in the same or different application running on the same or different server. |
|||
|
|||
Assume that you want to get informed when a `IdentityUser` entity created, updated or deleted. You can create a class like below: |
|||
|
|||
````csharp |
|||
public class MyDistributedIdentityUserChangeEventHandler : |
|||
IDistributedEventHandler<EntityCreatedEto<EntityEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<EntityEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<EntityEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityCreatedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "created" event |
|||
} |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityUpdatedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "updated" event |
|||
} |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityDeletedEto<EntityEto> eventData) |
|||
{ |
|||
if (eventData.Entity.EntityType == "Volo.Abp.Identity.IdentityUser") |
|||
{ |
|||
var userId = Guid.Parse(eventData.Entity.KeysAsString); |
|||
//...handle the "deleted" event |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* It implements multiple `IDistributedEventHandler` interfaces: **Created**, **Updated** and **Deleted**. Because, the distributed event bus system publishes events individually. There is no "Changed" event like the local event bus. |
|||
* It subscribes to `EntityEto`, which is a generic event class that is **automatically published** for all type of entities by the ABP framework. This is why it checks the **entity type** (checking the entity type as string since we assume that there is no type safe reference to the `IdentityUser` entity). |
|||
|
|||
Pre-built application modules do not define specialized event types yet (like `IdentityUserEto` - "ETO" means "Event Transfer Object"). This feature is on the road map and will be available in a short term ([follow this issue](https://github.com/abpframework/abp/issues/3033)). Once it is implemented, you will be able to subscribe to individual entity types. Example: |
|||
|
|||
````csharp |
|||
public class MyDistributedIdentityUserCreatedEventHandler : |
|||
IDistributedEventHandler<EntityCreatedEto<IdentityUserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
public async Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> eventData) |
|||
{ |
|||
var userId = eventData.Entity.Id; |
|||
var userName = eventData.Entity.UserName; |
|||
//...handle the "created" event |
|||
} |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
* This handler is executed only when a new user has been created. |
|||
|
|||
> The only pre-defined specialized event class is the `UserEto`. For example, you can subscribe to the `EntityCreatedEto<UserEto>` to get notified when a user has created. This event also works for the Identity module. |
|||
|
|||
## See Also |
|||
|
|||
* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) |
|||
@ -0,0 +1,166 @@ |
|||
# Customizing the Application Modules: Overriding Services |
|||
|
|||
You may need to **change behavior (business logic)** of a depended module for your application. In this case, you can use the power of the [dependency injection system](Dependency-Injection.md) to replace a service, controller or even a page model of the depended module by your own implementation. |
|||
|
|||
**Replacing a service** is possible for any type of class registered to the dependency injection, including services of the ABP Framework. |
|||
|
|||
You have different options can be used based on your requirement those will be explained in the next sections. |
|||
|
|||
> Notice that some service methods may not be virtual, so you may not be able to override. We make all virtual by design. If you find any method that is not overridable, please [create an issue](https://github.com/abpframework/abp/issues/new) or do it yourself and send a **pull request** on GitHub. |
|||
|
|||
## Replacing an Interface |
|||
|
|||
If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityAppService`, you can re-implement the same interface and replace the current implementation by your class. Example: |
|||
|
|||
````csharp |
|||
public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
`MyIdentityUserAppService` replaces the `IIdentityUserAppService` by naming convention (since both ends with `IdentityUserAppService`). If your class name doesn't match, you need to manually expose the service interface: |
|||
|
|||
````csharp |
|||
[ExposeServices(typeof(IIdentityUserAppService))] |
|||
public class TestAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
The dependency injection system allows to register multiple services for the same interface. The last registered one is used when the interface is injected. It is a good practice to explicitly replace the service. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IIdentityUserAppService))] |
|||
public class TestAppService : IIdentityUserAppService, ITransientDependency |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
In this way, there will be a single implementation of the `IIdentityUserAppService` interface, while it doesn't change the result for this case. Replacing a service is also possible by code: |
|||
|
|||
````csharp |
|||
context.Services.Replace( |
|||
ServiceDescriptor.Transient<IIdentityUserAppService, MyIdentityUserAppService>() |
|||
); |
|||
```` |
|||
|
|||
You can write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md). |
|||
|
|||
## Overriding a Service Class |
|||
|
|||
In most cases, you will want to change one or a few methods of the current implementation for a service. Re-implementing the complete interface would not be efficient in this case. As a better approach, inherit from the original class and override the desired method. |
|||
|
|||
### Example: Overriding an Application Service |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
public class MyIdentityUserAppService : IdentityUserAppService |
|||
{ |
|||
//... |
|||
public MyIdentityUserAppService( |
|||
IdentityUserManager userManager, |
|||
IIdentityUserRepository userRepository, |
|||
IGuidGenerator guidGenerator |
|||
) : base( |
|||
userManager, |
|||
userRepository, |
|||
guidGenerator) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input) |
|||
{ |
|||
if (input.PhoneNumber.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpValidationException( |
|||
"Phone number is required for new users!", |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult( |
|||
"Phone number can not be empty!", |
|||
new []{"PhoneNumber"} |
|||
) |
|||
} |
|||
); } |
|||
|
|||
return await base.CreateAsync(input); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This class **overrides** the `CreateAsync` method of the `IdentityUserAppService` [application service](Application-Services.md) to check the phone number. Then calls the base method to continue to the **underlying business logic**. In this way, you can perform additional business logic **before** and **after** the base logic. |
|||
|
|||
You could completely **re-write** the entire business logic for a user creation without calling the base method. |
|||
|
|||
### Example: Overriding a Domain Service |
|||
|
|||
````csharp |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IdentityUserManager))] |
|||
public class MyIdentityUserManager : IdentityUserManager |
|||
{ |
|||
public MyIdentityUserManager( |
|||
IdentityUserStore store, |
|||
IOptions<IdentityOptions> optionsAccessor, |
|||
IPasswordHasher<IdentityUser> passwordHasher, |
|||
IEnumerable<IUserValidator<IdentityUser>> userValidators, |
|||
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, |
|||
ILookupNormalizer keyNormalizer, |
|||
IdentityErrorDescriber errors, |
|||
IServiceProvider services, |
|||
ILogger<IdentityUserManager> logger, |
|||
ICancellationTokenProvider cancellationTokenProvider |
|||
) : base( |
|||
store, |
|||
optionsAccessor, |
|||
passwordHasher, |
|||
userValidators, |
|||
passwordValidators, |
|||
keyNormalizer, |
|||
errors, |
|||
services, |
|||
logger, |
|||
cancellationTokenProvider) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IdentityResult> CreateAsync(IdentityUser user) |
|||
{ |
|||
if (user.PhoneNumber.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpValidationException( |
|||
"Phone number is required for new users!", |
|||
new List<ValidationResult> |
|||
{ |
|||
new ValidationResult( |
|||
"Phone number can not be empty!", |
|||
new []{"PhoneNumber"} |
|||
) |
|||
} |
|||
); |
|||
} |
|||
|
|||
return await base.CreateAsync(user); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This example class inherits from the `IdentityUserManager` [domain service](Domain-Services.md) and overrides the `CreateAsync` method to perform the same phone number check implemented above. The result is same, but this time we've implemented it inside the domain service assuming that this is a **core domain logic** for our system. |
|||
|
|||
> `[ExposeServices(typeof(IdentityUserManager))]` attribute is **required** here since `IdentityUserManager` does not define an interface (like `IIdentityUserManager`) and dependency injection system doesn't expose services for inherited classes (like it does for the implemented interfaces) by convention. |
|||
|
|||
Check the [localization system](Localization.md) to learn how to localize the error messages. |
|||
|
|||
### Overriding Other Classes |
|||
|
|||
Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. |
|||
|
|||
## How to Find the Services? |
|||
|
|||
[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services. |
|||
@ -0,0 +1,9 @@ |
|||
# Overriding the User Interface |
|||
|
|||
You may want to override a page, a component, a JavaScript, CSS or an image file of your depended module. Overriding the UI completely depends on the UI framework you're using. Select the UI framework to continue: |
|||
|
|||
* [ASP.NET Core (MVC / Razor Pages)](UI/AspNetCore/Customization-User-Interface.md) |
|||
* [Angular](UI/Angular/Customization-User-Interface.md) |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,3 @@ |
|||
# IdentityServer Integration |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
# Account Module |
|||
|
|||
TODO |
|||
@ -1,3 +1,3 @@ |
|||
# IdentityServer Module |
|||
# Blogging Module |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
## Basic Theme |
|||
|
|||
TODO |
|||
@ -0,0 +1,52 @@ |
|||
# 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 | |
|||
|
|||
## What's Next? |
|||
|
|||
- [Custom Setting Page](./Custom-Setting-Page.md) |
|||
@ -0,0 +1,290 @@ |
|||
# Config State |
|||
|
|||
`ConfigStateService` is a singleton service, i.e. provided in root level of your application, and is actually a façade for interacting with application configuration state in the `Store`. |
|||
|
|||
## Before Use |
|||
|
|||
In order to use the `ConfigStateService` you must inject it in your class as a dependency. |
|||
|
|||
```js |
|||
import { ConfigStateService } from '@abp/ng.core'; |
|||
|
|||
@Component({ |
|||
/* class metadata here */ |
|||
}) |
|||
class DemoComponent { |
|||
constructor(private config: ConfigStateService) {} |
|||
} |
|||
``` |
|||
|
|||
You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**. |
|||
|
|||
## Selector Methods |
|||
|
|||
`ConfigStateService` has numerous selector methods which allow you to get a specific configuration or all configurations from the `Store`. |
|||
|
|||
### How to Get All Configurations From the Store |
|||
|
|||
You can use the `getAll` method of `ConfigStateService` to get all of the configuration object from the store. It is used as follows: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const config = this.config.getAll(); |
|||
``` |
|||
|
|||
### How to Get a Specific Configuration From the Store |
|||
|
|||
You can use the `getOne` method of `ConfigStateService` to get a specific configuration property from the store. For that, the property name should be passed to the method as parameter. |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const currentUser = this.config.getOne("currentUser"); |
|||
``` |
|||
|
|||
On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`: |
|||
|
|||
```js |
|||
const tenantId = this.config.getDeep("currentUser.tenantId"); |
|||
``` |
|||
|
|||
or by giving an array of keys as parameter: |
|||
|
|||
```js |
|||
const tenantId = this.config.getDeep(["currentUser", "tenantId"]); |
|||
``` |
|||
|
|||
FYI, `getDeep` is able to do everything `getOne` does. Just keep in mind that `getOne` is slightly faster. |
|||
|
|||
#### Config State Properties |
|||
|
|||
Please refer to `Config.State` type for all the properties you can get with `getOne` and `getDeep`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7). |
|||
|
|||
### How to Get the Application Information From the Store |
|||
|
|||
The `getApplicationInfo` method is used to get the application information from the environment variables stored as the config state. This is how you can use it: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const appInfo = this.config.getApplicationInfo(); |
|||
``` |
|||
|
|||
This method never returns `undefined` or `null` and returns an empty object literal (`{}`) instead. In other words, you will never get an error when referring to the properties of `appInfo` above. |
|||
|
|||
#### Application Information Properties |
|||
|
|||
Please refer to `Config.Application` type for all the properties you can get with `getApplicationInfo`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21). |
|||
|
|||
### How to Get API URL From the Store |
|||
|
|||
The `getApplicationInfo` method is used to get a specific API URL from the environment variables stored as the config state. This is how you can use it: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const apiUrl = this.config.getApiUrl(); |
|||
// environment.apis.default.url |
|||
|
|||
const searchUrl = this.config.getApiUrl("search"); |
|||
// environment.apis.search.url |
|||
``` |
|||
|
|||
This method returns the `url` of a specific API based on the key given as its only parameter. If there is no key, `'default'` is used. |
|||
|
|||
### How to Get All Settings From the Store |
|||
|
|||
You can use the `getSettings` method of `ConfigStateService` to get all of the settings object from the configuration state. Here is how you get all settings: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const settings = this.config.getSettings(); |
|||
``` |
|||
|
|||
In addition, the method lets you search settings by **passing a keyword** to it. |
|||
|
|||
```js |
|||
const localizationSettings = this.config.getSettings("Localization"); |
|||
/* |
|||
{ |
|||
'Abp.Localization.DefaultLanguage': 'en' |
|||
} |
|||
*/ |
|||
``` |
|||
|
|||
Beware though, **settings search is case sensitive**. |
|||
|
|||
### How to Get a Specific Setting From the Store |
|||
|
|||
You can use the `getSetting` method of `ConfigStateService` to get a specific setting from the configuration state. Here is an example: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); |
|||
// 'en' |
|||
``` |
|||
|
|||
### How to Get a Specific Permission From the Store |
|||
|
|||
You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method. |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity"); |
|||
// true |
|||
``` |
|||
|
|||
You may also **combine policy keys** to fine tune your selection: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy( |
|||
"Abp.Identity && Abp.Account" |
|||
); |
|||
// false |
|||
|
|||
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy( |
|||
"Abp.Identity || Abp.Account" |
|||
); |
|||
// true |
|||
``` |
|||
|
|||
Please consider the following **rules** when creating your permission selectors: |
|||
|
|||
- Maximum 2 keys can be combined. |
|||
- `&&` operator looks for both keys. |
|||
- `||` operator looks for either key. |
|||
- Empty string `''` as key will return `true` |
|||
- Using an operator without a second key will return `false` |
|||
|
|||
### How to Get Translations From the Store |
|||
|
|||
The `getLocalization` method of `ConfigStateService` is used for translations. Here are some examples: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const identity = this.config.getLocalization("AbpIdentity::Identity"); |
|||
// 'identity' |
|||
|
|||
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY"); |
|||
// 'AbpIdentity::IDENTITY' |
|||
|
|||
const defaultValue = this.config.getLocalization({ |
|||
key: "AbpIdentity::IDENTITY", |
|||
defaultValue: "IDENTITY" |
|||
}); |
|||
// 'IDENTITY' |
|||
``` |
|||
|
|||
Please check out the [localization documentation](./Localization.md) for details. |
|||
|
|||
## Dispatch Methods |
|||
|
|||
`ConfigStateService` has several dispatch methods which allow you to conveniently dispatch predefined actions to the `Store`. |
|||
|
|||
### How to Get Application Configuration From Server |
|||
|
|||
The `dispatchGetAppConfiguration` triggers a request to an endpoint that responds with the application state and then places this response to the `Store` as configuration state. |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
this.config.dispatchGetAppConfiguration(); |
|||
// returns a state stream which emits after dispatch action is complete |
|||
``` |
|||
|
|||
Note that **you do not have to call this method at application initiation**, because the application configuration is already being received from the server at start. |
|||
|
|||
### How to Patch Route Configuration |
|||
|
|||
The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter. |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const newRouteConfig: Partial<ABP.Route> = { |
|||
name: "Home", |
|||
path: "home", |
|||
children: [ |
|||
{ |
|||
name: "Dashboard", |
|||
path: "dashboard" |
|||
} |
|||
] |
|||
}; |
|||
|
|||
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig); |
|||
// returns a state stream which emits after dispatch action is complete |
|||
``` |
|||
|
|||
### How to Add a New Route Configuration |
|||
|
|||
The `dispatchAddRoute` adds a new route to the configuration state in the `Store`. For this, the route config should be passed as the parameter of the method. |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const newRoute: ABP.Route = { |
|||
name: "My New Page", |
|||
iconClass: "fa fa-dashboard", |
|||
path: "page", |
|||
invisible: false, |
|||
order: 2, |
|||
requiredPolicy: "MyProjectName::MyNewPage" |
|||
}; |
|||
|
|||
this.config.dispatchAddRoute(newRoute); |
|||
// returns a state stream which emits after dispatch action is complete |
|||
``` |
|||
|
|||
The `newRoute` will be placed as at root level, i.e. without any parent routes and its url will be stored as `'/path'`. |
|||
|
|||
If you want **to add a child route, you can do this:** |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
const newRoute: ABP.Route = { |
|||
parentName: "AbpAccount::Login", |
|||
name: "My New Page", |
|||
iconClass: "fa fa-dashboard", |
|||
path: "page", |
|||
invisible: false, |
|||
order: 2, |
|||
requiredPolicy: "MyProjectName::MyNewPage" |
|||
}; |
|||
|
|||
this.config.dispatchAddRoute(newRoute); |
|||
// returns a state stream which emits after dispatch action is complete |
|||
``` |
|||
|
|||
The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`. |
|||
|
|||
#### Route Configuration Properties |
|||
|
|||
Please refer to `ABP.Route` type for all the properties you can pass to `dispatchSetEnvironment` in its parameter. It can be found in the [common.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27). |
|||
|
|||
### How to Set the Environment |
|||
|
|||
The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used: |
|||
|
|||
```js |
|||
// this.config is instance of ConfigStateService |
|||
|
|||
this.config.dispatchSetEnvironment({ |
|||
/* environment properties here */ |
|||
}); |
|||
// returns a state stream which emits after dispatch action is complete |
|||
``` |
|||
|
|||
Note that **you do not have to call this method at application initiation**, because the environment variables are already being stored at start. |
|||
|
|||
#### Environment Properties |
|||
|
|||
Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13). |
|||
@ -0,0 +1,42 @@ |
|||
# Custom Setting Page |
|||
|
|||
There are several settings tabs from different modules. You can add custom settings page 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: ` |
|||
custom-settings works! |
|||
`, |
|||
}) |
|||
export class YourCustomSettingsComponent { |
|||
// Your component logic |
|||
} |
|||
``` |
|||
|
|||
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' |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
Navigate to `/setting-management` route to see the changes: |
|||
|
|||
 |
|||
@ -0,0 +1,3 @@ |
|||
# Angular User Interface Customization Guide |
|||
|
|||
* [Replacing a component](Component-Replacement.md) |
|||
@ -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: 8.1 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 depended 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,470 @@ |
|||
# ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide |
|||
|
|||
This document explains how to override the user interface of a depended [application module](../../Modules/Index.md) for ASP.NET Core MVC / Razor Page applications. |
|||
|
|||
## Overriding a Page |
|||
|
|||
This section covers the [Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/) development, which is the recommended approach to create server rendered user interface for ASP.NET Core. Pre-built modules typically uses the Razor Pages approach instead of the classic MVC pattern (next sections will cover the MVC pattern too). |
|||
|
|||
You typically have three kind of override requirement for a page: |
|||
|
|||
* Overriding **only the Page Model** (C#) side to perform additional logic without changing the page UI. |
|||
* Overriding **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page. |
|||
* **Completely overriding** the page. |
|||
|
|||
### Overriding a Page Model (C#) |
|||
|
|||
````csharp |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Identity.Web.Pages.Identity.Users; |
|||
|
|||
namespace Acme.BookStore.Web.Pages.Identity.Users |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(EditModalModel))] |
|||
public class MyEditModalModel : EditModalModel |
|||
{ |
|||
public MyEditModalModel( |
|||
IIdentityUserAppService identityUserAppService, |
|||
IIdentityRoleAppService identityRoleAppService |
|||
) : base( |
|||
identityUserAppService, |
|||
identityRoleAppService) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
//TODO: Additional logic |
|||
await base.OnPostAsync(); |
|||
//TODO: Additional logic |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* This class inherits from and replaces the `EditModalModel` for the users and overrides the `OnPostAsync` method to perform additional logic before and after the underlying code. |
|||
* It uses `ExposeServices` and `Dependency` attributes to replace the class. |
|||
|
|||
### Overriding a Razor Page (.CSHTML) |
|||
|
|||
Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through the [Virtual File System](../../Virtual-File-System.md). |
|||
|
|||
Virtual File system allows us to **embed resources into assemblies**. In this way, pre-built modules define the razor pages inside their NuGet packages. When you depend a module, you can override any file added to the virtual file system by that module, including pages/views. |
|||
|
|||
#### Example |
|||
|
|||
This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md). |
|||
|
|||
Physical files override the embedded files defined in the same location. The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path: |
|||
|
|||
 |
|||
|
|||
You typically want to copy the original `.cshtml` file of the module, then make the necessary changes. You can find the original file [here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml). Do not copy the `Login.cshtml.cs` file which is the code behind file for the razor page and we don't want to override it yet (see the next section). |
|||
|
|||
That's all, you can change the file content however you like. |
|||
|
|||
### Completely Overriding a Razor Page |
|||
|
|||
You may want to completely override a page; the razor and the c# file related to the page. |
|||
|
|||
In such a case; |
|||
|
|||
1. Override the C# page model class just like described above, but don't replace the existing page model class. |
|||
2. Override the Razor Page just described above, but also change the @model directive to point your new page model. |
|||
|
|||
#### Example |
|||
|
|||
This example overrides the **login page** defined by the [Account Module](../../Modules/Account.md). |
|||
|
|||
Create a page model class deriving from the ` LoginModel ` (defined in the ` Volo.Abp.Account.Web.Pages.Account ` namespace): |
|||
|
|||
````csharp |
|||
public class MyLoginModel : LoginModel |
|||
{ |
|||
public MyLoginModel( |
|||
IAuthenticationSchemeProvider schemeProvider, |
|||
IOptions<AbpAccountOptions> accountOptions |
|||
) : base( |
|||
schemeProvider, |
|||
accountOptions) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override Task<IActionResult> OnPostAsync(string action) |
|||
{ |
|||
//TODO: Add logic |
|||
return base.OnPostAsync(action); |
|||
} |
|||
|
|||
//TODO: Add new methods and properties... |
|||
} |
|||
```` |
|||
|
|||
You can override any method or add new properties/methods if needed. |
|||
|
|||
> Notice that we didn't use `[Dependency(ReplaceServices = true)]` or `[ExposeServices(typeof(LoginModel))]` since we don't want to replace the existing class in the dependency injection, we define a new one. |
|||
|
|||
Copy `Login.cshtml` file into your solution as just described above. Change the **@model** directive to point to the `MyLoginModel`: |
|||
|
|||
````xml |
|||
@page |
|||
... |
|||
@model Acme.BookStore.Web.Pages.Account.MyLoginModel |
|||
... |
|||
```` |
|||
|
|||
That's all! Make any change in the view and run your application. |
|||
|
|||
#### Replacing Page Model Without Inheritance |
|||
|
|||
You don't have to inherit from the original page model class (like done in the previous example). Instead, you can completely **re-implement** the page yourself. In this case, just derive from `PageModel`, `AbpPageModel` or any suitable base class you need. |
|||
|
|||
## Overriding a View Component |
|||
|
|||
The ABP Framework, pre-built themes and modules define some **re-usable view components**. These view components can be replaced just like a page described above. |
|||
|
|||
### Example |
|||
|
|||
The screenshot below was taken from the **basic theme** comes with the application startup template. |
|||
|
|||
 |
|||
|
|||
[The basic theme](../../Themes/Basic.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it. |
|||
|
|||
First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below: |
|||
|
|||
 |
|||
|
|||
Then change the `Default.cshtml` as you like. Example content can be like that: |
|||
|
|||
````xml |
|||
<a href="/"> |
|||
<img src="~/logos/bookstore-logo.png" width="250" height="60"/> |
|||
</a> |
|||
```` |
|||
|
|||
Now, you can run the application to see the result: |
|||
|
|||
 |
|||
|
|||
If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system. |
|||
|
|||
### Overriding the Theme |
|||
|
|||
Just as explained above, you can replace any component, layout or c# class of the used theme. See the [theming document](Theming.md) for more information on the theming system. |
|||
|
|||
## Overriding Static Resources |
|||
|
|||
Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the Virtual File System to handle it. |
|||
|
|||
## Manipulating the Bundles |
|||
|
|||
The [Bundling & Minification](Bundling-Minification.md) system provides an **extensible and dynamic** system to create **script** and **style** bundles. It allows you to extend and manipulate the existing bundles. |
|||
|
|||
### Example: Add a Global CSS File |
|||
|
|||
For example, ABP Framework defines a **global style bundle** which is added to every page (actually, added to the layout by the themes). Let's add a **custom style file** to the end of the bundle files, so we can override any global style. |
|||
|
|||
First, create a CSS file and locate it in a folder inside the `wwwroot`: |
|||
|
|||
 |
|||
|
|||
Define some custom CSS rules inside the file. Example: |
|||
|
|||
````css |
|||
.card-title { |
|||
color: orange; |
|||
font-size: 2em; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.btn-primary { |
|||
background-color: red; |
|||
} |
|||
```` |
|||
|
|||
Then add this file to the standard global style bundle in the `ConfigureServices` method of your [module](../../Module-Development-Basics.md): |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
StandardBundles.Styles.Global, //The bundle name! |
|||
bundleConfiguration => |
|||
{ |
|||
bundleConfiguration.AddFiles("/styles/my-global-styles.css"); |
|||
} |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
#### The Global Script Bundle |
|||
|
|||
Just like the `StandardBundles.Styles.Global`, there is a `StandardBundles.Scripts.Global` that you can add files or manipulate the existing ones. |
|||
|
|||
### Example: Manipulate the Bundle Files |
|||
|
|||
The example above adds a new file to the bundle. You can do more if you create a **bundle contributor** class. Example: |
|||
|
|||
````csharp |
|||
public class MyGlobalStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.Clear(); |
|||
context.Files.Add("/styles/my-global-styles.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can add the contributor to an existing bundle: |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
StandardBundles.Styles.Global, |
|||
bundleConfiguration => |
|||
{ |
|||
bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor)); |
|||
} |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
It is not a good idea to clear all CSS files. In a real world scenario, you can find and replace a specific file with your own file. |
|||
|
|||
### Example: Add a JavaScript File for a Specific Page |
|||
|
|||
The examples above works with the global bundle added to the layout. What if you want to add a CSS/JavaScript file (or replace a file) for a specific page defines inside a depended module? |
|||
|
|||
Assume that you want to run a **JavaScript code** once the user enters to the **Role Management** page of the Identity Module. |
|||
|
|||
First, create a standard JavaScript file under the `wwwroot`, `Pages` or `Views` folder (ABP support to add static resources inside these folders by default). We prefer the `Pages/Identity/Roles` folder to follow the conventions: |
|||
|
|||
 |
|||
|
|||
Content of the file is simple: |
|||
|
|||
````js |
|||
$(function() { |
|||
abp.log.info('My custom role script file has been loaded!'); |
|||
}); |
|||
```` |
|||
|
|||
Then add this file to the bundle of the role management page: |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.ScriptBundles |
|||
.Configure( |
|||
typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName, |
|||
bundleConfig => |
|||
{ |
|||
bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js"); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` is the safe way to get the bundle name for the role management page. |
|||
|
|||
> Notice that not every page defines such page bundles. They define only if needed. |
|||
|
|||
In addition to adding new CSS/JavaScript file to a page, you also can replace the existing one (by defining a bundle contributor). |
|||
|
|||
## Layout Customization |
|||
|
|||
Layouts are defined by the theme ([see the theming](Theming.md)) by design. They are not included in a downloaded application solution. In this way you can easily **upgrade** the theme and get new features. You can not **directly change** the layout code in your application unless you replace it by your own layout (will be explained in the next sections). |
|||
|
|||
There are some common ways to **customize the layout** described in the next sections. |
|||
|
|||
### Menu Contributors |
|||
|
|||
There are two **standard menus** defined by the ABP Framework: |
|||
|
|||
 |
|||
|
|||
* `StandardMenus.Main`: The main menu of the application. |
|||
* `StandardMenus.User`: The user menu (generally at the top right of the screen). |
|||
|
|||
Rendering the menus is a responsibility of the theme, but **menu items** are determined by the modules and your application code. Just implement the `IMenuContributor` interface and **manipulate the menu items** in the `ConfigureMenuAsync` method. |
|||
|
|||
Menu contributors are executed whenever need to render the menu. There is already a menu contributor defined in the **application startup template**, so you can take it as an example and improve if necessary. See the [navigation menu](Navigation-Menu.md) document for more. |
|||
|
|||
### Toolbar Contributors |
|||
|
|||
[Toolbar system](Toolbars.md) is used to define **toolbars** on the user interface. Modules (or your application) can add **items** to a toolbar, then the theme renders the toolbar on the **layout**. |
|||
|
|||
There is only one **standard toolbar** (named "Main" - defined as a constant: `StandardToolbars.Main`). For the basic theme, it is rendered as shown below: |
|||
|
|||
In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here. |
|||
|
|||
#### Example: Add a Notification Icon |
|||
|
|||
In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project: |
|||
|
|||
 |
|||
|
|||
**NotificationViewComponent.cs** |
|||
|
|||
````csharp |
|||
public class NotificationViewComponent : AbpViewComponent |
|||
{ |
|||
public async Task<IViewComponentResult> InvokeAsync() |
|||
{ |
|||
return View("/Pages/Shared/Components/Notification/Default.cshtml"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
**Default.cshtml** |
|||
|
|||
````xml |
|||
<div id="MainNotificationIcon" style="color: white; margin: 8px;"> |
|||
<i class="far fa-bell"></i> |
|||
</div> |
|||
```` |
|||
|
|||
Now, we can create a class implementing the `IToolbarContributor` interface: |
|||
|
|||
````csharp |
|||
public class MyToolbarContributor : IToolbarContributor |
|||
{ |
|||
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|||
{ |
|||
if (context.Toolbar.Name == StandardToolbars.Main) |
|||
{ |
|||
context.Toolbar.Items |
|||
.Insert(0, new ToolbarItem(typeof(NotificationViewComponent))); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This class adds the `NotificationViewComponent` as the first item in the `Main` toolbar. |
|||
|
|||
Finally, you need to add this contributor to the `AbpToolbarOptions`, in the `ConfigureServices` of your module: |
|||
|
|||
````csharp |
|||
Configure<AbpToolbarOptions>(options => |
|||
{ |
|||
options.Contributors.Add(new MyToolbarContributor()); |
|||
}); |
|||
```` |
|||
|
|||
That's all, you will see the notification icon on the toolbar when you run the application: |
|||
|
|||
 |
|||
|
|||
`NotificationViewComponent` in this sample simply returns a view without any data. In real life, you probably want to **query database** (or call an HTTP API) to get notifications and pass to the view. If you need, you can add a `JavaScript` or `CSS` file to the global bundle (as described before) for your toolbar item. |
|||
|
|||
See the [toolbars document](Toolbars.md) for more about the toolbar system. |
|||
|
|||
### Layout Hooks |
|||
|
|||
[Layout Hooks](Layout-Hooks.md) system allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Then you can then add a **view component** into a hook point. |
|||
|
|||
#### Example: Add Google Analytics Script |
|||
|
|||
Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project: |
|||
|
|||
 |
|||
|
|||
**NotificationViewComponent.cs** |
|||
|
|||
````csharp |
|||
public class GoogleAnalyticsViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
**Default.cshtml** |
|||
|
|||
````html |
|||
<script> |
|||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
|||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
|||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
|||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
|||
|
|||
ga('create', 'UA-xxxxxx-1', 'auto'); |
|||
ga('send', 'pageview'); |
|||
</script> |
|||
```` |
|||
|
|||
Change `UA-xxxxxx-1` with your own code. |
|||
|
|||
You can then add this component to any of the hook points in the `ConfigureServices` of your module: |
|||
|
|||
````csharp |
|||
Configure<AbpLayoutHookOptions>(options => |
|||
{ |
|||
options.Add( |
|||
LayoutHooks.Head.Last, //The hook name |
|||
typeof(GoogleAnalyticsViewComponent) //The component to add |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
Now, the GA code will be inserted in the `head` of the page as the last item. You (or the modules you are using) can add multiple items to the same hook. All of them will be added to the layout. |
|||
|
|||
The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout: |
|||
|
|||
````csharp |
|||
Configure<AbpLayoutHookOptions>(options => |
|||
{ |
|||
options.Add( |
|||
LayoutHooks.Head.Last, |
|||
typeof(GoogleAnalyticsViewComponent), |
|||
layout: StandardLayouts.Application //Set the layout to add |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
See the layouts section below to learn more about the layout system. |
|||
|
|||
### Layouts |
|||
|
|||
Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts: |
|||
|
|||
* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc. |
|||
* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default. |
|||
* "**Empty**": Empty and minimal layout. |
|||
|
|||
These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are standard layout names and implemented by all the themes out of the box. |
|||
|
|||
#### Layout Location |
|||
|
|||
You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary. |
|||
|
|||
#### ITheme |
|||
|
|||
ABP Framework uses the `ITheme` service to get the layout location by the layout name. You can replace this service to dynamically select the layout location. |
|||
|
|||
#### IThemeManager |
|||
|
|||
`IThemeManager` is used to obtain the current theme and get the layout path. Any page can determine the layout of its own. Example: |
|||
|
|||
````html |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Theming |
|||
@inject IThemeManager ThemeManager |
|||
@{ |
|||
Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty); |
|||
} |
|||
```` |
|||
|
|||
This page will use the empty layout. You use `ThemeManager.CurrentTheme.GetEmptyLayout();` extension method as a shortcut. |
|||
|
|||
If you want to set the layout for all the pages under a specific folder, then write the code above in a `_ViewStart.cshtml` file under that folder. |
|||
@ -0,0 +1,3 @@ |
|||
# Layout Hooks |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
# Navigation Menu |
|||
|
|||
TODO |
|||
@ -0,0 +1,86 @@ |
|||
# Buttons |
|||
|
|||
## Introduction |
|||
|
|||
`abp-button` is the main element to create buttons. |
|||
|
|||
Basic usage: |
|||
|
|||
````xml |
|||
<abp-button button-type="Primary">Click Me</abp-button> |
|||
```` |
|||
|
|||
## Demo |
|||
|
|||
See the [buttons demo page](https://bootstrap-taghelpers.abp.io/Components/Buttons) to see it in action. |
|||
|
|||
## Attributes |
|||
|
|||
### button-type |
|||
|
|||
A value indicates the main style/type of the button. Should be one of the following values: |
|||
|
|||
* `Default` (default value) |
|||
* `Primary` |
|||
* `Secondary` |
|||
* `Success` |
|||
* `Danger` |
|||
* `Warning` |
|||
* `Info` |
|||
* `Light` |
|||
* `Dark` |
|||
* `Outline_Primary` |
|||
* `Outline_Secondary` |
|||
* `Outline_Success` |
|||
* `Outline_Danger` |
|||
* `Outline_Warning` |
|||
* `Outline_Info` |
|||
* `Outline_Light` |
|||
* `Outline_Dark` |
|||
* `Link` |
|||
|
|||
### size |
|||
|
|||
A value indicates the size of the button. Should be one of the following values: |
|||
|
|||
* `Default` (default value) |
|||
* `Small` |
|||
* `Medium` |
|||
* `Large` |
|||
* `Block` |
|||
* `Block_Small` |
|||
* `Block_Medium` |
|||
* `Block_Large` |
|||
|
|||
### busy-text |
|||
|
|||
A text that is shown when the button is busy. |
|||
|
|||
### text |
|||
|
|||
The text of the button. This is a shortcut if you simply want to set a text to the button. Example: |
|||
|
|||
````xml |
|||
<abp-button button-type="Primary" text="Click Me" /> |
|||
```` |
|||
|
|||
In this case, you can use a self-closing tag to make it shorter. |
|||
|
|||
### icon |
|||
|
|||
Used to set an icon for the button. It works with the [Font Awesome](https://fontawesome.com/) icon classes by default. Example: |
|||
|
|||
````xml |
|||
<abp-button icon="address-card" text="Address" /> |
|||
```` |
|||
|
|||
##### icon-type |
|||
|
|||
If you don't want to use font-awesome, you have two options: |
|||
|
|||
1. Set `icon-type` to `Other` and write the CSS class of the font icon you're using. |
|||
2. If you don't use a font icon use the opening and closing tags manually and write any code inside the tags. |
|||
|
|||
### disabled |
|||
|
|||
Set `true` to make the button initially disabled. |
|||
@ -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,26 @@ |
|||
# ABP Tag Helpers |
|||
|
|||
ABP Framework defines a set of **tag helper components** to simply the user interface development for ASP.NET Core (MVC / Razor Pages) applications. |
|||
|
|||
## Bootstrap Component Wrappers |
|||
|
|||
Most of the tag helpers are [Bootstrap](https://getbootstrap.com/) (v4+) wrappers. Coding bootstrap is not so easy, not so type-safe and contains too much repetitive HTML tags. ABP Tag Helpers makes it **easier** and **type safe**. |
|||
|
|||
We don't aim to wrap bootstrap components 100%. Writing **native bootstrap style code** is still possible (actually, tag helpers generates native bootstrap code in the end), but we suggest to use the tag helpers wherever possible. |
|||
|
|||
ABP Framework also adds some **useful features** to the standard bootstrap components. |
|||
|
|||
Here, the list of components those are wrapped by the ABP Framework: |
|||
|
|||
* [Buttons](Buttons.md) |
|||
* ... |
|||
|
|||
> Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples. |
|||
|
|||
## Form Elements |
|||
|
|||
See [demo](https://bootstrap-taghelpers.abp.io/Components/FormElements). |
|||
|
|||
## Dynamic Inputs |
|||
|
|||
See [demo](https://bootstrap-taghelpers.abp.io/Components/DynamicForms). |
|||
@ -0,0 +1,3 @@ |
|||
# ASP.NET Core MVC / Razor Pages Theming |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
# Toolbars |
|||
|
|||
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). |
|||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,140 @@ |
|||
# 自动API控制器 |
|||
|
|||
创建[应用程序服务](Application-Services.md)后, 通常需要创建API控制器以将此服务公开为HTTP(REST)API端点. 典型的API控制器除了将方法调用重定向到应用程序服务并使用[HttpGet],[HttpPost],[Route]等属性配置REST API之外什么都不做. |
|||
|
|||
ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制器. 大多数时候你不关心它的详细配置,但它可以完全被自定义. |
|||
|
|||
## 配置 |
|||
|
|||
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此示例代码配置包含类`BookStoreApplicationModule`的程序集中的所有应用程序服务.下图显示了[Swagger UI](https://swagger.io/tools/swagger-ui/)上的API内容. |
|||
|
|||
 |
|||
|
|||
### 例子 |
|||
|
|||
一些示例方法名称和按约定生成的相应路由: |
|||
|
|||
| 服务方法名称 | HTTP Method | 路由 | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| 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在确定服务方法的HTTP Method时使用命名约定: |
|||
|
|||
- **Get**: 如果方法名称以`GetList`,`GetAll`或`Get`开头. |
|||
- **Put**: 如果方法名称以`Put`或`Update`开头. |
|||
- **Delete**: 如果方法名称以`Delete`或`Remove`开头. |
|||
- **Post**: 如果方法名称以`Create`,`Add`,`Insert`或`Post`开头. |
|||
- **Patch**: 如果方法名称以`Patch`开头. |
|||
- 其他情况, **Post** 为 **默认方式**. |
|||
|
|||
如果需要为特定方法自定义HTTP Method, 则可以使用标准ASP.NET Core的属性([HttpPost], [HttpGet], [HttpPut]... 等等.). 这需要添加[Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core)的Nuget包. |
|||
|
|||
### 路由 |
|||
|
|||
路由根据一些惯例生成: |
|||
|
|||
* 它始终以 **/api**开头. |
|||
* 接着是**路由路径**. 默认值为"**/app**", 可以进行如下配置: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
然后获得一本书的路由将是'**/api/volosoft/book-store/book/{id}**'. 此示例使用两级根路径,但通常使用单个级别的深度. |
|||
|
|||
* 接着 **标准化控制器/服务名称**. 会删除`AppService`,`ApplicationService`和`Service`的后缀并将其转换为 **camelCase**. 如果你的应用程序服务类名称为`BookAppService`.那么它将变为`/book`. |
|||
* 如果要自定义命名, 则设置`UrlControllerNameNormalizer`选项. 它是一个委托允许你自定义每个控制器/服务的名称. |
|||
* 如果该方法具有 '**id**'参数, 则会在路由中添加'**/{id}**'. |
|||
* 如有必要,它会添加操作名称. 操作名称从服务上的方法名称获取并标准化; |
|||
* 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`. |
|||
* 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复. |
|||
* 将结果转换为**camelCase**. |
|||
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`. |
|||
* 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method. |
|||
* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId'). |
|||
|
|||
## 服务选择 |
|||
|
|||
创建的HTTP API控制器并不是应用服务所独有的功能. |
|||
|
|||
### IRemoteService 接口 |
|||
|
|||
如果一个类实现了`IRemoteService`接口, 那么它会被自动选择为API控制器. 由于应用程序服务本身实现了`IRemoteService`接口, 因此它自然就成为API控制器. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService`可用于将实现`IRemoteService`接口的类标记为远程服务或禁用它. 例如: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate 选项 |
|||
|
|||
你可以通过提供`TypePedicate`选项进一步过滤类以成为API控制器: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
如果你不想将此类型公开为API控制器, 则可以在类型检查时返回`false`. |
|||
|
|||
## API Explorer |
|||
|
|||
API Explorer是可以由客户端获取API结构的服务. Swagger使用它为endpoint创建文档和test UI. |
|||
|
|||
默认情况下, HTTP API控制器会自动启用API Explorer, 可以使用`RemoteService`按类或方法的级别控制它. 例如: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
禁用`IsMetadataEnabled`从而从API Explorer中隐藏此服务, 并且无法被发现. 但是它仍然可以被知道确切API路径/路由的客户端使用. |
|||
@ -0,0 +1,165 @@ |
|||
# 动态 C# API 客户端 |
|||
|
|||
ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 `HttpClient` 或者其他低级的HTTP功能调用远程服务并获取数据. |
|||
|
|||
## 服务接口 |
|||
|
|||
你的service或controller需要实现一个在服务端和客户端共享的接口.因此,首先需要在一个共享的类库项目中定义一个服务接口.例如: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
为了能自动被发现,你的接口需要实现`IRemoteService`接口.由于`IApplicationService`继承自`IRemoteService`接口.所以`IBookAppService`完全满足这个条件. |
|||
|
|||
在你的服务中实现这个类,你可以使用[auto API controller system](Auto-API-Controllers.md)将你的服务暴漏为一个REST API 端点. |
|||
|
|||
## 客户端代理生成 |
|||
|
|||
首先,将[Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget包添加到你的客户端项目中: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
然后给你的模块添加`AbpHttpClientModule`依赖: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //添加依赖 |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
现在,已经可以创建客户端代理了.例如: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //用来创建客户端代理 |
|||
typeof(BookStoreApplicationModule) //包含应用服务接口 |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//创建动态客户端代理 |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientproxies`方法获得一个程序集,找到这个程序集中所有的服务接口,创建并注册代理类. |
|||
|
|||
### Endpoint配置 |
|||
|
|||
`appsettings.json`文件中的`RemoteServices`节点被用来设置默认的服务地址.下面是最简单的配置: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
查看下面的"RemoteServiceOptions"章节获取更多详细配置. |
|||
|
|||
## 使用 |
|||
|
|||
可以很直接地使用.只需要在你的客户端程序中注入服务接口: |
|||
|
|||
````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}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
本例注入了上面定义的`IBookAppService`服务接口.当客户端调用服务方法的时候动态客户端代理就会创建一个HTTP调用. |
|||
|
|||
### IHttpClientProxy接口 |
|||
|
|||
你可以像上面那样注入`IBookAppService`来使用客户端代理,也可以注入`IHttpClientProxy<IBookAppService>`获取更多明确的用法.这种情况下你可以使用`IHttpClientProxy<T>`接口的`Service`属性. |
|||
|
|||
## 配置 |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### 多个远程服务端点 |
|||
|
|||
上面的例子已经配置了"Default"远程服务端点.你可能需要为不同的服务创建不同的端点.(就像在微服务方法中一样,每个微服务具有不同的端点).在这种情况下,你可以在你的配置文件中添加其他的端点: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies`方法有一个可选的参数来定义远程服务的名字: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName`参数会匹配通过`AbpRemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点. |
|||
|
|||
### 作为默认服务 |
|||
|
|||
当你为`IBookAppService`创建了一个服务代理,你可以直接注入`IBookAppService`来使用代理客户端(像上面章节中将的那样).你可以传递`asDefaultService:false`到`AddHttpClientProxies`方法来禁用此功能. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
如果你的程序中已经有一个服务的实现并且你不想用你的客户端代理重写或替换其他的实现,就需要使用`asDefaultServices:false` |
|||
|
|||
> 如果你禁用了`asDefaultService`,你只能使用`IHttpClientProxy<T>`接口去使用客户端代理.(参见上面的相关章节). |
|||
@ -0,0 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
|
|||
TODO |
|||
@ -0,0 +1,23 @@ |
|||
# JavaScript API |
|||
|
|||
ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api. |
|||
|
|||
## 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 @@ |
|||
# 自动API控制器 |
|||
文档已经移动到其他位置. |
|||
|
|||
创建[应用程序服务](Application-Services.md)后, 通常需要创建API控制器以将此服务公开为HTTP(REST)API端点. 典型的API控制器除了将方法调用重定向到应用程序服务并使用[HttpGet],[HttpPost],[Route]等属性配置REST API之外什么都不做. |
|||
|
|||
ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制器. 大多数时候你不关心它的详细配置,但它可以完全被自定义. |
|||
|
|||
## 配置 |
|||
|
|||
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示: |
|||
|
|||
````csharp |
|||
[DependsOn(BookStoreApplicationModule)] |
|||
public class BookStoreWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options |
|||
.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此示例代码配置包含类`BookStoreApplicationModule`的程序集中的所有应用程序服务.下图显示了[Swagger UI](https://swagger.io/tools/swagger-ui/)上的API内容. |
|||
|
|||
 |
|||
|
|||
### 例子 |
|||
|
|||
一些示例方法名称和按约定生成的相应路由: |
|||
|
|||
| 服务方法名称 | HTTP Method | 路由 | |
|||
| ----------------------------------------------------- | ----------- | -------------------------- | |
|||
| 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在确定服务方法的HTTP Method时使用命名约定: |
|||
|
|||
- **Get**: 如果方法名称以`GetList`,`GetAll`或`Get`开头. |
|||
- **Put**: 如果方法名称以`Put`或`Update`开头. |
|||
- **Delete**: 如果方法名称以`Delete`或`Remove`开头. |
|||
- **Post**: 如果方法名称以`Create`,`Add`,`Insert`或`Post`开头. |
|||
- **Patch**: 如果方法名称以`Patch`开头. |
|||
- 其他情况, **Post** 为 **默认方式**. |
|||
|
|||
如果需要为特定方法自定义HTTP Method, 则可以使用标准ASP.NET Core的属性([HttpPost], [HttpGet], [HttpPut]... 等等.). 这需要添加[Microsoft.AspNetCore.Mvc.Core](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Core)的Nuget包. |
|||
|
|||
### 路由 |
|||
|
|||
路由根据一些惯例生成: |
|||
|
|||
* 它始终以 **/api**开头. |
|||
* 接着是**路由路径**. 默认值为"**/app**", 可以进行如下配置: |
|||
|
|||
````csharp |
|||
Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.RootPath = "volosoft/book-store"; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
然后获得一本书的路由将是'**/api/volosoft/book-store/book/{id}**'. 此示例使用两级根路径,但通常使用单个级别的深度. |
|||
|
|||
* 接着 **标准化控制器/服务名称**. 会删除`AppService`,`ApplicationService`和`Service`的后缀并将其转换为 **camelCase**. 如果你的应用程序服务类名称为`BookAppService`.那么它将变为`/book`. |
|||
* 如果要自定义命名, 则设置`UrlControllerNameNormalizer`选项. 它是一个委托允许你自定义每个控制器/服务的名称. |
|||
* 如果该方法具有 '**id**'参数, 则会在路由中添加'**/{id}**'. |
|||
* 如有必要,它会添加操作名称. 操作名称从服务上的方法名称获取并标准化; |
|||
* 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`. |
|||
* 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复. |
|||
* 将结果转换为**camelCase**. |
|||
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`. |
|||
* 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method. |
|||
* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId'). |
|||
|
|||
## 服务选择 |
|||
|
|||
创建的HTTP API控制器并不是应用服务所独有的功能. |
|||
|
|||
### IRemoteService 接口 |
|||
|
|||
如果一个类实现了`IRemoteService`接口, 那么它会被自动选择为API控制器. 由于应用程序服务本身实现了`IRemoteService`接口, 因此它自然就成为API控制器. |
|||
|
|||
### RemoteService Attribute |
|||
|
|||
`RemoteService`可用于将实现`IRemoteService`接口的类标记为远程服务或禁用它. 例如: |
|||
|
|||
````csharp |
|||
[RemoteService(IsEnabled = false)] //or simply [RemoteService(false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
### TypePredicate 选项 |
|||
|
|||
你可以通过提供`TypePedicate`选项进一步过滤类以成为API控制器: |
|||
|
|||
````csharp |
|||
services.Configure<AbpAspNetCoreMvcOptions>(options => |
|||
{ |
|||
options.ConventionalControllers |
|||
.Create(typeof(BookStoreApplicationModule).Assembly, opts => |
|||
{ |
|||
opts.TypePredicate = type => { return true; }; |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
如果你不想将此类型公开为API控制器, 则可以在类型检查时返回`false`. |
|||
|
|||
## API Explorer |
|||
|
|||
API Explorer是可以由客户端获取API结构的服务. Swagger使用它为endpoint创建文档和test UI. |
|||
|
|||
默认情况下, HTTP API控制器会自动启用API Explorer, 可以使用`RemoteService`按类或方法的级别控制它. 例如: |
|||
|
|||
````csharp |
|||
[RemoteService(IsMetadataEnabled = false)] |
|||
public class PersonAppService : ApplicationService |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
|
|||
禁用`IsMetadataEnabled`从而从API Explorer中隐藏此服务, 并且无法被发现. 但是它仍然可以被知道确切API路径/路由的客户端使用. |
|||
[点击链接跳转到自动API控制器文档](../API/Auto-API-Controllers.md) |
|||
@ -1,354 +1,3 @@ |
|||
文档已经移动到其他位置. |
|||
|
|||
## ASP.NET Core MVC 捆绑 & 压缩 |
|||
|
|||
有许多方法可以捆绑&压缩客户端资源(JavaScript和CSS文件). 最常见的方式是: |
|||
|
|||
* 使用Visual Studio[捆绑&压缩](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier)扩展或者其它的[NuGet相关包](https://www.nuget.org/packages/BuildBundlerMinifier/). |
|||
|
|||
* 使用[Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/)及其插件. |
|||
|
|||
ABP内置了简单,动态,强大,模块化的方式. |
|||
|
|||
### Volo.Abp.AspNetCore.Mvc.UI.Bundling 包 |
|||
|
|||
> 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它. |
|||
|
|||
将`Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget包安装到你的项目中: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
然后将`AbpAspNetCoreMvcUiBundlingModule`依赖项添加到你的模块上: |
|||
|
|||
````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 |
|||
|
|||
创建bundle的最简单方法是使用`abp-script-bundle`或`abp-style-bundle` tag helpers. 例如: |
|||
|
|||
````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> |
|||
```` |
|||
|
|||
`abp-script-bundle`定义了一个带有**唯一名称**的样式包:`MyGlobalBundle`. 使用方法很容易理解. 让我们看看它是如何*工作的*: |
|||
|
|||
* 当首次请求时,ABP从提供的文件中 **(延迟)lazy** 创建. 后续将从 **缓存** 中返回内容. 这意味着如果你有条件地将文件添加到包中,它只执行一次, 并且条件的任何更改都不会影响下一个请求的包. |
|||
* 在`development`环境中ABP会将包文件**单独**添加到页面中, 其他环境(`staging`,`production`...)会自动捆绑和压缩. |
|||
* 捆绑文件可以是**物理**文件或[**虚拟/嵌入**](../Virtual-File-System.md)的文件. |
|||
* ABP自动将 **版本查询字符串(version query string)** 添加到捆绑文件的URL中,以防止浏览器缓存. 如:?_v=67872834243042(从文件的上次更改日期生成). 即使捆绑文件单独添加到页面(在`development`环境中), 版本控制仍然有效. |
|||
|
|||
#### 导入 Bundling Tag Helpers |
|||
|
|||
> 默认情况下已在启动模板导入. 大多数情况下,你不需要手动安装它. |
|||
|
|||
要使用`bundle tag helpers`, 你需要将其添加到`_ViewImports.cshtml`文件或页面中: |
|||
|
|||
```` |
|||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|||
```` |
|||
|
|||
#### 未命名的 Bundles |
|||
|
|||
对于razor bundle tag helpers, `name`是**可选**. 如果没有定义一个名字,它将根据使用的捆绑文件名自动**计算生成**(they are **concatenated** and **hashed**) 例: |
|||
|
|||
````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> |
|||
```` |
|||
|
|||
这将潜在地创建**两个不同的bundles**(一个包括`my-global-style.css`而另一个则不包括). |
|||
|
|||
**未命名的** bundles优点: |
|||
|
|||
* 可以**有条件地将项目**添加到捆绑包中. 但这可能会导致基于条件的捆绑的存在多种变化. |
|||
|
|||
**命名** bundles优点: |
|||
|
|||
* 其他模块可以通过其名称为捆绑包做出贡献(参见下面的部分). |
|||
|
|||
#### 单个文件 |
|||
|
|||
如果你只需要在页面中添加一个文件, 你可以使用`abp-script`或`abp-style`而不需要包含在`abp-script-bundle`或`abp-style-bundle`中. 例: |
|||
|
|||
````xml |
|||
<abp-script src="/scripts/my-script.js" /> |
|||
```` |
|||
|
|||
对于上面的示例,包名称将是 *scripts.my-scripts*("/"替换为"."). 所有捆绑功能也可以按预期应用于单个文件. |
|||
|
|||
### Bundling 选项 |
|||
|
|||
如果你需要在 **多个页面中使用相同的包** 或想要使用更多 **强大功能**, 你可以在[模块](../Module-Development-Basics.md)类中进行**配置**. |
|||
|
|||
#### 创建一个新的捆绑包 |
|||
|
|||
用法示例: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<BundlingOptions>(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" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> 你可以在脚本和样式包中使用相同的名称(*MyGlobalBundle*), 因为它们被添加到不同的集合(`ScriptBundles`和`StyleBundles`). |
|||
|
|||
在定义bundle之后, 可以使用上面定义的相同tag helpers将其包括在页面中. 例如: |
|||
|
|||
````html |
|||
<abp-script-bundle name="MyGlobalBundle" /> |
|||
```` |
|||
|
|||
这次tag helper定义中没有定义文件, 因为捆绑文件是由代码定义的. |
|||
|
|||
#### 配置现有的 Bundle |
|||
|
|||
ABP也支持[模块化](../Module-Development-Basics.md)捆绑. 模块可以修改由依赖模块创建的捆绑包. |
|||
例如: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyWebModule))] |
|||
public class MyWebExtensionModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<BundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddFiles( |
|||
"/scripts/my-extension-script.js" |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> 无法通过代码配置未命名的bundle tag helpers, 因为它们的名称在开发时是未知的. 建议始终使用bundle tag helper的名称. |
|||
|
|||
### Bundle 贡献者 |
|||
|
|||
将文件添加到现有bundle似乎很有用. 如果你需要**替换**bundle中的文件或者你想**有条件地**添加文件怎么办? 定义bundle贡献者可为此类情况提供额外的功能. |
|||
|
|||
一个bundle的贡献者使用自定义版本bootstrap.css替换示例: |
|||
|
|||
````C# |
|||
public class MyExtensionGlobalStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.ReplaceOne( |
|||
"/libs/bootstrap/css/bootstrap.css", |
|||
"/styles/extensions/bootstrap-customized.css" |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
然后你可以按照下面的代码使用这个贡献者: |
|||
|
|||
````C# |
|||
services.Configure<BundlingOptions>(options => |
|||
{ |
|||
options |
|||
.ScriptBundles |
|||
.Configure("MyGlobalBundle", bundle => { |
|||
bundle.AddContributors(typeof(MyExtensionGlobalStyleContributor)); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
贡献者也可以在bundle tag helpers中使用. |
|||
例如: |
|||
|
|||
````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`和`abp-script`标签可以使用`type`属性(而不是`src`属性), 如本示例所示. 添加bundle贡献者时, 其依赖关系也会自动添加到bundle中. |
|||
|
|||
#### 贡献者依赖关系 |
|||
|
|||
bundle贡献者可以与其他贡献者具有一个或多个依赖关系. |
|||
例如: |
|||
|
|||
````C# |
|||
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
添加bundle贡献者时,其依赖关系将 **自动并递归** 添加. **依赖顺序** 通过阻止 **重复** 添加的依赖关系. 即使它们处于分离的bundle中,也会阻止重复. ABP在页面中组织所有bundle并消除重复. |
|||
|
|||
创建贡献者和定义依赖关系是一种跨不同模块组织bundle创建的方法. |
|||
|
|||
|
|||
#### 贡献者扩展 |
|||
|
|||
在某些高级应用场景中, 当用到一个bundle贡献者时,你可能想做一些额外的配置. 贡献者扩展可以和被扩展的贡献者无缝衔接. |
|||
|
|||
下面的示例为 prism.js 脚本库添加一些样式: |
|||
|
|||
````csharp |
|||
public class MyPrismjsStyleExtension : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/prismjs/plugins/toolbar/prism-toolbar.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
然后你可以配置 `BundleContributorOptions` 去扩展已存在的 `PrismjsStyleBundleContributor`. |
|||
|
|||
````csharp |
|||
Configure<BundleContributorOptions>(options => |
|||
{ |
|||
options |
|||
.Extensions<PrismjsStyleBundleContributor>() |
|||
.Add<MyPrismjsStyleExtension>(); |
|||
}); |
|||
```` |
|||
|
|||
任何时候当 `PrismjsStyleBundleContributor` 被添加到bundle中时, `MyPrismjsStyleExtension` 也会被自动添加. |
|||
|
|||
#### 访问 IServiceProvider |
|||
|
|||
虽然很少需要它, 但是`BundleConfigurationContext`有一个`ServiceProvider`属性, 你可以在`ConfigureBundle`方法中解析服务依赖. |
|||
|
|||
#### 标准包装贡献者 |
|||
|
|||
将特定的NPM包资源(js,css文件)添加到包中对于该包非常简单. 例如, 你总是为bootstrap NPM包添加`bootstrap.css`文件. |
|||
|
|||
所有[标准NPM包](Client-Side-Package-Management.md)都有内置的贡献者. 例如,如果你的贡献者依赖于bootstrap,你可以声明它,而不是自己添加bootstrap.css. |
|||
|
|||
````C# |
|||
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency |
|||
public class MyExtensionStyleBundleContributor : BundleContributor |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
使用标准包的内置贡献者: |
|||
|
|||
* 防止你输入**无效的资源路径**. |
|||
* 如果资源 **路径发生变化** (依赖贡献者将处理它),则防止更改你的贡献者. |
|||
* 防止多个模块添加**重复文件**. |
|||
* 以递归方式管理依赖项(如果需要,添加依赖项的依赖项). |
|||
|
|||
##### Volo.Abp.AspNetCore.Mvc.UI.Packages 包 |
|||
|
|||
> 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它. |
|||
|
|||
标准包贡献者在`Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet包中定义. |
|||
将它安装到你的项目中: |
|||
|
|||
```` |
|||
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages |
|||
```` |
|||
|
|||
然后将`AbpAspNetCoreMvcUiPackagesModule`模块依赖项添加到你的模块中; |
|||
|
|||
````C# |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace MyCompany.MyProject |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] |
|||
public class MyWebModule : AbpModule |
|||
{ |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
#### Bundle 继承 |
|||
|
|||
在某些特定情况下, 可能需要从其他bundle创建一个 **新** bundle **继承**, 从bundle继承(递归)会继承该bundle的所有文件/贡献者. 然后派生的bundle可以添加或修改文件/贡献者**而无需修改**原始bundle. |
|||
例如: |
|||
|
|||
````c# |
|||
services.Configure<BundlingOptions>(options => |
|||
{ |
|||
options |
|||
.StyleBundles |
|||
.Add("MyTheme.MyGlobalBundle", bundle => { |
|||
bundle |
|||
.AddBaseBundles("MyGlobalBundle") //Can add multiple |
|||
.AddFiles( |
|||
"/styles/mytheme-global-styles.css" |
|||
); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
### 主题 |
|||
|
|||
主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md). |
|||
|
|||
### 最佳实践 & 建议 |
|||
|
|||
建议为应用程序定义多个包, 每个包用于不同的目的. |
|||
|
|||
* **全局包**: 应用程序中的每个页面都包含全局样式/脚本包. 主题已经定义了全局样式和脚本包. 你的模块可以为他们做出贡献. |
|||
* **布局包**: 这是针对单个布局的特定包. 仅包含在所有页面之间共享的资源使用布局. 使用bundling tag helpers创建捆绑包是一种很好的做法. |
|||
* **模块包**: 用于单个模块页面之间的共享资源. |
|||
* **页面包**: 为每个页面创建的特定包. 使用bundling tag helpers创建捆绑包作为最佳实践. |
|||
|
|||
在性能,网络带宽使用和捆绑包的数量之间建立平衡. |
|||
|
|||
### 参见 |
|||
|
|||
* [客户端包管理](Client-Side-Package-Management.md) |
|||
* [主题](Theming.md) |
|||
[点击链接跳转到ASP.NET Core MVC 捆绑 & 压缩文档](../UI/AspNetCore/Bundling-Minification.md) |
|||
@ -1,115 +1,3 @@ |
|||
文档已经移动到其他位置. |
|||
|
|||
## ASP.NET Core MVC 客户端包管理 |
|||
|
|||
ABP框架可以与任何类型的客户端包管理系统一起使用. 甚至你可以决定不使用包管理系统并手动管理依赖项. |
|||
|
|||
但是, ABP框架最适用于**NPM/Yarn**. 默认情况下,内置模块配置为与NPM/Yarn一起使用. |
|||
|
|||
最后, 我们建议[**Yarn**](https://classic.yarnpkg.com/)而不是NPM,因为它更快,更稳定并且与NPM兼容. |
|||
|
|||
### @ABP NPM Packages |
|||
|
|||
ABP是一个模块化平台. 每个开发人员都可以创建模块, 模块应该在**兼容**和**稳定**状态下协同工作. |
|||
|
|||
一个挑战是依赖NPM包的**版本**. 如果两个不同的模块使用相同的JavaScript库但其不同(并且可能不兼容)的版本会怎样. |
|||
|
|||
为了解决版本问题, 我们创建了一套**标准包**, 这取决于一些常见的第三方库. 一些示例包是[@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@ abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap)和[@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). 你可以从[Github存储库](https://github.com/volosoft/abp/tree/master/npm/packs)中查看**列表**. |
|||
|
|||
**标准包**的好处是: |
|||
|
|||
* 它取决于包装的**标准版本**. 取决于此包是**安全**,因为所有模块都依赖于相同的版本. |
|||
* 它包含将库资源(js,css,img...文件)从**node_modules**文件夹复制到**wwwroot/libs**文件夹的gulp任务. 有关更多信息, 请参阅 *映射库资源* 部分. |
|||
|
|||
依赖标准包装很容易. 只需像往常一样将它添加到**package.json**文件中. 例如: |
|||
|
|||
```` |
|||
{ |
|||
... |
|||
"dependencies": { |
|||
"@abp/bootstrap": "^1.0.0" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
建议依赖于标准软件包, 而不是直接依赖于第三方软件包. |
|||
|
|||
#### 安装包 |
|||
|
|||
依赖于NPM包后, 你应该做的就是从命令行运行**yarn**命令来安装所有包及其依赖项: |
|||
|
|||
```` |
|||
yarn |
|||
```` |
|||
|
|||
虽然你可以使用`npm install`,但如前所述,建议使用[Yarn](https://classic.yarnpkg.com/). |
|||
|
|||
#### 贡献包 |
|||
|
|||
如果你需要不在标准软件包中的第三方NPM软件包,你可以在Github[repository](https://github.com/volosoft/abp)上创建Pull请求. 接受遵循这些规则的拉取请求: |
|||
|
|||
* 对于NPM上的`package-name`, 包名称应该命名为`@abp/package-name`(例如:`bootstrap`包的`@abp/bootstrap`). |
|||
* 它应该是**最新的稳定**版本的包. |
|||
* 它应该只依赖于**单个**第三方包. 它可以依赖于多个`@abp/*`包. |
|||
* 包应包含一个`abp.resourcemapping.js`文件格式,如*映射库资源*部分中所定义. 此文件应仅映射所依赖包的资源. |
|||
* 你还需要为你创建的包创建[bundle贡献者](Bundling-Minification.md). |
|||
|
|||
有关示例, 请参阅当前标准包. |
|||
|
|||
### 映射库资源 |
|||
|
|||
使用NPM包和NPM/Yarn工具是客户端库的事实标准. NPM/Yarn工具在Web项目的根文件夹中创建一个**node_modules**文件夹. |
|||
|
|||
下一个挑战是将所需的资源(js,css,img ...文件)从`node_modules`复制到**wwwroot**文件夹内的文件夹中,以使其可供客户端/浏览器访问. |
|||
|
|||
ABP将基于[Gulp](https://gulpjs.com/)的任务定义为**将资源**从**node_modules**复制到**wwwroot/libs**文件夹. 每个**标准包**(参见*@ABP NPM Packages*部分)定义了自己文件的映射. 因此, 大多数情况你只配置依赖项. |
|||
|
|||
**启动模板**已经配置为开箱即用的所有这些. 本节将介绍配置选项. |
|||
|
|||
#### 资源映射定义文件 |
|||
|
|||
模块应该定义一个名为`abp.resourcemapping.js`的JavaScript文件,其格式如下例所示: |
|||
|
|||
````js |
|||
module.exports = { |
|||
aliases: { |
|||
"@node_modules": "./node_modules", |
|||
"@libs": "./wwwroot/libs" |
|||
}, |
|||
clean: [ |
|||
"@libs" |
|||
], |
|||
mappings: { |
|||
|
|||
} |
|||
} |
|||
```` |
|||
|
|||
* **aliases**部分定义了可在映射路径中使用的标准别名(占位符). **@node_modules**和 **@libs**是必需的(通过标准包), 你可以定义自己的别名以减少重复. |
|||
* **clean**部分是在复制文件之前要清理的文件夹列表. |
|||
* **mappings**部分是要复制的文件/文件夹的映射列表.此示例不会复制任何资源本身,但取决于标准包. |
|||
|
|||
示例映射配置如下所示: |
|||
|
|||
````js |
|||
mappings: { |
|||
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/", |
|||
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/" |
|||
} |
|||
```` |
|||
|
|||
#### 使用 Gulp |
|||
|
|||
正确配置`abp.resourcemapping.js`文件后, 可以从命令行运行gulp命令: |
|||
|
|||
```` |
|||
gulp |
|||
```` |
|||
|
|||
当你运行`gulp`时,所有包都会将自己的资源复制到**wwwroot/libs**文件夹中. 只有在**package.json**文件中对依赖项进行更改时, 才需要运行`yarn&gulp`. |
|||
|
|||
> 运行Gulp命令时, 使用package.json文件解析应用程序的依赖关系. Gulp任务自动发现并映射来自所有依赖项的所有资源(递归). |
|||
|
|||
#### 参见 |
|||
|
|||
* [捆绑 & 压缩](Bundling-Minification.md) |
|||
* [主题](Theming.md) |
|||
[点击链接跳转到ASP.NET Core MVC 客户端包管理文档](../UI/AspNetCore/Client-Side-Package-Management.md) |
|||
@ -1,165 +1,3 @@ |
|||
# 动态 C# API 客户端 |
|||
文档已经移动到其他位置. |
|||
|
|||
ABP可以自动创建C# API 客户端代理来调用远程HTTP服务(REST APIS).通过这种方式,你不需要通过 `HttpClient` 或者其他低级的HTTP功能调用远程服务并获取数据. |
|||
|
|||
## 服务接口 |
|||
|
|||
你的service或controller需要实现一个在服务端和客户端共享的接口.因此,首先需要在一个共享的类库项目中定义一个服务接口.例如: |
|||
|
|||
````csharp |
|||
public interface IBookAppService : IApplicationService |
|||
{ |
|||
Task<List<BookDto>> GetListAsync(); |
|||
} |
|||
```` |
|||
|
|||
为了能自动被发现,你的接口需要实现`IRemoteService`接口.由于`IApplicationService`继承自`IRemoteService`接口.所以`IBookAppService`完全满足这个条件. |
|||
|
|||
在你的服务中实现这个类,你可以使用[auto API controller system](Auto-API-Controllers.md)将你的服务暴漏为一个REST API 端点. |
|||
|
|||
## 客户端代理生成 |
|||
|
|||
首先,将[Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget包添加到你的客户端项目中: |
|||
|
|||
```` |
|||
Install-Package Volo.Abp.Http.Client |
|||
```` |
|||
|
|||
然后给你的模块添加`AbpHttpClientModule`依赖: |
|||
|
|||
````csharp |
|||
[DependsOn(typeof(AbpHttpClientModule))] //添加依赖 |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
} |
|||
```` |
|||
|
|||
现在,已经可以创建客户端代理了.例如: |
|||
|
|||
````csharp |
|||
[DependsOn( |
|||
typeof(AbpHttpClientModule), //用来创建客户端代理 |
|||
typeof(BookStoreApplicationModule) //包含应用服务接口 |
|||
)] |
|||
public class MyClientAppModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
//创建动态客户端代理 |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly |
|||
); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientproxies`方法获得一个程序集,找到这个程序集中所有的服务接口,创建并注册代理类. |
|||
|
|||
### Endpoint配置 |
|||
|
|||
`appsettings.json`文件中的`RemoteServices`节点被用来设置默认的服务地址.下面是最简单的配置: |
|||
|
|||
```` |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
查看下面的"RemoteServiceOptions"章节获取更多详细配置. |
|||
|
|||
## 使用 |
|||
|
|||
可以很直接地使用.只需要在你的客户端程序中注入服务接口: |
|||
|
|||
````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}"); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
本例注入了上面定义的`IBookAppService`服务接口.当客户端调用服务方法的时候动态客户端代理就会创建一个HTTP调用. |
|||
|
|||
### IHttpClientProxy接口 |
|||
|
|||
你可以像上面那样注入`IBookAppService`来使用客户端代理,也可以注入`IHttpClientProxy<IBookAppService>`获取更多明确的用法.这种情况下你可以使用`IHttpClientProxy<T>`接口的`Service`属性. |
|||
|
|||
## 配置 |
|||
|
|||
### RemoteServiceOptions |
|||
|
|||
默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpRemoteServiceOptions>(options => |
|||
{ |
|||
options.RemoteServices.Default = |
|||
new RemoteServiceConfiguration("http://localhost:53929/"); |
|||
}); |
|||
|
|||
//... |
|||
} |
|||
```` |
|||
|
|||
### 多个远程服务端点 |
|||
|
|||
上面的例子已经配置了"Default"远程服务端点.你可能需要为不同的服务创建不同的端点.(就像在微服务方法中一样,每个微服务具有不同的端点).在这种情况下,你可以在你的配置文件中添加其他的端点: |
|||
|
|||
````json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:53929/" |
|||
}, |
|||
"BookStore": { |
|||
"BaseUrl": "http://localhost:48392/" |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`AddHttpClientProxies`方法有一个可选的参数来定义远程服务的名字: |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
remoteServiceName: "BookStore" |
|||
); |
|||
```` |
|||
|
|||
`remoteServiceName`参数会匹配通过`AbpRemoteServiceOptions`配置的服务端点.如果`BookStore`端点没有定义就会使用默认的`Default`端点. |
|||
|
|||
### 作为默认服务 |
|||
|
|||
当你为`IBookAppService`创建了一个服务代理,你可以直接注入`IBookAppService`来使用代理客户端(像上面章节中将的那样).你可以传递`asDefaultService:false`到`AddHttpClientProxies`方法来禁用此功能. |
|||
|
|||
````csharp |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(BookStoreApplicationModule).Assembly, |
|||
asDefaultServices: false |
|||
); |
|||
```` |
|||
|
|||
如果你的程序中已经有一个服务的实现并且你不想用你的客户端代理重写或替换其他的实现,就需要使用`asDefaultServices:false` |
|||
|
|||
> 如果你禁用了`asDefaultService`,你只能使用`IHttpClientProxy<T>`接口去使用客户端代理.(参见上面的相关章节). |
|||
[点击链接跳转到动态 C# API 客户端文档](../API/Dynamic-CSharp-API-Clients.md) |
|||
@ -1,3 +1,3 @@ |
|||
# abp.auth JavaScript API |
|||
文档已经移动到其他位置. |
|||
|
|||
TODO |
|||
[点击链接跳转到JavaScript Auth文档](../../API/JavaScript-API/Auth.md) |
|||
|
|||
@ -1,24 +1,3 @@ |
|||
# JavaScript API |
|||
|
|||
ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api. |
|||
|
|||
## 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 |
|||
文档已经移动到其他位置. |
|||
|
|||
[点击链接跳转到JavaScript API文档](../../API/JavaScript-API/Index.md) |
|||
|
|||
@ -1,3 +1,3 @@ |
|||
## Dynamic Forms |
|||
文档已经移动到其他位置. |
|||
|
|||
目前还没有文档. 你现在可以看到[组件演示](http://bootstrap-taghelpers.abp.io/Components/DynamicForms). |
|||
[点击链接跳转到Dynamic Forms文档](../../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) |
|||
@ -1,3 +1,3 @@ |
|||
## ABP Tag Helpers |
|||
文档已经移动到其他位置. |
|||
|
|||
"ABP tag helpers" 文档还在创建中. 你现在可以参阅[组件演示](http://bootstrap-taghelpers.abp.io/). |
|||
[点击链接跳转到ABP Tag Helpers文档](../../UI/AspNetCore/Tag-Helpers/Index.md) |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
# Theming |
|||
|
|||
TODO |
|||
文档已经移动到其他位置. |
|||
|
|||
[点击链接跳转到Theming文档](../UI/AspNetCore/Theming.md) |
|||
@ -1,274 +1,4 @@ |
|||
# 小部件 |
|||
|
|||
ABP为创建**可重用的部件**提供了模型和基础设施. 部件系统是[ASP.NET Core ViewComponents](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components)的扩展. 在你有以下需求时,小部件会非常有用; |
|||
文档已经移动到其他位置. |
|||
|
|||
* 在可复用的 **[模块](../Module-Development-Basics.md)** 中定义部件. |
|||
* 在部件中引用 **scripts & styles** 脚本. |
|||
* 使用部件创建 **[仪表盘](Dashboards.md)**. |
|||
* 支持 **[授权](../Authorization.md)** 与 **[捆绑`bundling`](Bundling-Minification.md)** 的部件 |
|||
|
|||
## 基本部件定义 |
|||
|
|||
### 创建一个视图组件 |
|||
|
|||
第一部,创建一个新的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(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
继承 `AbpViewComponent` 不是必需的. 你也可以继承ASP.NET Core的 `ViewComponent`. `AbpViewComponent` 只是定义了一些基本的实用属性. |
|||
|
|||
**Default.cshtml**: |
|||
|
|||
```xml |
|||
<div class="my-simple-widget"> |
|||
<h2>My Simple Widget</h2> |
|||
<p>This is a simple widget!</p> |
|||
</div> |
|||
``` |
|||
|
|||
### 定义部件 |
|||
|
|||
添加 `Widget` attribute 到 `MySimpleWidgetViewComponent` 类,将此视图组件标记为部件: |
|||
|
|||
````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(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## 渲染部件 |
|||
|
|||
渲染部件的用法是ASP.NET Core的标准用法. 在razor view/page中使用 `Component.InvokeAsync` 方法, 就像渲染一个View Component一样. 例如: |
|||
|
|||
````xml |
|||
@await Component.InvokeAsync("MySimpleWidget") |
|||
@await Component.InvokeAsync(typeof(MySimpleWidgetViewComponent)) |
|||
```` |
|||
|
|||
第一行代码使用名称渲染了部件,第二行代码使用type渲染了View Comonent. |
|||
|
|||
## 部件名称 |
|||
|
|||
默认下名称是根据View Conponent组件的名称计算的, 比如你的视图组件名是 `MySimpleWidgetViewComponent`, 那么部件的名称就是 `MySimpleWidget` (删除`ViewComponent`后缀). 这与ASP.NET Core的默认视图组件名称的方式一样. |
|||
|
|||
想要自定义组件名称,只需要使用ASP.NET Core的 `ViewComponent` attribute: |
|||
|
|||
```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会通过自定义的名称去处理部件. |
|||
|
|||
> 如果视图组件名与视图组件的文件夹名称不匹配,那么需要像本例中那样去手动编写视图路径. |
|||
|
|||
### 显示名称 |
|||
|
|||
你还可以定义对于使用者友好的本地化显示名称. 需要时在UI中使用显示名称. 显示名称是可选的,在 `Widget` attribute 的`DisplayName`属性中定义: |
|||
|
|||
````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(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
参阅 [本地化文档](../Localization.md) 学习关于本地化资源的更多内容. |
|||
|
|||
## 引用 Style & Script |
|||
|
|||
当部件含有样式和scirpt文件时,会存在一些挑战; |
|||
|
|||
* 使用部件的页面应该将 **script & styles** 文件引用到页面中. |
|||
* 页面还需要解析部件的 `依赖库/文件`. |
|||
|
|||
将资源与部件正确的关联在一起时,ABP会解决这些问题. 使用正确的方法,就不用担心部件的依赖关系. |
|||
|
|||
### 定义一个简单的文件路径 |
|||
|
|||
下面的示例中部件添加了样式和scirpt文件: |
|||
|
|||
````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会考虑到这些依赖关系, 在view/page中使用正确的方法添加部件 . 样式和script可以是物理文件也可以是虚拟文件. 它于[虚拟文件系统](../Virtual-File-System.md)完全集成]. |
|||
|
|||
### 定义 Bundle |
|||
|
|||
页面中使用的组件的所有资源都做为捆绑包添加(如果没有其他配置,会在生产中合并和压缩). 除了简单的添加文件,你还可以充分的利用捆绑功能. |
|||
|
|||
下面的示例与上面的代码相同,但是在添加文件时文件路径替换成了 `BundleContributor`: |
|||
|
|||
````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"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
捆绑系统非常强大,如果你的部件使用了JavaScript库来呈现图表, 你可以将它声明为依赖项, 如果之前未添加JavaScript库. 则会自动添加到页面中. 使用这种方式让页面使用部件时不用关心依赖项. |
|||
|
|||
参阅 [捆包&压缩 文档](Bundling-Minification.md) 了解更多内容. |
|||
|
|||
## 授权 |
|||
|
|||
某些组件可能只对通过身份验证或授权的用户可用,这时可以使用 `Widget` attribute 的以下属性: |
|||
|
|||
* `RequiresAuthentication` (`bool`): 设置为true,只有通过身份验证的用户(登录用户)可用. |
|||
* `RequiredPolicies` (`List<string>`): 授权用户的策略名称列表. 有关策略的详细信息请参阅[授权文档](../Authorization.md). |
|||
|
|||
示例: |
|||
|
|||
````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(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## 部件选项 |
|||
|
|||
`AbpWidgetOptions` 是 `Widget` attribute 替代, 你可以使用它去配置部件: |
|||
|
|||
```csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets.Add<MySimpleWidgetViewComponent>(); |
|||
}); |
|||
``` |
|||
|
|||
将上面的代码写到[模块](../Module-Development-Basics.md)的 `ConfigureServices` 方法中. `AbpWidgetOptions` 可以完成 `Widget` attribute 的所有功能. 比如为组件添加样式: |
|||
|
|||
````csharp |
|||
Configure<AbpWidgetOptions>(options => |
|||
{ |
|||
options.Widgets |
|||
.Add<MySimpleWidgetViewComponent>() |
|||
.WithStyles("/Pages/Components/MySimpleWidget/Default.css"); |
|||
}); |
|||
```` |
|||
|
|||
> 提示: `AbpWidgetOptions` 还可以更改现有的部件配置. 如果要修改应用程序使用的模块内的组件配置,这会很有用. 使用 `options.Widgets.Find` 获取现有的 `WidgetDefinition`. |
|||
[点击链接跳转到小部件文档](../UI/AspNetCore/Widgets.md) |
|||
@ -0,0 +1,3 @@ |
|||
## Creating a Settings Tab |
|||
|
|||
TODO... |
|||
@ -0,0 +1,3 @@ |
|||
# Component Replacement |
|||
|
|||
TODO... |
|||
@ -0,0 +1,3 @@ |
|||
# Angular用户界面自定义指南 |
|||
|
|||
* [替换组件](Component-Replacement.md) |
|||