ASP.NET Core–adding a second EF database context [code-first] to external project

The process is pretty much straight forward with some caveats that I’ll explain below.

First I moved the Data/Migrations and Models folder out of my web app into to my new project (figure 1).  I got an unexpected compile error - it was a rather obscure error from the _LoginPartial.cshtml.gs.s file – which is the compiled file.   To resolve this I had to update the _LoginPartial.cshtml manually since intellisense was not working in this file (figure 2).  Once I did that, and updated namespaces for the existing ApplicationUser references, my web app was compiling and running again.

SNAGHTML2a91d3df
Figure 1

image
Figure 2

I created my new TripleDbContext as well as the Triple table (see figure 1).  I then added the code pointed to below so that my new TripleDbContext will have a connection string when needed.

image

Figure 3

If you don’t have the dotnet –ef tool installed you can go to the root of your web app [command prompt] and type the following:

dotnet tool install --global dotnet-ef

After installed you’ll need need to switch to the “project that contains your xxxDbContext” and use the following command line.  Since we have more than one context, e.g., ApplicationDbContext (security) and TripleDbContext (new) we have to provide the –context or it will complain, likewise we have to specify the DbContextOptions<TripleDbContext> in the constructor for options (see figure 4).

dotnet ef migrations add "Initial" -o "Data/Migrations" --context TripleDbContext

dotnet ef database update --context TripleDbContext

Note that I provide the connection string for the OnConfiguring() function from the environment – this will only be needed when configuring the database, e.g., doing the two commands above.

image

Figure 4

With that you’ll find your database has the new database table.

image

Figure 5

ASP.NET CORE - Uncaught Syntax Error: Unexpected token ‘<’

http causes error when deployed

After getting past the 500 error when deployed to Azure successfully I thought I was out of the woods, however when I deployed to my ISP I got the unexpected token when trying to login; I was able to register without issue.

My goal, which I achieved, was to be able to deploy the same code to both Azure and my ISP.  Once deployed to my ISP I would login and it would loop back to the login screen, emptying out my login/password – no error to let me know something was wrong.  I hit F12 and see I have a 302 error with a “samesite=none”.

Disclaimer: this effort is simply to allow me to deploy and test my code.  When it comes time that I have SSL on both sites I will update the code as required to fully implement security – this is for development purposes.

image
Figure 1.


I then configured my web application (ref highlighted in figure 3) so that it emulated a deployed application by setting the environement to “Production” and disable SSL.  Then when I ran the app locally I saw more details in my Visual Studio output window.  It displays more on the “SameSite=None”.  Note: if I click on enable SSL (figure 3) I could run locally – it runs locally in SSL.  The problem is when you are running with http.

image
Figure 2


image
Figure 3.


To resolve this issue I implemented the extension from the following website:
https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/

image
Figure 4.


NOTE: that I have one exception to their code [on line 135 below] and that is that I return true.  When false I still had the problem.

image

The extension code follows:

namespace MyWebApp.Extensions
{
using global::Microsoft.AspNetCore.Builder;
using global::Microsoft.AspNetCore.Http;
using global::Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.DependencyInjection
{
public static class SameSiteCookiesServiceCollectionExtensions
{
/// <summary>
/// -1 defines the unspecified value, which tells ASPNET Core to NOT
/// send the SameSite attribute. With ASPNET Core 3.1 the
/// <seealso cref="SameSiteMode" /> enum will have a definition for
/// Unspecified.
/// </summary>
private const SameSiteMode Unspecified = (SameSiteMode)(-1);

/// <summary>
/// Configures a cookie policy to properly set the SameSite attribute
/// for Browsers that handle unknown values as Strict. Ensure that you
/// add the <seealso cref="Microsoft.AspNetCore.CookiePolicy.CookiePolicyMiddleware" />
/// into the pipeline before sending any cookies!
/// </summary>
/// <remarks>
/// Minimum ASPNET Core Version required for this code:
/// - 2.1.14
/// - 2.2.8
/// - 3.0.1
/// - 3.1.0-preview1
/// Starting with version 80 of Chrome (to be released in February 2020)
/// cookies with NO SameSite attribute are treated as SameSite=Lax.
/// In order to always get the cookies send they need to be set to
/// SameSite=None. But since the current standard only defines Lax and
/// Strict as valid values there are some browsers that treat invalid
/// values as SameSite=Strict. We therefore need to check the browser
/// and either send SameSite=None or prevent the sending of SameSite=None.
/// Relevant links:
/// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1
/// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
/// - https://www.chromium.org/updates/same-site
/// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
/// - https://bugs.webkit.org/show_bug.cgi?id=198181
/// </remarks>
/// <param name="services">The service collection to register <see cref="CookiePolicyOptions" /> into.</param>
/// <returns>The modified <see cref="IServiceCollection" />.</returns>
public static IServiceCollection ConfigureNonBreakingSameSiteCookies(this IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = Unspecified;
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});

return services;
}

private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

if (DisallowsSameSiteNone(userAgent))
{
options.SameSite = Unspecified;
}
}
}

/// <summary>
/// Checks if the UserAgent is known to interpret an unknown value as Strict.
/// For those the <see cref="CookieOptions.SameSite" /> property should be
/// set to <see cref="Unspecified" />.
/// </summary>
/// <remarks>
/// This code is taken from Microsoft:
/// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
/// </remarks>
/// <param name="userAgent">The user agent string to check.</param>
/// <returns>Whether the specified user agent (browser) accepts SameSite=None or not.</returns>
private static bool DisallowsSameSiteNone(string userAgent)
{
// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the
// iOS networking stack.
// Notes from Thinktecture:
// Regarding https://caniuse.com/#search=samesite iOS versions lower
// than 12 are not supporting SameSite at all. Starting with version 13
// unknown values are NOT treated as strict anymore. Therefore we only
// need to check version 12.
if (userAgent.Contains("CPU iPhone OS 12")
|| userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}

// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// because they do not use the Mac OS networking stack.
// Notes from Thinktecture:
// Regarding https://caniuse.com/#search=samesite MacOS X versions lower
// than 10.14 are not supporting SameSite at all. Starting with version
// 10.15 unknown values are NOT treated as strict anymore. Therefore we
// only need to check version 10.14.
if (userAgent.Contains("Safari")
&& userAgent.Contains("Macintosh; Intel Mac OS X 10_14")
&& userAgent.Contains("Version/"))
{
return true;
}

// Cover Chrome 50-69, because some versions are broken by SameSite=None
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
// Notes from Thinktecture:
// We can not validate this assumption, but we trust Microsofts
// evaluation. And overall not sending a SameSite value equals to the same
// behavior as SameSite=None for these old versions anyways.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}

return true;
}
}
}
}

HTTP 500 Error for deployed ASP.NET CORE app – works when running locally

500 error when deployed

Object reference not set to an instance of an object.
Problem Id:
System.NullReferenceException at Microsoft.Extensions.DependencyInjection.IdentityServerBuilderConfigurationExtensions+<>c.<AddSigningCredentials>b__10_2

001browser1

This can be tough to diagnose if you are not deployed to Azure [with application insights enabled].  With app insights the message is not real helpful, but a clue lies in the “identityServerBuildConfigurationExtension”

003AppInsights

To verify the issue is with identity server configuration you can update the appsettings.json as shown below and redeploy.  If the site comes up but you can’t access the default “Fetch data” (see image) then this is the source of your problem; identity server configuration.

SNAGHTML952d1a08

in the appsettings.json add the following section:

    "Key": {
"Type": "Development"
}

image

The application will now run [in azure], however this configuration should not be used in a production environment.   The following link provides the steps [links] to “Deploy to Azure App Service”

Note: this fix will allow your site to work if you are deploying to Azure [because it is a secure site using https], however if you are trying to deploy to an ISP using http you'll have some more work because http causes error when deployed