Updating an ASP.NET Core 2.2 Web Site to .NET Core 3.1 LTS
Now that .NET Core 3.1 is out just this last week and it is a "LTS" or Long Term Support version, I thought it'd be a good time to update my main site and my podcast to .NET 3.1. You can read about what LTS means but quite simply it's that "LTS releases are supported for three years after the initial release."
I'm not sure about you, but for me, when I don't look at some code for a few months - in this case because it's working just fine - it takes some time for the context switch back in. For my podcast site and main site I honestly have forgotten what version of .NET they are running on.
Updating my site to .NET Core 3.1
First, it seems my main homepage is NET Core 2.2. I can tell because the csproj has a "TargetFramework" of netcoreapp2.2. So I'll start at the migration docs here to go from 2.2 to 3.0. .NET Core 2.2 reaches "end of life" (support) this month so it's a good time to update to the 3.1 version that will be supported for 3 years.
Here's my original csproj
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <RootNamespace>hanselman_core</RootNamespace> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" /> </ItemGroup> <ItemGroup> <None Update="IISUrlRewrite.xml"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> </Project>
and my 3.0 updated csproj. You'll note that most of it is deletions. Also note that I have a custom IISUrlRewrite.xml that I want to make sure gets to a specific place. You'll likely not have anything like this, but be aware.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <RootNamespace>hanselman_core</RootNamespace> </PropertyGroup> <ItemGroup> <None Update="IISUrlRewrite.xml"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> </Project>
Some folks are more little methodical about this, upgrading first to 3.0 and then to 3.1. You can feel free to jump all the way if you want. In this case the main breaking changes are from 2.x to 3.x so I'll upgrade the whole thing all in one step.
I compile and run and get an error "InvalidOperationException: Endpoint Routing does not support 'IApplicationBuilder.UseMvc(...)'. To use 'IApplicationBuilder.UseMvc' set 'MvcOptions.EnableEndpointRouting = false' inside 'ConfigureServices(...)." so I'll keep moving through the migration guide, as things change in major versions.
Per the docs, I can remove using Microsoft.AspNetCore.Mvc;
and add using Microsoft.Extensions.Hosting;
as IHostingEnvironment
becomes IWebHostEnvironment
. Since my app is a Razor Pages app I'll add a call to servicesAddRazorPages();
as well as calls to UseRouting
, UseAuthorization
(if needed) and most importantly, moving to endpoint routing like this in my Configure() call. I also move the call to bring in HealthChecks into the UseEndpoints call.
app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthcheck");
endpoints.MapRazorPages();
});
I also decide that I wanted to see what version I was running on, on the page, so I'd be able to better remember it. I added this call in my _layout.cshtml to output the version of .NET Core I'm using at runtime.
<div class="copyright">© Copyright @DateTime.Now.Year, Powered by @System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription</div>
In order versions of .NET, you couldn't get exactly what you wanted from RuntimeInformation.FrameworkDescription
, but it works fine in 3.x so it's perfect for my needs.
Finally, I notice that I was using my 15 year old IIS Rewrite Rules (because they work great) but I was configuring them like this:
using (StreamReader iisUrlRewriteStreamReader = File.OpenText(Path.Combine(env.ContentRootPath, "IISUrlRewrite.xml")))
{ var options = new RewriteOptions() .AddIISUrlRewrite(iisUrlRewriteStreamReader); app.UseRewriter(options); }
And that smells weird to me. Turns out there's an overload on AddIISUrlRewrite
that might be better. I don't want to be manually opening up a text file and streaming it like that, so I'll use an IFileProvider instead. This is a lot cleaner and I can remove a using System.IO;
var options = new RewriteOptions() .AddIISUrlRewrite(env.ContentRootFileProvider, "IISUrlRewrite.xml");
app.UseRewriter(options);
I also did a little "Remove and Sort Usings" refactoring and tidied up both Program.cs and Startup.cs to the minimum and here's my final complete Startup.cs.
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace hanselman_core { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks(); services.AddRazorPages().AddRazorPagesOptions(options => { options.Conventions.AddPageRoute("/robotstxt", "/Robots.Txt"); }); services.AddMemoryCache(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } var options = new RewriteOptions() .AddIISUrlRewrite(env.ContentRootFileProvider, "IISUrlRewrite.xml"); app.UseRewriter(options); app.UseHttpsRedirection(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthcheck");
endpoints.MapRazorPages();
}); } } }
And that's it. Followed the migration, changed a few methods and interfaces, and ended up removing a half dozen lines of code and in fact ended up with a simpler system. Here's the modified files for my update:
❯ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: Pages/Index.cshtml.cs
modified: Pages/Shared/_Layout.cshtml
modified: Program.cs
modified: Startup.cs
modified: hanselman-core.csproj
Updating the Web Site in Azure App Service and Azure DevOps
That all works locally, so I'll check in in and double check my Azure App Service Plan and Azure DevOps Pipeline to make sure that the staging - and then production - sites are updated.
ASP.NET Core apps can rely on a runtime that is already installed in the Azure App Service or one can do a "self contained" install. My web site needs .NET Core 3.1 (LTS) so ideally I'd change this dropdown in General Settings to get LTS and get 3.1. However, this only works if the latest stuff is installed on Azure App Service. At some point soon in the future .NET Core 3.1 will be on Azure App Service for Linux but it might be a week or so. At the time of this writing LTS is still 2.2.7 so I'll do a self-contained install which will take up more disk space but will be more reliable for my needs and will allow me full controll over versions.
I am running this on Azure App Service for Linux so it's running in a container. It didn't startup so I checked the logs at startup via the Log Stream and it says that the app isn't listening on Port 8080 - or at least it didn't answer an HTTP GET ping.
I wonder why? Well, I scrolled up higher in the logs and noted this error:
2019-12-10T18:21:25.138713683Z The specified framework 'Microsoft.AspNetCore.App', version '3.0.0' was not found.
Oops! Did I make sure that my csproj was 3.1? Turns out I put in netcoreapp3.0 even though I was thinking 3.1! I updated and redeployed.
It's important to make sure that your SDK - the thing that builds - lines up with the the runtime version. I have an Azure DevOps pipeline that is doing the building so I added a "use .NET Core SDK" task that asked for 3.1.100 explicitly.
Again, I need to make sure that my Pipeline includes that self-contained publish with a -r linux-x64 parameter indicating this is the runtime needed for a self-contained install.
Now my CI/CD pipeline is building for 3.1 and I've set my App Service to run on 3.1 by shipping 3.1 with my publish artifact. When .NET Core 3.1 LTS is released on App Service I can remove this extra argument and rely on the Azure App Service to manage the runtime.
All in all, this took about an hour and a half. Figure a day for your larger apps. Now I'll spend another hour (likely less) to update my podcast site.
Sponsor: Like C#? We do too! That’s why we've developed a fast, smart, cross-platform .NET IDE which gives you even more coding power. Clever code analysis, rich code completion, instant search and navigation, an advanced debugger... With JetBrains Rider, everything you need is at your fingertips. Code C# at the speed of thought on Linux, Mac, or Windows. Try JetBrains Rider today!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
"Starting with 3.0, EF Core only allows expressions in the top-level projection (the last Select() call in the query) to be evaluated on the client. When expressions in any other part of the query can't be converted to either SQL or a parameter, an exception is thrown."
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
It's definitely a change for the better - we've got a few queries on small(ish) static data tables that didn't have much a performance hit but need fixing.
This one too, which has luckily been fixed in 3.1!
https://github.com/aspnet/EntityFrameworkCore/issues/15862
I assume that should be 'In older versions of .NET....'
This is all great, and I'm all for upgrading to the latest version of the framework, but there is one tiny problem - we are using Microsoft OData library, which is not compatible with 3.*
According to blogs this will be resolved only in Q2 2020. In a way Microsoft OData team is not complient with support policy :)
Any comments on that?
This is all great, and I'm all for upgrading to the latest version of the framework, but there is one tiny problem - we are using Microsoft OData library, which is not compatible with 3.*
According to blogs this will be resolved only in Q2 2020. In a way Microsoft OData team is not complient with support policy :)
Any comments on that?
Specifically, I tried MvcScaffolding 1.0.9, and MvcScaffolding.VS2019 version 1.0.3. The T4Scaffolding.dll's manifest shows the TargetFrameworkAttribute is targeting .NET Framework v4.8.
Just a small typo I noticed. Below the first image, you write.
> and my 3.0 updated csproj.
But I think it should be
> and my 3.1 updated csproj.
Cheers and keep on doing awesome stuff! 🤘
Christian
Thanks a lot for this upgrade article.
We cannot migrate to 3.1 in VS 2017?
Like to ask one question about first api request time. I have an asp.net core 3.1 application and observing that first request is always slow. https://github.com/aspnet/AspNetCore/issues/17985
My first request response is very slow. Any improvement suggestion?
Comments are closed.
In your Azure DevOps pipelines, you can configure the task Use .NET Core to download and use the latest released patch of the SDK by writing "3.1.x" in the Version" field.
If you do this, you'll have all the bugfixes applied automatically to your homepage!