Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

First Steps in porting Markdown Monster WPF App to .NET Core 3.0


:P
On this page:
Edit this Post

Today I took the day to explore what it would take to port Markdown Monster to .NET Core. Most of the examples I've seen so far for ports or running applications on .NET Core are pretty trivial - you can see how the process works, but just looking at those examples I had a million questions of how certain things will be handled. So I decided to take a look.

For example:

  • How to handle non-.NET Standard NuGet Packages
  • How do external assembly loads (for addins in MM) work
  • Porting - How do I get my project ported
  • How to Xaml files get built
  • How do resources (Assets) get embedded into the project

I'll look at all of these things. Heads up: This is a long rambling post as I just wrote down a bunch of stuff as I was going through it. I edited out some extraneous stuff, but it's mostly just off the cuff, but if you're thinking about porting an application I think most of the things I describe here are things you are likely to run into yourself even if this application is a bit more esoteric as it includes some interop features due to the Web Browser control used the core UI.

Examples are based .NET Core 3.0 Preview 5 which is the latest Preview at the time of writing.

Porting the Project

Markdown Monster is comprised of one main WPF EXE project which contains the bulk of code, plus several Addin projects that handle things like Weblog Publishing, Screen Captures and the Snippet Editor. To start I unloaded the Addin projects to focus only on the main project first.

So the first thing I did is convert the MM exe project file to the new .NET SDK Project Format file by switching the old .NET project to a .NET SDK project. If you recall, .NET SDK projects are much simpler than the old .NET projects because you generally don't have to list every file that the project needs to build. Instead the project knows about common file types and automatically builds what it knows how to build. You only explicit add files that require special instructions like static files or folders to copy, or files to exclude from building or copying.

What's nice about that is that you can easily create a new project by basically deleting the old one and adding just a few simple things into the file.

To start I simply created a mostly empty project file from my old project:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
 
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AssemblyName>MarkdownMonster</AssemblyName>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

At this point I can actually open the project in Visual Studio as a new SDK style project.

I also had to delete the Properties.cs file that hold assembly configuration - that information by default gets generated by the project file info. Alternate you can also add <GenerateAssemblyInfo>false</GenerateAssemblyInfo> and keep Properties.cs. I recommend the former and put the assembly info into the project file.

Most likely you'll get errors that complain about duplicate Assembly directives. SDK projects include assembly directive information in the project file itself, so you can either delete the old AssemblyInfo.cs file, or add <GenerateAssemblyInfo>false</GenerateAssemblyInfo> into the property group above and leave the old file as is.

Note that you no longer have to add Xaml files explictly - the <UseWpf> tag automatically compiles all Xaml files in your project so the file can stay clean.

NuGet Packages

If I compile at that point I'll get hundreds of errors - that's because all of my dependencies are still missing. So the next step then is to move over all the package references into the new project as an item group.

  <ItemGroup>
    <PackageReference Include="Dragablz" Version="0.0.3.203" />
    <PackageReference Include="FontAwesome.WPF" Version="4.7.0.9" />
    <PackageReference Include="HtmlAgilityPack" Version="1.11.3" />
    <PackageReference Include="LibGit2Sharp" Version="0.26.0" />
    <PackageReference Include="LumenWorksCsvReader" Version="4.0.0" />
    <PackageReference Include="MahApps.Metro" Version="1.6.5" />
    <PackageReference Include="Markdig" Version="0.16.0" />
    <PackageReference Include="Microsoft.ApplicationInsights" Version="2.9.1" />
    <PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.1" />
    <PackageReference Include="NHunspell" Version="1.2.5554.16953" />
    <PackageReference Include="Westwind.Utilities" Version="3.0.25" />
  </ItemGroup>

The easiest way to do this is open your old packages config file and copy the packages into the item group. Replace package with PackageReference and id with include for quickly getting your references in. Note that you can remove any secondary package references that your project doesn't directly reference. For example Westwind.Utilities references Newtonsoft.Json - the old packages.config included Newtonsoft.Json but since it's a secondary dependency you can remove it. It'll still get pulled, but doesn't need to be explicitly referenced.

Also note this package:

<PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.1" />

which is required in order to provide all the required Windows API that are not part of .NET Core. This basically contains most of the functionality of the BCL/FCL that is Windows specific and is not part of .NET Core. It's what makes it possible for .NET Core to actually run WinForms and WPF applications with fairly good compatibility. If you want to see how much Windows specific code you have in your project get to a stable point where your code compiles, then remove that package 😃

What's interesting is that some of the references in question are not .NET Standard (or .NET Core) compliant - mainly the Windows specific ones like this older version of MahApps, Dragablz and FontAwesomeWPF. Most of the other assemblies actually have .NET Standard 2.0 versions that are automatically ugpraded to be used instead of the full framework ones.

It appears that it's not necessary that packages or assemblies added to the project are .NET Standard compliant which is a big relief given that most legacy projects are likely to have at least a few dependencies that are not on the new .NET train.

The full framework assemblies link in just fine, but we'll see what happens at runtime. Adding the dependencies has whittled down my error count to about 80.

Hunting down Errors

Next comes the task of whack-a-mole to try to track down a bunch of dependency errors. Making sure the right namespaces are in place and all the NuGet packages are loaded properly in the project. Not really sure why some of these are happening since there weren't any code or dependency changes but lots of namespaces required explicit reassignments in my case.

Windows vs. .NET Standard and Non-Windows

I also ran into a few errors with my own libraries due to some - what now turns out to be bad - assumptions.

In westwind.utilities which is my general purpose library for helpers there are a few Windows specific classes that long preceded the advent of .NET Core/Standard. I left them in the library, but when I built the .NET Standard version I explicitly bracketed them out with #if #endif compiler directives, so they wouldn't actually compile nor run on other platforms or in .NET Standard.

Here's what this looks like in Visual Studio when the active target is .NET Standard 2.0:

Basically I have a compiler flag when I build for full framework called NETFULL that allows me to bring in some code that doesn't show up in the .NET Standard assembly. It works well and is easy enough to manage.

Well now with .NET Core 3.0 supporting Windows (via additional assemblies those extra classes are missing).

In this case the solution is easy enough - the code is open source and I can just directly add the missing classes or components into the Markdown Monster (or a support) project. There were several items like this with various libraries (image conversion using GDI were a few others).

For future reference I'll have to rethink how this is done. Most likely anything that is Windows specific will have to be pulled out of the library and moved into a Windows specific version and then the .NET Standard version has to have a dependency on the Windows support libraries to provide the needed Windows features. There are very few features in Westwind.Utilities, mainly System level functions (WindowsUtilities) and the Shell utilities to open files folders and other applications on the desktop.

So there's a lot of small cleanup tasks for various files and types that are mostly internal. Basically cleaning up some references and removing some support libraries (mainly the CommonDialogs functions for the folder browser dialog which is now fixed in 3.0 to use the new style file browser).

This is to be expected with any sort of version update and while there were about 30 or so instances of errors or relocated references this only took 15 minutes to clean up.

Adding your Entry Point

If you are using an entry point other than app.xaml as I am you'll have to add that explicitly. You can add it to the in the main project group like this:

<StartupObject>StartUp</StartupObject>

or use Visual Studio's project dialog to pick the startup class:

At this point the project compiles! Yay!

Managing Resources and Copied Files

So at this point I was able to compile my project (with a number of warnings for packages that aren't 3.0 ‘compatible’):

Cool! That wasn't too bad…

But running the application still doesn't work at that point:

Ah - yes missing resources. Looks like each of the individual icons and image assets have to be added to the project individually as well:

<ItemGroup>
    <Resource Include="Assets/MarkdownMonster.png" />
    <Resource Include="Assets/MarkdownMonster_Icon_256.png" />
    <Resource Include="Assets/vsizegrip.png" />

    <Resource Include="Assets/folder.png" />
    <Resource Include="Assets/git.png" />
    <Resource Include="Assets/default_file.png" />
</ItemGroup>

That gets me a little further but I'm still missing some additional resources:

In this case I'm missing some support files that Markdown Monster copies into the output folder. Specifically it's the Web Content files used to render the editor and preview HTML.

Copied Resources

To add those I need to explicitly add those folders to be copied:

<ItemGroup>
    <None Update="Editor\**\*.*">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

<ItemGroup>
    <None Update="PreviewThemes\**\*.*">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

And that works.

At this point Markdown Monster is actually launching! Yay:

COM Interop and Dynamic Doesn't Work!

But… there are problems. As you can see the most important UI component - the freaking Editor - is missing and it's due to errors that are occurring when the editor is loading.

The problem is when accessing the Web Browser Control's internal components which are COM based, the application fails. It turns out the specific problem is accessing dynamic COM instances and then calling methods or accessing members on them. This just fails outright.

Any attempt to use dynamic just fails! I remember I talked about this in a previous post and at the time the word was that this would be fixed in 3.0.

Well apparently this is not the case at least not in Preview 4.

So Markdown Monster has a ton of interop that it does with the Markdown Editor which is an HTML component inside of a WebBrowser control. The WPF application calls into the custom behaviors that I've set up in JavaScript to interact with the editor. Any directives through the UI are routed through a central editor class and all of those interop calls use dynamic. These are lightweight method calls - calling JavaScript functions from C# code.

This has been working great in full framework but all of that's broken now in .NET Core 3.0.

The workaround is to use Reflection. To retrieve my Window I can do this instead of using dynamic using ReflectionUtils in Westwind.Utilities:

object window = ReflectionUtils.GetPropertyCom(doc, "parentWindow");

To get the editor and set the Markdown in the document I can do this:

if (AceEditor != null)
{

    // Doesn't work in Core 3.0
    //AceEditor.setvalue(markdown ?? string.Empty, position, keepUndoBuffer);

    ReflectionUtils.CallMethodCom(AceEditor, "setvalue", markdown ?? string.Empty, position,
        keepUndoBuffer);
}

It's doable, but besides being less efficient (dynamic does good job caching Reflection details) but fixing this mess would be a heck of a nightmare even though all of the Interop calls are mostly localized in the single MarkdownDocumentEditor class.

I can't quickly fix all of this, but after fixing a few strategic calls that set and retrieve the Markdown document using Reflection instead of dynamic I am now able to get the Markdown Monster editor to come up.

It works, but it's far from functional. The editor works but most commands won't, the refresh and browser sync isn't working.

Other Issues

Addins

The next thing that is causing me problems is how to deal with addins. Markdown Monster is based on a core editor, that you then plug additional features into. A number of base features like the Weblog Publishing, Screen Capture and Template Snippet engine are built using Addins that are completely separate from the main editor. The running app above is at this point not using any of the addins which I initially disabled to keep the migration process managable. Now it's time to add at least one back in.

Addins are separate DLL assemblies that are loaded from a special folder - Addins - in the bin folder for the stock addins that ship with MM, or in the %appdata\Markdown Monster\Addins folder. As such I need to build them into a special output folder when building and testing because MM expects them in these special folders.

It turns out that's not so easy to do with SDK projects, as the build output either doesn't pull any dependencies (default behavior), or pulls every dependency from all the project and NuGet packages. Since addins reference the main application, pretty much everything gets pulled in.

OK, here's how to create a WPF support assembly (addin) that may also contain some UI elements and forms in this case. As with the EXE I target netcoreapp3.0 and add <UseWpf>true</UseWpf> to have the compiler automatically pick up Xaml resources.

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AssemblyName>WeblogAddin</AssemblyName>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

This allows me to open the project in Visual Studio. Next I need to add all my existing packages and embedded resources/assets:

  <ItemGroup>
    <PackageReference Include="MahApps.Metro" version="1.6.5" />
    <PackageReference Include="Dragablz" version="0.0.3.203" />
    <PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.1" />
    <PackageReference Include="FontAwesome.WPF" Version="*" />
    <PackageReference Include="HtmlAgilityPack" version="1.11.3" />
    <PackageReference Include="Westwind.Utilities" version="3.0.25" />
    
    <!-- these are the only new, project specific ones -->
    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
  </ItemGroup>
  
  <ItemGroup>
    <ProjectReference Include="..\..\MarkdownMonster\MarkdownMonster.csproj" />
  </ItemGroup>
  
  <ItemGroup>
    <Resource Include="icon.png" />
    <Resource Include="icon_22.png" />
    <Resource Include="MarkdownMonster_Icon_128.png" />
  </ItemGroup>

Notice I also add a project reference back to the main Markdown Monster executable project which holds Addin APIs as well as the entire object model that can be automated from within the addin.

Getting this project to compile was considerably easier - it just worked right away.

Addin Location Madness

Markdown Monster Addins need to live in a very specific location so in order to actually get my addin to work I need to copy the output to a specific output folder. For internal addins like this one that folder lives in the Markdown Monster output folder in an \Addins folder. In classic .NET projects this used to be easy, but it's a bit tricky in .NET SDK projects because project output actually only builds the actual assembly into the output folder.

To make this work I need to add two directives <CopyLocalLockFileAssemblies> and <OutDir>:

 <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AssemblyName>WeblogAddin</AssemblyName>
    <UseWPF>true</UseWPF>

    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <OutDir>$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\Addins\WebLog</OutDir>
  </PropertyGroup>

  </PropertyGroup>

<CopyLocalLockFileAssemblies> is used to specify that the output folder should contain not just the target assembly but all of its dependencies. This turns out to be a bit messy, duplicating the entire dependency tree, but this was the only way I could get this to work without using an explicit post build task (which I'd rather avoid). Instead I plan on cleaning up the add-in as part of the final build script that builds final distribution package.

<OutDir> allows you to specify an output folder where you want the final assembly output to go. I know that sounds silly, but standard output creates the whole netcoreapp3.0 hierarchy which wouldn't work in this case. <OutDir> forces the output into the exact folder you specify without any prefixes.

I'm using various Build constants to force to a folder like this:

<projRoot>\MarkdownMonster\bin\Release\netcoreapp3.0\Addins\Weblog

When I build now I end up with an output folder that looks like this:

On the left is the 3.0 output, and on the right is the full framework output of the old project. Yeah - the right is a lot easier. Just to demonstrate without <CopyLocalLockFileAssemblies> I get just this:

That looks more reasonable, but this is actually useless for output as it doesn't include the required dependencies that are unique to this project. It's lacking the crucial dependent assemblies which are yamldotnet.dll and the CookComputing.XmlRpcV2.dll. Oddly this includes the MarkdownMonster assembly reference including the content files which seems crazy. So that wouldn't work. Using <CopyLocalLockFileAssemblies> at least gives me the dependencies I need so that the addin can load and get my final build files and I can clean up the duplicated packages during final packaging into a distributable.

The other alternative to make the output cleaner here would be to do a dotnet publish of the addin project and then copy just the required assemblies from the publish output (same as the previous screenshot) and copy just the files I need as an extra build step but that requires adding build automation even during development that I'd rather avoid if possible.

With all this done the addin now shows up on the tool bar and runs:

Cool. It works, but again a lot of pain trying to get this to work right.

Excluding Packages and Assembly References

So after a bit more trial and error I found a solution to cleaning up the output folder by explicitly excluding the packages and project references that you I don't want in the output. I have to explicitly specify how to use each package and the external reference:

<ItemGroup>
    <PackageReference Include="MahApps.Metro" version="1.6.5">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Dragablz" version="0.0.3.203">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="FontAwesome.WPF" Version="4.7.0.9">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="HtmlAgilityPack" version="1.11.3">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Westwind.Utilities" version="3.0.26">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>

    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
</ItemGroup>

<ItemGroup>
    <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\MarkdownMonster.exe">
        <Private>false</Private>
    </Reference> 
</ItemGroup>

With these settings I'm telling the project to only use the above NuGet packages for compilation, not for distribution and to make the project reference private. Note that I'm using the raw EXE rather than a project reference here which is less than ideal because it can cause compile ordering errors, but I couldn't find a way to get a <ProjectReference> to not include its output in the output folder. The above works, and it provides a nice clean addin folder.

Note the terrible naming. I would have assume <Private> to be true to not export, but it means the opposite here. Compile means compilation only which sort of makes sense, although there are options for none which would have made a lot more sense.

I have a follow up post that talks more about the output generation options.

Done Updating for now

For now though - I think I'm done upgrading because it's clear that the next step will be a lot of cleanup work - specifically related to the dynamic COM Interop changes. Not sure I'm ready to get into that with .NET Core 3.0 being still a ways away from release.

Is all of this Worth It?

I went through all of this to see what's involved in the process of conversion. The reality is that MM application has a lot of special things going on that are tripping me up. I think most ‘normal’ business applications that are pure .NET code, are likely to have a much easier time in a conversion without running into some of these edge cases like COM Interop and Weird Addin Loading scenarios.

he COM Intersop stuff that drives the editor in MM is a big hurdle to working because - even though all editor operations are abstracted in their own editor class - there's a lot of it and almost all parts of MM interact with that class. In order for me to get MM running I have to deal with the COM interop stuff.

At this point I don't know what the status is exactly of the dynamic COM Interop. In the past Microsoft has mentioned that this will be supported but nothing seems to have changed when I tried this last year with 2.2. I posted a Github issue to get some more information - we'll see what comes back from that.

Not Quick and Easy, but Easier than I tought

I do have to say, even though there's a bit of pain in this process it's not as bad as I thought it would be. I'm especially surprised that I'm able to run full framework assemblies that haven't been moved to .NET Standard or .NET Core which I thought was going to be deal breaker in the short term. But it looks like that is not going to be an issue as you can simply reference those packages. The Windows Compatibility Codepack seems to do a great job of providing the needed compatibility.

I forgot to mention that a few weeks ago I ran the .NET Compatibility Analyzer against MM and found that it was 99% compliant. There were some very small things for obsolete method calls that had to be cleaned up but not much else. All of the warnings were actually in code that's not called by this application. So all in all Microsoft has done an excellent job in making sure that your code that is Windows specific will run in .NET Core which is very nice indeed!

Distribution?

The larger concern I have is what .NET Core 3.0 means to distribution of an application. .NET Core 3.0 won't be a built-in Windows component. One of the nice things about full .NET Framework is that it's just there, pre-installed on most machines. That has its pros and cons as we all know, but from a distribution point of view it is very nice as it's guaranteed that any Windows 7 or newer machine these days at least has .NET 4.61 on it.

With .NET Core you have two choices that's not the case so you have two choices for deploying standalone desktop applications:

  • Use a Shared Runtime that may not be installed
  • Ship your runtime with your app and add a huge foot print

So, neither of these deployment scenarios is very appealing.

To give you an idea of the footprint of a dedicated x64 runtime install:

Currently full framework Markdown Monster is a 15 meg download and most of that size actually comes from Chrome related components (PDF generation). That's a 10th of the size of the dedicated runtime install at minimum. Yikes. From a rapid deploy and update cycle perspective the second option would not be an option for me.

I suspect most people will opt for the shared runtime deployment, but depending on your target audience it'll still be very likely your clients will have to download and install a runtime so your footprint won't be small either. The benefit with shared is that hopefully that will be a one time download for most and only an occasional update.

So most likely shared runtime it is. But for those of you that follow my Twitter Feed know how I've been railing against the proliferation of a million minor .NET Core Runtime versions being installed on your system and clogging up your hard drive. Those runtimes have to be maintained and cleaned up but who knows what app needs what?

Some guidance is needed on this point I think because this is likely to become a real nightmare. The good news is that .NET Core runtimes are backwards compatible so newer versions minor updates work if an exact match can be found. Of course then we are essentially back to potentially breaking runtimes with code that worked on one version but not on a newer one, which was one of the main selling points of .NET Core in the first place. Oh well… can't win 'em all.

What do you get?

Right now I'm not so sure that .NET Core 3.0 makes a ton of sense. At this point performance seems slightly worse (at least in Preview 5 and onward - much worse before) compared to running the full framework version. Startup time of Markdown Monster is 4-5 seconds with this version (Preview 5 in Release mode) compared to a little over 2 seconds with the full framework version.

I can't compare much beyond that because a lot of features in MM currently don't work due to the Interop features. Regular WPF stuff - animations window opening etc. all feels about the same but UI stuff is usually subjective anyway so it's hard to say.

I think ultimately the benefits of .NET Core over the full framework will be runtime fixes and framework improvements, but right now there's very little of that. However, one nice improvement I was able to integrate immediately is the new Folder dialog for the WinForms FolderBrowserDialog which allowed me to remove the (largish) Microsoft.WindowsAPICodePack-Shell library I'd been using for that. It's actually crazy that this feature was never added to full framework proper as the stock FolderOpenBrowserDialog is an abomination of user hostility.

Currently there also doesn't appear to be any real difference in the way you build a WinForms or WPF application. Sure it runs on .NET code but the apps still start the same as before and the code you write is not drastically different - unlike ASP.NET which actually had a lot of huge benefits by re-writing the core framework. But there are none of the benefits like the Startup pipeline and dependency injection etc. that you typically associate with .NET Core applications. I suspect that will come later, and it probably won't apply to legacy applications, but it's one of those facets that are currently hidden away.

So I'm on the fence. For now I think we can treat .NET Core 3.0 as a novelty. But farther down the line we hopefully see some improvements and fixes of old problems and bottlenecks in these old stodgey desktop frameworks as the open source development model fixes old bugs and perhaps even drives some new features.

It'll be interesting to see how these changes will affect Windows desktop development which has been stagnant for so long. One can hope…

If you want to see the converted, dual-targeted project you can check it out on Github.

Resources

this post created and published with Markdown Monster
Posted in .NET  WPF  Windows  

The Voices of Reason


 

MV10
April 25, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

Somewhere in all the WPF issues on github, they're tracking performance as a future problem to tackle. Right now their focus is just making everything work right.

Regarding deployment size, keep in mind there is ongoing work to add tree-shaking (as well as single-file deployments). Although I'm not sure if that would conflict with your plugin approach. (Again, there is a discussion of that scenario somewhere out there on github.)


Vatsan Madhavan
April 25, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

Hello Rick,

This is a great post - thanks for trying to use WPF on .NET Core 3.0. I love Markdown Monster, BTW!

One suggestion re your port at NET_Core_30_First_Shot/app.manifest, you'll likely have to set <dpiAware>true</dpiAware> or <dpiAwareness>system</dpiAwareness> (see doc) as a workaround for dotnet/wpf Issue #334.

Please continue filing issues on GitHub so that we'd know what to address.

If you get a test version of Markdown Monster working on .NET Core 3.0 (even with some functionality reduced), I'd love to give it a try!


Rick Strahl
April 25, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

@Vatsan - thanks for the feedback.

As to DPI Awareness, I can't remember now but I'm doing some stuff internal with API calls to set the DPI awareness due to some dual monitor issues I was having related to the screen capture. As it is that never worked reliably (I can capture properly on the main screen, but on the second screen the window detection often times is off). I remember that the default settings in the manifest didn't work and the workaround did.


Alberto Bar Noy
April 25, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

Hello Rick,

Great article! I do have a question however. On the "Was it all worth it part", why should one keep maintaining a WPF/WinForms app on .NET Core 3.x? Why not plan for a rewrite and package with electron like vscode was done? I mean what is the point apart from discarding full framework all together. This way one can enlarge one's audience and go beyond windows.


Rick Strahl
April 26, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

@Alberto - everytime I post something about WPF this comes up not surprisingly.

If you want to build a commercial quality Electron app you have a lot of work ahead of you building a professional desktop UI. Although there are lots of Web frameworks out there to help with UI design for Web sites, I haven't seen anything that is specifically tailored to desktop UIs. Take a close look at VS Code and realize that all that UI - the custom tabs, the menus, the tree - all that stuff is hand built. if you build your own desktop app with Electron you'll end up building a lot of UI components to make that work well. VS Code is OSS so you could potentially lift a lot of that, but it's all pretty tightly coupled to VS Code. You can build a more Web like experience, but frankly that doesn't look or feel quite write on desktop apps often times.

Personally I rather build a platform specific tool which works great on the specific platform and take advantage of the more powerful UI framework (WPF) and the core desktop features that every application needs.

For other things that aren't commercial tools that might be a total different story of course. For internal apps or other less high profile then Web UIs are probably perfectly adequate, but then of course you should ask whether you need a desktop app at all and just build a Web site.


Vatsan Madhavan
April 26, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

I just heard from a colleague a day ago that they finished the work to enable crossgen for WindowsDesktop.App and that the updated bits with preview6 branding are already flowing into github.com/dotnet/core-sdk. Crossgen happens after the bits leave dotnet/wpf, btw. I haven’t had a chance to play with these yet, but I’d expect significant startup time improvements.

I think you may even be able to try it out at an app level today, though it’s likely still experimental


Daniel
April 26, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

hi Rick, have you tried different Tiered Compilation settings to improve startup time in .NET Core 3.0 ?


Scott G.
April 27, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

Hi Rick, I've been following your WPF/MM posts since you started. I maintain a large WPF app at work and use the embedded Web control - and of all the things that have caused me angst over the years it's that thing - but I just can't live without it.

Do you think it's time to revisit using Cef for Core 3?


Rick Strahl
April 27, 2019

# re: First Steps in porting Markdown Monster WPF App to .NET Core 3.0

@Scott - I was considering CefSharp for a while. I have a few examples going with it but for WPF CefSharp is problematic. They do render then draw an image which is in effect very slow for the WPF control. You can use the WinForms control and that's faster but a bit ugly to hook in and I'm not sure about stability. The other issue is that adding Chromium in any for this way will add 20 meg payload to the app which also sucks. IOW - it would double the size of MM just for the new browser control.

The other issue I had been struggling with was that I code to the COM control specifically and changing over would be a fairly big job. Over the last few days though I took a bit of time to isolate all the COM interop calls into a separate class and with that I should be able to more easily port going forward. Basically any interop call is explicitly wrapped with an intermediate method. I switched this interface to use raw Reflection so in theory this will get around the problem I describe in the article.

Re: CefSharp - at this point I'm pretty sure I have to ditch the IE control because there are rendering issue for some support controls. I'm going to wait however and see how the Edge Chromium integration works out. Supposedly there will be an updated WebView control that will be accessible from WPF. The big question will be how well (if at all) you can interop with that control. I expect that interface to be similar to the Edge interface of the WebView which unfortunately was pretty terrible (plus no support for loading disk resources natively as a URL - you needed to write an explicit file handler which seemed really dumb given the uses cases that an embedded Web Browser is meant to address).

So yeah - it keeps me awake at night too 😃 What I heard at the Summit this year from talking with some of the Edge folks sounded pretty promising though, although they often promise a lot and don't follow through. We'll see.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024