Another underappreciated library is TPL Dataflow. I rarely go for Parallel.ForEach and instead use an ActionBlock.
You mention that the limit of 50 parallel ops isn't quite enforced because some links may make an additional GET request. With an ActionBlock, you could create 2 simple Job records, one for making HEAD and one for GET requests, and have the HEAD job enqueue a GET job.
Basically, ActionBlock can be used like Task.WhenAll but with MaxDegreeOfParallelism support. In this example the overhead wouldn't be worth it, but it's great in situations where you don't know the number of jobs upfront (e.g. because one job could spawn others).
@Andrew - good catch. There were a couple of problems in that last snippet. Fixed. Thanks.
Hi Rick, Shouldn't the final code snippet be this instead? I'm using ASP .NET Core 8. context.Request.EnableBuffering was a method instead of a property. Sorry if I'm mistaken 😃
app.Use(async (context, next) => { context.Request.EnableBuffering(); await next(); });
I'm using this approach with ASP.NET identity to implement a seamless authentication experience my users. Users can (only) login using federated login (Google, Microsoft, Apple). Upon login ASP.NET Identity sets the standard auth cookie which covers everything except API accesses (using ForwardDefaultSelector). Before hitting any of the API endpoints, my SPA/Blazor WASM Clients issue a request to an endpoint that exchanges their auth cookie for a JWT:
ForwardDefaultSelector
[Authorize] [HttpGet("token")] public IActionResult Token() { // NOTE: user was authenticated using the existing Identity Cookie var user = HttpContext.User; var jwt = new JwtSecurityToken( issuer: appSettings.JwtConfig.Issuer, audience: appSettings.JwtConfig.Audience, claims: user.Claims, expires: DateTime.UtcNow.AddMinutes(15), signingCredentials: new SigningCredentials(jwtKey, SecurityAlgorithms.RsaSha256)); var token = new JwtSecurityTokenHandler().WriteToken(jwt); return Ok(token); }
@Matthew - nullable bool values are a common real life sceanarios for most applications. Any boolean choice is almost always: Yes or No, or not set yet.
In some cases you may not care about the unset state, but in others - as i do in this example - it matters. Unset or null in the context of IsLinkValid means the link has not been checked yet and it needs checking. Or the reverse I only need to check unchecked links.
null
IsLinkValid
Can someone please explain why you would ever have a situation where a Boolean value should be null? Isn't it more appropriate to use flags in that case where you can define more than two states for the variable? !link.IsLinkValid != null ← Can someone explain this and if this is a best practice?
@kejdajar - not surprised you're seeing better performance of K6 on native code, as opposed to running in a docker virtual environment - anything in Docker is likely to be much slower on the same given hardware.
Load testing on a local client Windows machine is very tricky due to the environment as you're running both the client and the server and under heavy load BOTH are putting a lot of strain on the processor.
Interesting for me: I have a fast network and ran WebSurge requests across the network for some of these requests and I was barely able to crack a third of the throughput. However, the same laptop running the server piece was not anywhere near 100% whereas when running local with both client and server running very close to 100%.
Even adding a second machine didn't change that very much - I actually ran into network saturation.
@RichardD - good catch. The double negative is the correct behavior, but yeah link.IsLinkValid is null is much easier to read for clarity.
link.IsLinkValid is null
That's what happens when you reverse an if expression from OR to AND 😄
if
@Ralph - yes but that's not what is needed in this case - ie. wait for completion of all before you can go on to the next operation.
Are you sure about this if statement?
if (onlyChangedLinks && !link.HasLinkChanged && !link.IsLinkValid != null)
Specifically, !link.IsLinkValid != null, which is equivalent to link.IsLinkValid == null or link.IsLinkValid is null.
!link.IsLinkValid != null
link.IsLinkValid == null
Aside from the confusing way of writing the condition, I suspect you only meant to ignore the link if it already has a state.
Another possible way would be Task.WhenEach from .NET 9 onwards.
Task.WhenEach
Thank you for this article! It provides a great baseline for understanding how many requests ASP.NET Core can handle on consumer-grade machines. I was experimenting with the K6 framework but struggled to achieve anywhere near 100,000 requests per second on my reasonably powerful machine. However, using your WebSurge tool, I managed to reach approximately 60,000 requests per second with your demo project. I also found WebSurge incredibly easy to use, making it an excellent choice for quickly showcasing application performance to project owners. Interestingly, for reasons I don't fully understand, K6 performs best on my system when installed directly on Windows, rather than running in Docker (even though I explicitly allocated all available memory and CPU to it). WebSurge, on the other hand, had no such performance issues.
Good writeup of an underappreciated feature of .NET. One possible correction: Per https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-9.0#system-threading-tasks-parallel-foreachasync-1(system-collections-generic-iasyncenumerable((-0))-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask))) I don't believe it's accurate that parallelism isn't limited if MaxDegreesOfParallelism is unspecified - the remarks indicate that the default limit is based on the Environment's ProcessorCount property.
I ran into this same issue, but I had the additional complication of the TabControl in question being part of a docking control where I don't have access to the TabControl to override (I couldn't find good way to override in that case since it removed by ability to sub class it).
For posterity (and a fragile alternative route that I'm totally going to use), I utilized the Lib.Harmony Nuget and patch removed the OnKeyDown event handler from TabControl). That looks like:
OnKeyDown
TabControl
Patch Class
[HarmonyPatch(typeof(TabControl), "OnKeyDown")] public static class TabControlOnKeyDownPatch { static bool Prefix(KeyEventArgs e) { // Intercept and prevent original OnKeyDown from running return false; } }
And then in my App_Startup I just add this:
App_Startup
private void App_Startup(object sender, StartupEventArgs e) { var harmony = new Harmony("some-id-i-made-up"); harmony.PatchAll(); }
Full disclosure, I think this nukes that event for every tab control in the process so you have to be all in. There might be a way to tailor that in Harmony but I haven't dug into it that far.
AzureSQLEdge works on ARM, see this post: https://threedots.ovh/blog/2021/04/sql-server-on-arm/ I tested on a dev PC: Windows on ARM: WSL Ubuntu VM: in a podman container. So far it appears to work although I have more testing to do. HOWEVER!!!! ... although it appears to work so far, according to: https://learn.microsoft.com/en-us/azure/azure-sql-edge/disconnected-deployment
So it's not really going to be of any use for any work where reliability and longevity are needed. A pity.
The only way this works for me is to specify "*" as the directory. I've added directory lines for the entire path to the .git folder and still get the error. Adding the wildcard makes it work. I'm a little uncomfortable with the security risk I think this might imply, but nothing else seems to work.
I'm running Ubuntu 24.04 and the volume is on an external USB drive. The drive is Veracrypt encrypted, but I don't think that should be an issue. git version is 2.43.0.
@Carlos - yes the Web Browser control still works and is still a built-in system component. I doubt it'll go away, but it won't be updated obviously. And more and more stuff doesn't work with it, but if you're rendering your own content and you stick to HTML 5.0 and ES2015 code it still works as it always has. I have several apps that use the old control that won't be updated and they all still work.
Hi, Rick. Is this still working with these days edge versions? Will work al least for a couple of years? Thanks in advance.
Will give this a try in my app.
@Rick Strahl Thanks for this wonderful walkthrough demystifying this technology. I can't believe how simple it really is, but in hindsight, I can't believe how complicated I assumed it was in the first place.
@Dieter... while I agree with Rick about the backup codes defeating the purpose, you should have some kind of backup methods in case the authenticator is lost or needs to be reset. For example, I recently changed my phone and while all my apps and all their settings carried over to the new phone, Authenticator was the one app that did not and I did not realize I needed to manually back it up until it was too late. Luckily, I have configured SMS as a backup on all my critical accounts.
If you really wanted to do passcodes, you would randomly generate them, ensure their uniqueness, and associate them in your database with the user. Then, when the user logs in and gets to the authenticator page, you have a link at the bottom that reads "can't use your authenticator app?". That should take them to a page prompting them to input the passcode, and if it matches, you log them in just like you would had they used the app. Preferably, invalidate the codes after first use and generate new codes for the user.
Thanks, helpful post. Found a typo that you may (or may not) wish to correct.
IncokeAsync()
Interesting topic! I am experimenting with this too. I have two WPF TabControls next to each other (like a split view). My goal is to know which tab is currently active. SelectedItem does not change if the user switches to the tab in the other control. A tab is considered 'active' if a child control (eg. TextBox) has user focus.
Maybe you have an idea how I could solve this problem?
@Isaac - hopefully in the future Microsoft will make this easier and the installers 'just works' as intended so we don't need all of this rigamarole.
Great post, thank you for sharing. I have a Windows ARM developer kit that I keep intending to set something up on, and running a SQL Server instance on it would be a great use case for my home lab as I am starting a job as a SQL Server Database Administrator soon (hopefully!), so doing this kind of cutting-edge stuff will be valuable for me to keep my skillset sharp.
@Steven - oh I like that! Adding to my helper library! Thanks!
Ah, the old "navigate the WPF tree" technique. One thing I've found useful here is to define enumerable primitives for different "axes" (visual/logical tree, ancestors/descendants, whatever) and then you can query the WPF tree using LINQ:
public static IEnumerable<DependencyObject> SelfAndAncestors(this DependencyObject currentControl) { while (currentControl != null) { yield return currentControl; currentControl = VisualTreeHelper.GetParent(currentControl); } }
Once you have simple helper methods like the above, then you can query it in any way you want:
var tab = src.SelfAndAncestors().OfType<MetroTabItem>().FirstOrDefault();
That way you have an "ancestor axis" that you can traverse, and the consuming code is more explicit - it's clear that it's looking for the first MetroTabItem and returns null if none is found (without having to check the XML documentation for FindAncestor).
MetroTabItem
FindAncestor
Nope - it's not working for me. I can't connect with server=(localDb)\MsSqlLocalDb. It always just hangs before failing.
server=(localDb)\MsSqlLocalDb
@Rick: So the auto instance feature of LocalDB does not work on ARM?
@Stephen - going to try the M1 scripts later today. I started the install earlier and the installer ran, so it looks like it likely works.
@Ralph - I wouldn't mind LocalDb, except for the fact that TCP/IP is not working and a new named pipe has to be used for every restart of the server. That requires you start it up manually.
@Duncan - thanks for the direct links - adding to the Resources of the post.
You can download the 2022 (v16) SqlLocalDb .msi directly from here: https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SqlLocalDB.msi - how I found the direct download is documented here: https://blog.dotsmart.net/2022/11/24/sql-server-2022-localdb-download/
In my opinion localDB is the better SQL Server for developers! The DB driver starts the instance automatically (no manual start necessary) and when not in use it doesn't get started 🚀
You don't even have to use named pipes...
Server=(localdb)\mssqllocaldb;Database=...;Integrated Security=......
Have you tried the scripts here? https://github.com/jimm98y/MSSQLEXPRESS-M1-Install/tree/main
I was able to install the latest SQL server using this method on my Mac (Windows 11 Arm on Parallels) fine!
@Thomas - yeah choices are good. Thanks for the feedback - I added a small section in the post to clarify that the log output provides this.
@Rick It's probably a matter of preference. But I really like having the option to enable/disable information like these by changing log levels for specific log categories. It requires you to update the appsettings.json file or create environment variables which kind of sucks. But very powerful.
Hey Rick where is part 3? Great resource!
@Thomas, that's not wrong - except when logging is off. I tend to run without information prompts and in production that is off by default.
But - you're right - I didn't think of that when we were hunting for ports 😀
Maybe I misunderstood something. But can't you just enable this through logging? ASP.NET Core outputs the URL and port it is hosted on through ILogger. If you enable info logging on the Microsoft.Hosting.Lifetime category it outputs log lines like these:
ILogger
Microsoft.Hosting.Lifetime
Now listening on: https://localhost:7028 Now listening on: http://localhost:5010
@lytico - cool. I had no idea about ivkm - that might come in handy for other things too. Thanks!
you can use plantuml native in .net over ikvm. the latest ikvm-releases support that, and you can generate the images in process without a server.
260 get me ? Yeah it can be a real headache, I tried LongPath Tool Program which helped a lot.
You can bypass for PDF generation, but it requires a different mechanism. See this post:
Programmatic Html to PDF Generation using the WebView2 Control and .NET
Hi, would the use of CoreWebView2Controller allow us to set up and render WebView2 with NavigateToString without needing to deal with the visibility issue? Or same visibility issue exists? I only need to print from the content loaded via NavigateToString. Thanks.
Thanks Matt! That was my answer!! This has been bugging me for ages.... 😦
Anyone noticed webview2 hang while running in batch mode
Dude this is freaking awesome!!!
Thanks for posting all of the WebView2 content, Rick. It has been super helpful. As a Minnesotan I've gotta say I'm a little jealous of your location...
Nice!!! Thanks Bro! It worked flawlessly. You Are Da MAN!!!
We encounter the same issue on a regular basis as well. However, most of the time especially for new package versions, clearing the http-cache is also sufficient.
dotnet nuget locals --clear http-cache
This significantly reduces the time of the next nuget restore. 😃
nuget restore
With Git 2.46 you can have * in folder paths so in you case:
[safe] directory = d:/projects/*
Checking in from 2024. This is still useful, solved my problem!