Skip to main content
Version: Next

Project-Level Service Integration

Files referenced in this document

Table of Contents​

  1. Introduction
  2. Core Design and Inheritance Mechanism
  3. Service Property Analysis
  4. Variable Listening Mechanism in StartAsync
  5. OnTagValueChanged Event Handling and Async Best Practices
  6. Independent DI Scope in ProcessAsync
  7. Resource Management and Code Safety under High Concurrency
  8. Background Job Integration Strategy
  9. Conclusion

Introduction​

This article 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 serves as a typical example for building scalable, high-performance pluggable 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 achieves standardized service lifecycle management (start, stop), unified service registration mechanisms, and deep integration with the project context.

The service is registered as a singleton service in CMSPluginEntry via dependency injection (AddSingleton<IProjectService, MyPluginNameProjectService>), ensuring only one instance exists throughout the project lifecycle, thereby efficiently managing listening resources and state.

Section sources

Service Property Analysis​

The service defines its core metadata and behavioral characteristics by overriding base class properties:

  • Key: "MyPluginName", serving as the unique identifier for the service, used by IProjectServiceRunner for precise matching and invocation during scheduling.
  • Description: "MyPluginName Service", a readable name displayed in the management interface for operations personnel.
  • AuthRequired: true, indicating that the service requires authorization verification during execution, enhancing service invocation security.

These properties collectively constitute the service's "identity declaration," forming the basis of the service registration and discovery mechanism.

Section sources

Variable Listening Mechanism in StartAsync​

The StartAsync method contains the core logic for service startup, implementing dynamic monitoring of specified runtime variables.

Channel Creation and Event Subscription​

  1. Variable Definition: Explicitly declares variable names to monitor (MyPluginName_Variable1, MyPluginName_Variable2) and their descriptions via the _monitorVariableNames dictionary.
  2. Channel Creation: Uses FlowVariableChannelListener to create a dedicated communication channel. Through the CreateChannel method, passing the service Key, timeout (30 seconds), and variable filter (_monitorVariableNames.Keys.ToHashSet()), ensuring the channel receives only relevant variable change events.
  3. Event Subscription: Registers the OnTagValueChanged method as the handler for the TagChanged event, establishing a response link from "variable change" to "business processing".

This mechanism implements lightweight event-driven monitoring, avoiding the performance overhead of polling.

Diagram sources

Section sources

OnTagValueChanged Event Handling and Async Best Practices​

OnTagValueChanged is the core entry point for event handling, designed following best practices for high-concurrency scenarios.

Non-Blocking Async 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 offload business processing to the thread pool:

_ = Task.Run(async () =>
{
// Execute time-consuming business logic in a 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 without blocking platform core flows.
  • Concurrency: Leveraging the thread pool to handle high-frequency event triggers.

Section sources

Independent DI Scope in ProcessAsync​

The ProcessAsync method demonstrates how to safely access dependent services in a background thread.

Creating Independent Scope​

Creates a brand new dependency injection scope via _serviceProvider.CreateScope(). This scope:

  • Possesses an independent IServiceProvider instance.
  • Can safely resolve Scoped lifecycle services (like Repositories, Unit of Work).
  • Automatically releases all resources after the using block ends, preventing memory leaks.

Service Access Example​

var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using var uow = unitOfWorkManager.Begin(requiresNew: true);
var myEntityNameRepository = scope.ServiceProvider.GetRequiredService<IMyEntityNameRepository>();

This code demonstrates how to safely access database repositories within a transaction, perform data queries or updates, and commit changes via uow.SaveChangesAsync().

Section sources

Resource Management and Code Safety under High Concurrency​

In high-concurrency scenarios, MyPluginNameProjectService design 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 statements in ProcessAsync to ensure IServiceScope and IUnitOfWork are released in time.

Code Safety​

Code comments explicitly state that CMS.CodeAnalysis analyzers 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 enforce asynchronous non-blocking principles via compile errors, ensuring system stability at the source.

Section sources

Background Job Integration Strategy​

Although background job code in StopAsync is commented out, it reveals the strategy for integration with IBackgroundJobManager.

Trigger Strategy​

Services can enqueue tasks (like MyPluginNameArgs) into the background job queue via the 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 main service responsiveness.

Diagram sources

Section sources

Conclusion​

MyPluginNameProjectService is a well-designed project-level background service template. It integrates into the platform architecture via inheritance and interface implementation, responds to variable changes using event-driven and asynchronous processing, safely accesses data services via independent scopes, and achieves decoupled long-running tasks via background jobs. Its design fully considers performance, security, and maintainability, serving as an excellent practice guide for developing complex background services.