Extending DS Server with Custom Digital Signature APIs
In this article, we will explore how to extend the functionality of DS Server by integrating custom digital signature APIs. We will cover the necessary steps to create a plugin that allows DS Server to utilize custom signature services, enhancing its capabilities and providing more options for users.

This open-source DS Server signature plugin reference implementation allows you to extend DS Server with your own digital signing logic and certificate handling without modifying the core. The plugin shows how the new architecture allows you to add fully integrated API endpoints and custom UI components.
Why a Signature Plugin?
DS Server supports digital signatures by default through the DocumentViewer component, which allows users to sign documents directly in the browser.
However, many organizations need to route signed documents through custom signing logic to apply corporate certificates, integrate with internal PKI systems, or trigger compliance workflows, for instance.
This plugin does exactly that. It adds a custom /signatures/sign API endpoint to the DS Server. This endpoint receives signed documents from the DocumentViewer and passes them to your signing process.
How It Works
When a document is signed in the web viewer, the signed document data is posted to the signature-plugin/signatures/sign endpoint.
The plugin uses TX Text Control .NET Server for ASP.NET to load the document and access the signature fields. Then, it applies a digital signature using a .pfx certificate file.
The certificate configuration is flexible. Both the path and the password can be adjusted in the DS Server app.settings file.
"SignaturePlugin": {
"CertificatePath": "certificate.pfx",
"CertificatePassword": "password"
}
This makes it easy to integrate different certificates for each environment, such as testing, staging, or production.
The Plugin Structure
The plugin implements the IPlugin
interface and registers its own services, routes, and controllers. Below is a brief overview of the main class:
using DSSignaturePlugin.Services;
using Microsoft.AspNetCore.Builder;
sing Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TXTextControl.DocumentServices.Plugin.Abstractions;
public class SignaturePlugin : IPlugin
{
public string Name => "Text Control DS Server Signature Plugin";
public string Description => "Adds a /signature-plugin/signatures/sign endpoint.";
public string Version => "1.0.0";
/// <summary>
/// The base path of the plugin's web interface. This is used to create a
/// link in the DS Server plugin overview.
/// </summary>
public string UIBasePath => "/signature-plugin";
public void ConfigureServices(IServiceCollection services, PluginContext context)
{
var certificateName = context.Configuration["SignaturePlugin:CertificatePath"] ?? "certificate.pfx";
var certificatePassword = context.Configuration["SignaturePlugin:CertificatePassword"] ?? "password";
services.AddSingleton(new CertificateSettings { CertificatePath = certificateName, CertificatePassword = certificatePassword });
var asm = typeof(SignaturePlugin).Assembly;
services.AddControllersWithViews()
.AddApplicationPart(asm);
}
public void ConfigureMiddleware(WebApplication app, PluginContext context)
{
var state = app.Services.GetService<CertificateSettings>() ?? throw new InvalidOperationException("SignatureSettings service not registered.");
var group = app.MapGroup(UIBasePath);
group.MapControllerRoute(
name: "signatureplugin-mvc",
pattern: "{controller=SignatureUi}/{action=Index}/{id?}")
.WithMetadata(new Microsoft.AspNetCore.Routing.SuppressLinkGenerationMetadata());
}
public void OnStart(IServiceProvider services, PluginContext context)
{
// This method is called when the plugin is started. You can use this to
// initialize resources or perform startup logic. Implementing OnStart is optional.
var logger = services.GetService<ILogger<SignaturePlugin>>();
var settings = services.GetService<CertificateSettings>();
logger?.LogInformation("{Name} (v{Version}) started. Certificate: {Certificate}", Name, Version, settings?.CertificatePath);
}
public void OnStop()
{
// Cleanup logic if needed. This is also optional.
}
}
In ConfigureServices
, the plugin:
- Reads the certificate configuration and registers it as a singleton service.
- Adds the plugin's MVC controllers and views to DS Server via
AddApplicationPart
.
ConfigureMiddleware
, it maps a new route group under /signature-plugin, which hosts both API and UI routes.
The Signing Controller
The SignController is at the heart of the plugin. The SignController defines the custom web API that receives signed documents from the DocumentViewer and applies server-side digital signatures.
using DSSignaturePlugin.Services;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using TXTextControl;
namespace DSSignaturePlugin.Controllers
{
[ApiController]
//[Authorize(AuthenticationSchemes = "TXTextControl")]
[Route("signature-plugin/signatures/[controller]")]
public class SignController : ControllerBase
{
private readonly CertificateSettings m_settings;
public SignController(CertificateSettings settings)
{
m_settings = settings;
}
// Testing endpoint to verify that the controller is reachable
[HttpGet]
public string Get()
{
return "Hello from SignController!";
}
[HttpPost]
public string Post([FromBody] object data)
{
if (data is null) throw new ArgumentException("Request body is required.");
// robust manual deserialization
SignatureData signatureData;
try
{
if (data is JsonElement je)
signatureData = je.Deserialize<SignatureData>()!;
else
signatureData = JsonSerializer.Deserialize<SignatureData>(data.ToString() ?? string.Empty)!;
if (signatureData is null)
throw new JsonException("Failed to deserialize SignatureData.");
}
catch (JsonException ex)
{
throw new ArgumentException($"Invalid JSON: {ex.Message}");
}
if (string.IsNullOrWhiteSpace(signatureData?.SignedDocument?.Document))
throw new ArgumentException("Missing signed document data.");
byte[] docBytes;
try
{
docBytes = Convert.FromBase64String(signatureData.SignedDocument.Document);
}
catch (FormatException)
{
throw new ArgumentException("Signed document is not valid Base64.");
}
var assemblyPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
// prepare paths
var certPath = Path.Combine(assemblyPath!, "Certificates", m_settings.CertificatePath);
var docsDir = Path.Combine(assemblyPath!, "Documents");
Directory.CreateDirectory(docsDir);
using var cert = new X509Certificate2(certPath, m_settings.CertificatePassword, X509KeyStorageFlags.Exportable);
using var tx = new TXTextControl.ServerTextControl();
tx.Create();
// load the internal format
tx.Load(docBytes, TXTextControl.BinaryStreamType.InternalUnicodeFormat);
// signature fields (null/empty-safe)
var signatureFields = (signatureData.SignatureBoxes ?? Enumerable.Empty<SignatureBox>())
.Where(b => !string.IsNullOrWhiteSpace(b?.Name))
.Select(b => new DigitalSignature(cert, null, b!.Name))
.ToArray();
var saveSettings = new TXTextControl.SaveSettings
{
CreatorApplication = "Your Application",
SignatureFields = signatureFields
};
var uniqueFileName = $"{Guid.NewGuid():N}.pdf";
var outputPath = Path.Combine(docsDir, uniqueFileName);
tx.Save(outputPath, TXTextControl.StreamType.AdobePDF, saveSettings);
return uniqueFileName;
}
}
}
When the DocumentViewer submits a signed document, it is sent as Base64-encoded data to the POST /signature-plugin/signatures/sign endpoint.
The controller deserializes the incoming JSON into a SignatureData
object and extracts the document data.
TX Text Control .NET Server for ASP.NET opens the signed document and finds all defined signature fields. Then, it applies a digital signature to each field. The controller saves the newly signed PDF document to a local folder and returns the file name or another custom response. This creates a clear distinction between the client-side signature input and the server-side signing process. This ensures that the final document is securely signed with your organization's certificate.
The application that uses the DocumentViewer simply specifies the API endpoint to which the signed document should be sent for processing using the RedirectAfterSignature
property.
@Html.TXTextControl().DocumentViewer(settings =>
{
settings.BasePath = "http://localhost";
settings.OAuthSettings.ClientId = "dsserver.nBAcn5eU21mPkpPHz4XrNQnMPLWpkOeT";
settings.OAuthSettings.ClientSecret = "HWwOaZ29NT7EgLFFqzSevV1s8pj760mu";
settings.Dock = DocumentViewerSettings.DockStyle.Fill;
settings.DocumentData = base64String;
settings.SignatureSettings = new SignatureSettings() {
ShowSignatureBar = true,
OwnerName = "Paul Paulsen",
SignerName = "Tim Typer",
SignerInitials = "TT",
RedirectUrlAfterSignature = "http://localhost/signature-plugin/signatures/sign",
SignatureBoxes = new SignatureBox[] {
new SignatureBox("txsign") {
SigningRequired = true, Style = SignatureBox.SignatureBoxStyle.Signature }
}};
}).Render()
UI Integration in DS Server Portal
In addition to the backend logic, this plugin adds custom UI controllers and Razor views to the DS Server web portal. These allow administrators and users to list and view signed documents directly in the DS Server interface.
The plugin appears as a fully integrated part of the portal by registering its own routes, controllers, and views. This demonstrates the flexibility of the DS Server plug-in model for extending both API and UI functionality.
Try It Yourself
The signature plugin is an excellent starting point for developing your own DS Server extensions, including custom signing workflows, automated post-processing, and document analytics. With the DS Server Plugin API, you can seamlessly add endpoints, services, and UI elements that integrate seamlessly with DS Server, while keeping your logic cleanly separated.
Download and Fork This Sample on GitHub
We proudly host our sample code on github.com/TextControl.
Please fork and contribute.
Requirements for this sample
- TX Text Control .NET for ASP.NET 33.0
- DS Server 4.1
ASP.NET
Integrate document processing into your applications to create documents such as PDFs and MS Word documents, including client-side document editing, viewing, and electronic signatures.
- Angular
- Blazor
- React
- JavaScript
- ASP.NET MVC, ASP.NET Core, and WebForms
Related Posts
Why PDF/UA and PDF/A-3a Matter: Accessibility, Archiving, and Legal Compliance
It is more important than ever to ensure that documents are accessible, archivable, and legally compliant. PDF/UA and PDF/A-3a are two effective standards for addressing these needs. This article…
Meet Text Control at DDC 2025 in Cologne
Join us at the .NET Developer Conference (DDC) 2025 in Cologne from November 24-27. Visit our booth to explore the latest in document generation and reporting with Text Control's DS Server and…
Building an Ecosystem around DS Server: Join Us as a Plug-in Pioneer
DS Server 4.1.0 introduces a plug-in architecture that transforms the platform into an extensible ecosystem. Text Control invites developers, ISVs, and domain experts to co-innovate, build the…
Convert Markdown to PDF in a Console Application on Linux and Windows
Learn how to convert Markdown files to PDF in a console application on Linux and Windows using TX Text Control .NET Server for ASP.NET. This tutorial provides step-by-step instructions and code…
Mining PDFs with Regex in C#: Practical Patterns, Tips, and Ideas
Mining PDFs with Regex in C# can be a powerful technique for extracting information from documents. This article explores practical patterns, tips, and ideas for effectively using regular…