How to create or use ready Shims for porting from .net framework to .net core / standard?

Viewed 1704

How to create or use ready Shims for .net framework 4.6.1 elements to port them (from .net framework 4.6.1) to .net core 2.0 / .net standard 2.0?


Some classes of interest:, it would be nice to have shims for classes like:

System.Windows.Threading.Dispatcher

or

System.ComponentModel.ItemPropertyInfo.Descriptor

even

System.Windows.Controls.MenuItem

and many more...


Context:

The application (the code) is not 100% well organized. Business logic is not 100% separated from UI logic. The answer "do refactoring first" is definitely a good answer. But in my case things are not 100% how they should ideally be.


Approximate example, a try to do it mannually:

System.Windows.Threading.Dispatcher is not implemented in Core 2.0.

One could try to add:

public enum DispatcherShimPriority
{
    Background
    //...
}

public interface DispaicherShim
{
    void Invoke(Action action, DispatcherShimPriority prio);
    void BeginInvoke(Action action, DispatcherShimPriority, prio);
}

Followed by two implementations of this interface:

public class DispatcherCore: DispaicherShim;

and

public class DispatcherFramework: DispaicherShim;

Followed by a a class (let's call it Shims) in a multitargeted project:

public static DispaicherShim CreateDispatcher()
{
#if NETCOREAPP2_0
    return new DispatcherCore();
#else
    return new DispatcherFramework();
#endif       
}

The result is the shim, which could be used in different APIs.

Is this a correct approach?


Actually, creating such shims requires much routine work. I have a feeling that this work is not necessary to be performed. I have a feeling that there is a ready solution for this problem...


I'm aware of Microsoft.Windows.Compatibility package. The question is rather related to porting when WPF is involved with many wpf-specific elements. Those elements are not in Microsoft.Windows.Compatibility package, but, unfortunately, they are used across my assemblies, which are candidates for retargeting to .Net Core 2.0. I mean shimming those classes, which are not in Microsoft.Windows.Compatibility.

Ok, we have this Microsoft.Windows.Compatibility.Shims, but i'm not sure that it is useful in my case; especially after reading the following text:

Microsoft.Windows.Compatibility.Shims: This package provides infrastructure services and shouldn't be referenced directly from your code....


Upd: emphasizing that the final target is .net core 2.0

Upd2: the whole task is to port the major part of a WPF app to .net core (leaving working WPF app) for potential web-client. The major part contains .net framework elements which are not implemented for .net core.

Upd3: Couple of words about complete strategy: The more complete strategy is Shared projects, first approach in this article (#if) . There are 2 major steps in my strategy: one is to gradually port code, starting from base libraries and finnishing at top libraries, But with intense use of stubs and PlatformNotSupportedExceptions. The second step is to move from top libraries to base libraries substituting stubs and exceptions by .net core implementations, On demand (!) - no need to subsitute all stubs and exceptions.

Upd4 We have already split portable tests from non-portable tests (into two libs). It is very important that we run the tests during the porting process.

2 Answers

Moving from Standard .Net to .Net Core is not just an upgrade, you could almost call it a move to a new platform considering how things are put together. Moving to .Net core means learning and creating a new framework where existing code can be copied.

Due to the large differences between .Net core 1, 1.1, 2.0 and 2.1 The migration process form these has changed a lot, so there is no 1 size fits all "shim" and having some kind of wrapper or migration tool would be quickly made obsolete. Work needs to be done to migrate your code.

Some core OS APIs are similar but a lot of framework code has been moved or changed, so chasing like for like swaps might also be difficult. Its really worth doing some R&D to see where the differences are not to mention the use fo 3rd party libraries etc.

Following are at least satisfactory approaches:

Thanks Firda from Czech Republic . This is his answer

1) Generic shim is enough for me (snippets may help)

public abstract class Shim<TImpl>
{
    internal TImpl It { get; }
    protected Shim(TImpl it) { It = it; }
}

EXAMPLE:

public class DispatcherPriorityShim : Shim<
#if NETFULL
    DispatcherPriority
#elif NETCORE
    string
#endif
>
{
    public DispatcherPriorityShim(string it)
#if NETFULL
        : base((DispatcherPriority)Enum.Parse(typeof(DispatcherPriority), it))
#elif NETCORE
        : base(it)
#endif
    { }
}

My sdk-style .csproj file to make clear about NETFULL and NETCORE:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup><TargetFrameworks>netstandard2.0;netcoreapp2.0;net461</TargetFrameworks></PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' OR '$(TargetFramework)' == 'netstandard2.0'">
    <DefineConstants>NETCORE;</DefineConstants></PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net461'">
    <DefineConstants>NETFULL;</DefineConstants></PropertyGroup>
</Project>

1.a) Visual Studio snippets

drv

#if NETFULL

#elif NETCORE

#endif

shimenum

namespace PortabilityLibrary.Shims
{
  public class $enumname$Shim : Shim<
#if NETFULL
    $enumname$
#elif NETCORE
    string
#endif
>
  {
        public $enumname$Shim(string it)
#if NETFULL
        : base(($enumname$)Enum.Parse(typeof($enumname$), it))
#elif NETCORE
          : base(it)
#endif
        { }
  }
}

shimsnip

namespace PortabilityLibrary.Shims
{
  public class $classname$Shim : Shim<
#if NETFULL
    $classname$
#elif NETCORE
    $classname$
//NullObject
#endif
>
  {
        public $classname$Shim()
#if NETFULL
        : base(new $classname$())
#elif NETCORE
        : base(new $classname$())
    //: base(new NullObject())
#endif
        {}
  }
}

shimmeth

        public void $methodname$()
        {
#if NETFULL
        It.$methodname$();
#elif NETCORE
        It.$methodname$();
        //throw new ShimException();
#endif
        }

shimprop - not yet


2) Case when inheritance needed.

public interface IShimOne
{
    void MethodOne();
}
public interface IShimTwo: IShimOne
{
    void MethodTwo();
}
#if NETFULL
class One: RealOne, IShimOne {}
class Two: RealTwo, IShimTwo {}
public static class ShimFactory
{
    public static IShimOne CreateOne() { return new One(); }
    public static IShimTwo CreateTwo() { return new Two(); }
}

2.a) Objects for inheritance

public class WrapperOne
{
    protected IShimOne It { get; }
    protected WrapperOne(IShimOne it) { It = it; }
    public WrapperOne() { It = ShimFactory.CreateOne(); }
    public void MethodOne() { It.MethodOne(); }
}
public class WrapperTwo: WrapperOne
{
    protected new IShimTwo It => (IShimTwo)base.It;
    protected WrapperTwo(IShimTwo it): base(it) {}
    public WrapperTwo(): base(ShimFactory.CreateTwo()) {}
    public void MethodTwo() { It.MethodTwo(); }

3) Ready "counterparts" for GUI controls (Eto.Forms)

(actually, Eto.Forms has wider application - they are the shims)

This framework can be used to build applications that run across multiple platforms using their native toolkit, with an easy to use API. This will make your applications look and work as a native application on all platforms, using a single UI codebase...

//Not fully implemented, just showing the idea:

#if NETFULL
using System.Windows.Controls;
#elif NETCORE
using Eto.Forms;
#endif

namespace PortabilityLibrary.Shims
{
    public class MenuItemShim : Shim<
#if NETFULL
    MenuItem
#elif NETCORE
    MenuItem
#endif
    >
    {
        public MenuItemShim(EventHandler<EventArgs> dlg)
#if NETFULL
        : base(new MenuItem(/*not implemented*/))
#elif NETCORE
        : base(new ButtonMenuItem(dlg))
#endif
        { }
    }
}
Related