CKFinder 3 – ASP.NET Connector Documentation
Plugins

Introduction

CKFinder uses two types of plugins:

  • JavaScript plugins, used by the CKFinder frontend, which can be used to alter and extend CKFinder UI.
  • ASP.NET connector plugins that can be used to change and extend the behavior of the server-side connector.

Below you can find information and examples for CKFinder ASP.NET connector plugins. For details about JavaScript plugins please refer to the Creating CKFinder 3.x JavaScript Plugins documentation.

Plugin Installation

Manual installation requires downloading plugin source and unpacking it inside the CKFinder plugins directory. The directory structure needs to follow the following pattern:

plugins
└── PluginName
├── PluginName.dll
└── PluginDependency.dll

Configuration

After installation the plugin has to be enabled in the CKFinder configuration file. See Plugins for details.

Plugins usually offer a few configuration options that can be set in the main CKFinder configuration file. Please check plugin documentation for details.

Plugin Development

Default CKFinder behavior can be changed and extended with custom plugins. There are a few constraints that a plugin must meet in order to be recognized as valid:

  • The plugin name should be unique.
  • The plugin class needs to implement the IPlugin interface.
  • It has to be exported using ExportAttribute.

Below is an example of a properly defined plugin:

using System.Collections.Generic;
using System.ComponentModel.Composition;
[Export(typeof(IPlugin))]
public class MyPlugin : IPlugin
{
public void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options)
{
}
}

Note: A plugin may implement the IDisposable interface if it needs to clean up resources during unload.

Plugin Interface

Each CKFinder plugin has to implement the IPlugin interface:

public interface IPlugin
{
void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options);
}

It has one method that is invoked at the application start. In this method you should register all your event handlers and custom commands.

Plugin Configuration

Plugins are configured in their Initialize method.

Options for plugins are passed in the ConnectorBuilder.AddPlugin method or as a collection of <option /> elements in the <plugins /> section of the configuration file.

If you are using XML configuration and need to supply multiple values for a key, just add options with the same key.

The plugin receives a dictionary with a collection of values as a parameter in the Initialize method: IReadOnlyDictionary<string, IReadOnlyCollection<string>> options. It is up to the developer to check if options contain valid values. If an option is not provided at all, the dictionary will return an empty collection of values.

Plugin Structure Example

For example, let us assume you want to create a plugin named ImageWatermark. The plugin should work in the following way:

  • Listen for the file upload event, after the file is validated (and if it is an image — also resized), and just before it is saved.
  • If the file is a supported image type, get uploaded file content and add a watermark.
  • Set the watermarked image as uploaded file content.

The basic code structure of the ImageWatermark plugin class can look as below:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using ImageProcessor;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
[Export(typeof(IPlugin))]
public class WatermarkPlugin : IPlugin, IDisposable
{
private Image _watermarkImage;
private object _fileUploadSubscription;
private IEventAggregator _eventAggregator;
/*
* Initialize the plugin.
* This method is called just once when the plugin is being loaded.
*/
public void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options)
{
/*
* Get the path to the watermark image.
* Warning: For simplicity this code is unsafe and will throw InvalidOperationException
* if watermarkPath was not provided in the options.
*/
var watermarkImagePath = options["watermarkPath"].First();
_watermarkImage = Image.FromFile(watermarkImagePath);
/*
* Resolve IEventAggregator.
* IEventAggregator is a singleton and it is safe to resolve it at any time in the plugin.
*/
_eventAggregator = componentResolver.Resolve<IEventAggregator>();
/*
* Subscribe to the FileUploadEvent event.
*/
_fileUploadSubscription = _eventAggregator.Subscribe<FileUploadEvent>(
next => async messageContext => await OnFileUpload(messageContext, next));
}
/*
* Dispose resources.
* This method will be called when the plugin implements the IDisposable interface and the plugin needs to be unloaded.
*/
public void Dispose()
{
/*
* Unsubscribe from the FileUploadEvent event.
*/
_eventAggregator.Unsubscribe(_fileUploadSubscription);
/*
* Free memory used by the watermark image.
*/
_watermarkImage.Dispose();
}
/*
* The FileUploadEvent handler.
*/
private async Task OnFileUpload(
MessageContext<FileUploadEvent> messageContext,
EventHandlerFunc<FileUploadEvent> next)
{
var stream = messageContext.Message.Stream;
/*
* Resolve IImageSettings.
* IImageSettings contains CKFinder's internal settings for images.
*/
var imageSettings = messageContext.ComponentResolver.Resolve<IImageSettings>();
/*
* Save the original position of the stream.
*/
var originalPosition = stream.Position;
/*
* Create ImageFactory.
* ImageFactory is a third-party component. See http://imageprocessor.org/ for more information.
*/
using (var imageFactory = new ImageFactory())
{
try
{
/*
* Load the image from the stream.
*/
imageFactory.Load(stream);
/*
* Restore the original position of the stream.
*/
stream.Position = originalPosition;
}
catch (ImageFormatException)
{
/*
* This is not an image or the format is not supported.
* We do not care about this now, just call the next event handler in
* the chain and return to caller.
*/
await next(messageContext);
return;
}
/*
* Apply the watermark image.
*/
imageFactory.Overlay(new ImageLayer
{
Image = _watermarkImage, Opacity = 50, Size = imageFactory.Image.Size
});
/*
* Determine the encoding format based on the file name.
*/
var format = ImageFormats.GetFormatForFile(messageContext.Message.File);
format.Quality = imageSettings.Quality;
/*
* Apply the encoding format and save the modified image to the stream.
*/
imageFactory.Format(format);
imageFactory.Save(stream);
}
/*
* Call the next event handler in the chain.
*/
await next(messageContext);
}
}

You can find a complete working example of the ImageWatermark plugin on GitHub.