Browse Source

Merge branch 'rel-4.3' of https://github.com/abpframework/abp into rel-4.3

pull/8506/head
Alper Ebicoglu 6 years ago
parent
commit
29699ff48a
  1. 2
      docs/en/CLI-New-Command-Samples.md
  2. 3
      docs/en/Customizing-Application-Modules-Guide.md
  3. 10
      docs/en/Getting-Started-Create-Solution.md
  4. 6
      docs/en/Getting-Started-Running-Solution.md
  5. 5
      docs/en/Index.md
  6. 6
      docs/en/Tutorials/Part-2.md
  7. 257
      docs/en/Tutorials/Todo/Index.md
  8. BIN
      docs/en/Tutorials/Todo/run-without-iisexpress.png
  9. BIN
      docs/en/Tutorials/images/vs-run-without-iisexpress.png
  10. 2
      docs/en/UI/Angular/Service-Proxies.md
  11. 128
      docs/en/UI/Blazor/Data-Table-Column-Extensions.md
  12. 109
      docs/en/UI/Blazor/Entity-Action-Extensions.md
  13. 89
      docs/en/UI/Blazor/Page-Header.md
  14. 126
      docs/en/UI/Blazor/Page-Toolbar-Extensions.md
  15. BIN
      docs/en/images/data-table-colum-extension-blazor-component-render-solution.png
  16. BIN
      docs/en/images/data-table-colum-extension-blazor-component-render.png
  17. BIN
      docs/en/images/page-header-toolbar-blazor.png
  18. BIN
      docs/en/images/page-toolbar-button-blazor.png
  19. BIN
      docs/en/images/page-toolbar-custom-component-blazor.png
  20. BIN
      docs/en/images/table-column-extension-example-blazor.png
  21. BIN
      docs/en/images/user-action-blazor-extension-click-me.png
  22. BIN
      docs/en/images/user-action-extension-on-blazor-project.png
  23. 3
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs
  24. 41
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor
  25. 12
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor.cs
  26. 23
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs
  27. 2
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs
  28. 14
      modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs
  29. 7
      modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs
  30. 16
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js
  31. 1
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs
  32. 1
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs
  33. 7
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs
  34. 143
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
  35. 5
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
  36. 5
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json
  37. 1
      modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj
  38. 5
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs
  39. 23
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs
  40. 38
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs
  41. 9
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostChangedEvent.cs
  42. 20
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs
  43. 1
      modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/SettingManagementApplicationModule.cs
  44. 1
      templates/app/angular/src/app/home/home.component.scss
  45. 1
      templates/app/angular/src/app/home/home.component.ts
  46. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Controllers/AccountController.cs
  47. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor
  48. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor

2
docs/en/CLI-New-Command-Samples.md

@ -167,7 +167,7 @@ ABP CLI always uses the latest version. In order to create a solution from a pre
## Choose database management system
The default database management system (DBMS) is `Entity Framework Core` / ` SQL Server`. You can choose a DBMS by passing `--database-management-system` parameter. Accepted values are `SqlServer`, `MySQL`, `SQLite`, `Oracle-Devart`, `PostgreSQL`. The default value is `SqlServer`.
The default database management system (DBMS) is `Entity Framework Core` / ` SQL Server`. You can choose a DBMS by passing `--database-management-system` parameter. [Accepted values](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseManagementSystem.cs) are `SqlServer`, `MySQL`, `SQLite`, `Oracle`, `Oracle-Devart`, `PostgreSQL`. The default value is `SqlServer`.
* Angular UI, **PostgreSQL** database, creates the project in a new folder:

3
docs/en/Customizing-Application-Modules-Guide.md

@ -75,6 +75,7 @@ There are some low level systems that you can control entity actions, table colu
Entity action extension system allows you to add a new action to the action menu for an entity on the user interface;
* [Entity Action Extensions for ASP.NET Core UI](UI/AspNetCore/Entity-Action-Extensions.md)
* [Entity Action Extensions for Blazor UI](UI/Blazor/Entity-Action-Extensions.md)
* [Entity Action Extensions for Angular](UI/Angular/Entity-Action-Extensions.md)
#### Data Table Column Extensions
@ -82,6 +83,7 @@ Entity action extension system allows you to add a new action to the action menu
Data table column extension system allows you to add a new column in the data table on the user interface;
* [Data Table Column Extensions for ASP.NET Core UI](UI/AspNetCore/Data-Table-Column-Extensions.md)
* [Data Table Column Extensions for Blazor UI](UI/Blazor/Data-Table-Column-Extensions.md)
* [Data Table Column Extensions for Angular](UI/Angular/Data-Table-Column-Extensions.md)
#### Page Toolbar
@ -89,6 +91,7 @@ Data table column extension system allows you to add a new column in the data ta
Page toolbar system allows you to add components to the toolbar of a page;
* [Page Toolbar Extensions for ASP.NET Core UI](UI/AspNetCore/Page-Toolbar-Extensions.md)
* [Page Toolbar Extensions for Blazor UI](UI/Blazor/Page-Toolbar-Extensions.md)
* [Page Toolbar Extensions for Angular](UI/Angular/Page-Toolbar-Extensions.md)
#### Others

10
docs/en/Getting-Started-Create-Solution.md

@ -15,7 +15,7 @@
We will use the ABP CLI to create a new ABP project.
> Alternatively, you can **create and download** projects from [ABP Framework website](https://abp.io/get-started) by easily selecting the all the options from the page.
> Alternatively, you can **create and download** projects from the [ABP Framework website](https://abp.io/get-started) by easily selecting all options from the page.
Use the `new` command of the ABP CLI to create a new project:
@ -41,6 +41,12 @@ abp new Acme.BookStore{{if UI == "NG"}} -u angular{{else if UI == "Blazor"}} -u
> [ABP CLI document](./CLI.md) covers all of the available commands and options.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
### The Solution Structure
The solution has a layered structure (based on the [Domain Driven Design](Domain-Driven-Design.md)) and contains unit & integration test projects. See the [application template document](Startup-Templates/Application.md) to understand the solution structure in details.
@ -49,7 +55,7 @@ The solution has a layered structure (based on the [Domain Driven Design](Domain
#### MongoDB Transactions
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable the it in the *YourProjectMongoDbModule* class's `ConfigureServices` method:
The [startup template](Startup-templates/Index.md) **disables** transactions in the `.MongoDB` project by default. If your MongoDB server supports transactions, you can enable it in the *YourProjectMongoDbModule* class's `ConfigureServices` method:
```csharp
Configure<AbpUnitOfWorkDefaultOptions>(options =>

6
docs/en/Getting-Started-Running-Solution.md

@ -193,12 +193,6 @@ It may take a longer time for the first build. Once it finishes, it opens the An
Enter **admin** as the username and **1q2w3E*** as the password to login to the application. The application is up and running. You can start developing your application based on this startup template.
## Mobile Development
If you want to include a [React Native](https://reactnative.dev/) project in your solution, add `-m react-native` (or `--mobile react-native`) argument to project creation command. This is a basic React Native startup template to develop mobile applications integrated to your ABP based backends.
See the [Getting Started with the React Native](Getting-Started-React-Native.md) document to learn how to configure and run the React Native application.
## See Also
* [Web Application Development Tutorial](Tutorials/Part-1.md)

5
docs/en/Index.md

@ -4,8 +4,9 @@ ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to
## Getting Started
* [Getting Started Guide](Getting-Started.md) is the easiest way to start a new web application with the ABP Framework.
* [Web Application Development Tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack web application.
* [Quick Start](Tutorials/Todo/Index.md) is a single-part, quick-start tutorial to build a simple application with the ABP Framework. Start with this tutorial if you want to quickly understand how ABP works.
* [Getting Started](Getting-Started.md) guide can be used to create and run ABP based solutions with different options and details.
* [Web Application Development Tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack web application with all aspects of a real-life solution.
### UI Framework Options

6
docs/en/Tutorials/Part-2.md

@ -447,7 +447,11 @@ For more information, see the [RoutesService document](../UI/Angular/Modifying-t
[ABP CLI](../CLI.md) provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your HTTP APIs from the client side. Before running `generate-proxy` command, your host must be up and running.
Run the following command in the `angular` folder:
> **Warning**: There is a problem with IIS Express; it doesn't allow to connect to the application from another process. If you are using Visual Studio, select the `Acme.BookStore.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below:
![vs-run-without-iisexpress](images/vs-run-without-iisexpress.png)
Once the host application is running, execute the following command in the `angular` folder:
```bash
abp generate-proxy

257
docs/en/Tutorials/Todo/Index.md

@ -3,7 +3,8 @@
````json
//[doc-params]
{
"UI": ["MVC", "Blazor"]
"UI": ["MVC", "Blazor", "BlazorServer", "NG"],
"DB": ["EF", "Mongo"]
}
````
@ -15,39 +16,59 @@ You can find source code of the completed application [here](https://github.com/
## Pre-Requirements
### IDE
* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 5.0+](https://dotnet.microsoft.com/download/dotnet) development.
You will need to an IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 5.0+](https://dotnet.microsoft.com/download/dotnet) development.
{{if DB=="Mongo"}}
### ABP CLI
* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/)
We will use the ABP CLI to create new solutions with the ABP Framework. You can run the following command in a command-line terminal to install it:
{{end}}
{{if UI=="NG"}}
* [Node v14.x](https://nodejs.org/)
{{end}}
## Creating a New Solution
We will use the [ABP CLI](../../CLI.md) to create new solutions with the ABP Framework. You can run the following command in a command-line terminal to install it:
````bash
dotnet tool install -g Volo.Abp.Cli
````
## Creating a New Solution
Create an empty folder, open a command-line terminal and execute the following command in the terminal:
Then create an empty folder, open a command-line terminal and execute the following command in the terminal:
````bash
abp new TodoApp{{if UI=="Blazor"}} -u blazor{{end}}
abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}}
````
This will create a new solution, named *TodoApp*. Open the solution in your favorite IDE.
{{if UI=="NG"}}
This will create a new solution, named *TodoApp* with `angular` and `aspnet-core` folders. Once the solution is ready, open the ASP.NET Core solution in your favorite IDE.
{{else}}
This will create a new solution, named *TodoApp*. Once the solution is ready, open it in your favorite IDE.
{{end}}
### Create the Database
If you are using Visual Studio, right click to the `TodoApp.DbMigrator` project, select *Set as StartUp Project*, then hit *Ctrl+F5* to run it without debugging. It will create the initial database and seed the initial data.
{{if DB=="EF"}}
> Some IDEs (e.g. Rider) may have problems for the first run since *DbMigrator* adds the initial migration and re-compiles the project. In this case, open a command-line terminal in the folder of the `.DbMigrator` project and execute the `dotnet run` command.
{{end}}
### Run the Application
{{if UI=="MVC"}}
{{if UI=="MVC" || UI=="BlazorServer"}}
It is good to run the application before starting the development. Ensure the `TodoApp.Web` project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the initial UI:
It is good to run the application before starting the development. Ensure the {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the initial UI:
{{else if UI=="Blazor"}}
@ -62,6 +83,33 @@ Ensure the `TodoApp.HttpApi.Host` project is the startup project, then run the a
You can explore and test your HTTP API with this UI. Now, we can set the `TodoApp.Blazor` as the startup project and run it to open the actual Blazor application UI:
{{else if UI=="NG"}}
It is good to run the application before starting the development. The solution has two main applications;
* `TodoApp.HttpApi.Host` (in the .NET solution) host the server-side HTTP API.
* `angular` folder contains the Angular application.
Ensure the `TodoApp.HttpApi.Host` project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the server-side HTTP API on the [Swagger UI](https://swagger.io/tools/swagger-ui/):
![todo-swagger-ui-initial](todo-swagger-ui-initial.png)
You can explore and test your HTTP API with this UI. If that works, we can run the Angular client application.
First, run the following command to restore the NPM packages;
````bash
npm install
````
It will take some time to install all the packages. Then you can run the application using the following command:
````bash
npm start
````
This command takes time, but eventually runs and opens the application in your default browser:
{{end}}
![todo-ui-initial](todo-ui-initial.png)
@ -91,6 +139,8 @@ namespace TodoApp
## Database Integration
{{if DB=="EF"}}
Next step is to setup the [Entity Framework Core](../../Entity-Framework-Core.md) configuration.
### Mapping Configuration
@ -137,7 +187,28 @@ You can apply changes to the database using the following command, in the same c
dotnet ef database update
````
> If you are using Visual Studio, you may want to use `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="Blazor"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC.
> If you are using Visual Studio, you may want to use `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC.
{{else if DB=="Mongo"}}
Next step is to setup the [MongoDB](../../MongoDB.md) configuration. Open the `TodoAppMongoDbContext` class in the `MongoDb` folder of the *TodoApp.MongoDB* project and make the following changes;
1. Add a new property to the class:
````csharp
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>();
````
2. Add the following code inside the `CreateModel` method:
````csharp
modelBuilder.Entity<TodoItem>(b =>
{
b.CollectionName = "TodoItems";
});
````
{{end}}
Now, we can use ABP repositories to save and retrieve todo items, as we'll do in the next section.
@ -212,7 +283,7 @@ namespace TodoApp
_todoItemRepository = todoItemRepository;
}
// TODO: Implement the methods
// TODO: Implement the methods here...
}
}
````
@ -445,7 +516,7 @@ If you open the [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering t
![todo-api](todo-api.png)
{{else if UI=="Blazor"}}
{{else if UI=="Blazor" || UI=="BlazorServer"}}
### Index.razor.cs
@ -490,8 +561,12 @@ namespace TodoApp.Blazor.Pages
This class uses the `ITodoAppService` to perform operations for the todo items. It manipulates the `TodoItems` list after create and delete operations. In this way, we don't need to refresh the whole todo list from the server.
{{if UI=="Blazor"}}
See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how we could inject and use the application service interface from the Blazor application which is running on the browser! But now, let's continue and complete the application.
{{end # Blazor}}
### Index.razor
Open the `Index.razor` file in the `Pages` folder of the *TodoApp.Blazor* project and replace the content with the following code block:
@ -570,6 +645,8 @@ This is a simple styling for the todo page. We believe that you can do much bett
Now, you can run the application again to see the result.
{{if UI=="Blazor"}}
### Dynamic C# Proxies & Auto API Controllers
In the `Index.razor.cs` file, we've injected (with the `[Inject]` attribute) and used the `ITodoAppService` just like using a local service. Remember that the Blazor application is running on the browser while the implementation of this application service is running on the server.
@ -582,6 +659,156 @@ If you run the `TodoApp.HttpApi.Host` application, you can see the Todo API:
![todo-api](todo-api.png)
{{end # Blazor}}
{{else if UI=="NG"}}
### Service Proxy Generation
ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server.
You first need to run the `TodoApp.HttpApi.Host` project since the proxy generator reads API definitions from the server application.
> **Warning**: There is a problem with IIS Express; it doesn't allow to connect to the application from another process. If you are using Visual Studio, select the `TodoApp.HttpApi.Host` instead of IIS Express in the run button drop-down list, as shown in the figure below:
![run-without-iisexpress](run-without-iisexpress.png)
Once you run the `TodoApp.HttpApi.Host` project, open a command-line terminal in the `angular` folder and type the following command:
````bash
abp generate-proxy
````
If everything goes well, it should generate an output like shown below:
````bash
CREATE src/app/proxy/generate-proxy.json (170978 bytes)
CREATE src/app/proxy/README.md (1000 bytes)
CREATE src/app/proxy/todo.service.ts (794 bytes)
CREATE src/app/proxy/models.ts (66 bytes)
CREATE src/app/proxy/index.ts (58 bytes)
````
We can then use the `todoService` to use the server-side HTTP APIs, as we'll do in the next section.
### home.component.ts
Open the `/angular/src/app/home/home.component.ts` file and replace its content with the following code block:
````js
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { TodoItemDto, TodoService } from '@proxy';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
todoItems: TodoItemDto[];
newTodoText: string;
constructor(
private todoService: TodoService,
private toasterService: ToasterService)
{ }
ngOnInit(): void {
this.todoService.getList().subscribe(response => {
this.todoItems = response;
});
}
create(): void{
this.todoService.create(this.newTodoText).subscribe((result) => {
this.todoItems = this.todoItems.concat(result);
this.newTodoText = null;
});
}
delete(id: string): void {
this.todoService.delete(id).subscribe(() => {
this.todoItems = this.todoItems.filter(item => item.id !== id);
this.toasterService.info('Deleted the todo item.');
});
}
}
````
We've used the `todoService` to get the list of todo items and assigned the returning value to the `todoItems` array. We've also added `create` and `delete` methods. These methods will be used in the view side.
### home.component.html
Open the `/angular/src/app/home/home.component.html` file and replace its content with the following code block:
````html
<div class="container">
<div class="card">
<div class="card-header">
<div class="card-title">TODO LIST</div>
</div>
<div class="card-body">
<!-- FORM FOR NEW TODO ITEMS -->
<form class="form-inline" (ngSubmit)="create()">
<input
name="NewTodoText"
type="text"
[(ngModel)]="newTodoText"
class="form-control mr-2"
placeholder="enter text..."
/>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<!-- TODO ITEMS LIST -->
<ul id="TodoList">
<li *ngFor="let todoItem of todoItems">
<i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {%{{{ todoItem.text }}}%}
</li>
</ul>
</div>
</div>
</div>
````
### home.component.scss
As the final touch, open the `/angular/src/app/home/home.component.scss` file and replace its content with the following code block:
````css
#TodoList{
list-style: none;
margin: 0;
padding: 0;
}
#TodoList li {
padding: 5px;
margin: 5px 0px;
border: 1px solid #cccccc;
background-color: #f5f5f5;
}
#TodoList li i
{
opacity: 0.5;
}
#TodoList li i:hover
{
opacity: 1;
color: #ff0000;
cursor: pointer;
}
````
This is a simple styling for the todo page. We believe that you can do much better :)
Now, you can run the application again to see the result.
{{end}}
## Conclusion

BIN
docs/en/Tutorials/Todo/run-without-iisexpress.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Tutorials/images/vs-run-without-iisexpress.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

2
docs/en/UI/Angular/Service-Proxies.md

@ -10,6 +10,8 @@ To avoid manual effort, we might use a tool like [NSWAG](https://github.com/Rico
ABP introduces an endpoint that exposes server-side method contracts. When the `generate-proxy` command is run, ABP CLI makes an HTTP request to this endpoint and generates better-aligned client proxies in TypeScript. It organizes folders according to namespaces, adds barrel exports, and reflects method signatures in Angular services.
> Before you start, please make sure you start the backend application with `dotnet run`. There is a [known limitation about Visual Studio](#known-limitations), so please do not run the project using its built-in web server.
Run the following command in the **root folder** of the angular application:
```bash

128
docs/en/UI/Blazor/Data-Table-Column-Extensions.md

@ -0,0 +1,128 @@
# Data Table Column Extensions for Blazor UI
Data table column extension system allows you to add a **new table column** on the user interface. The example below adds a new column with the "Email Confirmed" title:
![datatable-column-extension-](../../images/table-column-extension-example-blazor.png)
You can use the standard column options to fine control the table column.
> Note that this is a low level API to find control the table column. If you want to show an extension property on the table, see the [module entity extension](../../Module-Entity-Extensions.md) document.
## How to Set Up
### Create a C# File
First, add a new C# file to your solution. We added inside the `/Pages/Identity/` folder of the `.Blazor` project:
![user-action-extension-on-solution](../../images/user-action-extension-on-blazor-project.png)
We will use the [component override system](Customization-Overriding-Components.md) in the Blazor. After creating a class inherits from the `UserManagement` component, we will override the `SetTableColumnsAsync` method and add the table column programmatically.
Here, the content of the overridden `SetTableColumnsAsync` method.
```csharp
protected override async ValueTask SetTableColumnsAsync()
{
await base.SetTableColumnsAsync();
var confirmedColumn = new TableColumn
{
Title = "Email Confirmed",
Data = nameof(IdentityUserDto.EmailConfirmed)
};
TableColumns.Get<UserManagement>().Add(confirmedColumn);
}
```
Here, the entire content of the file.
```csharp
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Blazor.Pages.Identity;
namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity
{
[ExposeServices(typeof(UserManagement))]
[Dependency(ReplaceServices = true)]
public class CustomizedUserManagement : UserManagement
{
protected override async ValueTask SetTableColumnsAsync()
{
await base.SetTableColumnsAsync();
var confirmedColumn = new TableColumn
{
Title = "Email Confirmed",
Data = nameof(IdentityUserDto.EmailConfirmed)
};
TableColumns.Get<UserManagement>().Add(confirmedColumn);
}
}
}
```
## Customizing Data Table Columns
This section explains how to customize data table columns using the properties in the `TableColumn` type.
* `Title`: Title of the column.
* `Data`: Name of the field in the supplied model.
* `Component`: Type of the component that you want to render. See the "Rendering Custom Components In The Data Table Columns" section for details.
* `Actions`: Action lists for the column. You can render additional action columns by adding actions to this collection.
* `ValueConverter`: Simple converter function that is being called before rendering the content.
* `Sortable`: A boolean field indicating whether the column is sortable or not.
* `DisplayFormat`: You can specify a custom format for the column.
* `DisplayFormatProvider` : You can provide a custom `IFormatProvider` for the column. Default value is `CultureInfo.CurrentCulture`.
## Rendering Custom Components In The Data Table Columns
This section explains how to render custom blazor components in data table columns. In this example, we're going to display custom icons instead of text representations of the property.
First of all, create a blazor component. We will name it `CustomTableColumn`.
![data-table-colum-extension-blazor-component-render-solution](../../images/data-table-colum-extension-blazor-component-render-solution.png)
Add an object parameter named `Data`.
```csharp
public class CustomTableColumn
{
[Parameter]
public object Data { get; set; }
}
```
Navigate to the razor file and paste the following code.
```csharp
@using System
@using Volo.Abp.Identity
@if (Data.As<IdentityUserDto>().EmailConfirmed)
{
<Icon class="text-success" Name="IconName.Check" />
}
else
{
<Icon class="text-danger" Name="IconName.Times" />
}
```
Navigate back to the `CustomizedUserManagement` class, and use `Component` property to specify the custom blazor component.
```csharp
protected override async ValueTask SetTableColumnsAsync()
{
await base.SetTableColumnsAsync();
var confirmedColumn = new TableColumn
{
Title = "Email Confirmed",
Component = typeof(CustomTableColumn)
};
TableColumns.Get<UserManagement>().Add(confirmedColumn);
}
```
Run the project and you will see the icons instead of text fields.
![data-table-colum-extension-blazor-component-render](../../images/data-table-colum-extension-blazor-component-render.png)

109
docs/en/UI/Blazor/Entity-Action-Extensions.md

@ -0,0 +1,109 @@
# Entity Action Extensions for Blazor UI
Entity action extension system allows you to add a **new action** to the action menu for an entity. A **Click Me** action was added to the *User Management* page below:
![user-action-extension-click-me](../../images/user-action-blazor-extension-click-me.png)
You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can access to the current entity in your code.
## How to Set Up
In this example, we will add a "Click Me!" action and execute a C# code for the user management page of the [Identity Module](../../Modules/Identity.md).
### Create a C# File
First, add a new C# file to your solution. We added inside the `/Pages/Identity/` folder of the `.Blazor` project:
![user-action-extension-on-solution](../../images/user-action-extension-on-blazor-project.png)
We will use the [component override system](Customization-Overriding-Components.md) in the Blazor. After creating a class inherits from the `UserManagement` component, we will override the `SetToolbarItemsAsync` method and add the entity action programmatically.
Here, the content of the overridden `SetToolbarItemsAsync` method.
```csharp
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
var clickMeAction = new EntityAction()
{
Text = "Click Me!",
Clicked = (data) =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}
};
EntityActions.Get<UserManagement>().Add(clickMeAction);
}
```
In the `Clicked` property, you can do anything you need.
Here, the entire content of the file.
```csharp
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity.Blazor.Pages.Identity;
namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity
{
[ExposeServices(typeof(UserManagement))]
[Dependency(ReplaceServices = true)]
public class CustomizedUserManagement : UserManagement
{
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
var clickMeAction = new EntityAction()
{
Text = "Click Me!",
Clicked = (data) =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}
};
EntityActions.Get<UserManagement>().Add(clickMeAction);
}
}
}
```
## Customizing Entity Actions
This section explains how to customize entity actions using the properties in the `EntityAction` type.
Here, the list of the properties that you use in the `EntityAction`.
* `Text` : Entity action text.
* `Clicked` : Click event handler for the action. You can use the `data` parameter to access the selected item in the `DataGrid`.
* `Icon` : Icon for the action.
* `Color` : Color for the action.
* `Visible`: Visible function to determine the actions' visibility based on the data grid items individually. You can make the action invisible for some data grid items. You can also use the `data` parameter to access the selected item in the `DataGrid`.
* `Confirmation`: Confirmation message for the action. You can use the `data` parameter to access the selected item in the `DataGrid`.
#### Example
```csharp
var clickMeAction = new EntityAction()
{
Text = "Click Me!",
Clicked = (data) =>
{
//TODO: Write your custom code
return Task.CompletedTask;
},
Color = Blazorise.Color.Danger,
Icon = "fas fa-hand-point-right",
ConfirmationMessage = (data) => "Are you sure you want to click to the action?",
Visible = (data) =>
{
//TODO: Write your custom visibility action
//var selectedUser = data.As<IdentityUserDto>();
}
};
```

89
docs/en/UI/Blazor/Page-Header.md

@ -1,3 +1,90 @@
# Blazor UI: Page Header
TODO
You can use the`PageHeader` component to set the page title, the breadcrumb items and the toolbar items for a page. Before using the `PageHeader` component, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.Layout` namespace.
Once you add the `PageHeader` component to your page, you can control the related values using the parameters.
## Page Title
You can use the `Title` parameter to control the page header.
```csharp
<PageHeader Title="Book List">
</PageHeader>
```
## Breadcrumb
> **The [Basic Theme](Basic-Theme.md) currently doesn't implement the breadcrumbs.**
Breadcrumbs can be added using the `BreadcrumbItems` property.
**Example: Add Language Management to the breadcrumb items.**
Create a collection of `Volo.Abp.BlazoriseUI.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter.
```csharp
public partial class Index
{
protected List<BreadcrumbItem> BreadcrumbItems { get; } = new();
protected override void OnInitialized()
{
BreadcrumbItems.Add(new BreadcrumbItem("Language Management"));
}
}
```
Navigate back to the razor page.
```csharp
<PageHeader BreadcrumbItems="@BreadcrumbItems" />
```
The theme then renders the breadcrumb. An example render result can be:
![breadcrumbs-example](../../images/breadcrumbs-example.png)
* The Home icon is rendered by default. Set `BreadcrumbShowHome` to `false` to hide it.
* Breadcrumb items will be activated based on current navigation. Set `BreadcrumbShowCurrent` to `false` to disable it.
You can add as many items as you need. `BreadcrumbItem` constructor gets three parameters:
* `text`: The text to show for the breadcrumb item.
* `url` (optional): A URL to navigate to, if the user clicks to the breadcrumb item.
* `icon` (optional): An icon class (like `fas fa-user-tie` for Font-Awesome) to show with the `text`.
## Page Toolbar
Page toolbar can be set using the `Toolbar` property.
**Example: Add a "New Item" toolbar item to the page toolbar.**
Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method.
```csharp
public partial class Index
{
protected PageToolbar Toolbar { get; } = new();
protected override void OnInitialized()
{
Toolbar.AddButton("New Item", () =>
{
//Write your click action here
return Task.CompletedTask;
}, icon:IconName.Add);
}
}
```
Navigate back to the razor page and set the `Toolbar` parameter.
```csharp
<PageHeader Toolbar="@Toolbar" />
```
An example render result can be:
![breadcrumbs-example](../../images/page-header-toolbar-blazor.png)

126
docs/en/UI/Blazor/Page-Toolbar-Extensions.md

@ -0,0 +1,126 @@
# Page Toolbar Extensions for Blazor UI
Page toolbar system allows you to add components to the toolbar of any page. The page toolbar is the area right to the header of a page. A button ("Import users from excel") was added to the user management page below:
![page-toolbar-button](../../images/page-toolbar-button-blazor.png)
You can add any type of view component item to the page toolbar or modify existing items.
## How to Set Up
In this example, we will add an "Import users from excel" button and execute a C# code for the user management page of the [Identity Module](../../Modules/Identity.md).
### Create a C# File
First, add a new C# file to your solution. We added inside the `/Pages/Identity/` folder of the `.Blazor` project:
![user-action-extension-on-solution](../../images/user-action-extension-on-blazor-project.png)
We will use the [component override system](Customization-Overriding-Components.md) in the Blazor. After creating a class inherits from the `UserManagement` component, we will override the `SetToolbarItemsAsync` method and add the toolbar item programmatically.
Here, the content of the overridden `SetToolbarItemsAsync` method.
```csharp
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.AddButton("Import users from excel", () =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}, "file-import", Blazorise.Color.Secondary);
}
```
> In order to use the `AddButton` extension method, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars` namespace.
Here, the entire content of the file.
```csharp
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity.Blazor.Pages.Identity;
namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity
{
[ExposeServices(typeof(UserManagement))]
[Dependency(ReplaceServices = true)]
public class CustomizedUserManagement : UserManagement
{
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.AddButton("Import users from excel", () =>
{
//TODO: Write your custom code
return Task.CompletedTask;
}, "file-import", Blazorise.Color.Secondary);
}
}
}
```
When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `Order` to set the order of the button component relative to the other components).
## Advanced Use Cases
While you typically want to add a button action to the page toolbar, it is possible to add any type of blazor component.
### Add A Blazor Component to a Page Toolbar
First, create a new blazor component in your project:
![page-toolbar-custom-component-blazor](../../images/page-toolbar-custom-component-blazor.png)
For this example, we've created a `MyToolbarComponent` component under the `/Pages/Identity/` folder.
`MyToolbarComponent.razor` content:
````csharp
<Button Color="Color.Dark">CLICK ME</Button>
````
We will leave the `MyToolbarComponent.razor.cs` file empty.
Then you can add the `MyToolbarComponent` to the user management page toolbar:
````csharp
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.AddComponent<MyToolbarComponent>();
}
````
* If your component accepts parameters, you can pass them as key/value pairs using the `arguments` parameter.
#### Permissions
If your button/component should be available based on a [permission/policy](../../Authorization.md), you can pass the permission/policy name as the `RequiredPolicyName` parameter to the `AddButton` and `AddComponent` methods.
### Add a Page Toolbar Contributor
If you perform advanced custom logic while adding an item to a page toolbar, you can create a class that implements the `IPageToolbarContributor` interface or inherits from the `PageToolbarContributor` class:
````csharp
public class MyToolbarContributor : PageToolbarContributor
{
public override Task ContributeAsync(PageToolbarContributionContext context)
{
context.Items.Insert(0, new PageToolbarItem(typeof(MyToolbarComponent)));
return Task.CompletedTask;
}
}
````
* You can use `context.ServiceProvider` to resolve dependencies if you need.
Then add your class to the `Contributors` list:
````csharp
protected override async ValueTask SetToolbarItemsAsync()
{
await base.SetToolbarItemsAsync();
Toolbar.Contributors.Add(new PageContributor());
}
````

BIN
docs/en/images/data-table-colum-extension-blazor-component-render-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
docs/en/images/data-table-colum-extension-blazor-component-render.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/images/page-header-toolbar-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/images/page-toolbar-button-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/en/images/page-toolbar-custom-component-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
docs/en/images/table-column-extension-example-blazor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/images/user-action-blazor-extension-click-me.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/en/images/user-action-extension-on-blazor-project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

3
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs

@ -1,6 +1,7 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Globalization;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns
@ -11,11 +12,13 @@ namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns
public string Data { get; set; }
[CanBeNull]
public string DisplayFormat { get; set; }
public IFormatProvider DisplayFormatProvider { get; set; } = CultureInfo.CurrentCulture;
[CanBeNull]
public Type Component { get; set; }
public List<EntityAction> Actions { get; set; }
[CanBeNull]
public Func<object,string> ValueConverter { get; set; }
public bool Sortable { get; set; }
public TableColumn()
{

41
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor

@ -25,11 +25,11 @@
if (action.ConfirmationMessage != null)
{
<EntityAction TItem="TItem"
Color="@(action.Color!=null ? (Blazorise.Color)action.Color : Blazorise.Color.Primary)"
Color="@(action.Color != null ? (Blazorise.Color) action.Color : Blazorise.Color.Primary)"
Icon="@action.Icon"
Clicked="async () => await action.Clicked(context)"
ConfirmationMessage="() => action.ConfirmationMessage.Invoke(context)"
Visible="@(action.Visible!=null ? action.Visible(context) : true)"
Visible="@(action.Visible != null ? action.Visible(context) : true)"
Text="@action.Text">
</EntityAction>
}
@ -37,9 +37,9 @@
{
<EntityAction TItem="TItem"
Clicked="async () => await action.Clicked(context)"
Color="@(action.Color!=null ? (Blazorise.Color)action.Color : Blazorise.Color.None)"
Color="@(action.Color != null ? (Blazorise.Color) action.Color : Blazorise.Color.None)"
Icon="@action.Icon"
Visible="@(action.Visible!=null ? action.Visible(context) : true)"
Visible="@(action.Visible != null ? action.Visible(context) : true)"
Text="@action.Text">
</EntityAction>
}
@ -62,7 +62,26 @@
{
if (!ExtensionPropertiesRegex.IsMatch(column.Data))
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title"/>
@if (column.ValueConverter == null)
{
<DataGridColumn TItem="TItem"
Field="@column.Data"
Caption="@column.Title"
Sortable="@column.Sortable"
DisplayFormat="@column.DisplayFormat"
DisplayFormatProvider="@column.DisplayFormatProvider"/>
}
else
{
<DataGridColumn TItem="TItem"
Field="@column.Data"
Caption="@column.Title"
Sortable="@column.Sortable">
<DisplayTemplate>
@(GetConvertedFieldValue(context, column))
</DisplayTemplate>
</DataGridColumn>
}
}
else
{
@ -87,14 +106,7 @@
{
if (column.ValueConverter != null)
{
if (column.DisplayFormat == null)
{
@(column.ValueConverter(propertyValue))
}
else
{
@(string.Format(column.DisplayFormat, column.ValueConverter(propertyValue)))
}
@(GetConvertedFieldValue(context, column))
}
else
{
@ -104,10 +116,9 @@
}
else
{
@(string.Format(column.DisplayFormat, propertyValue))
@(string.Format(column.DisplayFormatProvider, column.DisplayFormat, propertyValue))
}
}
}
}
</DisplayTemplate>

12
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor.cs

@ -46,5 +46,17 @@ namespace Volo.Abp.BlazoriseUI.Components
builder.CloseComponent();
};
}
protected virtual string GetConvertedFieldValue(TItem item, TableColumn columnDefinition)
{
var convertedValue = columnDefinition.ValueConverter.Invoke(item);
if (!columnDefinition.DisplayFormat.IsNullOrEmpty())
{
return string.Format(columnDefinition.DisplayFormatProvider, columnDefinition.DisplayFormat,
convertedValue);
}
return convertedValue;
}
}
}

23
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs

@ -76,6 +76,29 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
"/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json"
);
//MyCompanyName.MyProjectName.Blazor.Server
ChangeProjectReference(
context,
"/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj",
"EntityFrameworkCore.DbMigrations",
"MongoDB"
);
ChangeNamespaceAndKeyword(
context,
"/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs",
"MyCompanyName.MyProjectName.EntityFrameworkCore",
"MyCompanyName.MyProjectName.MongoDB",
"MyProjectNameEntityFrameworkCoreDbMigrationsModule",
"MyProjectNameMongoDbModule"
);
ChangeConnectionStringToMongoDb(
context,
"/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/appsettings.json"
);
//MyCompanyName.MyProjectName.HttpApi.HostWithIds
ChangeProjectReference(

2
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs

@ -18,5 +18,7 @@ namespace Volo.Blogging.Admin.Blogs
Task<BlogDto> UpdateAsync(Guid id, UpdateBlogDto input);
Task DeleteAsync(Guid id);
Task ClearCacheAsync(Guid id);
}
}

14
modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs

@ -3,18 +3,22 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Caching;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Admin.Blogs
{
public class BlogManagementAppService : BloggingAdminAppServiceBase, IBlogManagementAppService
{
private readonly IBlogRepository _blogRepository;
public BlogManagementAppService(IBlogRepository blogRepository)
private readonly IDistributedCache<List<PostCacheItem>> _postsCache;
public BlogManagementAppService(IBlogRepository blogRepository, IDistributedCache<List<PostCacheItem>> postsCache)
{
_blogRepository = blogRepository;
_postsCache = postsCache;
}
public async Task<ListResultDto<BlogDto>> GetListAsync()
@ -63,5 +67,11 @@ namespace Volo.Blogging.Admin.Blogs
{
await _blogRepository.DeleteAsync(id);
}
[Authorize(BloggingPermissions.Blogs.ClearCache)]
public async Task ClearCacheAsync(Guid id)
{
await _postsCache.RemoveAsync(id.ToString());
}
}
}

7
modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs

@ -54,5 +54,12 @@ namespace Volo.Blogging.Admin
{
await _blogManagementAppService.DeleteAsync(id);
}
[HttpGet]
[Route("clear-cache/{id}")]
public async Task ClearCacheAsync(Guid id)
{
await _blogManagementAppService.ClearCacheAsync(id);
}
}
}

16
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js

@ -52,6 +52,22 @@
});
},
},
{
text: l("ClearCache"),
visible: abp.auth.isGranted(
'Blogging.Blog.ClearCache'
),
confirmMessage: function (data) {
return l("ClearCacheConfirmationMessage");
},
action: function (data) {
volo.blogging.admin.blogManagement
.clearCache(data.record.id)
.then(function () {
_dataTable.ajax.reload();
})
}
}
],
},
},

1
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs

@ -15,6 +15,7 @@ namespace Volo.Blogging
blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit"));
blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete"));
blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create"));
blogs.AddChild(BloggingPermissions.Blogs.ClearCache, L("Permission:ClearCache"));
var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts"));
posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit"));

1
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs

@ -13,6 +13,7 @@ namespace Volo.Blogging
public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
public const string ClearCache = Default + ".ClearCache";
}
public static class Posts

7
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs

@ -20,6 +20,13 @@ namespace Volo.Blogging
CreateMap<Post, PostWithDetailsDto>().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount).Ignore(x=>x.Tags);
CreateMap<Comment, CommentWithDetailsDto>().Ignore(x => x.Writer);
CreateMap<Tag, TagDto>();
CreateMap<Post, PostCacheItem>().Ignore(x=>x.CommentCount).Ignore(x=>x.Tags);
CreateMap<PostCacheItem, PostWithDetailsDto>()
.IgnoreModificationAuditedObjectProperties()
.IgnoreDeletionAuditedObjectProperties()
.Ignore(x=>x.Writer)
.Ignore(x => x.CommentCount)
.Ignore(x => x.Tags);
}
}
}

143
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.EventBus.Local;
using Volo.Blogging.Comments;
using Volo.Blogging.Tagging;
using Volo.Blogging.Tagging.Dtos;
@ -20,41 +21,92 @@ namespace Volo.Blogging.Posts
private readonly IPostRepository _postRepository;
private readonly ITagRepository _tagRepository;
private readonly ICommentRepository _commentRepository;
private readonly IDistributedCache<ListResultDto<PostWithDetailsDto>> _postsCache;
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService, IDistributedCache<ListResultDto<PostWithDetailsDto>> postsCache)
private readonly IDistributedCache<List<PostCacheItem>> _postsCache;
private readonly ILocalEventBus _localEventBus;
public PostAppService(
IPostRepository postRepository,
ITagRepository tagRepository,
ICommentRepository commentRepository,
IBlogUserLookupService userLookupService,
IDistributedCache<List<PostCacheItem>> postsCache,
ILocalEventBus localEventBus
)
{
UserLookupService = userLookupService;
_postRepository = postRepository;
_tagRepository = tagRepository;
_commentRepository = commentRepository;
_postsCache = postsCache;
_localEventBus = localEventBus;
}
public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName)
{
var cacheKey = id.ToString() + "-" + tagName;
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
return await _postsCache.GetOrAddAsync(
cacheKey,
async () => await GetPostsByBlogIdAndTagName(id, tagName),
() => new DistributedCacheEntryOptions
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
}
);
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId)
{
return await _postsCache.GetOrAddAsync(
var postCacheItems = await _postsCache.GetOrAddAsync(
blogId.ToString(),
async () => await GetTimeOrderedPostsAsync(blogId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
);
var postsWithDetails = ObjectMapper.Map<List<PostCacheItem>, List<PostWithDetailsDto>>(postCacheItems);
foreach (var post in postsWithDetails)
{
if (post.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(post.CreatorId.Value);
if (creatorUser != null)
{
post.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
}
return new ListResultDto<PostWithDetailsDto>(postsWithDetails);
}
public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input)
@ -107,6 +159,7 @@ namespace Volo.Blogging.Posts
await _commentRepository.DeleteOfPost(id);
await _postRepository.DeleteAsync(id);
await PublishPostChangedEventAsync(post.BlogId);
}
[Authorize(BloggingPermissions.Posts.Update)]
@ -128,6 +181,7 @@ namespace Volo.Blogging.Posts
var tagList = SplitTags(input.Tags);
await SaveTags(tagList, post);
await PublishPostChangedEventAsync(post.BlogId);
return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}
@ -153,66 +207,16 @@ namespace Volo.Blogging.Posts
var tagList = SplitTags(input.Tags);
await SaveTags(tagList, post);
await PublishPostChangedEventAsync(post.BlogId);
return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetPostsByBlogIdAndTagName(Guid id, string tagName)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedPostsAsync(Guid blogId)
private async Task<List<PostCacheItem>> GetTimeOrderedPostsAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
return ObjectMapper.Map<List<Post>, List<PostCacheItem>>(posts);
}
private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null)
@ -300,5 +304,14 @@ namespace Volo.Blogging.Posts
return Task.FromResult(filteredPostDtos);
}
private async Task PublishPostChangedEventAsync(Guid blogId)
{
await _localEventBus.PublishAsync(
new PostChangedEvent
{
BlogId = blogId
});
}
}
}

5
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json

@ -12,6 +12,7 @@
"Permission:Posts": "Posts",
"Permission:Tags": "Tags",
"Permission:Comments": "Comments",
"Permission:ClearCache": "Clear cache",
"Title": "Title",
"Delete": "Delete",
"Reply": "Reply",
@ -53,6 +54,8 @@
"Blogs": "Blogs",
"Tags": "Tags",
"ShareOn": "Share on",
"TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!"
"TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!",
"ClearCache": "Clear cache",
"ClearCacheConfirmationMessage": "Are you sure you want to clear the cache?"
}
}

5
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json

@ -12,6 +12,7 @@
"Permission:Posts": "Yazılar",
"Permission:Tags": "Etiketler",
"Permission:Comments": "Yorumlar",
"Permission:ClearCache": "Önbelleği temizle",
"Title": "Başlık",
"Delete": "Sil",
"Reply": "Yanıtla",
@ -53,6 +54,8 @@
"Blogs": "Bloglar",
"Tags": "Etiketler",
"ShareOn": "Paylaş",
"TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!"
"TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!",
"ClearCache": "Önbelleği temizle",
"ClearCacheConfirmationMessage": "Önbelleği temizlemek istediğinizden emin misiniz?"
}
}

1
modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj

@ -15,6 +15,7 @@
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.Domain\Volo.Abp.Users.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
</ItemGroup>
</Project>

5
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;
@ -13,7 +14,9 @@ namespace Volo.Blogging
[DependsOn(
typeof(BloggingDomainSharedModule),
typeof(AbpDddDomainModule),
typeof(AbpAutoMapperModule))]
typeof(AbpAutoMapperModule),
typeof(AbpCachingModule)
)]
public class BloggingDomainModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

23
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace Volo.Blogging.Posts
{
public class PostCacheInvalidator : ILocalEventHandler<PostChangedEvent>, ITransientDependency
{
protected IDistributedCache<List<PostCacheItem>> Cache { get; }
public PostCacheInvalidator(IDistributedCache<List<PostCacheItem>> cache)
{
Cache = cache;
}
public virtual async Task HandleEventAsync(PostChangedEvent post)
{
await Cache.RemoveAsync(post.BlogId.ToString());
}
}
}

38
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Blogging.Tagging;
using Volo.Blogging.Users;
namespace Volo.Blogging.Posts
{
[Serializable]
public class PostCacheItem : ICreationAuditedObject
{
public Guid Id { get; set; }
public Guid BlogId { get; set; }
public string Title { get; set; }
public string CoverImage { get; set; }
public string Url { get; set; }
public string Content { get; set; }
public string Description { get; set; }
public int ReadCount { get; set; }
public int CommentCount { get; set; }
public List<Tag> Tags { get; set; }
public Guid? CreatorId { get; set; }
public DateTime CreationTime { get; set; }
}
}

9
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostChangedEvent.cs

@ -0,0 +1,9 @@
using System;
namespace Volo.Blogging.Posts
{
public class PostChangedEvent
{
public Guid BlogId { get; set; }
}
}

20
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -568,10 +568,24 @@ namespace Volo.Docs.Pages.Documents.Project
UserPreferences.Remove(key + "_Value");
}
var values = DocumentPreferences?.Parameters?.FirstOrDefault(p => p.Name == key)?.Values;
if (values == null)
{
continue;
}
if (!values.Any(v => v.Key == value))
{
var defaultValue = values.FirstOrDefault();
UserPreferences.Add(key, defaultValue.Key);
UserPreferences.Add(key + "_Value", defaultValue.Value);
continue;
}
UserPreferences.Add(key, value);
UserPreferences.Add(key + "_Value",
DocumentPreferences?.Parameters?.FirstOrDefault(p => p.Name == key)?.Values
.FirstOrDefault(v => v.Key == value).Value);
UserPreferences.Add(key + "_Value", values.FirstOrDefault(v => v.Key == value).Value);
}
if (DocumentPreferences?.Parameters == null)

1
modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/SettingManagementApplicationModule.cs

@ -6,7 +6,6 @@ namespace Volo.Abp.SettingManagement
[DependsOn(
typeof(AbpSettingManagementDomainModule),
typeof(AbpSettingManagementApplicationContractsModule),
typeof(AbpSettingManagementDomainModule),
typeof(AbpEmailingModule)
)]
public class AbpSettingManagementApplicationModule : AbpModule

1
templates/app/angular/src/app/home/home.component.scss

@ -0,0 +1 @@
/* Styles for the home component */

1
templates/app/angular/src/app/home/home.component.ts

@ -5,6 +5,7 @@ import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
get hasLoggedIn(): boolean {

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Controllers/AccountController.cs

@ -1,6 +1,6 @@
using Volo.Abp.AspNetCore.Mvc.Authentication;
namespace MyCompanyName.MyProjectName.Web.Controllers
namespace MyCompanyName.MyProjectName.Blazor.Server.Tiered.Controllers
{
public class AccountController : ChallengeAccountController
{

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor

@ -12,6 +12,11 @@
<h1>Welcome to the Application</h1>
<p class="lead px-lg-5 mx-lg-5">@L["LongWelcomeMessage"]</p>
@if (!CurrentUser.IsAuthenticated)
{
<a class="btn btn-primary" href="/Account/Login" ><i class="fa fa-sign-in"></i> @L["Login"]</a>
}
</div>
<div class="my-3 text-center">

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor

@ -12,6 +12,11 @@
<h1>Welcome to the Application</h1>
<p class="lead px-lg-5 mx-lg-5">@L["LongWelcomeMessage"]</p>
@if (!CurrentUser.IsAuthenticated)
{
<a class="btn btn-primary" href="/Account/Login" ><i class="fa fa-sign-in"></i> @L["Login"]</a>
}
</div>
<div class="my-3 text-center">

Loading…
Cancel
Save