Contents tagged with .NET Core
-
Tiny Difference, Big Consequences Reloaded: SignalR in .NET Core 3.1 vs. .NET 5
In a past blog post I wrote about a surprising change I encountered during the migration of a .NET Core 2.2 application to .NET Core 3.0. I have just migrated that same application to .NET 5 and guess what, I stumbled across another “tiny difference with big consequences”.
The situation: I have a WPF desktop client that communicates with an ASP.NET Core server that in turn communicates with a small TypeScript browser application. I use Web API for accessing files and meta data, and SignalR to call remote functions.
After the migration I ran the application and things were working fine – until a certain point, where I received the following exception in a call of SignalR’s
HubConnection.InvokeCoreAsync()
(actual function name replaced with “doSomething”):Microsoft.AspNetCore.SignalR.HubException
HResult=0x80131500
Message=Failed to invoke 'doSomething' due to an error on the server.
Source=System.Private.CoreLibOn the server side, my own code was not even called.
It took me a while to notice that one of the parameters of the function had the type of a class with a specific constellation of constructors:
- A public constructor with parameters
- A private default constructor.
It turned out while SignalR in .NET Core 3.1 has no problem (de)serializing an instance of a class with a private default constructor, SignalR in .NET 5 does. So simply removing that constructor fixed the problem. In terms forcing the usage of the other constructor when explicitly creating objects of that type, nothing changed; I did not need a private default constructor for that.
P.S. Do not ask me why I had a private default constructor in the first place. But the project is a couple of years old now, and things like this do happen.
-
Ways to Create a Thumbnail for Video and Image Files in C#
In my spare time, I work on a software for the courtside advertising system of a local sports arena. To create the playlists for the advertising loops on the LED modules, users can drag and drop preview thumbnails of video and image files. Which brings us to the topic of this blog post, i.e. how to create these thumbnails.
Ways to create thumbnails
Win API
When I started the project in C#/WPF many years ago, I used some Win API code from a StackOverflow answer by Daniel Peñalba.
StorageFile.GetThumbnailAsync
After a failed attempt at a UWP rewrite, my software now runs on .NET Core 3.1, with a WPF management client, a ASP.NET/SignalR server and a HTML5/Typescript playback client (using a Chrome browser in kiosk mode, although I may change that when Microsoft Edge WebView2 comes out of preview).
Working with UWP introduced me to StorageFile.GetThumbnailAsync, which I kept using because the StorageFile API also gives me easy access to metadata (e.g. width, height and duration of a video).
The StorageFile API is not exactly known for high performance, but in my case this is not an issue, because the number of media files added in one go is typically in single digits.
FFmpeg
As mentioned above, StorageFile.GetThumbnailAsync works for me. So, why am I considering another method for the next version of my software?
For an answer I have to digress a bit: My software initially used individual MediaElement controls for the eleven LED modules. While I did manage to sync up the video playback, making it robust and reliable for many hours of looped playback required many workarounds in the code. To make matters worse, the reliability depended on the video format.
The videos to be played come from a variety of sources; some are produced by advertising agencies or video production companies, others are the work of semi-professionals. Some match the requested specs, many do not. In the end, I made it a habit to transcode each video myself.
Fortunately, this did not cause a noticeable loss of quality. Which encouraged me to try a different approach when I rewrote the playback to be performed in a browser. Now my software uses FFmpeg to combine the individual videos into one MP4 file. FFmpeg is “A complete, cross-platform solution to record, convert and stream audio and video” and this quote from the website is not an understatement.
In addition to offering a wide variety of features to manipulate inputs and outputs, FFmpeg also has been able to handle virtually any media file it has come across so far. Until now I could not fully benefit from this robustness because StorageFile.GetThumbnailAsync does not work with the same range of file formats. For example, on my computer, MOV files cause an exception (note the emphasis, YMMV).
FFmpeg to the rescue: Creating a thumbnail from a video or image file is something that FFmpeg can do easily – with two caveats:
- It is slower than GetThumbnailAsync (at least when calling the executable as a separate process, like I do)
- It does not work for general purpose files, only for videos or images.
My plan is to continue to use StorageFile.GetThumbnailAsync as the default method and to use FFmpeg as a fallback in case of an exception.
Demo code
I have posted a Visual Studio demo project on https://github.com/RWeigelt/ThumbnailCreationDemo. The code shows the use of StorageFile.GetThumbnailAsync and FFmpeg (for Win API, see the StackOverflow answer). Please note that the code is not intended to be a ready-to-use library; instead, you should run the demo, maybe set a few breakpoints, follow the code and use the parts that are of interest to you.
A few prerequisites:
- Your Visual Studio 2019 (I used 16.7.1) should be able to compile .NET Core 3.1 command line applications.
- You must have FFmpeg on your computer. You can get it from https://ffmpeg.org.
Before you run the project, change the following line in “Program.cs” so the path matches the location of the FFmpeg executable on your computer:
const string _FfmpegExeFilePath = @"C:\Program Files\FFmpeg\bin\ffmpeg.exe";
The project comes with two example files, “Image.png” and “Video.wmv”, with 768 x 80 pixels each (the size of a LED advertising module). During a build, the files will be copied to the output directory.
Running the project inside Visual Studio creates two thumbnail files for each media file (“…thumb1.png” and “…thumb2.png”). At the end you will be offered to open an Explorer window for the output directory, so you do not need dig around in the file system.
While the code for using StorageFile.GetThumbnailAsync is straightforward, the usage of FFmpeg needs some explanation.
The call of
ffmpeg.exe
is hidden behind a Task using a TaskCompletionSource. For the purpose of the demo code, the Task returns the duration in milliseconds (instead of e.g. the exit code of the FFmpeg process). This is something you may want to change for production code.The command line parameters:
- For video files,
-ss 5
means “seek to the position five seconds into the media file”.-an
prevents audio streams from being handled in any way-i
specifies the input file-vframes 1
gets one frame from the input file-s
specifies the output size-y
to confirm overwriting an existing file- The last parameter is the output file; the extension of the thumbnail file tells FFmpeg that we want the file in PNG format.
Note that the order of the parameters makes a difference: Parameters in front of the input file (
-i
) are treated as options that affect the input, parameter behind affect the output.For instance, the
-ss 5
in front of the-i
simply seeks into the file (as one would expect). If the-ss 5
is placed behind the-i
, then FFmpeg will actually decode five seconds of the input file frame by frame, which takes more time. The difference is noticeable even for this simple demo, try it out. For more information see https://trac.ffmpeg.org/wiki/Seeking.What about metadata?
As I wrote, on my computer, calling StorageFile.GetThumbnailAsync causes an exception for a MOV file (yes, this could surely be fixed with the right codec pack, but for the moment I keep things the way they are to test error handling).
But I can retrieve metadata using StorageFile.Properties – that is because the metadata is not determined at the moment I ask for it, it simply is read from the file without actually looking at its content. This is fortunate because for my purposes, reading the metadata via the StorageFile API is much easier than using e.g. ffprobe’s output.
-
Notes on Migrating a WPF Application to .NET Core 3.0
Recently, I migrated a WPF application from .NET Framework 4.7.2 to .NET Core 3.0, which took me about two hours in total. The application consisted of four assemblies (one EXE, one UI class library, two other library assemblies).
A Google search for “migrate wpf to .net core 3” brings up enough helpful documentation to get started, so this post is mainly a reminder to myself what to look out for the next migration – which may be far enough in the future that I’ll have forgotten important bits by then.
How to get started
My starting point was the Microsoft article “Migrating WPF apps to .NET Core”. A lot of text, which makes it easy to get impatient and start just skimming for keywords. Tip: Do take your time.
In my case, I somehow missed an important detail concerning the
AssemblyInfo.cs
file at first.AssemblyInfo.cs
When you migrate to .NET Core and use the new project file format, you have to decide what you want to do about the
AssemblyInfo.cs
file.If you create a new .NET Core project, that file is autogenerated from information you can enter in the “Packages” section of the project properties in Visual Studio (the information is stored in the project file).
In the .NET Framework version of the UI class library, I used the attributes
XmlnsDefinition
andXmlnsPrefix
to make the XAML in the EXE project prettier. That’s why I wanted to keep using anAssemblyInfo.cs
file I could edit manually. For this, I had to add the following property:<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
PreBuild Step
I use a pre-build step in my UI library project (calling a tool called ResMerger), After the migration, macros like
(ProjectDir)
were no longer resolved. The migration document does not cover pre/post build events (at the time of this writing). But the article “Additions to the csproj format for .NET Core” does, in the section “Build events”.PreBuild event in the old csproj file:
<PropertyGroup> <PreBuildEvent>"$(SolutionDir)tools\ResMerger.exe" "$(ProjectDir)Themes\\" $(ProjectName) "Generic.Source.xaml" "Generic.xaml" </PreBuildEvent> </PropertyGroup>
And in the new csproj file:
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Exec Command=""$(SolutionDir)tools\ResMerger.exe" "$(ProjectDir)Themes\\" $(ProjectName) Generic.Source.xaml Generic.xaml" /> </Target>
Note that the text of the PreBuild step is now stored in an XML attribute, and thus needs to be XML escaped. If you have a lot of PreBuild/PostBuild steps to deal with, it’s maybe a good idea to use the Visual Studio UI to copy and paste the texts before and after migrating the project file.
WPF Extended Toolkit
My application uses some controls from the Extended WPF Toolkit. An official version for .NET Core 3 has been announced but is not available yet. For my purposes I had success with a fork on https://github.com/dotnetprojects/WpfExtendedToolkit, your experience may be different.
Switching git branches
Switching back and forth between the git branches for the .NET framework and .NET Core versions inside Visual Studio results in error messages. To get rid of them, I do the typical troubleshooting move of exiting Visual Studio, deleting all
bin
andobj
folders, restarting Visual Studio and rebuilding the solution. Fortunately, I don’t need to maintain the .NET framework in the future. The migration is done and I can move on.Automating the project file conversion
For the next application, I’ll likely use the Visual Studio extension “Convert Project To .NET Core 3” by Brian Lagunas. I installed the extension and ran it on a test project – a quick look at the result was promising.
-
JSON Serialization in .NET Core 3: Tiny Difference, Big Consequences
Recently, I migrated a web API from .NET Core 2.2 to version 3.0 (following the documentation by Microsoft). After that, the API worked fine without changes to the (WPF) client or the controller code on the server – except for one function that looked a bit like this (simplified naming, obviously):
[HttpPost] public IActionResult SetThings([FromBody] Thing[] things) { ... }
The
Thing
class has anItems
property of typeList<Item>
, theItem
class has aSubItems
property of typeList<SubItem>
.What I didn’t expect was that after the migration, all
SubItems
lists were empty, while theItems
lists contained, well, items.But it worked before! I didn’t change anything!
In fact, I didn’t touch my code, but something else changed: ASP.NET Core no longer uses Json.NET by NewtonSoft. Instead, JSON serialization is done by classes in the new System.Text.Json namespace.
The Repro
Here’s a simple .NET Core 3.0 console application for comparing .NET Core 2.2 and 3.0.
The program creates an object hierarchy, serializes it using the two different serializers, deserializes the resulting JSON and compares the results (data structure classes not shown yet for story-telling purposes):
class Program { private static Item CreateItem() { var item = new Item(); item.SubItems.Add(new SubItem()); item.SubItems.Add(new SubItem()); item.SubItems.Add(new SubItem()); item.SubItems.Add(new SubItem()); return item; } static void Main(string[] args) { var original = new Thing(); original.Items.Add(CreateItem()); original.Items.Add(CreateItem()); var json = System.Text.Json.JsonSerializer.Serialize(original); var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(original); Console.WriteLine($"JSON is equal: {String.Equals(json, json2, StringComparison.Ordinal)}"); Console.WriteLine(); var instance1 = System.Text.Json.JsonSerializer.Deserialize<Thing>(json); var instance2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Thing>(json); Console.WriteLine($".Items.Count: {instance1.Items.Count} (System.Text.Json)"); Console.WriteLine($".Items.Count: {instance2.Items.Count} (Json.NET)"); Console.WriteLine(); Console.WriteLine($".Items[0].SubItems.Count: {instance1.Items[0].SubItems.Count} (System.Text.Json)"); Console.WriteLine($".Items[0].SubItems.Count: {instance2.Items[0].SubItems.Count} (Json.NET)"); } }
The program writes the following output to the console:
JSON is equal: True .Items.Count: 2 (System.Text.Json) .Items.Count: 2 (Json.NET) .Items[0].SubItems.Count: 0 (System.Text.Json) .Items[0].SubItems.Count: 4 (Json.NET)
As described, the sub-items are missing after deserializing with System.Text.Json.
The Cause
Now let’s take a look at the classes for the data structures:
public class Thing { public Thing() { Items=new List<Item>(); } public List<Item> Items { get; set; } } public class Item { public Item() { SubItems = new List<SubItem>(); } public List<SubItem> SubItems { get; } } public class SubItem { }
There’s a small difference between the two list properties:
- The
Items
property of classThing
has a getter and a setter. - The
Subitems
property of classItem
only has a getter.
(I don’t even remember why one list-type property does have a setter and the other does not)
Apparently, Json.NET determines that while it cannot set the
SubItems
property directly, it can add items to the list (because the property is not null).The new deserialization in .NET Core 3.0, on the other hand, does not touch a property it cannot set.
I don’t see this as a case of “right or wrong”. The different behaviors are simply the result of different philosophies:
- Json.NET favors “it just works.”
- System.Text.Json works along the principle “if the property does not have a setter, there is probably a reason for that.”
The Takeways
- Replacing any non-trivial library “A” with another library “B” comes with a risk.
- Details. It’s always the details.
- Consistency in your code increases the chances of consistent behavior when something goes wrong.
- The