diff --git a/docs/en/Blob-Storing.md b/docs/en/Blob-Storing.md index 1cb8acb78c..2b49a896cd 100644 --- a/docs/en/Blob-Storing.md +++ b/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`, which returns exactly the same container. -The name of the default container is `Default`. +The name of the default container is `default`. ### Named Containers diff --git a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md index dc3d5c172f..9587a111bf 100644 --- a/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md +++ b/docs/en/Distributed-Event-Bus-RabbitMQ-Integration.md @@ -1,3 +1,134 @@ # Distributed Event Bus RabbitMQ Integration -TODO \ No newline at end of file +> 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(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(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. \ No newline at end of file diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 03beb814d2..c22e3c85e5 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -1,3 +1,181 @@ # Distributed Event Bus -TODO \ No newline at end of file +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 + { + 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` 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, + 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` 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. diff --git a/docs/en/Event-Bus.md b/docs/en/Event-Bus.md index cf2b57c018..a0e6859465 100644 --- a/docs/en/Event-Bus.md +++ b/docs/en/Event-Bus.md @@ -1,3 +1,10 @@ # Event Bus -TODO \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index 859ec8d28e..d0f494faea 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -1,3 +1,227 @@ # Local Event Bus -TODO \ No newline at end of file +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 + { + 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` 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, + 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` 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>, + ITransientDependency + { + public async Task HandleEventAsync( + EntityCreatedEventData eventData) + { + var userName = eventData.Entity.UserName; + var email = eventData.Entity.Email; + //... + } + } +} +```` + +This class subscribes to the `EntityCreatedEventData`, 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` is published just after an entity was successfully created. +* `EntityUpdatedEventData` is published just after an entity was successfully updated. +* `EntityDeletedEventData` is published just after an entity was successfully deleted. +* `EntityChangedEventData` 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` is published just before saving a new entity to the database. +* `EntityUpdatingEventData` is published just before an existing entity is being updated. +* `EntityDeletingEventData` is published just before an entity is being deleted. +* `EntityChangingEventData` 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). \ No newline at end of file diff --git a/docs/en/RabbitMq.md b/docs/en/RabbitMq.md new file mode 100644 index 0000000000..11dc305b41 --- /dev/null +++ b/docs/en/RabbitMq.md @@ -0,0 +1,3 @@ +# RabbitMQ + +TODO! \ No newline at end of file diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 86a3e231c5..ff42c6f440 100644 --- a/docs/en/Repositories.md +++ b/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 diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index a7c6b0b163..afcffe50a5 100644 --- a/docs/en/docs-nav.json +++ b/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" } ] } diff --git a/docs/zh-Hans/Repositories.md b/docs/zh-Hans/Repositories.md index db4701a573..86d2f2e43d 100644 --- a/docs/zh-Hans/Repositories.md +++ b/docs/zh-Hans/Repositories.md @@ -38,6 +38,8 @@ public class PersonAppService : ApplicationService } ```` +> 参阅 "*IQueryable & 异步操作*" 部分了解如何使用 **异步扩展方法**, 如 `ToListAsync()` (建议始终使用异步) 而不是 `ToList()`. + 在这个例子中; * `PersonAppService` 在它的构造函数中注入了 `IRepository` . @@ -119,4 +121,133 @@ public class PersonRepository : EfCoreRepository, IPe } ```` -你可以直接使用数据库访问提供程序 (本例中是 `DbContext` ) 来执行操作. 有关基于EF Core的自定义仓储的更多信息, 请参阅[EF Core 集成文档](Entity-Framework-Core.md). \ No newline at end of file +你可以直接使用数据库访问提供程序 (本例中是 `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`的异步扩展方法,而是提供 `IMongoQueryable`. 你需要先将查询强制转换为 `IMongoQueryable` 才能使用异步扩展方法. + +**示例: 转换Cast `IQueryable` 为 `IMongoQueryable` 并且使用 `ToListAsync()`** + +````csharp +var people = ((IMongoQueryable)_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` 对象的服务,**不依赖于实际的数据库提供程序**. + +**示例: 注入并使用 `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 _productRepository; + private readonly IAsyncQueryableExecuter _asyncExecuter; + + public ProductAppService( + IRepository productRepository, + IAsyncQueryableExecuter asyncExecuter) + { + _productRepository = productRepository; + _asyncExecuter = asyncExecuter; + } + + public async Task> GetListAsync(string name) + { + //Create the query + var query = _productRepository + .Where(p => p.Name.Contains(name)) + .OrderBy(p => p.Name); + + //Run the query asynchronously + List products = await _asyncExecuter.ToListAsync(query); + + //... + } + } +} +```` + +> `ApplicationService` 和 `DomainService` 基类已经预属性注入了 `AsyncExecuter` 属性,所以你可直接使用. + +ABP框架使用实际数据库提供程序的API异步执行查询.虽然这不是执行查询的常见方式,但它是使用异步API而不依赖于数据库提供者的最佳方式. + +此方法建议; + +* 如果你正在构建一个没有数据库提供程序集成包的**可重用库**,但是在某些情况下需要执行 `IQueryable`对象. + +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`. \ No newline at end of file diff --git a/docs/zh-Hans/SignalR-Integration.md b/docs/zh-Hans/SignalR-Integration.md index 3dea373f5a..5e17b1c75c 100644 --- a/docs/zh-Hans/SignalR-Integration.md +++ b/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) \ No newline at end of file +* [微软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) \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs index 1e5f138f97..81696d5e71 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Select2/Select2ScriptContributor.cs +++ b/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"); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index e8489ad62c..824e376934 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/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>>'; diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs index 038d53af6d..278ed46839 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs +++ b/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"; } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AbpDistributedEntityEventOptions.cs new file mode 100644 index 0000000000..19652fca8e --- /dev/null +++ b/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(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorList.cs new file mode 100644 index 0000000000..328c74e324 --- /dev/null +++ b/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, IAutoEntityDistributedEventSelectorList + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs new file mode 100644 index 0000000000..d2a0622f54 --- /dev/null +++ b/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 + ) + ); + } + + /// + /// Adds a specific entity type and the types derived from that entity type. + /// + /// Type of the entity + public static void Add([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) + ) + ); + } + + /// + /// Adds all entity types. + /// + 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 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 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)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs index 24a757fb08..15a41bad9d 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs +++ b/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 options, + IOptions options, IHybridServiceScopeFactory hybridServiceScopeFactory) { HybridServiceScopeFactory = hybridServiceScopeFactory; diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionary.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionary.cs similarity index 79% rename from framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionary.cs rename to framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionary.cs index aae99d0cb7..3988ceb3b5 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionary.cs +++ b/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 { diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionaryItem.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionaryItem.cs similarity index 87% rename from framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionaryItem.cs rename to framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EtoMappingDictionaryItem.cs index d634c44b4d..2550c83a53 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/EtoMappingDictionaryItem.cs +++ b/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 { diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IAutoEntityDistributedEventSelectorList.cs new file mode 100644 index 0000000000..fc130eb55c --- /dev/null +++ b/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 + { + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs index c9219feffc..cad12b1450 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs +++ b/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 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 + ); + } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs index 1d1d7768f2..342e7b77bb 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs +++ b/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 Handlers { get; } - public EtoMappingDictionary EtoMappings { get; set; } public AbpDistributedEventBusOptions() { Handlers = new TypeList(); - EtoMappings = new EtoMappingDictionary(); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs index 683c99d417..41079bfa9e 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs +++ b/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(options => + Configure(options => { + options.AutoEventSelectors.Add(); options.EtoMappings.Add(); }); } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs index f39c4c88fb..3179303215 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs +++ b/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(validate: true); }); - Configure(options => + Configure(options => { options.EtoMappings.Add(typeof(BloggingDomainModule)); options.EtoMappings.Add(typeof(BloggingDomainModule)); diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs index ced1c1ceda..e375f6ba0b 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/GetAllInput.cs +++ b/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; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json index 32d0b3bc27..b73f43317c 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json +++ b/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" } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index 0868e9eb80..cb6443d268 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/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> 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 diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml index 876d475dfc..1afd658afe 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml @@ -22,31 +22,194 @@ } - - - -

Filters ...

- @* - - *@ -
-
- - - - - @L["Actions"] - @L["Name"] - @L["Version"] - @L["LanguageCode"] - @L["FileName"] - @L["Format"] - @L["CreationTime"] - @L["LastUpdatedTime"] - @L["LastSignificantUpdateTime"] - @L["LastCachedTime"] - - - - -
+@section styles { + +} + +
+ + +
+ +
+
+
+
+
@L["Name"].Value
+
+ + +
+
+ +
+
+
+
@L["Version"].Value
+
+ + +
+
+ +
+
+
+
@L["LanguageCode"].Value
+
+ + +
+
+ + +
+
+
+
@L["FileName"].Value
+
+ + +
+
+ +
+
+
+
@L["Format"].Value
+
+ + +
+
+ + +
+
+
+
@L["CreationTime"].Value
+
+ + + + - + + +
+
+ +
+
+
+
@L["LastUpdateTime"].Value
+
+ + + + - + + +
+
+ +
+
+
+
@L["LastSignificantUpdateTime"].Value
+
+ + + + - + + +
+
+ +
+
+
+
@L["LastCachedTime"].Value
+
+ + + + - + + +
+
+ +
+ +
+
+
+
+ + + + + @L["Actions"] + @L["Name"] + @L["Version"] + @L["LanguageCode"] + @L["FileName"] + @L["Format"] + @L["CreationTime"] + @L["LastUpdatedTime"] + @L["LastSignificantUpdateTime"] + @L["LastCachedTime"] + + + + +
+
\ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs index def325bd0d..559eaab5eb 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml.cs +++ b/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 { diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.css b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.css new file mode 100644 index 0000000000..006512da9a --- /dev/null +++ b/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; } diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js index ed5e5793de..dc77bbc687 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js +++ b/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(); }); }); diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.min.css b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.min.css new file mode 100644 index 0000000000..f98ba4beab --- /dev/null +++ b/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;} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.scss b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.scss new file mode 100644 index 0000000000..97147313df --- /dev/null +++ b/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; + } +} diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj index 3098bab9d0..164c28cdb2 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj +++ b/modules/docs/src/Volo.Docs.Admin.Web/Volo.Docs.Admin.Web.csproj @@ -13,13 +13,16 @@ 2.8 + + + + - @@ -29,8 +32,7 @@ - - + diff --git a/modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json b/modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json new file mode 100644 index 0000000000..32ae3754e2 --- /dev/null +++ b/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" + } +] \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json.defaults b/modules/docs/src/Volo.Docs.Admin.Web/compilerconfig.json.defaults new file mode 100644 index 0000000000..b3519312c4 --- /dev/null +++ b/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 + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs index 5e53b858ce..ecc52d1f26 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs +++ b/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(validate: true); }); - Configure(options => + Configure(options => { options.EtoMappings.Add(typeof(DocsDomainModule)); options.EtoMappings.Add(typeof(DocsDomainModule)); diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs index c09cb261ec..3773775b88 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentRepository.cs +++ b/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> GetAllAsync(Guid? projectId, + Task> 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, diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs index 46145043b1..f096bc10d9 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Documents/EFCoreDocumentRepository.cs +++ b/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> GetAllAsync(Guid? projectId, + public async Task> 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); } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs index 78710e553e..b3b5210986 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs +++ b/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> GetAllAsync(Guid? projectId, + public async Task> 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>() - .PageBy>(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>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); } - public async Task GetAllCountAsync(Guid? projectId, + public async Task 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>() .PageBy>(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; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index f757c56f1e..07c4d2e971 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/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(validate: true); }); - Configure(options => + Configure(options => { options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index 778bde3802..9db3ff4581 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/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]); } } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs index d9fb330a02..ccfbb85359 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs +++ b/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(options => + { + options.AutoEventSelectors.Add(); + }); + } + public override void OnApplicationInitialization(ApplicationInitializationContext context) { SeedTestData(context); diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs index 95e8f925c4..b3fc9b1ba3 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs +++ b/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>().Value; - options.EtoMappings.ShouldContain(m => m.Key == typeof(IdentityUser) && m.Value.EtoType == typeof(UserEto)); - options.Handlers.ShouldContain(h => h == typeof(DistributedUserUpdateHandler)); + GetRequiredService>() + .Value + .EtoMappings + .ShouldContain(m => m.Key == typeof(IdentityUser) && m.Value.EtoType == typeof(UserEto)); + + GetRequiredService>() + .Value + .Handlers + .ShouldContain(h => h == typeof(DistributedUserUpdateHandler)); } [Fact] diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 01001aeb35..9e36576e56 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/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(validate: true); }); - Configure(options => + Configure(options => { options.EtoMappings.Add(typeof(AbpIdentityServerDomainModule)); options.EtoMappings.Add(typeof(AbpIdentityServerDomainModule)); diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs index f882f8659e..5bf2aeb0b9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs +++ b/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(validate: true); }); - Configure(options => + Configure(options => { options.EtoMappings.Add(); }); diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 3aff601f7a..d3da082eeb 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/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 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), diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts index 85e574d63d..a16925ebd7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts +++ b/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); } diff --git a/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs b/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs index b351a33fb6..a8c5d58ac4 100644 --- a/samples/MicroserviceDemo/applications/BackendAdminApp.Host/BackendAdminAppHostModule.cs +++ b/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; diff --git a/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs b/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs index 9b96532e4c..83317c3f0f 100644 --- a/samples/MicroserviceDemo/microservices/IdentityService.Host/IdentityServiceHostModule.cs +++ b/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(options => + { + options.AutoEventSelectors.Add(); + }); context.Services.AddStackExchangeRedisCache(options => {