Project Level Service Integration
Files Referenced in this Document
- MyPluginNameProjectService.cs
- MyPluginNameJob.cs
- CMSPluginEntry.cs
- CMSPluginModule.cs
- CMSPluginMyPluginNameExtensions.cs
Contents
- Introduction
- Core Design and Inheritance Mechanism
- Service Property Parsing
- Variable Monitoring Mechanism in StartAsync
- OnTagValueChanged Event Handling and Asynchronous Best Practices
- Independent Dependency Injection Scope in ProcessAsync
- Resource Management and Code Safety under High Concurrency
- Background Job Integration Strategy
- Conclusion
Introduction
This document deeply analyzes the core integration mechanism of MyPluginNameProjectService as a project-level background service. Based on the platform's project service architecture, this service implements key capabilities such as monitoring runtime variable changes, asynchronous business processing, independent scope service access, and background job scheduling. It is a typical example of building scalable, high-performance plugin-based background services.
Core Design and Inheritance Mechanism
MyPluginNameProjectService becomes a platform-recognizable and schedulable project-level background service component by inheriting from BaseProjectService and implementing the IProjectService interface. This design pattern implements standardized service lifecycle management (start, stop), unified service registration mechanism, and deep integration with project context.
The service is registered as a singleton service (AddSingleton<IProjectService, MyPluginNameProjectService>) via dependency injection in CMSPluginEntry, ensuring only one instance exists throughout the entire project lifecycle, thus efficiently managing listening resources and state.
Service Property Parsing
The service defines its core metadata and behavioral characteristics by overriding base class properties:
- Key:
"MyPluginName", serving as the unique identifier of the service for precise matching and calling byIProjectServiceRunner. - Description:
"MyPluginName Service", readable name displayed in the management interface for operations personnel. - AuthRequired:
true, indicating that this service requires authorization verification when executed, enhancing service call security.
These properties collectively constitute the service's "identity declaration", forming the basis of the service registration and discovery mechanism.
Variable Monitoring Mechanism in StartAsync
The StartAsync method is the core logic of service startup, implementing dynamic monitoring of specified runtime variables.
Channel Creation and Event Subscription
- Variable Definition: Explicitly declares variable names to be monitored (
MyPluginName_Variable1,MyPluginName_Variable2) and their descriptions via_monitorVariableNamesdictionary. - Channel Creation: Uses
FlowVariableChannelListenerto create a dedicated communication channel. ViaCreateChannelmethod, passes serviceKey, timeout (30 seconds), and variable filter (_monitorVariableNames.Keys.ToHashSet()) to ensure the channel only receives concerned variable change events. - Event Subscription: Registers
OnTagValueChangedmethod as the handler forTagChangedevent, establishing a response link from "variable change" to "business processing".
This mechanism implements event-driven lightweight monitoring, avoiding performance overhead from polling.
OnTagValueChanged Event Handling and Asynchronous Best Practices
OnTagValueChanged is the core entry for event handling, designed following best practices for high concurrency scenarios.
Non-blocking Asynchronous Processing Pattern
The platform blocks the main thread when publishing TagChanged events. To avoid time-consuming business logic slowing down the entire variable system, MyPluginNameProjectService uses Task.Run to push business processing to the thread pool:
_ = Task.Run(async () =>
{
// Execute time-consuming business logic in new thread
await ProcessAsync();
});
This pattern achieves:
- Decoupling: Separation of event listening (platform base) and business processing (plugin logic).
- Non-blocking: Quick response to events, returning immediately, not blocking platform core flow.
- Concurrency: Utilizing thread pool to handle high-frequency event triggers.
Independent Dependency Injection Scope in ProcessAsync
The ProcessAsync method demonstrates how to safely access dependent services in a background thread.
Creation of Independent Scope
Creates a brand new dependency injection scope via _serviceProvider.CreateScope(). This scope:
- Has an independent
IServiceProviderinstance. - Can safely resolve
Scopedlifecycle services (like Repository, UnitOfWork). - Automatically releases all resources after
usingblock ends, preventing memory leaks.
Service Access Example
The code demonstrates how to safely access database repositories in a transaction, execute data queries or update operations, and commit changes via uow.SaveChangesAsync().
Resource Management and Code Safety under High Concurrency
In high concurrency scenarios, the design of MyPluginNameProjectService fully considers resource management and code safety.
Resource Management
- Listener Lifecycle: In
StopAsync, explicitly releases_channelListener.TokenviaDispose(), ensuring network resources and event subscriptions are correctly cleaned up. - Scope Management: Uses
usingstatement inProcessAsyncto ensureIServiceScopeandIUnitOfWorkare released in time.
Code Safety
Code comments explicitly state that CMS.CodeAnalysis analyzer should be used to check usage of IVariableDataCache.TagChanged. This analyzer can detect potential performance anti-patterns (like synchronous I/O operations) at compile time and force developers to follow asynchronous non-blocking principles through compilation errors, guaranteeing system stability from the source.
Background Job Integration Strategy
Although the background job code in StopAsync is commented out, it reveals the strategy for integration with IBackgroundJobManager.
Trigger Strategy
Services can put tasks (like MyPluginNameArgs) into the background job queue via EnqueueAsync method during specific lifecycle events (like stop) or business logic.
Job Implementation
The MyPluginNameJob class inherits from BackgroundJob<MyPluginNameArgs> and handles tasks in the Execute method. It obtains IUnitOfWorkManager and IMyEntityNameRepository via dependency injection, executing data operations in an independent background thread, ensuring responsiveness of the main service.
Conclusion
MyPluginNameProjectService is a well-designed project-level background service template. It integrates into the platform architecture through inheritance and interface implementation, uses event-driven and asynchronous processing to respond to variable changes, safely accesses data services through independent scopes, and implements decoupled long-running tasks through background jobs. Its design fully considers performance, security, and maintainability, serving as an excellent practice guide for developing complex background services.