Kestrel on Mac: Safari Can't Establish a Secure Connection

I ran into this issue earlier today after updating Visual Studio for Mac. I had tried debugging a site, and was met with the error message in Safari that it couldn’t establish a secure connection with the server. Kestrel was running, and the port was correctly open — it’s just that the SSL/TLS negotiation was failing.

I’d had to trust the development certificate before, so I ran:

dotnet dev-certs https --trust

And was met with this output:

Trusting the HTTPS development certificate was requested. If the certificate is not already trusted we will run the following command:
 'sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <<certificate>>'
 This command might prompt you for your password to install the certificate on the system keychain.
 A valid HTTPS certificate is already present.

I figured that perhaps the development certificate was expired or had changed, so I tried to run the following command to remove it (intending to trust the current version afterward):

dotnet dev-certs https --clean

But unfortunately, I was met with this error message:

Cleaning HTTPS development certificates from the machine. This operation might require elevated privileges. If that is the case, a prompt for credentials will be displayed.
 Password:[Entered sudo password here]
 There was an error trying to clean HTTPS development certificates on this machine.
 Removing the requested certificate would modify user trust settings, and has been denied.

Which was frustrating. I needed to open Keychain Access and find the certificate, which is just named localhost:

Keychain Access – Localhost Development Certificate

The certificate expiration date was actually still in the future, so the certificate itself was still valid. Though the one being used by dotnet was now different. Anyway, to fix the issue, I manually deleted the localhost certificate you see in the above screenshot. Then I ran the following command again:

dotnet dev-certs https --trust

And thankfully got the following output:

The HTTPS developer certificate was generated successfully.

After that, debugging and connecting to the local Kestrel instance worked correctly again.

ASP.NET Core: Section Scripts in a Partial View

A @section Scripts block does not work when placed in a partial view in ASP.NET Core, which is the same functionality as in ASP.NET MVC. Unfortunately, you don’t get any error messages if you try to add the section to a partial — it just does nothing. In many cases, having a scripts section in a partial view would be an anti-pattern, since the partial can be rendered an unknown number of times. However, there are times when I believe a scripts section is warranted in a partial, particularly when you’re trying to create dynamic JavaScript based on the model passed into the partial view. While you can’t just use the actual @section Scripts in a partial view, you can add some HTML helper extensions to accomplish the same thing.

Below is the code to accomplish this functionality — here are the helper extension methods that you’d add into a C# file in your project:

using System;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
public static class HtmlHelperExtensions
{
    private const string _partialViewScriptItemPrefix = "scripts_";
    public static IHtmlContent PartialSectionScripts(this IHtmlHelper htmlHelper, Func<object, HelperResult> template)
    {
        htmlHelper.ViewContext.HttpContext.Items[_partialViewScriptItemPrefix + Guid.NewGuid()] = template;
        return new HtmlContentBuilder();
    }
    public static IHtmlContent RenderPartialSectionScripts(this IHtmlHelper htmlHelper)
    {
        var partialSectionScripts = htmlHelper.ViewContext.HttpContext.Items.Keys
            .Where(k => Regex.IsMatch(
                k.ToString(),
                "^" + _partialViewScriptItemPrefix + "([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$"));
        var contentBuilder = new HtmlContentBuilder();
        foreach (var key in partialSectionScripts)
        {
            var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
            if (template != null)
            {
                var writer = new System.IO.StringWriter();
                template(null).WriteTo(writer, HtmlEncoder.Default);
                contentBuilder.AppendHtml(writer.ToString());
            }
        }
        return contentBuilder;
    }
}

PartialSectionScripts is called in the partial view in place of where you would otherwise be using @section Scripts.

RenderPartialSectionScripts would typically be called in your shared layout, e.g. _Layout.cshtml in the standard scaffolded projects, and will render any scripts added in partials via the PartialSectionScripts method call.

Here’s an example from a partial view of using PartialSectionScripts:

@Html.PartialSectionScripts(
    @<script>
        alert('Hello from the partial view!');
    </script>
)

And the example with the RenderPartialSectionScripts line added in your shared layout, where you would likely want to place it after your RenderSection and before the end of the body:

    @*...*@
    @RenderSection("Scripts", required: false)
    @Html.RenderPartialSectionScripts()
</body>
</html>

ASP.NET Core: Invalid non-ASCII or control character in header

I’m running some ASP.NET Core applications under Kestrel on Linux proxied by NGINX. I ran into an issue recently where I was getting the following exception when I redirected with certain strings:

System.InvalidOperationException: Invalid non-ASCII or control character in header: 0x00ED

The problem is that Kestrel does not support non-ASCII characters in the HTTP header, and I was redirecting when the string containing í, though many unicode characters would present similar problems. Here’s what a section of the problematic HTTP response looks like:

HTTP/1.1 302 Found
Location: /locations/juana-díaz
[Rest of the Response Here]

The solution is pretty simple: you need to encode strings that contain non-ASCII characters the same way you’d need to encode strings that contain any other characters with special meaning in a URL. The easiest thing to do is to use WebUtility.UrlEncode that’s in System.Net; for example:

using System.Net;
// ...
var encodedLocationName = WebUtility.UrlEncode(locationName);
return Redirect("~/locations/" + encodedLocationName);

Which will then produce a redirect that Kestrel is happier with, and looks something like this:

HTTP/1.1 302 Found
Location: /locations/juana-d%C3%ADaz
[Rest of the Response Here]