Skip to content

Writing a Basic Plugin

In the previous section, we created a plugin project using the scaffolding template. Now, open the Plugin.cs file in the project, and we will delve into its code structure.

Code Structure

Below is the default code generated by the template, taking sharwapi.apimgr as an example:

csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

using sharwapi.Contracts.Core;

namespace sharwapi.Plugin.apiMgr;
// Implementing IApiPlugin interface
public class SharwApiMgrPlugin : IApiPlugin
{
    // 1. Identity Information
    public string Name => "sharw.apimgr"; // Unique ID
    public string DisplayName => "API Manager"; // Display Name
    public string Version => "1.0.0"; // Version

    // Declare Dependencies (Optional)
    // Key: Dependent Plugin Name (Name)
    // Value: Dependent Version Range (Supports standard NuGet range syntax)
    public IReadOnlyDictionary<string, string> Dependencies => new Dictionary<string, string>
    {
        { "sharw.core", "[1.0, 2.0)" }, // Depends on sharw.core, version >=1.0 and <2.0
        { "another.plugin", "1.*" }     // Depends on another.plugin, major version 1
    };

    // Enable Automatic Route Prefix
    public bool UseAutoRoutePrefix => true;

    // Define Default Configuration (Optional)
    public object? DefaultConfig => new { MySetting = "DefaultValue" };

    // 2. Register Services
    public void RegisterServices(IServiceCollection services, IConfiguration configuration)
    {
        // Register your business services here
    }

    // 3. Configure Pipeline
    public void Configure(WebApplication app)
    {
        // Add request processing middleware here
    }

    // 4. Define Endpoints
    public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
    {
        // Define API endpoints here
    }
}

TIP

If you need more complex validation logic (e.g., optional dependencies), see Advanced Dependency Configuration

Meta Information

This part defines the plugin's information.

  • Name: The globally unique ID of the plugin.

    • Convention: Format is author.plugin (all lowercase). Automatically replaced when created using a template.
    • Example: "sharw.apimgr"
  • DisplayName: The display name of the plugin, can use Chinese.

  • Version: Plugin version number, following Semantic Versioning.

  • Dependencies: Declares plugin dependencies.

    • Key: The Name of the dependent plugin.
    • Value: The version range of the dependency.
    • Supported Formats:
      • Standard range: [1.0, 2.0) (>=1.0 and <2.0), 1.0 (>=1.0)
      • Floating range: 1.* (Any version with major version 1)
  • UseAutoRoutePrefix: Recommended to enable. When true, the main program automatically adds the /{plugin-name} prefix (e.g., /sharw.apimgr) to your endpoints.

  • DefaultConfig: Sets the default configuration file.

    • When the plugin loads for the first time and the configuration file does not exist, the main program automatically generates this object as a config/plugin-name.json file (e.g., /sharw.apimgr.json).
    • For detailed usage, refer to Plugin Configuration.
  • DataDirectory: The full path to the plugin's dedicated data directory. Defaults to {BaseDir}/data/{Name}/. The host automatically creates this directory on startup.

  • GetDataPath(relativePath): A shorthand method that combines a relative path and returns the full absolute path. For example, GetDataPath("plugin.db") returns {DataDirectory}/plugin.db.

Register Services (RegisterServices)

Detailed Explanation

In SharwAPI, we adopt the standard Dependency Injection (DI) pattern.

You don't need to manually create (new) complex objects (like database connection services, HTTP client services) in your code. Just register them in RegisterServices.

Thereafter, wherever you need to use these tools, the system will automatically inject the prepared instances for you to use directly.

Simple Dependency Injection Example:

Suppose my plugin needs to access Google, requiring a browser tool (HttpClient). I can call AddHttpClient in RegisterServices to add a singleton. During the subsequent execution of the plugin, I can let this HttpClient be injected into where I need it via dependency injection.

csharp
public void RegisterServices(IServiceCollection services, IConfiguration configuration)
{
    // Scenario: My plugin needs to access Google, requiring a browser tool (HttpClient)
    // Action: The main program registers this service
    // Note: To prevent conflicts, it is recommended to specify a name for the registered HttpClient here (i.e., sharw.apimgr.client below)
    services.AddHttpClient("sharw.apimgr.client", client =>
    {
        client.BaseAddress = new Uri("https://google.com");
        client.Timeout = TimeSpan.FromSeconds(10);
    });
    
    // Scenario: I wrote a class named MyDatabase to operate the database, and this class also needs to use the HttpClient service
    // Action: Register it so the entire plugin can share this single instance
    // Also, if this service declares a need for some other service, DI will automatically inject those dependencies into the constructor.
    // (Interface input is not mandatory; you can also input the MyDataBase class directly)
    services.AddSingleton<IMyDataBaseService>();
}
// My MyDataBase class
public class MyDataBaseService : IMyDataBaseService
{
    private readonly HttpClient _googleHttpClient;
    // Indicate need for HttpClient in constructor
    // After AddSingleton<IMyDataBaseService>() above
    // Dependency injection will automatically inject the httpClient.
    public MyDataBaseService(HttpClient googleHttpClient) 
    {
        _googleHttpClient = googleHttpClient;
    }
    // Other implementations...
}
interface IMyDataBaseService
{
    // Other things...
}
Advanced: Function Calls Between Plugins

RegisterServices is also the primary way for function calls between plugins.

  • Providing Functions: If you write a service class (e.g., MyDatabase), and want it to be used by other plugins, register it in the container (services.AddSingleton<MyDatabase>()).
  • Using Functions: Just like MyDatabase declaring a need for HttpClient, other plugins simply declare a need for MyDatabase in their constructors or routes, and the main program will automatically inject it into them.

Configure Pipeline (Configure)

Detailed Explanation

When a user accesses an API, the request doesn't arrive at the destination instantly; instead, it flows through a pipe like water. The Configure method allows you to install "checkpoints" in the pipe. All requests must pass through these checkpoints before reaching the specific API.

Code Example:

csharp
public void Configure(WebApplication app)
{
    // Install a simple middleware (valve)
    app.Use(async (context, next) =>
    {
        // Pre-processing
        Console.WriteLine($"[{Name}] Someone accessed: {context.Request.Path}");

        // Call next() to release
        // If you don't call next(), the request will be intercepted here and will never reach the API
        await next();
    });
}
Advanced: Information Passing Between Plugins

Configure can also be a way to pass request context information between plugins.

  • Passing Info: For example, an Auth plugin can parse the user Token here and store user information in HttpContext.
  • Getting Info: Subsequent Log plugins or business plugins can read this info from HttpContext to know "who the current user is".

For details, see: Configure Middleware #Communication Between Plugins

Define Endpoints (RegisterRoutes)

Detailed Explanation

This is the core part of the plugin, used to establish the mapping between API requests and code processing.

Code Example:

csharp
public void RegisterServices(IServiceCollection services, IConfiguration configuration)
{    
    // Register a class named MyDatabase to operate the database
    services.AddSingleton<MyDatabase>();
}

public void RegisterRoutes(IEndpointRouteBuilder app, IConfiguration configuration)
{
    // Note: If you enabled UseAutoRoutePrefix => true
    // The app here is already a route group containing the /{plugin-id} prefix

    // Define specific API endpoints
    // MyDatabase will be automatically injected, no manual creation needed
    // (Provided MyDatabase has been registered in RegisterServices)
    // Final URL: /sharw.apimgr/hello
    app.MapGet("/hello", (MyDatabase db) =>
    {
        return db.GetData();
    });
}