.NET 6升级至8启动报错:AugmentedRealityController实例创建失败
问题描述
将Web应用目标框架从.NET 6升级到.NET 8,且所有Microsoft包更新至8.0.4版本后,启动时抛出以下异常:
Application startup exception System.InvalidOperationException: The configuration is invalid. Creating the instance for type AugmentedRealityController failed. Property set method not found. ---> SimpleInjector.ActivationException: Property set method not found. ---> System.ArgumentException: Property set method not found. at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) at evoTrace.Web.Api.Security.ValidationParametersBuilder.<get_ValidationParametersOptionsMapperAction>b__8_0(TokenValidationParameters parameters) in C:\Projekte\evoTraceGS\src\Core\Web\evoTrace.Web.Api\Security\ValidationParametersBuilder.cs:line 52 at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options) at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name) at Microsoft.Extensions.Options.UnnamedOptionsManager`1.get_Value() at evoTrace.Core.Security.JwtValidator..ctor(IOptions`1 validationParameters) in C:\Projekte\evoTraceGS\src\Core\Core\evoTrace.Core.Security\JwtValidator.cs:line 21 at lambda_method47(Closure) at SimpleInjector.Scope.CreateAndCacheInstanceInternal(ScopedRegistration registration, Dictionary`2 cache) at SimpleInjector.Scope.GetInstanceInternal(ScopedRegistration registration) at lambda_method48(Closure) at SimpleInjector.Scope.CreateAndCacheInstanceInternal(ScopedRegistration registration, Dictionary`2 cache) at SimpleInjector.Scope.GetInstanceInternal(ScopedRegistration registration) at lambda_method50(Closure) at SimpleInjector.Scope.CreateAndCacheInstanceInternal(ScopedRegistration registration, Dictionary`2 cache) at SimpleInjector.Scope.GetInstanceInternal(ScopedRegistration registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration registration, Scope scope) at lambda_method52(Closure) at SimpleInjector.Scope.CreateAndCacheInstanceInternal(ScopedRegistration registration, Dictionary`2 cache) at SimpleInjector.Scope.GetInstanceInternal(ScopedRegistration registration) at lambda_method58(Closure) at SimpleInjector.Scope.CreateAndCacheInstanceInternal(ScopedRegistration registration, Dictionary`2 cache) at SimpleInjector.Scope.GetInstanceInternal(ScopedRegistration registration) at lambda_method244(Closure) at SimpleInjector.InstanceProducer.BuildAndReplaceInstanceCreatorAndCreateFirstInstance() at SimpleInjector.InstanceProducer.GetInstance() --- End of inner exception stack trace --- at SimpleInjector.InstanceProducer.GetInstance() at SimpleInjector.InstanceProducer.VerifyInstanceCreation() --- End of inner exception stack trace --- at SimpleInjector.InstanceProducer.VerifyInstanceCreation() at SimpleInjector.Container.VerifyInstanceCreation(InstanceProducer[] producersToVerify, Scope verificationScope) at SimpleInjector.Container.VerifyThatAllRootObjectsCanBeCreated(Scope verificationScope) at SimpleInjector.Container.VerifyInternal(Boolean suppressLifestyleMismatchVerification) at SimpleInjector.Container.Verify(VerificationOption option) at SimpleInjector.Container.Verify() at evoTrace.Web.Api.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) in C:\Projekte\evoTraceGS\src\Core\Web\evoTrace.Web.Api\Startup.cs:line 212 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder) at SimpleInjector.Integration.AspNetCore.RequestScopingStartupFilter.<>c__DisplayClass3_0.<Configure>b__0(IApplicationBuilder builder) at SimpleInjector.Integration.AspNetCore.RequestScopingStartupFilter.<>c__DisplayClass3_0.<Configure>b__0(IApplicationBuilder builder) at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
困惑点:应用完全不涉及AugmentedRealityController,但报错却指向该控制器。
报错触发代码位于ValidationParametersBuilder类:
internal class ValidationParametersBuilder { internal ValidationParametersBuilder() { var secretKey = ConfigReader.ReadStringValue(EnvVars.JWT_SECRET_KEY); var issuer = ConfigReader.ReadStringValue(EnvVars.JWT_ISSUER); var audience = ConfigReader.ReadStringValue(EnvVars.JWT_AUDIENCE); var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)); SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); ValidationParameters = new TokenValidationParameters { ValidIssuer = issuer, ValidateIssuer = true, ValidAudience = audience, ValidateAudience = true, ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, RequireExpirationTime = false, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; } public SigningCredentials SigningCredentials { get; } public TokenValidationParameters ValidationParameters { get; } public Action<TokenValidationParameters> ValidationParametersOptionsMapperAction => (parameters) => { foreach (var propertyInfo in ValidationParameters.GetType().GetProperties()) { var value = propertyInfo.GetValue(ValidationParameters); if (value != null) { propertyInfo.SetValue(parameters, value); // <---- 异常触发点 } } }; }
Startup.cs相关配置代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { AdjustEnvironment(env); app.UseSimpleInjector(_container); app.UseMiddleware<ErrorHandlingMiddleware>(_container); app.UseHealthChecks("/health", new HealthCheckOptions { ResultStatusCodes = { [HealthStatus.Healthy] = StatusCodes.Status200OK, [HealthStatus.Degraded] = StatusCodes.Status200OK, [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable }, AllowCachingResponses = false }); app.UseRouting(); if (env.IsDevelopment()) { app.UseCors(ConfigureSecurity.CORS_ALLOW_ANY_ORIGINS); } else { app.UseCors(ConfigureSecurity.CORS_ALLOW_SPECIFIC_ORIGINS); } app.UseAuthentication(); app.UseAuthorization(); app.UseMiddleware<NotificationMiddleware>(_container); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); _container.Verify(); }
问题原因
TokenValidationParameters属性变更:.NET 8的
Microsoft.IdentityModel.Tokens.TokenValidationParameters类新增了部分只读属性(无set方法),而你的代码通过反射遍历所有属性并尝试设置值,遇到这些只读属性时就会抛出"Property set method not found"异常。AugmentedRealityController的误导性:SimpleInjector容器验证时会尝试创建所有根对象(包括所有控制器),
AugmentedRealityController的依赖链最终会用到JwtValidator,而JwtValidator依赖IOptions<TokenValidationParameters>,配置该Options时触发了反射赋值的异常。报错指向该控制器只是因为它是触发依赖链验证的入口之一,并非控制器本身存在问题。
解决方案
方案一:过滤可写属性
修改ValidationParametersOptionsMapperAction,仅对有set方法的属性进行赋值:
public Action<TokenValidationParameters> ValidationParametersOptionsMapperAction => (parameters) => { foreach (var propertyInfo in ValidationParameters.GetType().GetProperties()) { // 只处理拥有set方法的属性 if (propertyInfo.CanWrite) { var value = propertyInfo.GetValue(ValidationParameters); if (value != null) { propertyInfo.SetValue(parameters, value); } } } };
方案二:手动指定属性赋值
放弃反射批量赋值,手动指定需要配置的属性,避免版本变更带来的未知风险:
public Action<TokenValidationParameters> ValidationParametersOptionsMapperAction => (parameters) => { parameters.ValidIssuer = ValidationParameters.ValidIssuer; parameters.ValidateIssuer = ValidationParameters.ValidateIssuer; parameters.ValidAudience = ValidationParameters.ValidAudience; parameters.ValidateAudience = ValidationParameters.ValidateAudience; parameters.ValidateIssuerSigningKey = ValidationParameters.ValidateIssuerSigningKey; parameters.IssuerSigningKey = ValidationParameters.IssuerSigningKey; parameters.RequireExpirationTime = ValidationParameters.RequireExpirationTime; parameters.ValidateLifetime = ValidationParameters.ValidateLifetime; parameters.ClockSkew = ValidationParameters.ClockSkew; // 根据业务需求添加其他需要配置的属性 };
内容的提问来源于stack exchange,提问作者Maxim

