An ASP.NET Core rich text editor that model-binds like any other field.
The editor is static JavaScript served from wwwroot. Mirror it into a hidden input and [HttpPost] Index(string htmlcode)receives the HTML through ordinary model binding — no SDK, no middleware, no cloud account. Uploads go to a controller you own.
Runs inside your app, not beside it.
string htmlcode.The view
Copy the richtexteditor/ folder into wwwrootand reference it. There is no NuGet package to install for the editor itself — it is static assets your app already knows how to serve.
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css" />
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<form action="" method="post">
<input name="htmlcode" id="inp_htmlcode" type="hidden" />
<div id="div_editor1" class="richtexteditor"></div>
<script>
var editor1 = new RichTextEditor(document.getElementById("div_editor1"));
// Mirror the editor into the hidden field so a normal POST carries the HTML.
editor1.attachEvent("change", function () {
document.getElementById("inp_htmlcode").value = editor1.getHTMLCode();
});
</script>
<button class="btn btn-primary">Submit</button>
</form>The controller
Because the value arrives in a normal form field, the action is unremarkable. That is the point: validation, anti-forgery, filters and model state all behave exactly as they do for any other input.
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(string htmlcode)
{
// Model binding hands you the editor's HTML as an ordinary string.
ViewData["IsPosted"] = true;
ViewData["PostedValue"] = htmlcode;
return View();
}Saving over AJAX instead
If you would rather not post the whole page, read the HTML on demand and send it yourself. The bundled sample does exactly this against an AjaxSaveHandler action.
function DoAjaxSave() {
var code = editor1.getHTMLCode();
var formdata = new FormData();
formdata.append("htmlcode", code);
var xh = new XMLHttpRequest();
xh.open("POST", "/Home/AjaxSaveHandler", true);
xh.onload = function () { DoAjaxLoad(); };
xh.send(formdata);
}Image and file uploads
The editor posts each inserted file to the path you set in rte-upload.js. The sample ships a controller that enforces a size limit and checks the extension against the declared content type before writing anything to disk.
// wwwroot/rte-upload.js — the editor calls this global for every inserted file
var uploadhandlerpath = "/RTEUpload/ImageUploadHandler";
// Controllers/RTEUploadController.cs
public class RTEUploadController : Controller
{
public async Task<IActionResult> ImageUploadHandler(string type, string name)
{
if (Request.ContentLength > 4000000)
return ReportError("file too big");
// Validate the extension against the declared content type,
// write the bytes under wwwroot, return the stored URL.
}
}Rendering what was saved
Remember that what you store is user-submitted HTML. Sanitize it server-side before rendering it back to other users — see why “nothing executes in the editor” is the wrong bar.
@* Render saved content so it matches what the author saw. This stylesheet targets bare elements, so link it on the pages that render editor output rather than in your shared layout. *@ <link rel="stylesheet" href="/richtexteditor/runtime/richtexteditor_preview.css" /> @(Html.Raw(ViewData["PostedValue"]))
Why .NET teams pick it
- Nothing leaves your server: uploads, documents and AI calls all route through endpoints you control — which is what makes it viable where data residency is a procurement requirement.
- Everything is included: AI Toolkit (bring your own key), tracked changes, comments, tables, and Word/PDF export ship in the base license — no premium add-on tiers.
- Perpetual license from $129: one purchase, no per-load metering — see pricing and the TinyMCE / CKEditor comparisons.
Also available for React, Vue, Angular, Svelte, Next.js, Blazor, PHP, and plain JavaScript.