Fix error because it is missing a public instance constructor

fix error because missing public instance constructor
Home » Technology » .Net » Fix error because it is missing a public instance constructor

Table of contents

We recently analyzed the correct way to convert the appsettings.json to an object in .NET. Well, in this article we’re going to look at how to fix an error resulting from using Bind or Get, although, as we’ll see below, the error can also be encountered in other scenarios.

Example codebase

The example code to fix the error because it is missing a public instance constructor can be found in the Github repository for this article, although we’ll list the main elements below.

First, we have a console application written in C# where we have installed the following packages to convert the appsettings.json object to a C# object:

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.7" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.7" />
</ItemGroup>

Then we have the following code inside the Program.cs class that only reads the json and converts it to an object through the Get and Bind alternatives:

using Microsoft.Extensions.Configuration;
using MissingAPublicInstanceConstructor;

internal class Program
{
    private static void Main(string[] args)
    {
        var build = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .Build();

        // Alternative 1
        var config1 = build.Get<Config>();

        // Alternative 2
        var config2 = new Config();
        build.Bind(config2);

        // Output
        Console.WriteLine($"config1?.PropString: '{config1?.PropString}'");
        Console.WriteLine($"config1?.PropClass1.PropString: '{config1?.PropClass1.PropString}'");

        Console.WriteLine($"config2.PropString: '{config2.PropString}'");
        Console.WriteLine($"config2.PropClass1.PropString: '{config2.PropClass1.PropString}'");
    }
}

Next we have the classes that represent our configuration, they are completely normal classes with simple and complex properties:

namespace MissingAPublicInstanceConstructor
{
    public class Config
    {
        public Config() { }
        public string PropString { get; set; }
        public int PropInt { get; set; }
        public Class1 PropClass1 { get; set; }
        public Class2 PropClass2 { get; set; }
    }
    public class Class1
    {
        public Class1() { }
        public string PropString { get; set; }
        public int PropInt { get; set; }
    }
    public class Class2
    {
        public Class2() { }
        public string PropString { get; set; }
        public int PropInt { get; set; }
    }
}

And finally we have the appsettings.json file that we will convert to an object of the Config.cs class:

{
  "PropString": "Text example",
  "PropInt": 123,
  "PropClass1": {
    "PropString": "Text Class1",
    "PropInt": 456
  },
  "PropClass2": {
    "PropString": "Text Class2",
    "PropInt": 789
  }
}

Full error description

The full error when running the application is as follows:

Unhandled exception. System.InvalidOperationException: Cannot create instance of type 'Project.Class' because it is missing a public instance constructor.
   at Microsoft.Extensions.Configuration.ConfigurationBinder.CreateInstance(Type , IConfiguration, BinderOptions, ParameterInfo[]&)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindInstance(Type , BindingPoint, IConfiguration, BinderOptions, Boolean)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindProperty(PropertyInfo, Object, IConfiguration, BinderOptions)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindProperties(Object, IConfiguration, BinderOptions, ParameterInfo[])
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindInstance(Type , BindingPoint, IConfiguration, BinderOptions, Boolean)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.Get(IConfiguration, Type , Action`1 )
   at Microsoft.Extensions.Configuration.ConfigurationBinder.Get[T](IConfiguration , Action`1 )
   at Microsoft.Extensions.Configuration.ConfigurationBinder.Get[T](IConfiguration )
   at Program.Main(String[] args) in C:\Users\Daniel\source\repos\CodeArco\Project\Solution\Program.cs:line 8

And the part that really interests us are the following two lines:

  • Cannot create instance of type ‘Project.Class’ because it is missing a public instance constructor, this line indicates that an instance of a project class cannot be created because it doesn’t have a public constructor and no parameters. As we’ll see later, this isn’t entirely true.
  • at Microsoft.Extensions.Configuration.ConfigurationBinder.Get, this line indicates that the error occurs in the Get method we use to convert the appsettings.json to an object.

When and why the error occurs

The most interesting part of this error is that the description is erroneous or confusing. That is, the error does not occur when a public constructor is missing, the error actually occurs when the following two points occur simultaneously:

  • Trim unused code is being used when publishing. If you don’t enable this option, the error will not appear.
  • Reflection is being used, directly or indirectly, somewhere in the code. In this case the method that uses reflection internally is Get to convert the appsettings.json to the object represented by the Config.cs class.

Given the above we can conclude that the error occurs because:

  • An instance of an object (in our case Config.cs) is being created through reflection using the public, parameterless constructor new().
  • Since this constructor isn’t referenced anywhere in the project the trim unused code removes it and, consequently, when it’s invoked by reflection, it throws a runtime error because it can’t be found.

It’s also interesting to know that by default C# creates public, parameterless constructors whenever your class doesn’t have other constructors. This means that it’s not always necessary to create them; we’ll look at this in more depth later.

It’s also important to keep in mind that this error can occur in other scenarios where reflection is used, not just in binding done through the Get method.

Not recommended solutions to avoid

Before proceeding to analyze the solution to the “because it is missing a public instance constructor” error, let’s see what the not recommended solutions are. These will not fix your error or will do so in a very questionable way:

  • Creating empty constructors without doing anything else in all affected classes. This will not fix the error. In our case the modification would consist of creating a new() in all the configuration classes.
    The problem does not occur because a new() is missing, the problem occurs because trim unused code is eliminating the new() since it is not being referenced anywhere in the code.
    As we mentioned before, C# creates parameterless, public constructors by default in classes that do not have any other constructors. Therefore, creating empty, public constructors is only necessary if you have other constructors in the class.
  • Referencing empty constructors in your code works and will fix the error but it’s a very messy solution that doesn’t follow clean code practices.
    In our case, if we use the following code in Program.cs the error will disappear but, as you can see, it looks pretty ugly:
      var c = new Config();
      var c1 = new Class1();
      var c2 = new Class2();
  • Disabling trim unused code will also fix the error but it’s a rather drastic solution that you should avoid. Below, we’ll see that the solution is to partially disable trim unused code. This way we’ll be able to fix the error while maintaining the benefits of trim unused code.
  • Using Bind instead of Get will not fix the problem, it will only camouflage it.
    As we discussed in the article converting appsettings.json to a C# object the Bind method is less restrictive or error-tolerant than Get. If you test it with trim unused code you’ll see that the error “because it is missing a public instance constructor” won’t appear, but a new one will appear when you try to read the configuration since it will be empty or initialized to the default values:
      Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
        at Program.Main(String[]) in C:\Users\Daniel\source\repos\CodeArco\MissingAPublicInstanceConstructor\MissingAPublicInstanceConstructor\Program.cs:line 24

Recommended solution with TrimmerRoot.xml

The first step to fix the error is to create public and parameterless constructors in all classes but… why should we create them if we previously mentioned that it’s not necessary? Well, because it’s only necessary to create them in classes that have other constructors, since C# only creates them automatically if there are no constructors. In other words, to avoid forgetting them in any class, we’re going to create them in all of them.

The second step is to create the TrimmerRoot.xml file in the root of your project with all the classes you don’t want to be code-trimmed:

<linker>
    <assembly fullname="MissingAPublicInstanceConstructor">
        <type fullname="MissingAPublicInstanceConstructor.Config" preserve="all" />
        <type fullname="MissingAPublicInstanceConstructor.Class1" preserve="all" />
        <type fullname="MissingAPublicInstanceConstructor.Class2" preserve="all" />
    </assembly>
</linker>

Note that MissingAPublicInstanceConstructor is the project name and that we have one line for each class used in the configuration.

The third step is to update the project’s .csproj and reference the previous file through the TrimmerRootDescriptor node:

<ItemGroup>
    <TrimmerRootDescriptor Include="TrimmerRoot.xml" />
</ItemGroup>

Finally, clean, recompile, and publish the application with trim unused code and check that the “because it is missing a public instance constructor” error has been resolved: