Repository navigation
Resolve all trim and AOT analyzer warnings - #4955
rockfordlhotka wants to merge 8 commits into
Conversation
…rnings Removes IL2026, IL2055, IL2057, IL2060, IL2096 and IL3050 from the Csla NoWarn list and resolves every warning they covered: - LoadProperty/LoadPropertyMarkDirty(IPropertyInfo, object) dispatch through PropertyInfo<T> to the strongly typed IManageProperties methods instead of MakeGenericMethod; reflection remains only as a fallback guarded by RuntimeFeature.IsDynamicCodeSupported - GraphMerger merges child collections through IGraphMergeable implemented by BusinessListBase and BusinessBindingListBase instead of MakeGenericMethod - Task<T> results are converted without a runtime generic instantiation - Array.CreateInstanceFromArrayType on .NET 9+ - Removed unused CreateGenericInstance(DI) and TypeSystem - Annotated inherently dynamic public API: MethodCaller.CallGenericMethod, CallGenericStaticMethodAsync, GetPropertyDescriptor, case-insensitive GetType, the obsolete DataContract readers/writers, PocoSerializer, BindingList based base class constructors, string based Find/ApplySort/ ApplyFilter on SortedBindingList/FilteredBindingList, DataMapper.Map - Justified suppressions for type-by-name resolution, the CoerceValue TypeConverter fallback and the DataAnnotation rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the rest of the Csla NoWarn list (IL2070, IL2072, IL2075, IL2111); Csla now builds with no trim/AOT analyzer warnings and nothing suppressed project-wide. Validated with a self-contained trimmed app, which surfaced runtime trimming bugs fixed here: - The data portal reflects over T's operation methods but only required PublicConstructors; IDataPortal<T>, IChildDataPortal<T>, DataPortal<T>, the portal factories, operation invokers and ChildDataPortal now require DynamicallyAccessedMembers.All, as do the list base classes' self type - IMobileObject requires PublicConstructors and MobileFormatter gets types through an annotated helper, so deserialization can recreate CSLA's own serializable types - The default RevalidatingInterceptor keeps its constructor Also: - RegisterProperty/RegisterMethod(Type, ...) no longer annotate objectType, which produced IL2111 in every user business class - Annotated MethodCaller Type APIs, PropertyInfo containingType, DataMap, GetChildItemType, IObjectFactoryLoader, ITypeMap, option registration generics, AuthorizeRequest; RequiresUnreferencedCode on DataMapper helpers and ObjectAdapter - CoerceValue no longer requires DynamicallyAccessedMembers.All - Justified suppressions for CSLA's reflection over business types - Fixed MethodCaller.FindMethod(Type, string, BindingFlags) infinite loop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Source/tests/Csla.AotSmokeTest is a console app published with NativeAOT that runs CSLA scenarios and exits non-zero on failure: data portal create/fetch/insert/update/delete/execute (including Task<T> results), editable and read-only child lists, data annotation, lambda, async and authorization rules, object authorization, clone, undo, MobileFormatter serialization, SaveAndMerge/GraphMerger, LoadProperty(IPropertyInfo), ObjectFactory, and [CslaImplementProperties] generated properties. CI publishes it after the tests, fails on any IL/AOT warning, and runs it. -p:CslaSmokeTrimOnly=true does a trimmed self-contained publish for machines without the native linker. The trimmed run found that [ObjectFactory(typeof(T))] didn't preserve the factory's methods; the attribute's Type constructor parameters now require DynamicallyAccessedMembers.All. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeAOT whole-program analysis in CI reported IL3050 for every enum or array property type: [DynamicallyAccessedMembers(All)] on a generic parameter keeps and reflection-enables all members of the type argument, including System.Enum.GetValues(Type) and Array.CreateInstance, which are RequiresDynamicCode. The warnings surfaced in user code (any enum PropertyInfo<T>) and in Csla's own SerializationInfo.GetValue<T> calls. Property value and field type parameters (P, F, R, PropertyInfo<T>, FieldData<T>, property info factories, SerializationInfo.GetValue/AddValue, IRuleContext, CoerceValue<D>) are never reflected over; removing the annotation produces no analyzer warnings. Business object and data portal type parameters keep DynamicallyAccessedMembers.All. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Property-loading fallback and interceptor preservation issues remain, and the smoke test bypasses the new task-result conversion.
3 open findings
What changed in this PR
Improves CSLA’s trimming and NativeAOT support, following the analyzer work in #4340 and addressing #3347 and #4956.
Changes:
- Replaces runtime generic reflection on key paths with typed dispatch.
- Refines preservation annotations and removes project-wide trim/AOT warning suppressions.
- Adds regression tests and a CI NativeAOT smoke test.
| File | Description |
|---|---|
| Source/tests/Csla.test/PropertyInfo/LoadPropertyDispatchTests.cs | Tests untyped property loading. |
| Source/tests/Csla.test/MethodCaller/FindMethodHierarchyTests.cs | Tests inherited method lookup. |
| Source/tests/Csla.test/GraphMerge/ChildCollectionMergeTests.cs | Tests child collection merging. |
| Source/tests/Csla.AotSmokeTest/Program.cs | Runs published smoke scenarios. |
| Source/tests/Csla.AotSmokeTest/Models.cs | Supplies smoke-test business types. |
| Source/tests/Csla.AotSmokeTest/Csla.AotSmokeTest.csproj | Configures native and trimmed publishing. |
| Source/Csla/Utilities.cs | Refines reflection and conversion annotations. |
| Source/Csla/SortedBindingList.cs | Marks trim-unsafe property lookups. |
| Source/Csla/Server/ObjectFactoryLoader.cs | Annotates factory type resolution. |
| Source/Csla/Server/ObjectFactoryAttribute.cs | Preserves factory members. |
| Source/Csla/Server/ObjectFactory.cs | Removes property-value preservation requirements. |
| Source/Csla/Server/IObjectFactoryLoader.cs | Defines factory preservation contract. |
| Source/Csla/Server/ChildDataPortalFactory.cs | Preserves child business members. |
| Source/Csla/Server/ChildDataPortal.cs | Strengthens generic preservation requirements. |
| Source/Csla/Server/AuthorizeRequest.cs | Annotates business type metadata. |
| Source/Csla/Serialization/Mobile/TypeMap.cs | Preserves serializer constructors. |
| Source/Csla/Serialization/Mobile/SerializationInfo.cs | Removes value-type preservation requirements. |
| Source/Csla/Serialization/Mobile/MobileFormatter.cs | Preserves serialized object constructors. |
| Source/Csla/Serialization/Mobile/ITypeMap.cs | Defines serializer preservation contract. |
| Source/Csla/Serialization/Mobile/IMobileObject.cs | Adds constructor preservation annotation. |
| Source/Csla/Serialization/Mobile/CustomSerializers/PocoSerializer.cs | Marks reflection serialization limitations. |
| Source/Csla/Serialization/Mobile/CslaXmlWriter.cs | Marks trimming and AOT limitations. |
| Source/Csla/Serialization/Mobile/CslaXmlReader.cs | Marks trimming and AOT limitations. |
| Source/Csla/Serialization/Mobile/CslaXmlBinaryWriter.cs | Marks trimming and AOT limitations. |
| Source/Csla/Serialization/Mobile/CslaXmlBinaryReader.cs | Marks trimming and AOT limitations. |
| Source/Csla/Serialization/Mobile/CslaReaderWriterFactory.cs | Preserves reader/writer constructors. |
| Source/Csla/Serialization/Mobile/Configuration/MobileFormatterOptions.cs | Annotates reader/writer registration. |
| Source/Csla/Rules/RuleContext.cs | Removes value-type preservation requirements. |
| Source/Csla/Rules/IRuleContext.cs | Aligns rule context annotations. |
| Source/Csla/Rules/CommonRules.cs | Documents validation reflection suppression. |
| Source/Csla/Rules/BusinessRules.cs | Annotates validation metadata lookup. |
| Source/Csla/Rules/AuthorizationRuleManager.cs | Documents authorization reflection suppression. |
| Source/Csla/Reflection/TaskConversionHelper.cs | Reworks task-result extraction. |
| Source/Csla/Reflection/ServiceProviderMethodInfo.cs | Removes generic conversion metadata. |
| Source/Csla/Reflection/ServiceProviderMethodCaller.cs | Uses shared task conversion. |
| Source/Csla/Reflection/MethodCaller.cs | Refines reflection and fixes hierarchy traversal. |
| Source/Csla/Reflection/DynamicMethodHandle.cs | Removes generic conversion handles. |
| Source/Csla/ReadOnlyBindingListBase.cs | Marks constructor trimming limitations. |
| Source/Csla/ReadOnlyBase.cs | Uses typed property dispatch. |
| Source/Csla/PropertyInfo.cs | Implements typed property loading. |
| Source/Csla/NameValueListBase.cs | Marks constructor trimming limitations. |
| Source/Csla/IDataPortalT.cs | Preserves business operation members. |
| Source/Csla/IDataPortalOperationInvoker.cs | Aligns invoker preservation contracts. |
| Source/Csla/IDataPortalFactory.cs | Strengthens portal preservation contract. |
| Source/Csla/IChildDataPortalT.cs | Preserves child operation members. |
| Source/Csla/IChildDataPortalFactory.cs | Strengthens child portal preservation contract. |
| Source/Csla/FilteredBindingList.cs | Marks trim-unsafe property lookups. |
| Source/Csla/DynamicListBase.cs | Preserves item operation members. |
| Source/Csla/DynamicBindingListBase.cs | Annotates binding-list limitations. |
| Source/Csla/DataPortalT.cs | Strengthens business type preservation. |
| Source/Csla/DataPortalClient/DataPortalFactory.cs | Aligns portal factory annotations. |
| Source/Csla/Data/ObjectAdapter.cs | Marks arbitrary reflection limitations. |
| Source/Csla/Data/DataMapper.cs | Marks trim-unsafe mapping APIs. |
| Source/Csla/Data/DataMap.cs | Preserves mapped members. |
| Source/Csla/Csla.csproj | Removes project-wide IL suppressions. |
| Source/Csla/CriteriaBase.cs | Removes property-value preservation requirements. |
| Source/Csla/Core/UndoableHandler.cs | Annotates reflected field requirements. |
| Source/Csla/Core/UndoableBase.cs | Documents hierarchy reflection suppressions. |
| Source/Csla/Core/TypeSystem.cs | Removes unused reflection helper. |
| Source/Csla/Core/TrimmingMessages.cs | Centralizes annotation explanations. |
| Source/Csla/Core/ReadOnlyBindingList.cs | Annotates binding-list limitations. |
| Source/Csla/Core/PropertyLoader.cs | Adds typed dispatch with reflection fallback. |
| Source/Csla/Core/MobileBindingList.cs | Marks constructor trimming limitations. |
| Source/Csla/Core/ManagedObjectBase.cs | Uses typed property dispatch. |
| Source/Csla/Core/IPropertyInfoFactory.cs | Relaxes property-value annotations. |
| Source/Csla/Core/IManageProperties.cs | Adds typed dirty-loading contract. |
| Source/Csla/Core/IGraphMergeable.cs | Defines typed collection merge dispatch. |
| Source/Csla/Core/GraphMerger.cs | Replaces reflective collection dispatch. |
| Source/Csla/Core/FieldManager/PropertyInfoManager.cs | Relaxes property-value annotations. |
| Source/Csla/Core/FieldManager/FieldDataManager.cs | Documents reflection preservation assumptions. |
| Source/Csla/Core/FieldManager/FieldData.cs | Removes value-type preservation requirement. |
| Source/Csla/Core/FieldManager/DefaultPropertyInfoFactory.cs | Aligns property factory annotations. |
| Source/Csla/Core/ExtendedBindingList.cs | Marks constructor trimming limitations. |
| Source/Csla/Core/BusinessBase.cs | Adds typed loading and preservation annotations. |
| Source/Csla/Configuration/Fluent/DataPortalServerOptions.cs | Preserves interceptor constructors. |
| Source/Csla/Configuration/Fluent/DataPortalConfigurationExtensions.cs | Isolates interceptor registration suppression. |
| Source/Csla/Configuration/Fluent/CslaOptions.cs | Preserves exception-handler constructors. |
| Source/Csla/CommandBase.cs | Implements typed dirty-loading behavior. |
| Source/Csla/BusinessListBase.cs | Implements typed graph merging. |
| Source/Csla/BusinessDocumentBase.cs | Relaxes property-value annotations. |
| Source/Csla/BusinessBindingListBase.cs | Adds typed merging and trimming annotations. |
| Source/Csla/BusinessBase.cs | Relaxes property registration annotations. |
| Source/Csla/ApplicationContext.cs | Removes unused generic activation helpers. |
| Source/csla.test.sln | Includes the smoke-test project. |
| CLAUDE.md | Documents publishing and annotation guidance. |
| .github/workflows/ci.yaml | Publishes and runs NativeAOT smoke tests. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…coverage - AddInterceptorProvider<T>(int index) now preserves T's public constructors like the parameterless overload - PropertyLoader's reflection fallback resolves the generic method on IManageProperties, so interface dispatch reaches ManagedObjectBase's private and CommandBase's explicit implementations; previously LoadPropertyMarkDirty with a widening conversion threw on those types - Regression tests for widening conversions on CommandBase and BusinessBase - The smoke test's object factory returns Task<T> after a real suspension, so the NativeAOT run verifies the data portal's Task<T> result extraction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The NativeAOT smoke test (with the object factory now returning Task<T>) showed TaskConversionHelper failing in the native executable: it read Result by reflection on the task's runtime type, an async state machine box whose members have no reflection metadata under NativeAOT. ConvertTask now walks to the Task<T> base type and reads Result there, with a DynamicDependency that keeps Task<T>'s public properties. Verified with a linux-x64 NativeAOT publish in a container (all smoke scenarios pass); CLAUDE.md documents that local option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| /// <see cref="Server.ObjectFactoryAttribute"/>, so they refer to types the | ||
| /// application uses. Trimmed applications must preserve those types. | ||
| /// </summary> | ||
| internal const string TypeNameResolutionJustification = |
There was a problem hiding this comment.
Move this into TrimmingMessages.cs
There was a problem hiding this comment.
Done in 429960c. TypeNameResolutionJustification now lives in Core/TrimmingMessages.cs. The usages in MethodCaller, ObjectFactoryLoader, MobileFormatter and FieldDataManager reference it there.
| /// <param name="throwOnError">true to throw an exception if the type can't be found.</param> | ||
| /// <param name="ignoreCase">true for a case-insensitive comparison of the type name.</param> | ||
| /// <exception cref="ArgumentException"><paramref name="typeName"/> is <see langword="null"/>, <see cref="string.Empty"/> or only consists of white spaces.</exception> | ||
| [RequiresUnreferencedCode("A case-insensitive type lookup can't be analyzed for trimming. Use GetType(string, bool) instead.")] |
There was a problem hiding this comment.
Use nameof() for the GetType method reference?
There was a problem hiding this comment.
Done in 429960c. The message is now TrimmingMessages.CaseInsensitiveTypeLookup, a const interpolated string: $"... Use {nameof(MethodCaller)}.{nameof(MethodCaller.GetType)}(string, bool) instead."
| /// <param name="parameters">Parameters for method</param> | ||
| /// <exception cref="ArgumentNullException"><paramref name="objectType"/> or <paramref name="typeParams"/> is <see langword="null"/>.</exception> | ||
| /// <exception cref="ArgumentException"> is <see langword="null"/>, <see cref="string.Empty"/> or only consists of white spaces.</exception> | ||
| [RequiresDynamicCode("Creates a generic method instantiation from runtime types.")] |
There was a problem hiding this comment.
Shouldn't they be moved into TrimmingMessages.cs as well?
There was a problem hiding this comment.
Done in 429960c. Moved to Core/TrimmingMessages.cs. I moved every other inline trimming message and justification this PR added there too (PropertyLoader, the interceptor registration, CoerceValue's fallback, ...), so the file now holds all RequiresUnreferencedCode/RequiresDynamicCode messages and UnconditionalSuppressMessage justifications, grouped into those two regions.
| /// <param name="parameters">Parameters for method</param> | ||
| /// <exception cref="ArgumentNullException"><paramref name="target"/> or <paramref name="typeParams"/> is <see langword="null"/>.</exception> | ||
| /// <exception cref="ArgumentException"> is <see langword="null"/>, <see cref="string.Empty"/> or only consists of white spaces.</exception> | ||
| [RequiresDynamicCode("Creates a generic method instantiation from runtime types.")] |
There was a problem hiding this comment.
Shouldn't they be moved into TrimmingMessages.cs as well?
There was a problem hiding this comment.
Done in 429960c. Moved to Core/TrimmingMessages.cs. I moved every other inline trimming message and justification this PR added there too (PropertyLoader, the interceptor registration, CoerceValue's fallback, ...), so the file now holds all RequiresUnreferencedCode/RequiresDynamicCode messages and UnconditionalSuppressMessage justifications, grouped into those two regions.
| public static async Task<object?> ConvertTask<T>(Task<T?> task) | ||
|
|
||
| [UnconditionalSuppressMessage("Trimming", "IL2075", | ||
| Justification = "Result is a public property of Task<T>, preserved by the DynamicDependency on ConvertTask.")] |
There was a problem hiding this comment.
Shouldn't they be moved into TrimmingMessages.cs as well?
There was a problem hiding this comment.
Done in 429960c. Moved to Core/TrimmingMessages.cs. I moved every other inline trimming message and justification this PR added there too (PropertyLoader, the interceptor registration, CoerceValue's fallback, ...), so the file now holds all RequiresUnreferencedCode/RequiresDynamicCode messages and UnconditionalSuppressMessage justifications, grouped into those two regions.
| /// to read the data from a stream | ||
| /// </summary> | ||
| [Obsolete("This type of serialization is unsupported. It will be removed with one of the next major versions of CSLA.NET.")] | ||
| [RequiresDynamicCode("DataContractSerializer is not compatible with NativeAOT.")] |
There was a problem hiding this comment.
Instead of annotating we should remove it now? Or when did we mark them obsolete?
There was a problem hiding this comment.
Agreed, removed in 429960c. They were marked [Obsolete] in v9.0.0 (#4336, Nov 2024), and nothing in the repo used them. This removes all four CslaXml* reader/writer types plus the also-obsolete CslaReaderWriterFactory.GetDataContractSerializer(). I added a breaking-change section to docs/Upgrading to CSLA 11.md: users who selected them through MobileReader<T>()/MobileWriter<T>() should drop those calls and update client and server together.
| /// to write the data to a stream | ||
| /// </summary> | ||
| [Obsolete("This type of serialization is unsupported. It will be removed with one of the next major versions of CSLA.NET.")] | ||
| [RequiresDynamicCode("DataContractSerializer is not compatible with NativeAOT.")] |
There was a problem hiding this comment.
Instead of annotating we should remove it now? Or when did we mark them obsolete?
There was a problem hiding this comment.
Agreed, removed in 429960c. They were marked [Obsolete] in v9.0.0 (#4336, Nov 2024), and nothing in the repo used them. This removes all four CslaXml* reader/writer types plus the also-obsolete CslaReaderWriterFactory.GetDataContractSerializer(). I added a breaking-change section to docs/Upgrading to CSLA 11.md: users who selected them through MobileReader<T>()/MobileWriter<T>() should drop those calls and update client and server together.
| /// to read the data from a stream | ||
| /// </summary> | ||
| [Obsolete("This type of serialization is unsupported. It will be removed with one of the next major versions of CSLA.NET.")] | ||
| [RequiresDynamicCode("DataContractSerializer is not compatible with NativeAOT.")] |
There was a problem hiding this comment.
Instead of annotating we should remove it now? Or when did we mark them obsolete?
There was a problem hiding this comment.
Agreed, removed in 429960c. They were marked [Obsolete] in v9.0.0 (#4336, Nov 2024), and nothing in the repo used them. This removes all four CslaXml* reader/writer types plus the also-obsolete CslaReaderWriterFactory.GetDataContractSerializer(). I added a breaking-change section to docs/Upgrading to CSLA 11.md: users who selected them through MobileReader<T>()/MobileWriter<T>() should drop those calls and update client and server together.
| /// to write the data to a stream | ||
| /// </summary> | ||
| [Obsolete("This type of serialization is unsupported. It will be removed with one of the next major versions of CSLA.NET.")] | ||
| [RequiresDynamicCode("DataContractSerializer is not compatible with NativeAOT.")] |
There was a problem hiding this comment.
Instead of annotating we should remove it now? Or when did we mark them obsolete?
There was a problem hiding this comment.
Agreed, removed in 429960c. They were marked [Obsolete] in v9.0.0 (#4336, Nov 2024), and nothing in the repo used them. This removes all four CslaXml* reader/writer types plus the also-obsolete CslaReaderWriterFactory.GetDataContractSerializer(). I added a breaking-change section to docs/Upgrading to CSLA 11.md: users who selected them through MobileReader<T>()/MobileWriter<T>() should drop those calls and update client and server together.
|
|
||
| #region CoerceValue | ||
|
|
||
| private const string TypeConverterFallbackJustification = |
There was a problem hiding this comment.
Shouldn't they be moved into TrimmingMessages.cs as well?
There was a problem hiding this comment.
Done in 429960c. Moved to Core/TrimmingMessages.cs. I moved every other inline trimming message and justification this PR added there too (PropertyLoader, the interceptor registration, CoerceValue's fallback, ...), so the file now holds all RequiresUnreferencedCode/RequiresDynamicCode messages and UnconditionalSuppressMessage justifications, grouped into those two regions.
…CslaXml* - Move all RequiresUnreferencedCode/RequiresDynamicCode messages and UnconditionalSuppressMessage justifications added for trimming into Core/TrimmingMessages.cs; the case-insensitive GetType message uses nameof - Localize the new exception messages (TaskConversionHelper, PropertyLoader, GraphMerger) in Resources.resx - Remove the CslaXmlReader/Writer and CslaXmlBinaryReader/Writer types and CslaReaderWriterFactory.GetDataContractSerializer, obsolete since CSLA 9; documented in Upgrading to CSLA 11 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Summary
This builds on #4340, which turned on the trim/AOT analyzers for
Csla.csprojand put every warning code inNoWarn. This PR resolves every trim/AOT analyzer warning in Csla and removes theNoWarnlist entirely. Csla now builds with zero IL warnings on net10.0 and net11.0, with nothing suppressed project-wide.It was also validated end to end with a self-contained, trimmed (
PublishTrimmed) app that does all of the following, which surfaced and fixed real runtime trimming bugs (see below):Task<T>), Save, and command Execute through the data portalAddNewLoadProperty(IPropertyInfo, object)Fixed rather than annotated (no runtime code generation or reflection on these paths)
LoadProperty/LoadPropertyMarkDirty(IPropertyInfo, object)(BusinessBase, ReadOnlyBase, ManagedObjectBase) dispatch throughPropertyInfo<T>to the typedIManagePropertiesmethods instead ofMakeGenericMethod.RuntimeFeature.IsDynamicCodeSupported. It covers customIPropertyInfoimplementations and widening conversions.#if IOSworkaround ladder is removed.GraphMergermerges child collections through an internalIGraphMergeableinterface implemented byBusinessListBase/BusinessBindingListBase, instead ofMakeGenericMethod.Task<T>data portal results are converted without a runtime generic instantiation.Array.CreateInstanceFromArrayTypeon .NET 9+.ApplicationContext.CreateGenericInstance(DI)andCore/TypeSystemhad no callers.Trimming correctness fixes found by the trimmed test app
IDataPortal<T>,IChildDataPortal<T>,DataPortal<T>, the portal factories, the operation invokers andChildDataPortal's generic methods only requiredPublicConstructorsonT, even though the data portal reflects overT's (private, possibly inherited) operation methods. They now requireDynamicallyAccessedMembers.All, as do the "self" type parameters ofBusinessListBase/BusinessBindingListBaseandDynamicListBase's item type, matchingBusinessBase<T>/ReadOnlyBase<T>. Before this,[Fetch]on aReadOnlyListBasesubclass failed in a trimmed app.FieldDataManager,BrokenRulesCollection, ...) because their public constructors were trimmed.IMobileObjectis now annotated withPublicConstructors, and MobileFormatter gets each serialized object's type through anIMobileObject-typed, annotated helper, so the trimmer keeps the public constructor of every serialized type.RevalidatingInterceptorlost its constructor, so the data portal failed to resolve interceptors. It's now registered through an annotated helper.Annotated (inherently dynamic public API)
[RequiresDynamicCode]/[RequiresUnreferencedCode]:MethodCaller.CallGenericMethod,CallGenericStaticMethodAsync,GetPropertyDescriptor, and the case-insensitiveGetType(string, bool, bool)overload.PocoSerializer<T>.[RequiresUnreferencedCode]:BindingList<T>()is itself[RequiresUnreferencedCode]in .NET), includingNameValueListBase.Find/ApplySort/ApplyFilteronSortedBindingList/FilteredBindingList.DataMapper.Mapand itsGet/SetFieldValue/SetPropertyValuehelpers, andObjectAdapter. These reflect over arbitrary objects.[DynamicallyAccessedMembers]on:MethodCaller'sType-taking public methodsPropertyInfo<T>'scontainingTypeparameterDataMapUtilities.GetChildItemTypeIObjectFactoryLoader.GetFactoryType(return value)ITypeMap.SerializerType/TypeMap<T, S>CslaOptions/DataPortalServerOptions/MobileFormatterOptionsregistration genericsAuthorizeRequest.ObjectTypeRegisterProperty(Type, PropertyInfo<P>)andRegisterMethod(Type, IMemberInfo)no longer put an annotation onobjectType. Previously every class deriving fromReadOnlyBase/BusinessBase/ManagedObjectBasegot IL2111 warnings in the user's build. A consumer project withIsAotCompatible=truenow only sees warnings forNameValueListBasesubclasses andDataMapper.Map.Justified suppressions
Every suppression is an
[UnconditionalSuppressMessage]with a justification. The shared justification texts are inCore/TrimmingMessages.cs.MethodCaller.GetType,ObjectFactoryLoader, MobileFormatter,FieldDataManagerdeserialization). The names come from types CSLA serialized or from[ObjectFactory]attributes.MethodCaller'sobject-based helpers and the data portal's reflection over business types. Business types are preserved by the data portal / base class annotations above.CoerceValue's last-resort TypeConverter fallback, which rethrows the original error if a converter was trimmed.CoerceValue's parameters no longer requireDynamicallyAccessedMembers.All.DataAnnotationrule.Removed obsolete APIs
The
CslaXmlReader/CslaXmlWriter/CslaXmlBinaryReader/CslaXmlBinaryWritertypes andCslaReaderWriterFactory.GetDataContractSerializer()are removed. They were obsolete since CSLA 9 (#4336), andDataContractSerializerisn't trim/AOT compatible. The breaking change is documented indocs/Upgrading to CSLA 11.md.All annotation messages and suppression justifications live in
Core/TrimmingMessages.cs. New exception messages are localized inResources.resx.Bug fixes found along the way
MethodCaller.FindMethod(Type, string, BindingFlags)looped forever when the method wasn't on the type or its direct base: it advanced withobjType.BaseTypeinstead oftmpType.BaseType.Behavior notes
LoadProperty(IPropertyInfo, object)with a matching value type no longer wraps exceptions inTargetInvocationException. A subclass that hides (new) the genericLoadProperty<P>is no longer called through this path.ManagedObjectBase.LoadPropertyMarkDirty(IPropertyInfo, object)previously threw for subclasses, and now works.CommandBasereturnsfalse, consistent with its existingIManagePropertiesbehavior.GraphMergerthrowsNotSupportedExceptionfor a child collection that isn't aBusinessListBase/BusinessBindingListBase.NameValueListBase) or callDataMapper.Map/ObjectAdapternow see IL2026 warnings.IDataPortal<T>/IChildDataPortal<T>with its own type parameter needs[DynamicallyAccessedMembers(All)]on that parameter, where it previously neededPublicConstructors.IObjectFactoryLoader/ITypeMapimplementations need the matching return/property annotations.NativeAOT smoke test (new CI step)
Source/tests/Csla.AotSmokeTest(added tocsla.test.sln) is a console app that CI publishes with NativeAOT after the unit tests. The step fails on any trim/AOT warning in the publish output, then runs the native exe, which exits non-zero if any scenario fails. The scenarios are:Task<T>-returning operation.SaveAndMerge/GraphMerger,LoadProperty(IPropertyInfo, object)[CslaImplementProperties]generated properties-p:CslaSmokeTrimOnly=truedoes a trimmed, self-contained publish for machines without the native linker. Both are documented in CLAUDE.md.The smoke test found two more problems, both fixed here:
[ObjectFactory(typeof(X))]didn't preserve the factory's methods. The attribute'sTypeconstructor parameters now requireDynamicallyAccessedMembers.All.[DynamicallyAccessedMembers(All)]on property-value generic parameters (P,PropertyInfo<T>,FieldData<T>,SerializationInfo.GetValue<T>, ...) reflection-enabledEnum.GetValues/Array.CreateInstance. Those ~200 annotations were unnecessary: removing them produces no analyzer warnings. Business object and data portal type parameters keepAll.Still not covered
The CI NativeAOT run covers the scenarios above in-process. Still untested under AOT:
NameValueListBaseand the Dynamic listsAPIs marked
[RequiresUnreferencedCode]/[RequiresDynamicCode]remain unsupported in trimmed/AOT apps. Declaring<IsAotCompatible>true</IsAotCompatible>is left as a separate decision.Test plan
dotnet build Source/csla.test.sln: 0 errors, all TFMsNoWarndotnet test Source/csla.test.sln --filter TestCategory!=SkipOnCIServer --settings Source/test.runsettings: all 23 test runs passLoadPropertyDispatchTests,ChildCollectionMergeTests(sync/async, nestedBusinessListBaseandBusinessBindingListBase),FindMethodHierarchyTestsIsAotCompatible=true(BusinessBase, ReadOnlyBase, list bases, CommandBase, NameValueListBase): only the intended IL2026 warningsPublishTrimmedapp: the full scenario listed in the summary runs successfullyCloses #3347
Closes #4956
🤖 Generated with Claude Code