Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

services.Configure<SomeDictionaryOptions>(configuration) failure at PublishAot #75869

Closed
xljiulang opened this issue Sep 20, 2022 · 7 comments
Closed

Comments

@xljiulang
Copy link

Description

II have an TestOptions type that works well under JIT, but the StringUserMap property is not work after PublishAOT, the code is as follows

public class TestOptions
{
    /// <summary>
    /// binding success
    /// </summary>
    public User[] UserArray { get; set; } = Array.Empty<User>();

    /// <summary>
    /// binding success
    /// </summary>
    public Dictionary<string, int> StringIntMap { get; set; } = new Dictionary<string, int>();

    /// <summary>
    /// binding failure
    /// </summary>
    public Dictionary<string, User> StringUserMap { get; set; } = new Dictionary<string, User>();

    public override string ToString()
    {
        return JsonSerializer.Serialize(this, TestOptionsContext.Default.TestOptions);
    }
}

public class User
{
    public string Name { get; set; } = string.Empty;

    public int Age { get; set; }
}

[JsonSerializable(typeof(TestOptions))]
public partial class TestOptionsContext : JsonSerializerContext
{
}

Reproduction Steps

binding testOptions to configuration

services.AddOptions<TestOptions>().Bind(configuration.GetSection("Test"));
----------
var testOptions = serviceProvider.GetRequiredService<IOptions<TestOptions>>();
logger.LogInformation(testOptions.Value.ToString());

publish as jit print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
		"map_user_0": {
			"Name": "userMap1",
			"Age": 0
		},
		"map_user_1": {
			"Name": "userMap2",
			"Age": 0
		}
	}
}

publish aot print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
	}
}

Expected behavior

StringUserMap property binding work at PublishAOT

Actual behavior

StringUserMap property binding is not work at PublishAOT

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 20, 2022
@ghost
Copy link

ghost commented Sep 20, 2022

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

II have an TestOptions type that works well under JIT, but the StringUserMap property is not work after PublishAOT, the code is as follows

public class TestOptions
{
    /// <summary>
    /// binding success
    /// </summary>
    public User[] UserArray { get; set; } = Array.Empty<User>();

    /// <summary>
    /// binding success
    /// </summary>
    public Dictionary<string, int> StringIntMap { get; set; } = new Dictionary<string, int>();

    /// <summary>
    /// binding failure
    /// </summary>
    public Dictionary<string, User> StringUserMap { get; set; } = new Dictionary<string, User>();

    public override string ToString()
    {
        return JsonSerializer.Serialize(this, TestOptionsContext.Default.TestOptions);
    }
}

public class User
{
    public string Name { get; set; } = string.Empty;

    public int Age { get; set; }
}

[JsonSerializable(typeof(TestOptions))]
public partial class TestOptionsContext : JsonSerializerContext
{
}

Reproduction Steps

binding testOptions to configuration

services.AddOptions<TestOptions>().Bind(configuration.GetSection("Test"));
----------
var testOptions = serviceProvider.GetRequiredService<IOptions<TestOptions>>();
logger.LogInformation(testOptions.Value.ToString());

publish as jit print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
		"map_user_0": {
			"Name": "userMap1",
			"Age": 0
		},
		"map_user_1": {
			"Name": "userMap2",
			"Age": 0
		}
	}
}

publish aot print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
	}
}

Expected behavior

StringUserMap property binding work at PublishAOT

Actual behavior

StringUserMap property binding is not work at PublishAOT

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: xljiulang
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@davidfowl
Copy link
Member

Configuration binding isn't trimmer/AOT friendly. Don't you get warnings when you publish AOT?

@xljiulang
Copy link
Author

There was a warning, but I don't know any way to solve the problem.

@ghost
Copy link

ghost commented Sep 20, 2022

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

II have an TestOptions type that works well under JIT, but the StringUserMap property is not work after PublishAOT, the code is as follows

public class TestOptions
{
    /// <summary>
    /// binding success
    /// </summary>
    public User[] UserArray { get; set; } = Array.Empty<User>();

    /// <summary>
    /// binding success
    /// </summary>
    public Dictionary<string, int> StringIntMap { get; set; } = new Dictionary<string, int>();

    /// <summary>
    /// binding failure
    /// </summary>
    public Dictionary<string, User> StringUserMap { get; set; } = new Dictionary<string, User>();

    public override string ToString()
    {
        return JsonSerializer.Serialize(this, TestOptionsContext.Default.TestOptions);
    }
}

public class User
{
    public string Name { get; set; } = string.Empty;

    public int Age { get; set; }
}

[JsonSerializable(typeof(TestOptions))]
public partial class TestOptionsContext : JsonSerializerContext
{
}

Reproduction Steps

binding testOptions to configuration

services.AddOptions<TestOptions>().Bind(configuration.GetSection("Test"));
----------
var testOptions = serviceProvider.GetRequiredService<IOptions<TestOptions>>();
logger.LogInformation(testOptions.Value.ToString());

publish as jit print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
		"map_user_0": {
			"Name": "userMap1",
			"Age": 0
		},
		"map_user_1": {
			"Name": "userMap2",
			"Age": 0
		}
	}
}

publish aot print

{
	"UserArray": [{
		"Name": "userArray1",
		"Age": 0
	}, {
		"Name": "userArray2",
		"Age": 0
	}],
	"StringIntMap": {
		"map_int_0": 0,
		"map_int_1": 1
	},
	"StringUserMap": {
	}
}

Expected behavior

StringUserMap property binding work at PublishAOT

Actual behavior

StringUserMap property binding is not work at PublishAOT

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: xljiulang
Assignees: -
Labels:

untriaged, area-Extensions-Configuration

Milestone: -

@eiriktsarpalis
Copy link
Member

There was a warning,

Some of the warnings might be emanating from the fact you're calling into a JsonSerializerOptions overload. Even though this is still using the source generator under wraps, you might want to consider calling into the JsonTypeInfo<T>/JsonSerializerContext overloads to at least eliminate that class of warnings.

You might also want to consider sharing a minimal reproduction of the issue you are experiencing (including inputs, expected behavior vs actual behavior -- I suppose the JSON you shared is serialization output for some input in the two configuration?). A minimal repro might help us provide you with more concrete recommendations.

@davidfowl
Copy link
Member

There may not be a workaround but please do share a runnable code sample (with the configuration).

@eerhardt
Copy link
Member

Using 7.0-rc1, the behavior I get is:

  1. A bunch of warnings including:
C:\Users\eerhardt\source\repos\WorkerService12\WorkerService12\Program.cs(11,1,11,90): warning IL3050: Using member 'Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions.Bind<TOptions>(OptionsBuilder<TOptions>, IConfiguration)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Binding strongly typed objects to configuration values may require generating dynamic code at runtime.
C:\Users\eerhardt\source\repos\WorkerService12\WorkerService12\Program.cs(11,1,11,90): warning IL2026: Using member 'Microsoft.Extensions.DependencyInjection.OptionsBuilderConfigurationExtensions.Bind<TOptions>(OptionsBuilder<TOptions>, IConfiguration)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.
  1. When running the app, both the User properties are not bound:
{"UserArray":[],"StringIntMap":{"one":1,"two":2},"StringUserMap":{}}

The real solution here is:

  1. Either don't use the Bind method in NativeAOT because it is not NativeAOT compatible.
  2. Wait until Developers can safely trim apps which need Configuration Binder #44493 is implemented which will create a source generator for the Configuration Binder technology.

However, in the meantime, if you really want to get this to work, add the following attributes to your Main method (or whatever method is calling services.AddOptions<TestOptions>().Bind(configuration.GetSection("Test"));):

    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(User))]
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary<string, User>))]

These attributes manually preserve all the members on the listed types. The allows the binding code to work, since the members are not trimmed.

Since the current behavior is by design (you are getting warnings saying that the Bind method doesn't work with trimming and NativeAOT), and we have #44493 tracking the real solution here, I'm going to close this issue.

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Sep 20, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Oct 20, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants