dotnet/aspnetcore · error · InvalidOperationException

The type ' ' declares a property matching the name ' ' that…

Error message

The type '{targetType.FullName}' declares a property matching the name '{propertyName}' that is not public. Persistent service properties must be public.

What it means

PersistentServicesRegistry.PropertiesAccessor scans a persistent service type for properties annotated with [PersistentState]. For each such property it requires a public setter so that restoration can write the deserialized value back. If propertyInfo.SetMethod is null (no setter) or not public (private/protected/internal setter), restoration cannot inject the value and the registry throws during service registration.

Solutions

  1. Make the setter public: change 'private set' to 'public set' on the [PersistentState] property.
  2. For init-only patterns, use 'public init' so the property is publicly settable during restoration.
  3. If the property must stay encapsulated, introduce a separate public property backed by the persistent state, or apply [PersistentState] to a public property that delegates to the encapsulated field.

Example fix

// before
[PersistentState] public int Count { get; private set; }

// after
[PersistentState] public int Count { get; set; }
Defensive patterns

Strategy: type-guard

Type guard

static bool HasPublicSetter(PropertyInfo p)
    => p.SetMethod != null && p.SetMethod.IsPublic;

// In a unit test for persistent service types:
foreach (var p in typeof(MyService).GetProperties())
{
    if (p.GetCustomAttribute<PersistentStateAttribute>() != null)
        Assert.True(HasPublicSetter(p));
}

Prevention

When it happens

Trigger: Declaring a property with [PersistentState] that has a private or protected setter, an init-only setter that is non-public, or no setter at all (read-only/auto-get-only). Registering such a type via PersistentServicesRegistry causes the PropertiesAccessor constructor to throw on first restoration.

Common situations: Encapsulating persistent service state with a private setter to enforce invariants (common DDD pattern) but still wanting it restored. Using { get; init; } without making the init accessor public. Migrating a service to persistent-state attributes and forgetting to expose the setter.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/e811946a5f8b191f. Report an issue: GitHub.

Appendix: source

Thrown at src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs:191

                foreach (var attribute in propertyInfo.GetCustomAttributes())
                {
                    if (attribute is PersistentStateAttribute persistentStateAttribute)
                    {
                        parameterAttribute = persistentStateAttribute;
                        break;
                    }
                }
                if (parameterAttribute == null)
                {
                    continue;
                }

                var propertyName = propertyInfo.Name;
                var key = ComputeKey(keyType, propertyName);
                keys.Add(new(key, propertyInfo.PropertyType));
                if (propertyInfo.SetMethod == null || !propertyInfo.SetMethod.IsPublic)
                {
                    throw new InvalidOperationException(
                        $"The type '{targetType.FullName}' declares a property matching the name '{propertyName}' that is not public. Persistent service properties must be public.");
                }

                if (propertyInfo.GetMethod == null || !propertyInfo.GetMethod.IsPublic)
                {
                    throw new InvalidOperationException(
                        $"The type '{targetType.FullName}' declares a property matching the name '{propertyName}' that is not public. Persistent service properties must be public.");
                }

                var restoreOptions = new RestoreOptions
                {
                    RestoreBehavior = parameterAttribute.RestoreBehavior,
                    AllowUpdates = parameterAttribute.AllowUpdates,
                };

                var propertySetter = new PropertySetter(targetType, propertyInfo);
                var propertyGetter = new PropertyGetter(targetType, propertyInfo);

View on GitHub (pinned to 3600ca084e)