Search code examples
c#interop.net-6.0binaryformatter

BinaryFormatter used in Interop assemblies throws UnsupportedException


Microsoft discourages the use of BinaryFormatter because it poses security problems. See: BinaryFormatter Obsoletion Strategy.

I have a .NET 6.0 WinForms code which uses the Microsoft.Office.Interop.Access.Dao interop assembly. I need it to insert an image into the Data field the Microsoft Access' system table MSysResources. This field has an Attachment Data Type. This is a multi-valued field. Using DAO is the only way of writing to this field. My (somewhat shortened) code goes like this (note: this code did work before I migrated to .NET 6.0):

using Microsoft.Office.Interop.Access.Dao;

namespace CySoft.RibbonPro.Services;

public class AccessImageResourceLoader : IAccessImageResourceLoader
{
    public void UpdateImages(string accdbFile, IEnumerable<KeyValuePair<string, Image>> images)
    {
        var dbe = new DBEngine(); // <====== This line throws the UnsupportedException =====

        Database db = dbe.OpenDatabase(accdbFile);
        Recordset rs = rs = db.OpenRecordset("SELECT * FROM MSysResources WHERE 0=1", R
            ecordsetTypeEnum.dbOpenDynaset, 0, LockTypeEnum.dbOptimistic);
        rs.AddNew();
        rs.Fields["Type"].Value = "img";
        rs.Fields["Name"].Value = name;
        rs.Fields["Extension"].Value = ext;

        Recordset2 rsAttachment = (Recordset2)rs.Fields["Data"].Value;
        rsAttachment.AddNew();
        Field2 dataField = (Field2)rsAttachment.Fields["FileData"];
        dataField.LoadFromFile(imageInfo.Key);
        rsAttachment.Update();

        rs.Update();
        rs.Close();
        db.Close();
    }
}

The details are for illustration only. The first code line creating the DBEngine throws the exception:

BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.

The call stack is:

at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.DeserializeUsingBinaryFormatter(StreamWrapper wrappedStream, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Deserialize(Stream o, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type, Assembly resourceAssembly)
at System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Type type, Boolean& isDesignTime, String& key)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Internal.Runtime.InteropServices.LicenseInteropProxy.GetCurrentContextInfo(RuntimeTypeHandle rth, Boolean& isDesignTime, IntPtr& bstrKey)
at CySoft.RibbonPro.Services.AccessImageResourceLoader.UpdateImages(String accdbFile, IEnumerable`1 images) in C:\Users\Oli\Documents\Proj\CySoft\CySoft.RibbonPro\CySoft.RibbonPro\Services\AccessImageResourceLoader.cs:line 21

Where AccessImageResourceLoader.cs:line 21 is var dbe = new DBEngine();

Microsoft wants people to use another type of serialization like JSON or XML. This is not an option in this case, because I am not using it directly. It is Microsoft's own COM library which uses it.

Question:
How can I insert or update a record using Access' Attachment data type in .NET 6+?

My Attempts

  • I have tried to do it with System.Data.OleDb. I can read the Attachment with OleDb. But any attempt to write to this field using OleDb throws an exception.

  • Setting the <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> tag in the project file does not help.

  • Settings the same configuration property in runtimeConfig.template.json does not help either.


I know that I could solve the problem by using Access automtation via an interop assembly. But it has the disadvantage to open the Microsoft Access application. Inserting the image through a database connection is much more elegant and did work before I migrated to .NET 6.0.


Solution

  • You can see here there is a switch to allow the binary serializer for the licenses file

    https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContextSerializer.cs#L20

    which is being read by the GetSavedLicenseKey method here

    https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContext.cs#L84-L89

    You can set this switch earlier on before initializing the DBEngine object by calling this:

    AppContext.SetSwitch("System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization", true);
    

    I haven't tried it myself but it should work.

    This runtime switch might also be settable in the csproj file as described here

    https://github.com/dotnet/runtime/blob/main/docs/workflow/trimming/feature-switches.md

    Any feature-switch which defines property can be set in csproj file or on the command line as any other MSBuild property. Those without predefined property name the value can be set with following XML tag in csproj file.

    <RuntimeHostConfigurationOption Include="<AppContext-Setting>"
                                    Value="false"
                                    Trim="true" />
    

    Final words: There is even more detail on upgrading to .NET 6.0 at this blog which has another method for this flag explained.

    https://www.textcontrol.com/blog/2021/12/21/migrate-a-windows-forms-desktop-application-to-dotnet-6/?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral