Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/4431/head
Armağan Ünlü 6 years ago
parent
commit
93db313669
  1. 2
      docs/en/Blob-Storing.md
  2. 133
      docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md
  3. 180
      docs/en/Distributed-Event-Bus.md
  4. 9
      docs/en/Event-Bus.md
  5. 226
      docs/en/Local-Event-Bus.md
  6. 3
      docs/en/RabbitMq.md
  7. 2
      docs/en/Repositories.md
  8. 13
      docs/en/docs-nav.json
  9. 133
      docs/zh-Hans/Repositories.md
  10. 3
      docs/zh-Hans/SignalR-Integration.md
  11. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs
  12. 34
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
  13. 2
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs
  14. 15
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs
  15. 9
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs
  16. 117
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs
  17. 6
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs
  18. 3
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionary.cs
  19. 2
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionaryItem.cs
  20. 9
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs
  21. 74
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs
  22. 2
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs
  23. 5
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs
  24. 4
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs
  25. 22
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs
  26. 12
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json
  27. 36
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs
  28. 219
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml
  29. 3
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs
  30. 3
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.css
  31. 71
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js
  32. 1
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.min.css
  33. 6
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.scss
  34. 8
      modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj
  35. 6
      modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json
  36. 63
      modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json.defaults
  37. 4
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs
  38. 23
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs
  39. 103
      modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs
  40. 124
      modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs
  41. 4
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs
  42. 2
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs
  43. 9
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs
  44. 15
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs
  45. 4
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs
  46. 4
      modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
  47. 7
      npm/ng-packs/packages/core/src/lib/services/list.service.ts
  48. 13
      npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts
  49. 1
      samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs
  50. 6
      samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs

2
docs/en/Blob-Storing.md

@ -169,7 +169,7 @@ public class ProfileAppService : ApplicationService
If you don't use the generic argument and directly inject the `IBlobContainer` (as explained before), you get the default container. Another way of injecting the default container is using `IBlobContainer<DefaultContainer>`, which returns exactly the same container.
The name of the default container is `Default`.
The name of the default container is `default`.
### Named Containers

133
docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md

@ -1,3 +1,134 @@
# Distributed Event Bus RabbitMQ Integration
TODO
> This document explains **how to configure the [RabbitMQ](https://www.rabbitmq.com/)** as the distributed event bus provider. See the [distributed event bus document](Distributed-Event-Bus.md) to learn how to use the distributed event bus system
## Installation
Use the ABP CLI to add [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.RabbitMQ` package.
* Run `abp add-package Volo.Abp.EventBus.RabbitMQ` command.
If you want to do it manually, install the [Volo.Abp.EventBus.RabbitMQ](https://www.nuget.org/packages/Volo.Abp.EventBus.RabbitMQ) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusRabbitMqModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
You can configure using the standard [configuration system](Configuration.md), like using the `appsettings.json` file, or using the [options](Options.md) classes.
### `appsettings.json` file configuration
This is the simplest way to configure the RabbitMQ settings. It is also very strong since you can use any other configuration source (like environment variables) that is [supported by the AspNet Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/).
**Example: The minimal configuration to connect to a local RabbitMQ server with default configurations**
````json
{
"RabbitMQ": {
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
* `ClientName` is the name of this application, which is used as the **queue name** on the RabbitMQ.
* `ExchangeName` is the **exchange name**.
See [the RabbitMQ document](https://www.rabbitmq.com/dotnet-api-guide.html#exchanges-and-queues) to understand these options better.
#### Connections
If you need to connect to another server than the localhost, you need to configure the connection properties.
**Example: Specify the host name (as an IP address)**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName"
}
}
}
````
Defining multiple connections is allowed. In this case, you can specify the connection that is used for the event bus.
**Example: Declare two connections and use one of them for the event bus**
````json
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123"
},
"SecondConnection": {
"HostName": "321.321.321.321"
}
},
"EventBus": {
"ClientName": "MyClientName",
"ExchangeName": "MyExchangeName",
"ConnectionName": "SecondConnection"
}
}
}
````
This allows you to use multiple RabbitMQ server in your application, but select one of them for the event bus.
You can use any of the [ConnectionFactry](http://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.ConnectionFactory.html#properties) properties as the connection properties.
**Example: Specify the connection port**
````csharp
{
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "123.123.123.123",
"Port": "5672"
}
}
}
}
````
### The Options Classes
`AbpRabbitMqOptions` and `AbpRabbitMqEventBusOptions` classes can be used to configure the connection strings and event bus options for the RabbitMQ.
You can configure this options inside the `ConfigureServices` of your [module](Module-Development-Basics.md).
**Example: Configure the connection**
````csharp
Configure<AbpRabbitMqOptions>(options =>
{
options.Connections.Default.UserName = "user";
options.Connections.Default.Password = "pass";
options.Connections.Default.HostName = "123.123.123.123";
options.Connections.Default.Port = 5672;
});
````
**Example: Configure the client and exchange names**
````csharp
Configure<AbpRabbitMqEventBusOptions>(options =>
{
options.ClientName = "TestApp1";
options.ExchangeName = "TestMessages";
});
````
Using these options classes can be combined with the `appsettings.json` way. Configuring an option property in the code overrides the value in the configuration file.

180
docs/en/Distributed-Event-Bus.md

@ -1,3 +1,181 @@
# Distributed Event Bus
TODO
Distributed Event bus system allows to **publish** and **subscribe** to events that can be **transferred across application/service boundaries**. You can use the distributed event bus to asynchronously send and receive messages between **microservices** or **applications**.
## Providers
Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are two providers implemented out of the box:
* `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider.
* `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it.
Using a local event bus as default has a few important advantages. The most important one is that: It allows you to write your code compatible to distributed architecture. You can write a monolithic application now that can be split into microservices later. It is a good practice to communicate between bounded contexts (or between application modules) via distributed events instead of local events.
For example, [pre-built application modules](Modules/Index.md) is designed to work as a service in a distributed system while they can also work as a module in a monolithic application without depending an external message broker.
## Publishing Events
There are two ways of publishing distributed events explained in the following sections.
### IDistributedEventBus
`IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event.
**Example: Publish a distributed event when the stock count of a product changes**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IDistributedEventBus _distributedEventBus;
public MyService(IDistributedEventBus distributedEventBus)
{
_distributedEventBus = distributedEventBus;
}
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
await _distributedEventBus.PublishAsync(
new StockCountChangedEvent
{
ProductId = productId,
NewCount = newCount
}
);
}
}
}
````
`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class:
````csharp
using System;
namespace AbpDemo
{
[EventName("MyApp.Product.StockChange")]
public class StockCountChangedEto
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
}
````
Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case).
> `Eto` is a suffix for **E**vent **T**ransfer **O**bjects we use by convention. While it is not required, we find it useful to identify such event classes (just like [DTOs](Data-Transfer-Objects.md) on the application layer).
#### Event Name
`EventName` attribute is optional, but suggested. If you don't declare it, the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case.
#### About Serialization for the Event Objects
Event transfer objects **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process.
Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs.
### Inside Entity / Aggregate Root Classes
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes.
**Example: Publish a distributed event inside an aggregate root method**
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//ADD an EVENT TO BE PUBLISHED
AddDistributedEvent(
new StockCountChangedEto
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
````
`AggregateRoot` class defines the `AddDistributedEvent` to add a new distributed event, that is published when the aggregate root object is saved (created, updated or deleted) into the database.
> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event.
#### IGeneratesDomainEvents Interface
Actually, adding distributed events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you.
> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example.
#### How It Was Implemented?
Calling the `AddDistributedEvent` doesn't immediately publish the event. The event is published when you save changes to the database;
* For EF Core, it is published on `DbContext.SaveChanges`.
* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system).
## Subscribing to Events
A service can implement the `IDistributedEventHandler<TEvent>` to handle the event.
**Example: Handle the `StockCountChangedEto` defined above**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace AbpDemo
{
public class MyHandler
: IDistributedEventHandler<StockCountChangedEto>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEto eventData)
{
var productId = eventData.ProductId;
}
}
}
````
That's all.
* `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEto` event occurs.
* If you are using a distributed message broker, like RabbitMQ, ABP automatically **subscribes to the event on the message broker**, gets the message, executes the handler.
* It sends **confirmation (ACK)** to the message broker if the event handler was successfully executed (did not throw any exception).
You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler<TEvent>` interface for each event type.
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.

9
docs/en/Event-Bus.md

@ -1,3 +1,10 @@
# Event Bus
TODO
An event bus is a mediator that transfers a message from a sender to a receiver. In this way, it provides a loosely coupled communication way between objects, services and applications.
## Event Bus Types
ABP Framework provides two type of event buses;
* **[Local Event Bus](Local-Event-Bus.md)** is suitable for in-process messaging.
* **[Distributed Event Bus](Distributed-Event-Bus.md)** is suitable for inter-process messaging, like microservices publishing and subscribing to distributed events.

226
docs/en/Local-Event-Bus.md

@ -1,3 +1,227 @@
# Local Event Bus
TODO
The Local Event Bus allows services to publish and subscribe to **in-process events**. That means it is suitable if two services (publisher and subscriber) are running in the same process.
## Publishing Events
There are two ways of publishing local events explained in the following sections.
### ILocalEventBus
`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event.
**Example: Publish a local event when the stock count of a product changes**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ILocalEventBus _localEventBus;
public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
//TODO: IMPLEMENT YOUR LOGIC...
//PUBLISH THE EVENT
await _localEventBus.PublishAsync(
new StockCountChangedEvent
{
ProductId = productId,
NewCount = newCount
}
);
}
}
}
````
`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class:
````csharp
using System;
namespace AbpDemo
{
public class StockCountChangedEvent
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
}
````
Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case).
### Inside Entity / Aggregate Root Classes
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes.
**Example: Publish a local event inside an aggregate root method**
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//ADD an EVENT TO BE PUBLISHED
AddLocalEvent(
new StockCountChangedEvent
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
````
`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database.
> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event.
#### IGeneratesDomainEvents Interface
Actually, adding local events are not unique to the `AggregateRoot` class. You can implement `IGeneratesDomainEvents` for any entity class. But, `AggregateRoot` implements it by default and makes it easy for you.
> It is not suggested to implement this interface for entities those are not aggregate roots, since it may not work for some database providers for such entities. It works for EF Core, but not works for MongoDB for example.
#### How It Was Implemented?
Calling the `AddLocalEvent` doesn't immediately publish the event. The event is published when you save changes to the database;
* For EF Core, it is published on `DbContext.SaveChanges`.
* For MongoDB, it is published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system).
## Subscribing to Events
A service can implement the `ILocalEventHandler<TEvent>` to handle the event.
**Example: Handle the `StockCountChangedEvent` defined above**
````csharp
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: your code that does somthing on the event
}
}
}
````
That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here.
* **Zero or more handlers** can subscribe to the same event.
* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler<TEvent>` interface for each event type.
> The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options.
## Transaction & Exception Behavior
When an event published, subscribed event handlers are immediately executed. So;
* If a handler **throws an exception**, it effects the code that published the event. That means it gets the exception on the `PublishAsync` call. So, **use try-catch yourself** in the event handler if you want to hide the error.
* If the event publishing code is being executed inside a [Unit Of Work](Unit-Of-Work.md) scope, the event handlers also covered by the unit of work. That means if your UOW is transactional and a handler throws an exception, the transaction is rolled back.
## Pre-Built Events
It is very common to **publish events on entity create, update and delete** operations. ABP Framework **automatically** publish these events for all entities. You can just subscribe to the related event.
**Example: Subscribe to an event that published when a user was created**
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<EntityCreatedEventData<IdentityUser>>,
ITransientDependency
{
public async Task HandleEventAsync(
EntityCreatedEventData<IdentityUser> eventData)
{
var userName = eventData.Entity.UserName;
var email = eventData.Entity.Email;
//...
}
}
}
````
This class subscribes to the `EntityCreatedEventData<IdentityUser>`, which is published just after a user was created. You may want to send a "Welcome" email to the new user.
There are two types of these events: events with past tense and events with continuous tense.
### Events with Past Tense
Events with past tense are published when the related unit of work completed and the entity change successfully saved to the database. If you throw an exception on these event handlers, it **can not rollback** the transaction since it was already committed.
The event types are;
* `EntityCreatedEventData<T>` is published just after an entity was successfully created.
* `EntityUpdatedEventData<T>` is published just after an entity was successfully updated.
* `EntityDeletedEventData<T>` is published just after an entity was successfully deleted.
* `EntityChangedEventData<T>` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events.
### Events with Continuous Tense
Events with continuous tense are published before completing the transaction (if database transaction is supported by the database provider being used). If you throw an exception on these event handlers, it **can rollback** the transaction since it is not completed yet and the change is not saved to the database.
The event types are;
* `EntityCreatingEventData<T>` is published just before saving a new entity to the database.
* `EntityUpdatingEventData<T>` is published just before an existing entity is being updated.
* `EntityDeletingEventData<T>` is published just before an entity is being deleted.
* `EntityChangingEventData<T>` is published just before an entity is being created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events.
#### How It Was Implemented?
Pre-build events are published when you save changes to the database;
* For EF Core, they are published on `DbContext.SaveChanges`.
* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system).

3
docs/en/RabbitMq.md

@ -0,0 +1,3 @@
# RabbitMQ
TODO!

2
docs/en/Repositories.md

@ -126,7 +126,7 @@ You can directly access the data access provider (`DbContext` in this case) to p
`IRepository` inherits from `IQueryable`, that means you can **directly use LINQ extension methods** on it, as shown in the example of the "*Generic Repositories*" section above.
**Example: Using the `Where(...)` &and the `ToList()` extension methods**
**Example: Using the `Where(...)` and the `ToList()` extension methods**
````csharp
var people = _personRepository

13
docs/en/docs-nav.json

@ -153,16 +153,23 @@
]
},
{
"text": "Events",
"text": "Event Bus",
"items": [
{
"text": "Event Bus (local)"
"text": "Overall",
"path": "Event-Bus.md"
},
{
"text": "Local Event Bus",
"path": "Local-Event-Bus.md"
},
{
"text": "Distributed Event Bus",
"path": "Distributed-Event-Bus.md",
"items": [
{
"text": "RabbitMQ Integration"
"text": "RabbitMQ Integration",
"path": "Distributed-Event-Bus-RabbitMQ-Integration.md"
}
]
}

133
docs/zh-Hans/Repositories.md

@ -38,6 +38,8 @@ public class PersonAppService : ApplicationService
}
````
> 参阅 "*IQueryable & 异步操作*" 部分了解如何使用 **异步扩展方法**, 如 `ToListAsync()` (建议始终使用异步) 而不是 `ToList()`.
在这个例子中;
* `PersonAppService` 在它的构造函数中注入了 `IRepository<Person, Guid>` .
@ -119,4 +121,133 @@ public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPe
}
````
你可以直接使用数据库访问提供程序 (本例中是 `DbContext` ) 来执行操作. 有关基于EF Core的自定义仓储的更多信息, 请参阅[EF Core 集成文档](Entity-Framework-Core.md).
你可以直接使用数据库访问提供程序 (本例中是 `DbContext` ) 来执行操作.
> 请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)了解如何自定义仓储.
## IQueryable & 异步操作
`IRepository` 继承自 `IQueryable`,这意味着你可以**直接使用LINQ扩展方法**. 如上面的*泛型仓储*示例.
**示例: 使用 `Where(...)``ToList()` 扩展方法**
````csharp
var people = _personRepository
.Where(p => p.Name.Contains(nameFilter))
.ToList();
````
`.ToList`, `Count()`... 是在 `System.Linq` 命名空间下定义的扩展方法. ([参阅所有方法](https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable)).
你通常想要使用 `.ToListAsync()`, `.CountAsync()`.... 来编写**真正的异步代码**.
但在你使用标准的[应用程序启动模板](Startup-Templates/Application.md)时会发现无法在应用层或领域层使用这些异步扩展方法,因为:
* 这里异步方法**不是标准LINQ方法**,它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)Nuget包中.
* 标准模板应用层与领域层**不引用**EF Core 包以实现数据库提供程序独立.
根据你的需求和开发模式,你可以根据以下选项使用异步方法.s
> 强烈建议使用异步方法! 在执行数据库查询时不要使用同步LINQ方法,以便能够开发可伸缩的应用程序.
### 选项-1: 引用EF Core
最简单的方法是在你想要使用异步方法的项目直接引用EF Core包.
> 添加[Volo.Abp.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore)NuGet包到你的项目间接引用EF Core包. 这可以确保你的应用程序其余部分兼容正确版本的EF Core.
当你添加NuGet包后,你可以使用全功能的EF Core扩展方法.
**示例: 直接使用 `ToListAsync()`**
````csharp
var people = _personRepository
.Where(p => p.Name.Contains(nameFilter))
.ToListAsync();
````
此方法建议;
* 如果你正在开发一个应用程序并且**不打算在将来** 更新FE Core,或者如果以后需要更改,你可以**容忍**它. 如果你正在开发最终的应用程序,这是合理的.
#### MongoDB
如果使用的是MongoDB,则需要将[Volo.Abp.MongoDB] NuGet包添加到项目中. 但在这种情况下你也不能直接使用异步LINQ扩展(例如`ToListAsync`),因为MongoDB不提供 `IQueryable<T>`的异步扩展方法,而是提供 `IMongoQueryable<T>`. 你需要先将查询强制转换为 `IMongoQueryable<T>` 才能使用异步扩展方法.
**示例: 转换Cast `IQueryable<T>``IMongoQueryable<T>` 并且使用 `ToListAsync()`**
````csharp
var people = ((IMongoQueryable<Person>)_personRepository
.Where(p => p.Name.Contains(nameFilter)))
.ToListAsync();
````
### 选项-2: 自定义仓储方法
你始终可以创建自定义仓储方法并使用特定数据库提供程序的API,比如这里的异步扩展方法. 有关自定义存储库的更多信息,请参阅[EF Core](Entity-Framework-Core.md)或[MongoDb](MongoDB.md)文档.
此方法建议;
* 如果你想**完全隔离**你的领域和应用层和数据库提供程序.
* 如果你开发可**重用的[应用模块](Modules/Index.md)**,并且不想强制使用特定的数据库提供程序,这应该作为一种[最佳实践](Best-Practices/Index.md).
### 选项-3: IAsyncQueryableExecuter
> 注意,此功能在ABP框架3.0以之后的版本可用,虽然它也可以用于较早的版本,但它提供的方法非常有限.
`IAsyncQueryableExecuter` 是一个用于异步执行 `IQueryable<T>` 对象的服务,**不依赖于实际的数据库提供程序**.
**示例: 注入并使用 `IAsyncQueryableExecuter.ToListAsync()` 方法**
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
private readonly IAsyncQueryableExecuter _asyncExecuter;
public ProductAppService(
IRepository<Product, Guid> productRepository,
IAsyncQueryableExecuter asyncExecuter)
{
_productRepository = productRepository;
_asyncExecuter = asyncExecuter;
}
public async Task<ListResultDto<ProductDto>> GetListAsync(string name)
{
//Create the query
var query = _productRepository
.Where(p => p.Name.Contains(name))
.OrderBy(p => p.Name);
//Run the query asynchronously
List<Product> products = await _asyncExecuter.ToListAsync(query);
//...
}
}
}
````
> `ApplicationService``DomainService` 基类已经预属性注入了 `AsyncExecuter` 属性,所以你可直接使用.
ABP框架使用实际数据库提供程序的API异步执行查询.虽然这不是执行查询的常见方式,但它是使用异步API而不依赖于数据库提供者的最佳方式.
此方法建议;
* 如果你正在构建一个没有数据库提供程序集成包的**可重用库**,但是在某些情况下需要执行 `IQueryable<T>`对象.
For example, ABP Framework uses the `IAsyncQueryableExecuter` in the `CrudAppService` base class (see the [application services](Application-Services.md) document).
例如,ABP框架在 `CrudAppService` 基类中(参阅[应用程序](Application-Services.md)文档)使用 `IAsyncQueryableExecuter`.

3
docs/zh-Hans/SignalR-Integration.md

@ -234,4 +234,5 @@ ABP框架不会更改SignalR. 就像在其他ASP.NET Core应用程序中一样,
## 另请参阅
* [微软SignalR文档](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction)
* [微软SignalR文档](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction)
* [使用ABP,SignalR和RabbitMQ在分布式体系结构中的实时消息传递](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ)

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs

@ -12,6 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2
{
//TODO: Add select2.full.min.js or localize!
context.Files.AddIfNotContains("/libs/select2/js/select2.min.js");
context.Files.AddIfNotContains("/libs/select2/js/select2-bootstrap-modal-patch.js");
}
}
}

34
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js

@ -304,7 +304,7 @@
}
}
configuration.language = datatables.defaultConfigurations.language;
configuration.language = datatables.defaultConfigurations.language();
if(configuration.dom){
configuration.dom += datatables.defaultConfigurations.dom;
@ -352,21 +352,23 @@
datatables.defaultConfigurations.scrollX = true;
datatables.defaultConfigurations.language = {
info: localize("PagerInfo"),
infoFiltered: localize("PagerInfoFiltered"),
infoEmpty: localize("PagerInfoEmpty"),
search: localize("PagerSearch"),
processing: localize("ProcessingWithThreeDot"),
loadingRecords: localize("LoadingWithThreeDot"),
lengthMenu: localize("PagerShowMenuEntries"),
emptyTable: localize("NoDataAvailableInDatatable"),
paginate: {
first: localize("PagerFirst"),
last: localize("PagerLast"),
previous: localize("PagerPrevious"),
next: localize("PagerNext")
}
datatables.defaultConfigurations.language = function () {
return {
info: localize("PagerInfo"),
infoFiltered: localize("PagerInfoFiltered"),
infoEmpty: localize("PagerInfoEmpty"),
search: localize("PagerSearch"),
processing: localize("ProcessingWithThreeDot"),
loadingRecords: localize("LoadingWithThreeDot"),
lengthMenu: localize("PagerShowMenuEntries"),
emptyTable: localize("NoDataAvailableInDatatable"),
paginate: {
first: localize("PagerFirst"),
last: localize("PagerLast"),
previous: localize("PagerPrevious"),
next: localize("PagerNext")
}
};
};
datatables.defaultConfigurations.dom = '<"dataTable_filters"f>rt<"row dataTable_footer"<"col-auto"l><"col-auto"i><"col"p>>';

2
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs

@ -3,6 +3,6 @@
[BlobContainerName(Name)]
public class DefaultContainer
{
public const string Name = "Default";
public const string Name = "default";
}
}

15
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs

@ -0,0 +1,15 @@
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class AbpDistributedEntityEventOptions
{
public IAutoEntityDistributedEventSelectorList AutoEventSelectors { get; }
public EtoMappingDictionary EtoMappings { get; set; }
public AbpDistributedEntityEventOptions()
{
AutoEventSelectors = new AutoEntityDistributedEventSelectorList();
EtoMappings = new EtoMappingDictionary();
}
}
}

9
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class AutoEntityDistributedEventSelectorList : List<NamedTypeSelector>, IAutoEntityDistributedEventSelectorList
{
}
}

117
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public static class AutoEntityDistributedEventSelectorListExtensions
{
public const string AllEntitiesSelectorName = "All";
public static void AddNamespace([NotNull] this IAutoEntityDistributedEventSelectorList selectors, [NotNull] string namespaceName)
{
Check.NotNull(selectors, nameof(selectors));
var selectorName = "Namespace:" + namespaceName;
if (selectors.Any(s => s.Name == selectorName))
{
return;
}
selectors.Add(
new NamedTypeSelector(
selectorName,
t => t.FullName?.StartsWith(namespaceName) ?? false
)
);
}
/// <summary>
/// Adds a specific entity type and the types derived from that entity type.
/// </summary>
/// <typeparam name="TEntity">Type of the entity</typeparam>
public static void Add<TEntity>([NotNull] this IAutoEntityDistributedEventSelectorList selectors)
where TEntity : IEntity
{
Check.NotNull(selectors, nameof(selectors));
var selectorName = "Entity:" + typeof(TEntity).FullName;
if (selectors.Any(s => s.Name == selectorName))
{
return;
}
selectors.Add(
new NamedTypeSelector(
selectorName,
t => typeof(TEntity).IsAssignableFrom(t)
)
);
}
/// <summary>
/// Adds all entity types.
/// </summary>
public static void AddAll([NotNull] this IAutoEntityDistributedEventSelectorList selectors)
{
Check.NotNull(selectors, nameof(selectors));
if (selectors.Any(s => s.Name == AllEntitiesSelectorName))
{
return;
}
selectors.Add(
new NamedTypeSelector(
AllEntitiesSelectorName,
t => typeof(IEntity).IsAssignableFrom(t)
)
);
}
public static void Add(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
string selectorName,
Func<Type, bool> predicate)
{
Check.NotNull(selectors, nameof(selectors));
if (selectors.Any(s => s.Name == selectorName))
{
throw new AbpException($"There is already a selector added before with the same name: {selectorName}");
}
selectors.Add(
new NamedTypeSelector(
selectorName,
predicate
)
);
}
public static void Add(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
Func<Type, bool> predicate)
{
selectors.Add(Guid.NewGuid().ToString("N"), predicate);
}
public static bool RemoveByName(
[NotNull] this IAutoEntityDistributedEventSelectorList selectors,
[NotNull] string name)
{
Check.NotNull(selectors, nameof(selectors));
Check.NotNull(name, nameof(name));
return selectors.RemoveAll(s => s.Name == name).Count > 0;
}
public static bool IsMatch([NotNull] this IAutoEntityDistributedEventSelectorList selectors, Type entityType)
{
Check.NotNull(selectors, nameof(selectors));
return selectors.Any(s => s.Predicate(entityType));
}
}
}

6
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Domain.Entities.Events.Distributed
@ -11,10 +10,11 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed
public class EntityToEtoMapper : IEntityToEtoMapper, ITransientDependency
{
protected IHybridServiceScopeFactory HybridServiceScopeFactory { get; }
protected AbpDistributedEventBusOptions Options { get; }
protected AbpDistributedEntityEventOptions Options { get; }
public EntityToEtoMapper(
IOptions<AbpDistributedEventBusOptions> options,
IOptions<AbpDistributedEntityEventOptions> options,
IHybridServiceScopeFactory hybridServiceScopeFactory)
{
HybridServiceScopeFactory = hybridServiceScopeFactory;

3
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionary.cs → framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionary.cs

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using Volo.Abp.EventBus.Distributed;
namespace Volo.Abp.EventBus.Distributed
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class EtoMappingDictionary : Dictionary<Type, EtoMappingDictionaryItem>
{

2
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionaryItem.cs → framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionaryItem.cs

@ -1,6 +1,6 @@
using System;
namespace Volo.Abp.EventBus.Distributed
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class EtoMappingDictionaryItem
{

9
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs

@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public interface IAutoEntityDistributedEventSelectorList : IList<NamedTypeSelector>
{
}
}

74
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
@ -22,13 +23,16 @@ namespace Volo.Abp.Domain.Entities.Events
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IEntityToEtoMapper EntityToEtoMapper { get; }
protected AbpDistributedEntityEventOptions DistributedEntityEventOptions { get; }
public EntityChangeEventHelper(
IUnitOfWorkManager unitOfWorkManager,
IEntityToEtoMapper entityToEtoMapper)
IEntityToEtoMapper entityToEtoMapper,
IOptions<AbpDistributedEntityEventOptions> distributedEntityEventOptions)
{
UnitOfWorkManager = unitOfWorkManager;
EntityToEtoMapper = entityToEtoMapper;
DistributedEntityEventOptions = distributedEntityEventOptions.Value;
LocalEventBus = NullLocalEventBus.Instance;
DistributedEventBus = NullDistributedEventBus.Instance;
@ -65,18 +69,32 @@ namespace Volo.Abp.Domain.Entities.Events
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
if (ShouldPublishDistributedEventForEntity(entity))
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityCreatedEto<>),
eto,
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityCreatedEto<>),
eto,
false
);
}
}
}
private bool ShouldPublishDistributedEventForEntity(object entity)
{
return DistributedEntityEventOptions
.AutoEventSelectors
.IsMatch(
ProxyHelper
.UnProxy(entity)
.GetType()
);
}
public virtual async Task TriggerEntityUpdatingEventAsync(object entity)
{
await TriggerEventWithEntity(
@ -96,15 +114,18 @@ namespace Volo.Abp.Domain.Entities.Events
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
if (ShouldPublishDistributedEventForEntity(entity))
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityUpdatedEto<>),
eto,
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityUpdatedEto<>),
eto,
false
);
}
}
}
@ -127,15 +148,18 @@ namespace Volo.Abp.Domain.Entities.Events
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
if (ShouldPublishDistributedEventForEntity(entity))
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityDeletedEto<>),
EntityToEtoMapper.Map(entity),
false
);
var eto = EntityToEtoMapper.Map(entity);
if (eto != null)
{
await TriggerEventWithEntity(
DistributedEventBus,
typeof(EntityDeletedEto<>),
eto,
false
);
}
}
}

2
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs

@ -5,12 +5,10 @@ namespace Volo.Abp.EventBus.Distributed
public class AbpDistributedEventBusOptions
{
public ITypeList<IEventHandler> Handlers { get; }
public EtoMappingDictionary EtoMappings { get; set; }
public AbpDistributedEventBusOptions()
{
Handlers = new TypeList<IEventHandler>();
EtoMappings = new EtoMappingDictionary();
}
}
}

5
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs

@ -4,7 +4,7 @@ using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.AutoMapper;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.TestApp.Application.Dto;
using Volo.Abp.Threading;
@ -45,8 +45,9 @@ namespace Volo.Abp.TestApp
private void ConfigureDistributedEventBus()
{
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.AutoEventSelectors.Add<Person>();
options.EtoMappings.Add<Person, PersonEto>();
});
}

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

@ -1,7 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;
using Volo.Blogging.Blogs;
using Volo.Blogging.Comments;
@ -25,7 +25,7 @@ namespace Volo.Blogging
options.AddProfile<BloggingDomainMappingProfile>(validate: true);
});
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<Blog, BlogEto>(typeof(BloggingDomainModule));
options.EtoMappings.Add<Comment, CommentEto>(typeof(BloggingDomainModule));

22
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs

@ -12,10 +12,28 @@ namespace Volo.Docs.Admin.Documents
[StringLength(DocumentConsts.MaxNameLength)]
public string Name { get; set; }
[StringLength(DocumentConsts.MaxVersionNameLength)]
public string Version { get; set; }
[StringLength(DocumentConsts.MaxLanguageCodeNameLength)]
public string LanguageCode { get; set; }
[StringLength(DocumentConsts.MaxVersionNameLength)]
public string Version { get; set; }
[StringLength(DocumentConsts.MaxFileNameNameLength)]
public string FileName { get; set; }
[StringLength(DocumentConsts.MaxFormatNameLength)]
public string Format { get; set; }
public DateTime? CreationTimeMin { get; set; }
public DateTime? CreationTimeMax { get; set; }
public DateTime? LastUpdatedTimeMin { get; set; }
public DateTime? LastUpdatedTimeMax { get; set; }
public DateTime? LastSignificantUpdateTimeMin { get; set; }
public DateTime? LastSignificantUpdateTimeMax { get; set; }
public DateTime? LastCachedTimeMin { get; set; }
public DateTime? LastCachedTimeMax { get; set; }
}
}

12
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json

@ -43,6 +43,16 @@
"ReIndexDocumentConfirmation": "Are you sure you want to reindex this item?",
"DeleteDocumentFromDbConfirmation": "Are you sure you want to delete this item from database?",
"DeleteFromDatabase": "Delete from database",
"Deleted": "Deleted"
"Deleted": "Deleted",
"Search": "Search",
"StartDate": "Start date",
"EndDate": "End date",
"CreationTime": "Creation time",
"LastUpdateTime": "Last update",
"LastSignificantUpdateTime": "Last significant update",
"Version": "Version",
"LanguageCode": "Language code",
"FileName": "File name",
"LastCachedTime": "Cache time"
}
}

36
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs

@ -137,20 +137,40 @@ namespace Volo.Docs.Admin.Documents
public async Task<PagedResultDto<DocumentDto>> GetAllAsync(GetAllInput input)
{
var totalCount = await _documentRepository.GetAllCountAsync(
projectId: input.ProjectId,
name: input.Name,
version: input.Version,
languageCode: input.LanguageCode,
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount
);
projectId: input.ProjectId,
name: input.Name,
version: input.Version,
languageCode: input.LanguageCode,
fileName: input.FileName,
format: input.Format,
creationTimeMin: input.CreationTimeMin,
creationTimeMax: input.CreationTimeMax,
lastUpdatedTimeMin: input.LastUpdatedTimeMin,
lastUpdatedTimeMax: input.LastUpdatedTimeMax,
lastSignificantUpdateTimeMin: input.LastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: input.LastSignificantUpdateTimeMax,
lastCachedTimeMin: input.LastCachedTimeMin,
lastCachedTimeMax: input.LastCachedTimeMax,
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount
);
var docs = await _documentRepository.GetAllAsync(
projectId: input.ProjectId,
name: input.Name,
version: input.Version,
languageCode: input.LanguageCode,
fileName: input.FileName,
format: input.Format,
creationTimeMin: input.CreationTimeMin,
creationTimeMax: input.CreationTimeMax,
lastUpdatedTimeMin: input.LastUpdatedTimeMin,
lastUpdatedTimeMax: input.LastUpdatedTimeMax,
lastSignificantUpdateTimeMin: input.LastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: input.LastSignificantUpdateTimeMax,
lastCachedTimeMin: input.LastCachedTimeMin,
lastCachedTimeMax: input.LastCachedTimeMax,
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount

219
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml

@ -22,31 +22,194 @@
<abp-script src="/Pages/Docs/Admin/Documents/index.js" />
}
<abp-card>
<abp-card-header>
<abp-row>
<h2>Filters ...</h2>
@*<abp-column size-md="_6" class="text-right">
<abp-button button-type="Primary" icon="plus" text="@L["ReIndexAllProjects"].Value" id="ReIndexAllProjects" />
</abp-column>*@
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="DocumentsTable" class="nowrap">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Name"]</th>
<th>@L["Version"]</th>
<th>@L["LanguageCode"]</th>
<th>@L["FileName"]</th>
<th>@L["Format"]</th>
<th>@L["CreationTime"]</th>
<th>@L["LastUpdatedTime"]</th>
<th>@L["LastSignificantUpdateTime"]</th>
<th>@L["LastCachedTime"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>
@section styles {
<abp-style src="/Pages/Docs/Admin/Documents/index.min.css" />
}
<div id="DocumentsContainer">
<abp-card>
<abp-card-header>
<form class="form-inline" autocomplete="off" id="FilterForm">
<div class="form-row">
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["Name"].Value</div>
</div>
<input type="text"
id="Name"
name="Name"
class="form-control mr-sm-2">
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["Version"].Value</div>
</div>
<input type="text"
id="Version"
name="Version"
class="form-control mr-sm-2">
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["LanguageCode"].Value</div>
</div>
<input type="text"
id="LanguageCode"
name="LanguageCode"
class="form-control mr-sm-2">
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["FileName"].Value</div>
</div>
<input type="text"
id="FileName"
name="FileName"
class="form-control mr-sm-2">
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["Format"].Value</div>
</div>
<input type="text"
id="Format"
name="Format"
class="form-control mr-sm-2">
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["CreationTime"].Value</div>
</div>
<span>
<input type="date"
id="CreationTimeMin"
name="CreationTimeMin"
class="form-control datepicker"
placeholder="@L["StartDate"]">
<span>-</span>
<input type="date"
id="CreationTimeMax"
name="CreationTimeMax"
placeholder="@L["EndDate"]"
class="form-control datepicker">
</span>
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["LastUpdateTime"].Value</div>
</div>
<span>
<input type="date"
id="LastUpdatedTimeMin"
name="LastUpdatedTimeMin"
class="form-control datepicker"
placeholder="@L["StartDate"]">
<span>-</span>
<input type="date"
id="LastUpdatedTimeMax"
name="LastUpdatedTimeMax"
placeholder="@L["EndDate"]"
class="form-control datepicker">
</span>
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["LastSignificantUpdateTime"].Value</div>
</div>
<span>
<input type="date"
id="LastSignificantUpdateTimeMin"
name="LastSignificantUpdateTimeMin"
class="form-control datepicker"
placeholder="@L["StartDate"]">
<span>-</span>
<input type="date"
id="LastSignificantUpdateTimeMax"
name="LastSignificantUpdateTimeMax"
placeholder="@L["EndDate"]"
class="form-control datepicker">
</span>
</div>
</div>
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">@L["LastCachedTime"].Value</div>
</div>
<span>
<input type="date"
id="LastCachedTimeMin"
name="LastCachedTimeMin"
class="form-control datepicker"
placeholder="@L["StartDate"]">
<span>-</span>
<input type="date"
id="LastCachedTimeMax"
name="LastCachedTimeMax"
placeholder="@L["EndDate"]"
class="form-control datepicker">
</span>
</div>
</div>
<div class="col-auto">
<abp-button button-type="Primary" icon="search" id="SearchButton" style="line-height: 25px;" text="@L["Search"].Value"></abp-button>
</div>
</div>
</form>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="DocumentsTable" class="nowrap">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Name"]</th>
<th>@L["Version"]</th>
<th>@L["LanguageCode"]</th>
<th>@L["FileName"]</th>
<th>@L["Format"]</th>
<th>@L["CreationTime"]</th>
<th>@L["LastUpdatedTime"]</th>
<th>@L["LastSignificantUpdateTime"]</th>
<th>@L["LastCachedTime"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>
</div>

3
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs

@ -1,6 +1,9 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Docs.Documents;
namespace Volo.Docs.Admin.Pages.Docs.Admin.Documents
{

3
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.css

@ -0,0 +1,3 @@
#DocumentsContainer .datepicker {
display: inline-block !important;
width: 7rem !important; }

71
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js

@ -1,8 +1,32 @@
$(function () {
var l = abp.localization.getResource('Docs');
var service = window.volo.docs.admin.documentsAdmin;
var _dataTable = $('#DocumentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
var getFormattedDate = function ($datePicker) {
return $datePicker.data().datepicker.getFormattedDate("yyyy-mm-dd");
};
var getFilter = function () {
return {
projectId: $("#ProjectId").val(),
name: $("#Name").val(),
version: $("#Version").val(),
languageCode: $("#LanguageCode").val(),
fileName: $("#FileName").val(),
format: $("#Format").val(),
creationTimeMin: getFormattedDate($("#CreationTimeMin")),
creationTimeMax: getFormattedDate($("#CreationTimeMax")),
lastUpdatedTimeMin: getFormattedDate($("#LastUpdatedTimeMin")),
lastUpdatedTimeMax: getFormattedDate($("#LastUpdatedTimeMax")),
lastSignificantUpdateTimeMin: getFormattedDate($("#LastSignificantUpdateTimeMin")),
lastSignificantUpdateTimeMax: getFormattedDate($("#LastSignificantUpdateTimeMax")),
lastCachedTimeMin: getFormattedDate($("#LastCachedTimeMin")),
lastCachedTimeMax: getFormattedDate($("#LastCachedTimeMax"))
};
};
var dataTable = $('#DocumentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
scrollX: true,
@ -10,7 +34,7 @@
searching: false,
autoWidth: false,
scrollCollapse: true,
ajax: abp.libs.datatables.createAjax(volo.docs.admin.documentsAdmin.getAll),
ajax: abp.libs.datatables.createAjax(service.getAll, getFilter),
columnDefs: [
{
rowAction: {
@ -21,11 +45,10 @@
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('RemoveFromCacheConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.removeFromCache(data.record.id)
service.removeFromCache(data.record.id)
.then(function () {
abp.message.success(l('RemovedFromCache'));
_dataTable.ajax.reload();
dataTable.ajax.reload();
});
}
},
@ -34,11 +57,10 @@
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('ReIndexDocumentConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.reindex(data.record.id)
service.reindex(data.record.id)
.then(function () {
abp.message.success(l('ReindexCompleted'));
_dataTable.ajax.reload();
dataTable.ajax.reload();
});
}
},
@ -47,11 +69,10 @@
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('DeleteDocumentFromDbConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.deleteFromDatabase(data.record.id)
service.deleteFromDatabase(data.record.id)
.then(function () {
abp.message.success(l('Deleted'));
_dataTable.ajax.reload();
dataTable.ajax.reload();
});
}
}
@ -76,14 +97,7 @@
},
{
target: 5,
data: "format",
render: function (data) {
if (data === 'md') {
return 'markdown';
}
return data;
}
data: "format"
},
{
target: 6,
@ -136,18 +150,15 @@
]
}));
$("#FilterForm input[type='text']").keypress(function (e) {
if (e.which === 13) {
dataTable.ajax.reload();
}
});
$("#ReIndexAllProjects").click(function (event) {
abp.message.confirm(l('ReIndexAllProjectConfirmationMessage'))
.done(function (accepted) {
if (accepted) {
volo.docs.admin.projectsAdmin
.reindexAll()
.then(function () {
abp.message.success(l('SuccessfullyReIndexAllProject'));
});
}
});
$("#SearchButton").click(function (e) {
e.preventDefault();
dataTable.ajax.reload();
});
});

1
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.min.css

@ -0,0 +1 @@
#DocumentsContainer .datepicker{display:inline-block !important;width:7rem !important;}

6
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.scss

@ -0,0 +1,6 @@
#DocumentsContainer {
.datepicker {
display: inline-block !important;
width: 7rem !important;
}
}

8
modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj

@ -13,13 +13,16 @@
<TypeScriptToolsVersion>2.8</TypeScriptToolsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Bundling\Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Packages\Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj" />
<ProjectReference Include="..\Volo.Docs.Admin.HttpApi\Volo.Docs.Admin.HttpApi.csproj" />
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
</ItemGroup>
<ItemGroup>
@ -29,8 +32,7 @@
<Content Remove="Pages\**\*.js" />
<Content Remove="Properties\launchSettings.json" />
<Content Remove="compilerconfig.json" />
<Content Remove="compilerconfig.json.defaults" />
<None Remove="Pages\Docs\Admin\Documents\index.js" />
<Content Remove="compilerconfig.json.defaults" />
<None Include="compilerconfig.json" />
<None Include="Properties\launchSettings.json" />
</ItemGroup>

6
modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json

@ -0,0 +1,6 @@
[
{
"outputFile": "Pages/Docs/Admin/Documents/index.css",
"inputFile": "Pages/Docs/Admin/Documents/index.scss"
}
]

63
modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json.defaults

@ -0,0 +1,63 @@
{
"compilers": {
"less": {
"autoPrefix": "",
"cssComb": "none",
"ieCompat": true,
"strictMath": false,
"strictUnits": false,
"relativeUrls": true,
"rootPath": "",
"sourceMapRoot": "",
"sourceMapBasePath": "",
"sourceMap": false
},
"sass": {
"autoPrefix": "",
"includePath": "",
"indentType": "space",
"indentWidth": 2,
"outputStyle": "nested",
"Precision": 5,
"relativeUrls": true,
"sourceMapRoot": "",
"lineFeed": "",
"sourceMap": false
},
"stylus": {
"sourceMap": false
},
"babel": {
"sourceMap": false
},
"coffeescript": {
"bare": false,
"runtimeMode": "node",
"sourceMap": false
},
"handlebars": {
"root": "",
"noBOM": false,
"name": "",
"namespace": "",
"knownHelpersOnly": false,
"forcePartial": false,
"knownHelpers": [],
"commonjs": "",
"amd": false,
"sourceMap": false
}
},
"minifiers": {
"css": {
"enabled": true,
"termSemicolons": true,
"gzip": false
},
"javascript": {
"enabled": true,
"termSemicolons": true,
"gzip": false
}
}
}

4
modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs

@ -4,7 +4,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
@ -34,7 +34,7 @@ namespace Volo.Docs
options.AddProfile<DocsDomainMappingProfile>(validate: true);
});
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<Document, DocumentEto>(typeof(DocsDomainModule));
options.EtoMappings.Add<Project, ProjectEto>(typeof(DocsDomainModule));

23
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs

@ -23,10 +23,21 @@ namespace Volo.Docs.Documents
string version,
CancellationToken cancellationToken = default);
Task<List<Document>> GetAllAsync(Guid? projectId,
Task<List<Document>> GetAllAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
@ -37,6 +48,16 @@ namespace Volo.Docs.Documents
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,

103
modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs

@ -24,16 +24,44 @@ namespace Volo.Docs.Documents
return await DbSet.Where(d => d.ProjectId == projectId).ToListAsync(cancellationToken: cancellationToken);
}
public async Task<List<Document>> GetAllAsync(Guid? projectId,
public async Task<List<Document>> GetAllAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var query = ApplyFilterForGetAll(DbSet, projectId, name, version, languageCode);
var query = ApplyFilterForGetAll(
DbSet,
projectId: projectId,
name: name,
version: version,
languageCode: languageCode,
format: format,
fileName: fileName,
creationTimeMin: creationTimeMin,
creationTimeMax: creationTimeMax,
lastUpdatedTimeMin: lastUpdatedTimeMin,
lastUpdatedTimeMax: lastUpdatedTimeMax,
lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax,
lastCachedTimeMin: lastCachedTimeMin,
lastCachedTimeMax: lastCachedTimeMax
);
query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? nameof(Document.Name) : sorting);
return await query.PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken);
}
@ -43,12 +71,39 @@ namespace Volo.Docs.Documents
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var query = ApplyFilterForGetAll(DbSet, projectId, name, version, languageCode);
var query = ApplyFilterForGetAll(
DbSet,
projectId: projectId,
name: name,
version: version,
languageCode: languageCode,
format: format,
fileName: fileName,
creationTimeMin: creationTimeMin,
creationTimeMax: creationTimeMax,
lastUpdatedTimeMin: lastUpdatedTimeMin,
lastUpdatedTimeMax: lastUpdatedTimeMax,
lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax,
lastCachedTimeMin: lastCachedTimeMin,
lastCachedTimeMax: lastCachedTimeMax
);
return await query.LongCountAsync(GetCancellationToken(cancellationToken));
}
@ -81,13 +136,47 @@ namespace Volo.Docs.Documents
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
CancellationToken cancellationToken = default)
{
return DbSet
.WhereIf(projectId.HasValue, d => d.ProjectId == projectId.Value)
.WhereIf(name != null, d => d.Name != null && d.Name.Contains(name))
.WhereIf(version != null, d => d.Version != null && d.Version == version)
.WhereIf(languageCode != null, d => d.LanguageCode != null && d.LanguageCode == languageCode);
.WhereIf(projectId.HasValue,
d => d.ProjectId == projectId.Value)
.WhereIf(name != null,
d => d.Name != null && d.Name.Contains(name))
.WhereIf(version != null,
d => d.Version != null && d.Version == version)
.WhereIf(languageCode != null,
d => d.LanguageCode != null && d.LanguageCode == languageCode)
.WhereIf(fileName != null,
d => d.FileName != null && d.FileName.Contains(fileName))
.WhereIf(format != null,
d => d.Format != null && d.Format == format)
.WhereIf(creationTimeMin.HasValue,
d => d.CreationTime.Date >= creationTimeMin.Value.Date)
.WhereIf(creationTimeMax.HasValue,
d => d.CreationTime.Date <= creationTimeMax.Value.Date)
.WhereIf(lastUpdatedTimeMin.HasValue,
d => d.LastUpdatedTime.Date >= lastUpdatedTimeMin.Value.Date)
.WhereIf(lastUpdatedTimeMax.HasValue,
d => d.LastUpdatedTime.Date <= lastUpdatedTimeMax.Value.Date)
.WhereIf(lastSignificantUpdateTimeMin.HasValue,
d => d.LastSignificantUpdateTime != null && d.LastSignificantUpdateTime.Value.Date >= lastSignificantUpdateTimeMin.Value.Date)
.WhereIf(lastSignificantUpdateTimeMax.HasValue,
d => d.LastSignificantUpdateTime != null && d.LastSignificantUpdateTime.Value.Date <= lastSignificantUpdateTimeMax.Value.Date)
.WhereIf(lastCachedTimeMin.HasValue,
d => d.LastCachedTime.Date >= lastCachedTimeMin.Value.Date)
.WhereIf(lastCachedTimeMax.HasValue,
d => d.LastCachedTime.Date <= lastCachedTimeMax.Value.Date);
}
}
}

124
modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs

@ -43,33 +43,88 @@ namespace Volo.Docs.Documents
x.Version == version, cancellationToken: cancellationToken);
}
public async Task<List<Document>> GetAllAsync(Guid? projectId,
public async Task<List<Document>> GetAllAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
return await
ApplyFilterForGetAll(GetMongoQueryable(), projectId, name, version, languageCode)
.OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As<IMongoQueryable<Document>>()
.PageBy<Document, IMongoQueryable<Document>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
ApplyFilterForGetAll(
GetMongoQueryable(),
projectId: projectId,
name: name,
version: version,
languageCode: languageCode,
format: format,
fileName: fileName,
creationTimeMin: creationTimeMin,
creationTimeMax: creationTimeMax,
lastUpdatedTimeMin: lastUpdatedTimeMin,
lastUpdatedTimeMax: lastUpdatedTimeMax,
lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax,
lastCachedTimeMin: lastCachedTimeMin,
lastCachedTimeMax: lastCachedTimeMax)
.OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As<IMongoQueryable<Document>>()
.PageBy<Document, IMongoQueryable<Document>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<long> GetAllCountAsync(Guid? projectId,
public async Task<long> GetAllCountAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
return await
ApplyFilterForGetAll(GetMongoQueryable(), projectId, name, version, languageCode)
ApplyFilterForGetAll(
GetMongoQueryable(),
projectId: projectId,
name: name,
version: version,
languageCode: languageCode,
format: format,
fileName: fileName,
creationTimeMin: creationTimeMin,
creationTimeMax: creationTimeMax,
lastUpdatedTimeMin: lastUpdatedTimeMin,
lastUpdatedTimeMax: lastUpdatedTimeMax,
lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin,
lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax,
lastCachedTimeMin: lastCachedTimeMin,
lastCachedTimeMax: lastCachedTimeMax)
.OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As<IMongoQueryable<Document>>()
.PageBy<Document, IMongoQueryable<Document>>(skipCount, maxResultCount)
.LongCountAsync(GetCancellationToken(cancellationToken));
@ -86,6 +141,16 @@ namespace Volo.Docs.Documents
string name,
string version,
string languageCode,
string fileName,
string format,
DateTime? creationTimeMin,
DateTime? creationTimeMax,
DateTime? lastUpdatedTimeMin,
DateTime? lastUpdatedTimeMax,
DateTime? lastSignificantUpdateTimeMin,
DateTime? lastSignificantUpdateTimeMax,
DateTime? lastCachedTimeMin,
DateTime? lastCachedTimeMax,
CancellationToken cancellationToken = default)
{
if (projectId.HasValue)
@ -108,6 +173,51 @@ namespace Volo.Docs.Documents
query = query.Where(d => d.LanguageCode != null && d.LanguageCode == languageCode);
}
if (fileName != null)
{
query = query.Where(d => d.FileName != null && d.FileName.Contains(fileName));
}
if (creationTimeMin.HasValue)
{
query = query.Where(d => d.CreationTime.Date >= creationTimeMin.Value.Date);
}
if (creationTimeMax.HasValue)
{
query = query.Where(d => d.CreationTime.Date <= creationTimeMax.Value.Date);
}
if (lastUpdatedTimeMin.HasValue)
{
query = query.Where(d => d.LastUpdatedTime.Date >= lastUpdatedTimeMin.Value.Date);
}
if (lastUpdatedTimeMax.HasValue)
{
query = query.Where(d => d.LastUpdatedTime.Date <= lastUpdatedTimeMax.Value.Date);
}
if (lastSignificantUpdateTimeMin.HasValue)
{
query = query.Where(d => d.LastSignificantUpdateTime != null && d.LastSignificantUpdateTime.Value.Date >= lastSignificantUpdateTimeMin.Value.Date);
}
if (lastSignificantUpdateTimeMax.HasValue)
{
query = query.Where(d => d.LastSignificantUpdateTime != null && d.LastSignificantUpdateTime.Value.Date <= lastSignificantUpdateTimeMax.Value.Date);
}
if (lastCachedTimeMin.HasValue)
{
query = query.Where(d => d.LastCachedTime.Date >= lastCachedTimeMin.Value.Date);
}
if (lastCachedTimeMax.HasValue)
{
query = query.Where(d => d.LastCachedTime.Date <= lastCachedTimeMax.Value.Date);
}
return query;
}
}

4
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs

@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
@ -29,7 +29,7 @@ namespace Volo.Abp.Identity
options.AddProfile<IdentityDomainMappingProfile>(validate: true);
});
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<IdentityUser, UserEto>(typeof(AbpIdentityDomainModule));
options.EtoMappings.Add<IdentityClaimType, IdentityClaimTypeEto>(typeof(AbpIdentityDomainModule));

2
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs

@ -131,7 +131,7 @@ namespace Volo.Abp.Identity
if (siblings.Any(ou => ou.DisplayName == organizationUnit.DisplayName))
{
throw new UserFriendlyException(Localizer["OrganizationUnitDuplicateDisplayNameWarning", organizationUnit.DisplayName]);
throw new UserFriendlyException(Localizer["Identity.OrganizationUnit.DuplicateDisplayNameWarning", organizationUnit.DisplayName]);
}
}

9
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.Identity;
@ -14,6 +15,14 @@ namespace Volo.Abp.Identity
)]
public class AbpIdentityDomainTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.AutoEventSelectors.Add<IdentityUser>();
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
SeedTestData(context);

15
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs

@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Testing.Utils;
using Volo.Abp.Uow;
@ -28,11 +29,17 @@ namespace Volo.Abp.Identity
}
[Fact]
public async Task Should_Register_Handler()
public void Should_Register_Handler()
{
var options = GetRequiredService<IOptions<AbpDistributedEventBusOptions>>().Value;
options.EtoMappings.ShouldContain(m => m.Key == typeof(IdentityUser) && m.Value.EtoType == typeof(UserEto));
options.Handlers.ShouldContain(h => h == typeof(DistributedUserUpdateHandler));
GetRequiredService<IOptions<AbpDistributedEntityEventOptions>>()
.Value
.EtoMappings
.ShouldContain(m => m.Key == typeof(IdentityUser) && m.Value.EtoType == typeof(UserEto));
GetRequiredService<IOptions<AbpDistributedEventBusOptions>>()
.Value
.Handlers
.ShouldContain(h => h == typeof(DistributedUserUpdateHandler));
}
[Fact]

4
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs

@ -6,7 +6,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AutoMapper;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Caching;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
@ -41,7 +41,7 @@ namespace Volo.Abp.IdentityServer
options.AddProfile<IdentityServerAutoMapperProfile>(validate: true);
});
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<ApiResource, ApiResourceEto>(typeof(AbpIdentityServerDomainModule));
options.EtoMappings.Add<Client, ClientEto>(typeof(AbpIdentityServerDomainModule));

4
modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs

@ -2,7 +2,7 @@
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.UI;
@ -26,7 +26,7 @@ namespace Volo.Abp.TenantManagement
options.AddProfile<AbpTenantManagementDomainMappingProfile>(validate: true);
});
Configure<AbpDistributedEventBusOptions>(options =>
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<Tenant, TenantEto>();
});

7
npm/ng-packs/packages/core/src/lib/services/list.service.ts

@ -1,6 +1,6 @@
import { Inject, Injectable, OnDestroy, Optional } from '@angular/core';
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
import { debounceTime, shareReplay, switchMap, tap } from 'rxjs/operators';
import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs';
import { catchError, debounceTime, filter, shareReplay, switchMap, tap } from 'rxjs/operators';
import { ABP } from '../models/common';
import { PagedResultDto } from '../models/dtos';
import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens/list.token';
@ -88,7 +88,8 @@ export class ListService<QueryParamsType = ABP.PageQueryParams> implements OnDes
this._isLoading$.next(true);
return this.query$.pipe(
switchMap(streamCreatorCallback),
switchMap(query => streamCreatorCallback(query).pipe(catchError(() => of(null)))),
filter(Boolean),
tap(() => this._isLoading$.next(false)),
shareReplay({ bufferSize: 1, refCount: true }),
takeUntilDestroy(this),

13
npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts

@ -36,8 +36,15 @@ export class NgxDatatableListDirective implements OnChanges, OnDestroy, OnInit {
private subscribeToSort() {
const sub = this.table.sort.subscribe(({ sorts: [{ prop, dir }] }) => {
this.list.sortKey = prop;
this.list.sortOrder = dir;
if (prop === this.list.sortKey && this.list.sortOrder === 'desc') {
this.list.sortKey = '';
this.list.sortOrder = '';
this.table.sorts = [];
this.cdRef.detectChanges();
} else {
this.list.sortKey = prop;
this.list.sortOrder = dir;
}
});
this.subscription.add(sub);
}
@ -45,7 +52,7 @@ export class NgxDatatableListDirective implements OnChanges, OnDestroy, OnInit {
private subscribeToIsLoading() {
const sub = this.list.isLoading$.subscribe(loading => {
this.table.loadingIndicator = loading;
this.cdRef.markForCheck();
this.cdRef.detectChanges();
});
this.subscription.add(sub);
}

1
samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs

@ -8,7 +8,6 @@ using ProductManagement;
using StackExchange.Redis;
using Microsoft.OpenApi.Models;
using MsDemo.Shared;
using Swashbuckle.AspNetCore.Swagger;
using Volo.Abp;
using Volo.Abp.AspNetCore.Authentication.OAuth;
using Volo.Abp.AspNetCore.Mvc.Client;

6
samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs

@ -13,6 +13,7 @@ using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Auditing;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Autofac;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.EventBus.RabbitMq;
@ -77,6 +78,11 @@ namespace IdentityService.Host
{
options.UseSqlServer();
});
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.AutoEventSelectors.Add<IdentityUser>();
});
context.Services.AddStackExchangeRedisCache(options =>
{

Loading…
Cancel
Save