The ElBruno.MarkItDotNet plugin system allows you to package custom converters as reusable satellite packages that are automatically discovered and loaded via dependency injection. This is how the Excel, PowerPoint, and AI packages extend the core library.
- Converters implement
IMarkdownConverterto handle one or more file formats - Plugins implement
IConverterPluginto bundle related converters - Service Extension provides a
AddMyPlugin()DI method to register the plugin - Automatic Discovery — the
ConverterRegistrydiscovers all registeredIConverterPlugininstances and loads their converters
No manual registration of individual converters needed — just call one AddMyPlugin() method, and all converters are available.
dotnet new classlib -n MyCompany.MyFormatPlugin
cd MyCompany.MyFormatPlugindotnet add package ElBruno.MarkItDotNetCreate one or more converters that implement IMarkdownConverter:
using ElBruno.MarkItDotNet;
using System.Text;
namespace MyCompany.MyFormatPlugin;
/// <summary>
/// Converter for .myformat files to Markdown.
/// </summary>
public class MyFormatConverter : IMarkdownConverter
{
public bool CanHandle(string fileExtension) =>
fileExtension.Equals(".myformat", StringComparison.OrdinalIgnoreCase);
public async Task<string> ConvertAsync(Stream fileStream, string fileExtension)
{
// Read from stream
using var reader = new StreamReader(fileStream, leaveOpen: true);
var content = await reader.ReadToEndAsync();
// Convert to Markdown
var markdown = ParseAndConvert(content);
return markdown;
}
private static string ParseAndConvert(string content)
{
// Your conversion logic here
return $"# Converted from MyFormat\n\n{content}";
}
}Create a class that implements IConverterPlugin:
using ElBruno.MarkItDotNet;
namespace MyCompany.MyFormatPlugin;
/// <summary>
/// Plugin that provides the MyFormat converter.
/// </summary>
public class MyFormatPlugin : IConverterPlugin
{
/// <inheritdoc />
public string Name => "MyFormat";
/// <inheritdoc />
public IEnumerable<IMarkdownConverter> GetConverters() =>
[
new MyFormatConverter(),
// Add more converters if needed
];
}Add a static class with an extension method:
using Microsoft.Extensions.DependencyInjection;
namespace MyCompany.MyFormatPlugin;
/// <summary>
/// Extension methods for registering MyFormatPlugin with dependency injection.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the MyFormat converter plugin to the service collection.
/// </summary>
public static IServiceCollection AddMyFormatPlugin(
this IServiceCollection services)
{
services.AddSingleton<IConverterPlugin>(new MyFormatPlugin());
return services;
}
}dotnet pack -c Release
dotnet nuget push bin/Release/MyCompany.MyFormatPlugin.1.0.0.nupkg --api-key <key> --source https://api.nuget.org/v3/index.jsonOnce your plugin is published, users can install and use it like this:
dotnet add package MyCompany.MyFormatPluginThen register it in their DI container:
using Microsoft.Extensions.DependencyInjection;
using ElBruno.MarkItDotNet;
using MyCompany.MyFormatPlugin;
var services = new ServiceCollection();
// Register core
services.AddMarkItDotNet();
// Register your plugin
services.AddMyFormatPlugin();
var provider = services.BuildServiceProvider();
var markdownService = provider.GetRequiredService<MarkdownService>();
// Your converter is now available
var result = await markdownService.ConvertAsync("document.myformat");
Console.WriteLine(result.Markdown);For plugins that need configuration:
namespace MyCompany.MyFormatPlugin;
public class MyFormatOptions
{
public string? CustomProperty { get; set; }
public int MaxSize { get; set; } = 10_000_000;
}public class MyFormatPlugin : IConverterPlugin
{
private readonly MyFormatOptions _options;
public MyFormatPlugin(MyFormatOptions? options = null)
{
_options = options ?? new MyFormatOptions();
}
public string Name => "MyFormat";
public IEnumerable<IMarkdownConverter> GetConverters() =>
[
new MyFormatConverter(_options),
];
}public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyFormatPlugin(
this IServiceCollection services,
Action<MyFormatOptions>? configure = null)
{
var options = new MyFormatOptions();
configure?.Invoke(options);
services.AddSingleton(options);
services.AddSingleton<IConverterPlugin>(new MyFormatPlugin(options));
return services;
}
}services.AddMyFormatPlugin(options =>
{
options.CustomProperty = "value";
options.MaxSize = 50_000_000;
});Converts Excel spreadsheets (.xlsx) to Markdown tables.
services.AddMarkItDotNetExcel();Converts PowerPoint slides (.pptx) to Markdown.
services.AddMarkItDotNetPowerPoint();Provides AI-powered converters (OCR, image captioning, audio transcription).
Requires IChatClient registration:
services.AddOpenAIChatClient("sk-...", "gpt-4-vision");
services.AddMarkItDotNetAI();- Keep converters focused — each plugin should handle a cohesive set of related formats
- Use meaningful names — plugin names should clearly indicate what they do
- Document dependencies — clearly state any external dependencies (NuGet packages, APIs, etc.)
- Support streaming — for large files, consider implementing
IStreamingMarkdownConverter - Handle errors gracefully — use meaningful error messages in
ConversionResult - Add tests — include unit tests for your converters
- Version appropriately — follow semantic versioning and clearly document breaking changes
The ConverterRegistry automatically discovers plugins through:
- Explicit registration — plugins registered as
IConverterPluginin DI are loaded on first use - Auto-loading — when
MarkdownServicequeries the registry, it loads all available plugins
This happens without any manual discovery or reflection — plugins are simply instances registered in the DI container.