Preamble
My use-case includes a front-end WYSIWYG editor. Taking user input in HTML5/CSS format from the CSHTML Front-End View. Receiving the input in the Backend Controller's action via POST request. And finally doing fancy Database stuff with it.
Sounds pretty easy. Using this beast of an editor, it's very straightforward and customizable.
View
WYSIWYG editor textarea
nested within a form
to send editor's raw HTML data using POST
<form class="form" asp-controller="CreationController" asp-action="CreateSnowflakeBlogpost" method="post">
<button type="submit" class="btn btn-link">Submit Snowflake Blogpost</button>
<textarea name="snowflakeHtmlContent" id="joditEditor"> </textarea>
</form>
Controller
Controller's action receiving POST parameter.
[HttpPost]
public async Task<IActionResult> CreateSnowflakeBlogpost(string snowflakeHtmlContent)
{
// store HTML content in DB and do fancy operations
// redirect to something else
return RedirectToAction("PreviewSnowflakeBlogpost");
}
The problem
HTML5/CSS tags are lost along the way when passing the POST data. Upon inspection, they are sent successfully from the View. The Action's parameter though has incorrect data.
Looks like sanitization is in motion here, stripping POST parameters of the HTML tags we want to deliberately keep.
It looks like there are possible solutions for this.
[Request.Unvalidated]
annotation. Deprecated.
[AllowHtml]
annotation. Deprecated. See here and here.
@Html.Raw(theString)
here but it's for passing unsafe data from Controller to View. Our use-case is the opposite.
- This question has a compilation of the previous points. All doesn't work.
The question
How do I pass my raw HTML/CSS data from View to Action? satisfying the following conditions:
No data-loss of markup.
Prevent unsafe data that poses XSS risk. As per the guidelines.
The solution
I ended up using Custom Model Binding which bypassed this overly-eager sanitization/data loss. As a result preserved the HTML tags I want.
However this introduces XSS risk. To counter-react passing unsafe data, I used HtmlSanitizer to omit unsafe HTML/CSS tags.
Action
Added [ModelBinder(typeof(AllowSanitizedHtmlBinder))]
annotation to parameter
[HttpPost]
public async Task<IActionResult> CreateSnowflakeBlogpost([ModelBinder(typeof(AllowSanitizedHtmlBinder))] string snowflakeHtmlContent)
{
// store HTML content in DB and do fancy operations
// redirect to something else
return RedirectToAction("PreviewSnowflakeBlogpost");
}
Custom Model Binder
This custom model binder is like a relay and prevents any data loss in our POST parameter. HtmlSanitizer
was used here before binding the value to prevent XSS.
// Custom Model Binding
using Microsoft.AspNetCore.Mvc.ModelBinding;
// HTML Sanitizer
using Ganss.XSS;
public class AllowSanitizedHtmlBinder: IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
// Sanitize HTML from harmful XSS markup
var sanitizer = new HtmlSanitizer();
var sanitizedValue = sanitizer.Sanitize(value);
bindingContext.Result = ModelBindingResult.Success(sanitizedValue);
return Task.CompletedTask;
}
}
[HELP] Missing piece -- understanding the root cause
With my working solution above, I still have no clue why HTML markup are sanitized and removed by default. Even though everyone is claiming this is not supported and such responsibility is app-specific.
As seen here and here:
You don't need [AllowHtml] anymore, because nobody denies HTML in ASP.NET Core 2.0
Don't need [AllowHtml] or RequestValidationEnabled because we don't have request validation in this system
Any help in demystifying the root cause would be HUGELY appreciated.
Sources
My solution was based on:
- This answer. Though
request.Unvalidated
is no longer supported.
- Custom Model Binding.
- HtmlSanitizer.
- This answer was helpful in pointing me in the right direction.