34

I have just started playing around with C# 9 and .NET 5.0, specifically the new record construct. I find I have a lot of excellent use cases for the shorthand syntax of the record types.

One of the use cases I've considered was using a record for the dto in IOptions<>, instead of regular classes, for ASP.NET Core applications. These option classes are usually quite plain so I thought it would be a perfect fit, but it seems I cannot get it to work easily, since configuring the application using IOptions<> requires the object to have a parameterless constructor.

public record MyOptions(string OptionA, int OptionB);

public class Startup
{
    public void ConfigureServices(IServiceCollection services) {
        services.Configure<MyOptions>(Configuration.GetSection(nameof(MyOptions)));
    }
...
}

public class MyController : Controller 
{
    private readonly MyOptions _options;
    public MyController(IOptions<MyOptions> options) {
        _options = options.Value;  // This throws an exception at runtime
    }
}

The example above throws the following exception when attempting to access the IOption<>.Value property:

System.MissingMethodException: 'No parameterless constructor defined for type 'AcmeSolution.MyOptions'.'

Is there any way to configure the IOptions configuration system to deserialize the options using the record's constructor instead of requiring a parameterless constructor?

I could use the longhand syntax for the records, but then there's really no benefit over using a class.

Nikolaj Dam Larsen
  • 5,455
  • 4
  • 32
  • 45

3 Answers3

20

Is there any way to configure the IOptions configuration system to deserialize the options using the record's constructor instead of requiring a parameterless constructor?

No, in general ASP.Net Core uses a lot of run-time type instancing, which requires constructor calls that are known beforehand, and in this case it requires a constructor with no arguments.

You can however make your record class have a parameter-less constructor:

public record MyOptions(string OptionA, int OptionB)
{
    public MyOptions(): this(default, default) {}
}

Is it worth it? Eh. Up to you. It's no worse than a regular class, performance-wise, so go with whatever you find clearer!

Edit: alternatively, you should be able to use this form:

public record MyOptions(string OptionA = default, int OptionB = default);
Blindy
  • 65,249
  • 10
  • 91
  • 131
  • I don't think you're going to get very far with this though, because `record` properties are defined as `{get; init;}`, ie they're not settable. It depends how ASP.Net Core writes to these things and how far they can force them to be set through reflection, but at least conceptually records are immutable. – Blindy Nov 20 '20 at 16:34
  • 1
    I am not able to make to short form working, can you confirm it is working ? – draco951 Dec 08 '20 at 15:53
  • You mean the alternative? No, but it should logically. The first one works as advertised though, right? – Blindy Dec 08 '20 at 16:02
  • I'm running into the same issue. The Microsoft documentation uses a record in their very first example: https://learn.microsoft.com/en-us/dotnet/core/extensions/options – Joshua Flanagan Jan 13 '21 at 02:31
  • @Blindy `init` is only relevant at compile-time. At runtime, it's just a normal setter, probably decorated with an attribute that lets the *compiler* know it's an `init`-only property. I don't think it's even possible for ASP.Net Core to be *un*able to set `init`-only properties. – Arshia001 Apr 30 '21 at 18:34
  • 2
    The `public record MyOptions(string OptionA = default, int OptionB = default);` seems not working. – Will Huang Feb 12 '22 at 14:08
13

C# 10

I know the question specifically references C# 9, but if you're living in the present, which I suspect most of you are, this works as expected with C# 10 (ASP.NET Core 6):

public record MyOptions
{
  public string MyProperty { get; init; }
}

// ...

builder.Services.Configure<MyOptions>(builder.Configuration);

Brandon Gano
  • 6,430
  • 1
  • 25
  • 25
  • 2
    It works indeed. I just wonder if this is documented somewhere since I cannot find anything mentioning it's possible. Probably it works just "by accident" since `init` setter is compiled similarly to "normal" setter just with some [additional metadata](https://sharplab.io/#v2:C4LglgNgNAJiDUAfAsAKAG4EMBOACA9rgLy4B2ApgO64DyADsGPqQM64Deau3uAgnWACq2CMVwAiAK4jxULj35gA0uQCeY8QGs1snrjQBfANxp8AOkXCIJKSICM4k6gD0z84pXqb21QCZHaGgAAgDMuEG+tAxMrGicqHqh4XYADHwCVhy4AObkwEa4LHkFBhoBCTxJQanpympZufm4YKRgTaU25QZAA=) for compiler to prevent setting it after construction. Which is fine I guess... – Mariusz Pawelski May 18 '22 at 14:19
  • what's the advantage of this over a class? I suspect I can just replace the `record` keyword above with `class` and nothing changes? – Tolu Jun 25 '23 at 20:09
  • @Tolu, the advantages and disadvantages of `class`, `struct`, and `record` will depend on your use case. You might find the accepted answer on this SO question useful: [When to use record vs class vs struct](https://stackoverflow.com/questions/64816714/when-to-use-record-vs-class-vs-struct). – Brandon Gano Jun 26 '23 at 21:08
-4

Add parameter less constructor

public MyOptions() {}

sadiq rashid
  • 468
  • 5
  • 8