Skip to main content
Version: Next

Project Level Service Integration

Files Referenced in this Document

Contents​

  1. Introduction
  2. Core Design and Inheritance Mechanism
  3. Service Property Parsing
  4. Variable Monitoring Mechanism in StartAsync
  5. OnTagValueChanged Event Handling and Asynchronous Best Practices
  6. Independent Dependency Injection Scope in ProcessAsync
  7. Resource Management and Code Safety under High Concurrency
  8. Background Job Integration Strategy
  9. 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 by IProjectServiceRunner.
  • 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​

  1. Variable Definition: Explicitly declares variable names to be monitored (MyPluginName_Variable1, MyPluginName_Variable2) and their descriptions via _monitorVariableNames dictionary.
  2. Channel Creation: Uses FlowVariableChannelListener to create a dedicated communication channel. Via CreateChannel method, passes service Key, timeout (30 seconds), and variable filter (_monitorVariableNames.Keys.ToHashSet()) to ensure the channel only receives concerned variable change events.
  3. Event Subscription: Registers OnTagValueChanged method as the handler for TagChanged event, 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 IServiceProvider instance.
  • Can safely resolve Scoped lifecycle services (like Repository, UnitOfWork).
  • Automatically releases all resources after using block 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.Token via Dispose(), ensuring network resources and event subscriptions are correctly cleaned up.
  • Scope Management: Uses using statement in ProcessAsync to ensure IServiceScope and IUnitOfWork are 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.