Support for .NET 8
As .NET 8 is released at Dotnetconf today, me and the teams are also happy to announce support for .NET 8 in Optimizely products!
We have tested the latest CMS, Commerce and Search & Navigation packages as well as the most common Optimizely provided addons using the .NET 8 release candidates. After a quick verification with the RTM version of .NET 8 I can now officially say that we support .NET 8.
Our DXP cloud service also supports running .NET 8 applications. Which runtime version is used is determined by which version you compile your project for, through the metadata that the build outputs to the [ProjectName].runtimeconfig.json file. If you want to you can also update the transitive dependencies from our packages to the Microsoft.Extensions.* packages. We have tested running in .NET 8 both with the lowest dependency version we require (6.0) and the 8.0 versions of those.
As mentioned before we continue to release assemblies compiled for .NET 6, as it is not required for us as library authors to change our compilation target for you to run in .NET 8 now that we have verified compatibility. We will change our compilation target too in the future, most likely as part of a future major version release.
If you run into any issues, as usual contact support.
Now, go get .NET 8 and build even better applications with Optimizely!
Great news! Thanks for this update!
Hi Johan, this sounds great! However, I just tested an Optimizely website locally under .NET 8, and once the website is up and running and responding to requests, I get constant console messages like this (every 2 seconds or so):
When compiling and running the exact same website (with the latest versions of all Nuget packages) under .NET 6.0 or 7.0, there are no such messages printed.
Should this be a source of concern, or is it just something with .NET 8 that causes info messages to be printed for things that have been happening silently in previous versions?
Also, do you know if DXP hosting environments will automatically be prepared for .NET 8, or is it done upon request?
By the way, I noticed that authentication using Microsoft.AspNetCore.Authentication.WsFederation that works with 6.0 and 7.0 may fail with .NET 8.0. I guess that this issue is unlikely to be Optimzely-related, though.
Hi Magnus, this sounds great! However, I just tested an Optimizely website locally under .NET 8, and once the website is up and running and responding to requests, I get constant console messages like this (every 2 seconds or so):
When compiling and running the exact same website (with the latest versions of all Nuget packages) under .NET 6.0 or 7.0, there are no such messages printed.
Should this be a source of concern, or is it just something with .NET 8 that causes info messages to be printed for things that have been happening silently in previous versions?
Also, do you know if DXP hosting environments will automatically be prepared for .NET 8, or is it done upon request?
By the way, I noticed that authentication using Microsoft.AspNetCore.Authentication.WsFederation that works with 6.0 and 7.0 may fail with .NET 8.0. I guess that this issue is unlikely to be Optimizely-related, though.
Sorry for the double almost identical comments – the delete button gives a jQuery error message: “TypeError: undefined is not an object (evaluating 'c._buttons')”
Thanks for bringing this to our attention. When running locally it could just have been your computer being on high total memory load at that time. But we will definitely look into it, I know there have been changes in how the GC works in .NET 8.
DXP will automatically select the runtime based on your compilation target as mentioned in the article.
There may very well be breaking changes / issues in .NET itself or surrounding packages because they are new major versions.
The first website I tried has a rather large content database. I have now also tested a smaller website in terms of content, which has a similar code base.
When running that second website locally under .NET 8.0, it takes a little while and perhaps 10 or 20 page requests before the GC messages start to appear. With the bigger website, they appear immediately after the first request, or at the latest after a few requests.
Both websites run without GC messages when targeting .NET 7.0. I can stop either website, edit the csproj file to change the targeting back and forth, and build and run the website again – and if I changed to 8.0, the GC info messages appear, and if I changed to 7.0, they do not appear.
So, the overall memory load on the computer is the same in both cases. It is under macOS 14 Sonoma, by the way.
My investigations so far show that at least on DXP (i.e. in a Linux container) it gets the memory measurement correct on .NET 8. It reports the same memory load level as the azure monitoring.
The event you see is when it detects memory load above what it sees as high (default 90% IIRC) and therefore compacts the cache. If you enable debug logging on the EPiServer.Hosting.Internal namespace you should see the actual percentage value reported. Note that this will be 0 until the first GC has happened.
This is great. I change the target from .NET 6 to .NET 8 today asweel and with simples checks it indicates a good running application (CMS and Customized Commerce B2C).
I have the same info messages mentioned by Otto G, but I had them already in NET 6, so.. I'm planing to take a look at https://docs.developers.optimizely.com/content-management-system/docs/monitoring-memorycache for this issue.
Hi again, when I turn on the debugging, the messages that appear after a few pages have been served (or sometimes before even a single page has been served) indicate “99 % of 24576 MB used”. 24 GB is the total amount of RAM installed in the computer. They go directly from no messages to 99%, and even after reportedly compacting the cache, the next message that comes shortly afterwards still says 99%.
I can monitor the process with the “top” command, and the memory consumption tends to be around 160 MB (differs somewhat between runs), and there is no real difference before and after the messages start appearing.
Before the messages have started:
After the messages have started appearing:
Here is part of the output from the same “dotnet run” invocation, with debug logging enabled, starting from the first memory-related line (these messages all appeared within a few seconds):
For completeness, here is a screen dump of the macOS activity monitor, right after a CMS process has stared outputting these messages – no visible effect on the overall memory pressure chart.
Under .NET 7.0, the memory consumption reported by “top” is about the same. With logging on, occasional memory debug messages are emitted, and they tend to show around 67%.
Output from “top”:
Memory debug messages:
Ok, so that sounds like it is a Mac OS specific .NET 8 issue. The 67 % you see seems to approximately match the memory pressure you see in the GUI. We're using the output from the GC.GetGCMemoryInfo method. It is the total "physical" memory it looks at (or container limits). What you're seeing as a percentage is the ratio between the reported MemoryLoadBytes and TotalAvailableMemoryBytes. It seems like MemoryLoadBytes is then returned incorrectly on your Mac when running in .NET 8. So could be a bug in .NET. Not disasterous for a dev environment, and in DXP production environments as mentioned it seems to work correctly.
Yes that seems to be the case. I have now installed the website under a Ubuntu virtual machine, connected to the same database. The memory usage shown in the debug messages slowly increases from about 25% to about 30% during use of the website. The virtual machine is configured with 4 GB of RAM, and the debug messages show 3911 MB as the total available. This seems more reasonable – but “top” shows just 5.1% in the memory column for the process, though. I am not sure exactly how to interpret the values from “top” in this context. But it does seem like a lot if the process indeed uses around 1 GB right from the start, increasing to around 1.2 GB, so possibly, the numbers are a bit off under Linux, too.
Sample debug printout:
Output from “top”:
How the GC measures the available memory and how that relates to what top will show you is way beyond my knowledge. But just pointing out again that the percentage is supposed to represent the total memory load on the "machine" (or container limit), including all other running processes, the OS itself etc. So there is no contradiction in the reported 30% memory usage on a freshly started container even if the site process running the site itself only uses 200 MB or so. The memory monitoring is designed for production scenarios when the site is the primary thing running on the host, so it makes sense to monitor the overall memory usage of the host rather than of the process.
Ah, right, I misunderstood the explanation of the percentage part of the debug printouts in the previous comments. Thank you for clarifying. Then it does seem plausible that the figures are right under Linux with .NET 8.0. That is reassuring.
The macOS bug is a bit annoying when testing things locally, though, and I wonder how it might affect testing of code that uses https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output?view=aspnetcore-8.0 and/or ISynchronizedObjectInstanceCache. (That is, if things will repeatedly and frequently be evicted from the caches, even though the memory is not actually full.)
Great to get the performance improvements and to use C# 12 etc but to really be happy we need support for Blazor SSR as well. Please put some prioritization on this, and not just only on the out-of-process setups/SaaS etc, to remain in good light with .NET developers. Still plenty of apps that will run on-prem or in PaaS that will live long with in-process ASP.NET templates.
Help out with votes: https://feedback.optimizely.com/ideas/CMS-I-416
Regarding Blazor, I suppose there cannot be much, if anything, that prevents an Optimizely CMS website from being rendered with Blazor SSR templates, though?
Articles like https://medium.com/capgemini-microsoft-team/using-blazor-in-your-existing-asp-net-core-mvc-applications-f1b09bd66f4b indicate that there has been good interoperability with cshtml Razor templates for quite some time. So, they are probably not so different from each other in how they integrate with routing and view resolution concepts in ASP.NET Core. Presumably, some configuration of view engines will be needed, and perhaps adjustment of the ViewResult objects returned by controllers.
Of course, making navigation between pages work like in a typical SPA website could potentially be a lot more work, unless there is already some clever solution to this built into Blazor SSR. (I do not know much about the capabilities of Blazor – I just got curious and wondered whether it really depends on the packaged part of the CMS if you can use Blazor SSR templates or not, or if it is just a matter of writing the website-specific code according to the desired template type.)
And things like Forms, which have a lot of automatic things going on behind the scenes in the cshtml views, may definitely be potential issues. But then again, perhaps they will work even inside of Blazor SSR templates (just a bit differently from other parts of the page), thanks to the similarities in technology.
Perhaps a demo project is needed, more than implementation of support as such?
I have done a lot of work with Blazor SSR already (using the Preview and RC versions, but not related to EPiServer.CMS).
Even with things are in dotnet 8 like HtmlRenderer().RenderComponentAsync<T>() that allows you to render a page/component into a string from anywhere and new RazorComponentResult<T>(someReadOnlyDictionary()) that you can use to return a "set SSR page/componenent" from MVC Controllers and minimal endpoints I'd say using that in the current CMS version will probably just mean a lot of "hacks" with extra steps and switching modes (between the new lightweight smooth way, and the old *Contexts and Renderers mess when needing to render something) resulting in poor performance.
You won't be able to use EditForm component as designed and you won't get the benefit of putting all code in the .razor-file - you need to model-bind in a separate controller or endpoint instead of using a OnSubmit-handler in the Blazor SSR page/component. Also, Tag and HTML Helpers are not a concept in any Blazor mode.
I wrote that feature request quite long ago but knowing more how dotnet 8 turned out my guess is that the order of things needed to be built by Optimizely to use Blazor SSR is something like this:
Personally I don't need to any support for the Blazor SPA modes and that type of routing.
I guess having the above SSR support, inititializing an interactive component "island" that uses Server or WASM mode would work without anything else needed.
Great news! I have tried to upgrade one of the projects but it didn't go well for me. The application threw an exception saying a constructor couldn't be found. Method not found: 'Void Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping..ctor
It seems that the fail originates from EPiServer.Cms.UI.AspNetIdentity.ApplicationUserProvider`1.GetUserAsync(String username)
The full stack trace:
huseyinerdinc did you figure this out? It looks like a version conflict between different versions of entity framework related projects. Do you have multiple projects in your solution referencing different EF package versions perhaps? If you cannot figure it out please send this as a support ticket if you haven't already.
Magnus Rahl, not yet. We will revisit this when it's time to upgrade the site to .NET 8. We don't have any other projects in the solution but we do reference the designer package to be able to use migrations.
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.16">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
When we run the VS profiler during startup we can see that there seems to be a lot of allocations going on in
EPiServer.Web.Routing.ContentEndpointRouteBuilderExtensions.MapContent()
In fact, 2.3M allocations (!), 145 MB of memory and this represent about 1/4 of the total memory allocated by the app during startup.
Not sure if this is intentional or if there is logic in your route builder extension that has run amoc.
We're on EPiServer.Cms.AspNetCore.Routing 12.19.1.
Sorry, was not able to edit my post (or delete it for some reason), I mean to say these tests were made with .NET 6 and the routing extensions represent 1/4 of the _allocations_ (not the actual memory consumption).
I will report this as a support ticket, just thought it was interesting finding with regards to the discussion.
CMS-31285 was raised with the development team re: heavy allocations during startup.
Apparently there is not that much that CMS allocates itself. Most allocations happen when registering endpoints for all shell modules using ASP.NET Core API, which is where the allocations are said to happen.
We too are getting these errors. We are running on Windows 11.
EPiServer.Framework.Cache.Internal.MemoryCacheCompactor: Information: High memory pressure event received.
@Eric this is not an error as such. It just means that the memory usage is at or above the target. As a reaction, to avoid running out of memory, the cache will be compacted. This should reduce the memory usage once the items removed from cache are garbage collected. You should see further log messages when a cache compact is initiated, if it is paused waiting for a GC to happen, and when it finishes (these messages may be on debug level, I don't remember). If you keep seeing this repeatedly over a short period time it can mean a few different things:
I recently went from using windows 11 to macOS and since then i have run into the same problem as mentioned above. We have a rather large CMS soulution (around 40 or so start pages) that runs on .Net 8 and a database hosted in Azure. After the first few requests when starting the project, Im constantly (every 1000ms) getting a dbug message prompted in the console saying "99% of 18432 MB used" followed by "Waiting for a Gen 2 GC before compacting the cache again". It may take up to 5min to load a single page, which makes the site completely unusable.
If i run the solution through a virtual desktop (Parallels), with 14gb memory allocated to windows, i still get a high memory load (around 60-90%), but the site is usable and responsive. Is there perhaps a memory leak somewhere? I really hope this problem is looked into, as running parallels for developing is not optimal. If someone has a solution or any tips to this problem im open for suggestions.
Specs: Macbook Pro M3 Pro chip, 18gb memory, running macOS Sonoma 14.3
@Magnus N, it seems that you are suffering from the very same problem that I mentioned, but since your CMS solution is so big, the frequent garbage collection attempts caused by the memory load reporting bug may have a tangible performance impact. That is, there are almost certainly no memory leaks, but there is a constant waste of system resources that can cause slowdowns.
In my case, I just disable the warning printouts by setting the log level for EPiServer.Hosting.Internal and EPiServer.Framework.Cache.Internal to Error, and the moderatly sized solutions that I work on run just fine, as the garbage collection triggered every second or so completes fast enough not to cause any problems.
Unfortunately, it seems that the supposed fix in .NET (see https://github.com/dotnet/runtime/issues/94846 and https://github.com/dotnet/runtime/pull/97102 – merged to the main branch in late January) has not yet made it into any public release.
And one thing that I unfortunately noticed only now – the pull request was “added to the 9.0.0 milestone”. If there is no intention to release this as a bug fix to .NET 8.0.x, we might have to wait until late 2025 before Optimizely CMS works properly on macOS with a long-term-support .NET release. (I guess one could run .NET 9 locally once it is released, but assuming that one still wants to keep using .NET 8 for production until the next LTS release, it would be really messy with regard to source code management – unless there is a way to override framework targeting without editing the csproj file.)
As the GitHub issue is now closed, I have no idea where to initiate a request for considering this fix for inclusion into a .NET 8 bug fix release. I think it is arguably a true bug that should be fixed in a minor release, not some kind of unfortunate “feature” of .NET 8 that must be kept, in case people rely on it. Because .NET is supposed to be cross-platform, and if anyone relies on the MemoryLoadBytes metric always being around 99% of the total installed memory, their code will fail on any other platform than macOS…
So, if anyone knows where in GitHub to properly open a request for fixing this already in an 8.0.x release, please do so and post a message here, and I will be happy to add a comment in GitHub supporting the petition.