Integrating PlainEdit.NET into ASP.NET Core ApplicationsPlainEdit.NET is a lightweight, extensible rich-text editor designed for .NET developers who need an unobtrusive, performant editing component. This article walks through integrating PlainEdit.NET into an ASP.NET Core application, covering installation, server-side setup, client-side initialization, customization, security considerations (including sanitization and XSS prevention), file uploads, persistence strategies, and deployment tips.
Table of contents
- Why choose PlainEdit.NET?
- Prerequisites
- Installation
- Server-side setup in ASP.NET Core
- Client-side initialization
- Customizing the toolbar and features
- Handling file uploads (images, attachments)
- Saving and loading content (persistence)
- Sanitization and security
- Performance considerations
- Testing and debugging
- Deployment checklist
- Conclusion
Why choose PlainEdit.NET?
PlainEdit.NET focuses on:
- Lightweight footprint — minimal CSS/JS so pages load fast.
- Extensibility — plugin model for adding custom buttons and behaviors.
- Server-friendly — integrates naturally with ASP.NET Core’s model binding and tag helpers.
- Control over output HTML — easier to sanitize and store than bulky editors that generate complex markup.
Prerequisites
- .NET 6.0 or later (examples use .NET 7 / ASP.NET Core 7, but concepts apply to 6+).
- Basic knowledge of ASP.NET Core MVC or Razor Pages.
- Familiarity with JavaScript and bundling (ES modules or traditional script includes).
- A project scaffolded with the ASP.NET Core Web App (Model-View-Controller) or Razor Pages template.
Installation
-
Client assets: include PlainEdit.NET’s JavaScript and CSS. If PlainEdit.NET is available as an npm package, install:
npm install plainedit.net
Or include the vendor files manually in wwwroot/lib/plainedit.net/ (js + css).
-
Optionally add a NuGet package if server-side helpers are provided:
dotnet add package PlainEdit.NET.ServerHelpers
(If such package exists; otherwise plain tag helpers and model binding suffice.)
-
Register static files and bundling as needed. If using LibMan, add the files to wwwroot via libman.json.
Server-side setup in ASP.NET Core
- Static files and routing (Program.cs): “`csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); // or AddRazorPages() var app = builder.Build();
app.UseStaticFiles(); app.UseRouting(); app.MapDefaultControllerRoute(); // MVC app.Run();
2. Model example for content editing: ```csharp public class Article { public int Id { get; set; } public string Title { get; set; } = ""; public string ContentHtml { get; set; } = ""; }
-
Controller actions (simplified):
public class ArticlesController : Controller { private readonly ApplicationDbContext _db; public ArticlesController(ApplicationDbContext db) => _db = db; [HttpGet] public IActionResult Create() => View(new Article()); [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create(Article model) { if (!ModelState.IsValid) return View(model); // Optional: sanitize model.ContentHtml here before saving _db.Add(model); await _db.SaveChangesAsync(); return RedirectToAction("Edit", new { id = model.Id }); } [HttpGet] public async Task<IActionResult> Edit(int id) { var article = await _db.Articles.FindAsync(id); if (article == null) return NotFound(); return View(article); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id, Article model) { if (id != model.Id) return BadRequest(); if (!ModelState.IsValid) return View(model); // sanitize again if needed _db.Update(model); await _db.SaveChangesAsync(); return RedirectToAction("Index"); } }
Client-side initialization
-
Include PlainEdit.NET assets in your layout (e.g., _Layout.cshtml):
<link rel="stylesheet" href="~/lib/plainedit.net/plainedit.css" /> <script src="~/lib/plainedit.net/plainedit.min.js"></script>
-
Add the editor to your view:
<form asp-action="Create" method="post"> <input asp-for="Title" /> <textarea id="content" name="ContentHtml">@Model.ContentHtml</textarea> <button type="submit">Save</button> </form> <script> document.addEventListener('DOMContentLoaded', () => { const editor = PlainEdit.init('#content', { toolbar: ['bold','italic','link','image','code'], placeholder: 'Write your article...' }); // Optionally keep textarea updated before submit document.querySelector('form').addEventListener('submit', () => { document.querySelector('#content').value = editor.getHtml(); }); }); </script>
If PlainEdit.NET binds directly to textareas and updates their value automatically, the manual submit handler can be omitted.
Customizing the toolbar and features
PlainEdit.NET typically exposes configuration options to control the toolbar, plugins, and behavior. Example options:
- toolbar: array of button IDs (e.g., bold, italic, unordered-list, link, image).
- paste: control paste behavior (plain/text or retain formatting).
- plugins: enable custom image upload, mentions, or code blocks.
Example adding a custom button:
PlainEdit.registerPlugin('insertTimestamp', { init(editor) { editor.addButton('timestamp', { icon: '⏱', title: 'Insert timestamp', onClick: () => { editor.insertHtml(new Date().toISOString()); } }); } }); PlainEdit.init('#content', { toolbar: ['bold','italic','timestamp'] });
Handling file uploads (images, attachments)
Two approaches:
- Upload files to the server immediately when inserted (recommended for reliability).
- Store images as data URLs (simpler but increases document size).
Server endpoint example (ImagesController):
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> UploadImage(IFormFile file) { if (file == null || file.Length == 0) return BadRequest(); var uploads = Path.Combine(_env.WebRootPath, "uploads"); Directory.CreateDirectory(uploads); var fileName = Guid.NewGuid() + Path.GetExtension(file.FileName); var filePath = Path.Combine(uploads, fileName); using (var stream = System.IO.File.Create(filePath)) { await file.CopyToAsync(stream); } var url = Url.Content($"~/uploads/{fileName}"); return Json(new { url }); }
Client-side hook to upload and insert:
PlainEdit.init('#content', { imageUploadUrl: '/images/upload', onImageUploadResponse(response) { // response.url expected return response.url; } });
Validate file types and size on the server and scan/clean as needed.
Saving and loading content (persistence)
- Store ContentHtml as a string (nvarchar(max) / text) in your database.
- Prefer storing images as files and only storing URLs in the HTML.
- For large-scale apps, consider object storage (S3-compatible) for media and CDN for delivery.
- Versioning: keep an edit history if you need rollback (store previous HTML snapshots).
Database example (EF Core migration):
public class AddArticle : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Articles", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Title = table.Column<string>(nullable: true), ContentHtml = table.Column<string>(type: "nvarchar(max)", nullable: true) }, constraints: table => { table.PrimaryKey("PK_Articles", x => x.Id); }); } }
Sanitization and security
- Always sanitize HTML on the server before saving or rendering. Use a robust sanitizer like Ganss.XSS (HtmlSanitizer) or AngleSharp.Sanitizer to remove scripts, disallowed attributes, and potentially dangerous tags.
- Remove or whitelist attributes like style only if safe. Prefer restricting inline styles.
- Use Content Security Policy (CSP) headers to reduce XSS risk.
- CSRF protection: keep [ValidateAntiForgeryToken] on POST endpoints.
- Validate uploaded files (MIME type check, size limit, virus scanning if possible).
- When rendering user content, encode where appropriate; if rendering raw HTML, ensure it’s sanitized.
Example server-side sanitization with HtmlSanitizer:
var sanitizer = new HtmlSanitizer(); sanitizer.AllowedSchemes.Add("data"); // if using data URLs (careful) var clean = sanitizer.Sanitize(userSuppliedHtml); model.ContentHtml = clean;
Performance considerations
- Lazy-load editor assets on pages that need them.
- Defer initialization until the editor is visible.
- Use server-side paging and truncate previews instead of loading full HTML.
- Offload heavy media to a CDN/object store.
- Cache rendered content where appropriate (e.g., HTML cached per article).
Testing and debugging
- Test editor on multiple browsers and devices; rich-text behavior varies.
- Check pasted content from Word/Google Docs to ensure sanitizer and paste settings behave as expected.
- Use browser devtools network tab to verify uploads and asset loading.
- Create automated tests for server-side sanitization and file upload validation.
Deployment checklist
- Ensure wwwroot includes PlainEdit.NET assets or they’re available via package manager/CDN.
- Configure production CSP and disable permissive dev-only settings.
- Set file upload size limits in Kestrel/IIS and validate on server.
- Use HTTPS and secure cookies.
- Monitor logs for upload errors and sanitizer exceptions.
Conclusion
Integrating PlainEdit.NET into an ASP.NET Core application is straightforward: include client assets, initialize the editor on your textarea, provide secure image/upload endpoints, sanitize content server-side, and persist HTML safely. With attention to sanitization, file handling, and performance, PlainEdit.NET offers a lightweight, flexible editing experience that fits naturally into ASP.NET Core workflows.
Leave a Reply