版权护体©作者:whuanle,微信公众号转载文章需要 《NCC开源社区》同意。
This article will delve into the Event Bus in ABP, and gradually build a complete system by integrating it into our base Framework project.
Source code link: https://github.com/whuanle/AbpBaseStruct
Event Bus
About Event Bus
In ABP, to facilitate inter-process communication, developers are provided with a feature called Event Bus
. The Event Bus is divided into Local Event Bus
and Distributed Event Bus
, and this article focuses on Local Event Bus
. The series tutorial will not cover Distributed Event Bus
for now.
The Event Bus
requires the Volo.Abp.EventBus
library, which is included in the ABP package, so no additional imports are needed.
The Event Bus is used in a publish-subscribe manner, where one party only needs to push events in a specified format without worrying about who is receiving the event and how it will be handled.
You can refer to the official documentation: https://docs.abp.io/en/latest/Local-Event-Bus
Why this is needed
Let's first list some code snippets you may have written when developing a project with controllers.
// Log 1
Task.Run(() =>
{
_apiLog.Info($"xxxxxxxx");
});
// Log 2
catch(Exception ex)
{
_apiLog.Error(ex);
}
// Log 3
_apiLog.Info($"Login Information: User [{userName}({clientAddress})]");
The author believes that one way to improve the above scenarios is to separate the function's functionality from log recording, allowing functions to simply push status and information through the Event Bus without worrying about how these contents should be handled.
Moreover, when a function executes certain steps and generates an event, developers often tend to new Thread
a new thread to perform other tasks, or use Task.Run
.
In reality, by utilizing the Event Bus, we can better isolate code and abide by the Single Responsibility Principle
. Of course, there are many more aspects worth using the Event Bus, but we won't delve into that here.
Previously, we created a global exception interceptor and a logging component, and in this article, we will combine various components of the web application using the Event Bus.
Event Bus Creation Process
Subscribe to Events
Create a service to subscribe to events. When a certain event occurs in the program, this service will be called.
The event service must inherit from the ILocalEventHandler<in TEvent>
interface and implement the following function:
Task HandleEventAsync(TEvent eventData);
In a system, there can be multiple event services, and the TEvent
type of each service must be different because the type of TEvent
serves as an identifier for the calling service. When a TEvent
event occurs, the system retrieves the service using TEvent
.
After creating the event service, it needs to be added to the dependency injection container. You can inherit from the ITransientDependency
interface and then scan the assembly to add it to the dependency injection container (as mentioned in the third article).
Events
This corresponds to the TEvent
mentioned above.
Assuming all event services in a system are placed into one container, the publisher can only transmit an event without specifying who will provide the corresponding service.
The container looks up services based on TEvent
.
An event is essentially a model class, and simple types like int
or string
can also be used (but do not use simple types for events), which are used to transmit information.
Typically, Event
is used as a suffix.
Publish Events
To publish an event, simply inject ILocalEventBus
.
private readonly ILocalEventBus _localEventBus;
public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
Then publish the event:
await _localEventBus.PublishAsync(
new TEvent
{
... ...
}
);
Global Exception Handling Added to Event Bus Functionality
Create Event
In AbpBase.Web
, create a Handlers
directory, and then create a HandlerEvents
directory under the Handlers
directory.
Then create a CustomerExceptionEvent.cs
file in the HandlerEvents
directory.
CustomerExceptionEvent
acts as an exception event for transmitting exception information, rather than merely logging Exception ex
.
The file content is as follows:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace AbpBase.Application.Handlers.HandlerEvents
{
/// <summary>
/// Global Exception Push Event
/// </summary>
public class CustomerExceptionEvent
{
/// <summary>
/// Record exception only
/// </summary>
/// <param name="ex"></param>
public CustomerExceptionEvent(Exception ex)
{
Exception = ex;
}
/// <summary>
/// The route address requested by the user when this exception occurred
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute)
{
Exception = ex;
Action = actionRoute;
}
/// <summary>
/// The method type in which this exception occurred
/// </summary>
/// <param name="ex"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, MethodBase method)
{
Exception = ex;
MethodInfo = (MethodInfo)method;
}
/// <summary>
/// Record exception information
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method)
{
Exception = ex;
Action = actionRoute;
MethodInfo = (MethodInfo)method;
}
/// <summary>
/// Current occurrence location
/// <example>
/// <code>
/// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod();
/// </code>
/// </example>
/// </summary>
public MethodInfo MethodInfo { get; private set; }
/// <summary>
/// The Action in which the exception occurred
/// </summary>
public string Action { get; private set; }
/// <summary>
/// Specific exception
/// </summary>
public Exception Exception { get; private set; }
}
}
Subscribe to Events
Subscribing to events means defining it as an event responder or service provider.
When an exception occurs, where shall the exception's location and information be sent? That is the responsibility of the subscribers.
Here, we define a class to handle the exception logs, which will process the exception information pushed by the program.
In the Handlers
directory of the AbpBase.Web
project, add a CustomerExceptionHandler
class that inherits from:
public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency
To handle events, services must inherit from ILocalEventHandler<T>
, and ITransientDependency
allows this service to be automatically injected into the container.
The file content is as follows:
using AbpBase.Application.Handlers.HandlerEvents;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace AbpBase.Application.Handlers
{
/// <summary>
/// Global Exception Logging
/// </summary>
public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency
{
private readonly ILogger _ILogger;
public CustomerExceptionHandler(ILogger logger)
{
_ILogger = logger;
}
public async Task HandleEventAsync(CustomerExceptionEvent eventData)
{
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.AppendLine();
stringBuilder.Append("Action: ");
stringBuilder.AppendLine(eventData.Action);
if (eventData.MethodInfo != null)
{
stringBuilder.Append("Class-Method: ");
stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName);
stringBuilder.AppendLine(eventData.MethodInfo?.Name);
}
stringBuilder.Append("Source: ");
stringBuilder.AppendLine(eventData.Exception.Source);
stringBuilder.Append("TargetSite: ");
stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString());
stringBuilder.Append("InnerException: ");
stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString());
stringBuilder.Append("Message: ");
stringBuilder.AppendLine(eventData.Exception.Message);
stringBuilder.Append("HelpLink: ");
stringBuilder.AppendLine(eventData.Exception.HelpLink);
_ILogger.Fatal(stringBuilder.ToString());
await Task.CompletedTask;
}
}
}
By implementing it this way, the recorded logs can have a good hierarchical structure.
Publish Events
After defining the event format and the service to subscribe to events, we will now create a publisher.
We'll make modifications to WebGlobalExceptionFilter
.
Add dependency injection:
private readonly ILocalEventBus _localEventBus;
public WebGlobalExceptionFilter(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
Publish the event:
public async Task OnExceptionAsync(ExceptionContext context)
{
if (!context.ExceptionHandled)
{
await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception,
context.ActionDescriptor?.DisplayName));
...
...
Testing
Create an Action:
[HttpGet("/T4")]
public string MyWebApi4()
{
int a = 1;
int b = 0;
int c = a / b;
return c.ToString();
}
Then navigate to https://localhost:5001/T4, and you will find that a request error occurs.
In the AbpBase.Web
project under the Logs
directory, open the -Fatal.txt
file.
You can see:
2020-09-16 18:49:27.750 +08:00 [FTL]
Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi)
Source: ApbBase.HttpApi
TargetSite: System.String MyWebApi4()
InnerException:
Message: Attempted to divide by zero.
HelpLink:
In addition to the exception message, we can easily see that the exception occurred in the TestController.MyWebApi4
location.
Log Events
If an exception occurs in a regular method, we can log it like this:
catch (Exception ex)
{
...
new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod());
...
}
MethodBase.GetCurrentMethod()
can retrieve the currently executing method information, and after obtaining this information, pass this parameter to the exception logging service, which will automatically parse which specific place the exception occurred.
Currently, since there are no services written in the web application, we will temporarily integrate this into the exception logging functionality. The Event Bus will be used again when services are written later.
Refer to the complete code at: https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase
The next article can be found at: https://www.cnblogs.com/whuanle/p/13061059.html
文章评论