From fcd82510b4eef8706ba8a6c7313ac3558264ab68 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Fri, 6 Feb 2026 20:54:01 -0600 Subject: [PATCH 1/5] Add CLAUDE.md with build commands and architecture guide Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..a4ad92cc36 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,108 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +CSLA .NET is a business object framework for .NET. Current version is **10.1.0** (set via Nerdbank.GitVersioning in `Source/version.json`). The core package targets `netstandard2.0`, `net462`, `net472`, `net48`, `net8.0`, `net9.0`, and `net10.0`. Requires **.NET SDK 10.0.100+** (see `Source/global.json`). + +## Build and Test Commands + +### Build everything (framework + tests) +``` +dotnet build Source\csla.test.sln +``` + +### Run all tests +``` +dotnet test Source\csla.test.sln --no-build --verbosity normal --filter TestCategory!=SkipOnCIServer --settings Source/test.runsettings +``` + +### Run a single test by name +``` +dotnet test Source\csla.test.sln --no-build --filter "FullyQualifiedName~Csla.Test.ClassName.MethodName" +``` + +### Build only framework (no tests) +``` +dotnet build Source\csla.build.sln +``` + +### Build MAUI projects (requires `dotnet workload install maui`) +``` +dotnet build Source/csla.maui.build.sln +``` + +### Build analyzers/generators +``` +dotnet build Source\Csla.Analyzers.sln +``` + +## Solution Files + +| Solution | Purpose | +|---|---| +| `Source\csla.test.sln` | **Primary** — framework + all tests (used by CI) | +| `Source\csla.build.sln` | Framework libraries only (no tests) | +| `Source\csla.maui.test.sln` | MAUI-specific build + tests | +| `Source\Csla.Analyzers.sln` | Roslyn analyzers | +| `Source\csla.benchmarks.sln` | BenchmarkDotNet performance tests | + +## Project Architecture + +### Core Framework (`Source/Csla/`) +The main NuGet package. Key subsystems: +- **Business base classes** — `BusinessBase`, `ReadOnlyBase`, `CommandBase`, `BusinessListBase`, `ReadOnlyListBase`, `NameValueListBase`, `DynamicListBase` (root-level .cs files) +- **Data Portal** — Client-side in `DataPortalClient/` (proxies: `LocalProxy`, `HttpProxy`, `HttpCompressionProxy`), server-side in `Server/` (`SimpleDataPortal`, `DataPortalBroker`, `DataPortalSelector`, `ChildDataPortal`). `DataPortalT.cs` and `IDataPortalT.cs` define the generic `DataPortal` / `IDataPortal` interfaces. +- **Rules engine** — `Rules/` contains business rules (`BusinessRule`, `CommonRules`) and authorization rules (`AuthorizationRule`). `BrokenRulesCollection` tracks validation state. +- **Serialization** — `Serialization/` with `MobileFormatter` for CSLA's custom serialization. Source generators for auto-serialization in `Csla.Generators/cs/AutoSerialization/`. +- **Configuration** — `Configuration/` with DI registration via `CslaBuilder` and `AddCsla()` extension methods. +- **Core infrastructure** — `Core/` (property management, undo/n-level undo, `ManagedObjectBase`), `Security/`, `State/`, `Reflection/` + +### UI Support Packages +- `Csla.AspNetCore` — ASP.NET Core integration (controllers, DI) +- `Csla.Blazor` / `Csla.Blazor.WebAssembly` — Blazor component support +- `Csla.Windows` — WinForms binding support +- `Csla.Xaml.Wpf` — WPF binding support +- `Csla.Xaml.Maui` — .NET MAUI support + +### Channel Packages +- `Csla.Channels.Grpc` — gRPC data portal channel +- `Csla.Channels.RabbitMq` — RabbitMQ data portal channel + +### Analyzers and Generators +- `Source/Csla.Analyzers/` — Roslyn analyzers shipped inside the Csla NuGet +- `Source/Csla.Generators/cs/AutoSerialization/` — Source generator for `[AutoSerialization]` +- `Source/Csla.Generators/cs/AutoImplementProperties/` — Source generator for auto-implementing CSLA properties +- `Source/Csla.Generator.DataPortalInterfaces.CSharp/` — Source generator for data portal interface generation + +### Test Projects (under `Source/tests/`) +- `Csla.test` — Main test suite (MSTest + FluentAssertions) +- `csla.netcore.test` — .NET-specific tests (MSTest + AwesomeAssertions) +- `Csla.Blazor.Test` / `Csla.Blazor.WebAssembly.Tests` — Blazor tests +- `Csla.Windows.Tests` — WinForms tests +- `Csla.Analyzers.Tests` — Analyzer unit tests +- `Csla.Generator.*.Tests` — Generator unit tests +- `GraphMergerTest` — Object graph merge testing + +## Coding Standards + +- **Indent**: 2 spaces (set in `.editorconfig`) +- **Line endings**: CRLF +- **C# style**: Allman braces, `var` everywhere, expression-bodied members preferred +- **Field naming**: `_fieldName` for instance fields +- **Nullable**: Enabled in framework code (`WarningsAsErrors=nullable`), disabled in test projects +- **Language version**: `latest` +- **Implicit usings**: Enabled +- **Assemblies are strong-named** using `CslaKey.snk` + +## Commit Message Format + +Include the GitHub issue number: `#999 Description of change` + +## CI Notes + +- CI runs on `windows-latest` with .NET 10 SDK +- Tests use `--settings Source/test.runsettings` which limits `MaxCpuCount` to 1 (tests must not run in parallel) +- Tests with `[TestCategory("SkipOnCIServer")]` are excluded from CI runs +- MAUI build only triggers when `Source/Csla.Xaml.Maui/`, `Source/Csla.Xaml.Shared/`, or `Source/csla.maui.test.sln` are modified From 0bcc6b95d204271ace3422a761080f739839c3cb Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Fri, 6 Feb 2026 21:34:07 -0600 Subject: [PATCH 2/5] #4359 Add source generator for explicit data portal operation dispatch Add a source generator that creates IDataPortalOperationMapping implementations for business types with data portal operation methods, eliminating the need for reflection-based invocation and enabling AOT trimming compatibility. - New generator project: Csla.Generator.DataPortalInterfaces.CSharp - New IDataPortalOperationMapping interface and DataPortalOperationNotSupportedException - DataPortalTarget tries generated dispatch first, falls back to reflection - Handles all 13 operation attributes, [Inject], overloads, async, nested classes - 13 snapshot tests covering all scenarios, full test suite passes with 0 regressions Co-Authored-By: Claude Opus 4.6 --- ...nerator.DataPortalInterfaces.CSharp.csproj | 46 +++ .../DataPortalInterfaceBuilder.cs | 249 ++++++++++++++ .../ContainerDefinitionsExtractor.cs | 110 ++++++ .../Discovery/DefinitionExtractionContext.cs | 52 +++ .../Discovery/OperationMethodExtractor.cs | 162 +++++++++ .../Discovery/TypeDefinitionExtractor.cs | 129 ++++++++ .../ExtractedContainerDefinition.cs | 28 ++ .../Extractors/ExtractedOperationMethod.cs | 47 +++ .../Extractors/ExtractedOperationParameter.cs | 45 +++ .../Extractors/ExtractedTypeDefinition.cs | 75 +++++ .../Extractors/GenerationResults.cs | 28 ++ ...IncrementalDataPortalInterfaceGenerator.cs | 152 +++++++++ Source/Csla/Csla.csproj | 1 + ...ataPortalOperationNotSupportedException.cs | 40 +++ Source/Csla/Server/DataPortalTarget.cs | 56 +++- .../Server/IDataPortalOperationMapping.cs | 30 ++ Source/csla.build.sln | 211 ++++++++++++ Source/csla.test.sln | 97 ++++++ ...r.DataPortalInterfaces.CSharp.Tests.csproj | 39 +++ .../DataPortalInterfaceGeneratorTests.cs | 312 ++++++++++++++++++ ...sonEdit.DataPortalOperations.g.verified.cs | 23 ++ ...ineItem.DataPortalOperations.g.verified.cs | 58 ++++ ...sonEdit.DataPortalOperations.g.verified.cs | 58 ++++ ...sonEdit.DataPortalOperations.g.verified.cs | 23 ++ ...Command.DataPortalOperations.g.verified.cs | 23 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 31 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 28 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 26 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 24 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 24 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 31 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 23 ++ ...sonEdit.DataPortalOperations.g.verified.cs | 23 ++ .../Helpers/TestHelper.cs | 91 +++++ 34 files changed, 2387 insertions(+), 8 deletions(-) create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs create mode 100644 Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs create mode 100644 Source/Csla/Server/DataPortalOperationNotSupportedException.cs create mode 100644 Source/Csla/Server/IDataPortalOperationMapping.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs create mode 100644 Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj new file mode 100644 index 0000000000..effe9ebe2f --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.0 + true + false + false + false + false + false + false + false + false + false + true + RS2008 + ..\..\..\..\..\Bin + ..\..\..\..\..\bin\packages\ + Csla.Generator.DataPortalInterfaces.CSharp + Csla.Generator.DataPortalInterfaces.CSharp + Csla.Generator.DataPortalInterfaces.CSharp + CSLA .NET Generator DataPortalInterfaces for CSharp + true + True + CSLA .NET Generators + CSLA;Roslyn;Generator + + + true + false + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs new file mode 100644 index 0000000000..daa1f10460 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Builds generated source for data portal interface implementation +//----------------------------------------------------------------------- + +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; + +namespace Csla.Generator.DataPortalInterfaces.CSharp +{ + + /// + /// Builds the generated source code that provides an explicit + /// implementation of IDataPortalOperationMapping for a business type + /// + internal class DataPortalInterfaceBuilder + { + + /// + /// Build the partial type definition with the interface implementation + /// + internal GenerationResults BuildPartialTypeDefinition(ExtractedTypeDefinition typeDefinition) + { + using var stringWriter = new StringWriter(); + var textWriter = new IndentedTextWriter(stringWriter, " "); + + textWriter.WriteLine("//"); + textWriter.WriteLine("#nullable enable"); + textWriter.WriteLine(); + + AppendContainerDefinitions(textWriter, typeDefinition); + AppendTypeDefinition(textWriter, typeDefinition); + AppendBlockStart(textWriter); + + AppendInvokeOperationAsyncMethod(textWriter, typeDefinition); + + AppendBlockEnd(textWriter); + AppendContainerDefinitionClosures(textWriter, typeDefinition); + + return new GenerationResults + { + FullyQualifiedName = typeDefinition.FullyQualifiedName, + GeneratedSource = stringWriter.ToString() + }; + } + + private void AppendBlockStart(IndentedTextWriter textWriter) + { + textWriter.WriteLine("{"); + textWriter.Indent++; + } + + private void AppendBlockEnd(IndentedTextWriter textWriter) + { + textWriter.Indent--; + textWriter.WriteLine("}"); + } + + private void AppendContainerDefinitions(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) + { + foreach (var containerDefinition in typeDefinition.ContainerDefinitions) + { + textWriter.WriteLine(containerDefinition.FullDefinition); + AppendBlockStart(textWriter); + } + } + + private void AppendContainerDefinitionClosures(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) + { + foreach (var containerDefinition in typeDefinition.ContainerDefinitions) + { + AppendBlockEnd(textWriter); + } + } + + private void AppendTypeDefinition(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) + { + textWriter.Write(typeDefinition.Scope); + textWriter.Write(" partial class "); + textWriter.Write(typeDefinition.TypeName); + textWriter.Write(typeDefinition.TypeParameters); + textWriter.WriteLine(" : Csla.Server.IDataPortalOperationMapping"); + } + + private void AppendInvokeOperationAsyncMethod(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) + { + textWriter.WriteLine("async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync("); + textWriter.Indent++; + textWriter.WriteLine("global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)"); + textWriter.Indent--; + AppendBlockStart(textWriter); + + // Group methods by their operation attribute types + var methodsByAttribute = GroupMethodsByAttribute(typeDefinition.OperationMethods); + + var isFirst = true; + foreach (var group in methodsByAttribute) + { + if (isFirst) + { + textWriter.Write("if"); + isFirst = false; + } + else + { + textWriter.Write("else if"); + } + + textWriter.WriteLine($" (operationType == typeof({group.Key}))"); + AppendBlockStart(textWriter); + + foreach (var method in group.Value) + { + AppendMethodDispatch(textWriter, method); + } + + AppendBlockEnd(textWriter); + } + + textWriter.WriteLine("throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria);"); + + AppendBlockEnd(textWriter); + } + + private void AppendMethodDispatch(IndentedTextWriter textWriter, ExtractedOperationMethod method) + { + var criteriaCount = method.CriteriaParameters.Count; + + if (criteriaCount == 0) + { + textWriter.WriteLine("if (criteria is null or { Length: 0 })"); + } + else + { + // Build pattern match: criteria is { Length: N } && criteria[0] is Type name && ... + var conditions = new List(); + conditions.Add($"criteria is {{ Length: {criteriaCount} }}"); + + for (int i = 0; i < criteriaCount; i++) + { + var param = method.CriteriaParameters[i]; + conditions.Add($"criteria[{i}] is {param.TypeDisplayName} p{i}_{SanitizeTypeName(param.TypeFullName)}"); + } + + textWriter.WriteLine($"if ({string.Join(" && ", conditions)})"); + } + + AppendBlockStart(textWriter); + + // Resolve injected parameters + foreach (var inject in method.InjectParameters) + { + if (inject.AllowNull) + { + textWriter.WriteLine($"var {inject.Name} = ({inject.TypeDisplayName}?)serviceProvider.GetService(typeof({inject.TypeDisplayName}));"); + } + else + { + textWriter.WriteLine($"var {inject.Name} = ({inject.TypeDisplayName})(serviceProvider.GetService(typeof({inject.TypeDisplayName})) ?? throw new global::System.InvalidOperationException($\"No service for type '{{typeof({inject.TypeDisplayName})}}' has been registered.\"));"); + } + } + + // Build method call + var args = new List(); + + // All parameters in original order (criteria + inject interleaved) + // We need to reconstruct the original parameter order + // The criteria params are in order, inject params are in order + // We emit them by building from both lists + var allParams = GetAllParametersInOrder(method); + foreach (var p in allParams) + { + if (p.IsInjected) + { + args.Add(p.Name); + } + else + { + var criteriaIndex = method.CriteriaParameters.IndexOf(p); + args.Add($"p{criteriaIndex}_{SanitizeTypeName(p.TypeFullName)}"); + } + } + + var argString = string.Join(", ", args); + + if (method.IsAsync) + { + textWriter.WriteLine($"await {method.MethodName}({argString}).ConfigureAwait(false);"); + } + else + { + textWriter.WriteLine($"{method.MethodName}({argString});"); + } + + textWriter.WriteLine("return;"); + AppendBlockEnd(textWriter); + } + + private static IList GetAllParametersInOrder(ExtractedOperationMethod method) + { + // Return all parameters in their original declaration order + // by merging criteria and inject lists + var all = new List(); + all.AddRange(method.CriteriaParameters); + all.AddRange(method.InjectParameters); + return all; + } + + private static Dictionary> GroupMethodsByAttribute( + IList methods) + { + var result = new Dictionary>(); + + foreach (var method in methods) + { + foreach (var attr in method.OperationAttributeNames) + { + if (!result.TryGetValue(attr, out var list)) + { + list = new List(); + result[attr] = list; + } + list.Add(method); + } + } + + return result; + } + + private static string SanitizeTypeName(string typeName) + { + // Remove global:: prefix and dots, convert to safe identifier + return typeName + .Replace("global::", "") + .Replace(".", "_") + .Replace("<", "_") + .Replace(">", "_") + .Replace(",", "_") + .Replace(" ", "") + .Replace("?", "Nullable"); + } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs new file mode 100644 index 0000000000..421b2c4768 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Extract container definitions for a type +//----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Text; +using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery +{ + + /// + /// Extract the container definitions for a type declaration + /// + internal static class ContainerDefinitionsExtractor + { + + /// + /// Get the container definitions for a type declaration, + /// from outermost to innermost + /// + public static IReadOnlyList GetContainerDefinitions( + DefinitionExtractionContext context, + TypeDeclarationSyntax typeDeclaration) + { + var containers = new List(); + var parent = typeDeclaration.Parent; + + while (parent != null) + { + if (parent is TypeDeclarationSyntax parentType) + { + containers.Insert(0, BuildTypeContainer(parentType)); + } + else if (parent is BaseNamespaceDeclarationSyntax namespaceDecl) + { + containers.Insert(0, BuildNamespaceContainer(namespaceDecl)); + break; + } + parent = parent.Parent; + } + + return containers; + } + + private static ExtractedContainerDefinition BuildNamespaceContainer(BaseNamespaceDeclarationSyntax namespaceDecl) + { + var fullNamespace = GetFullNamespace(namespaceDecl); + return new ExtractedContainerDefinition + { + Name = fullNamespace, + FullDefinition = $"namespace {fullNamespace}" + }; + } + + private static string GetFullNamespace(BaseNamespaceDeclarationSyntax namespaceDecl) + { + var parts = new List(); + var current = namespaceDecl; + + while (current != null) + { + parts.Insert(0, current.Name.ToString()); + current = current.Parent as BaseNamespaceDeclarationSyntax; + } + + return string.Join(".", parts); + } + + private static ExtractedContainerDefinition BuildTypeContainer(TypeDeclarationSyntax typeDecl) + { + var sb = new StringBuilder(); + + foreach (var modifier in typeDecl.Modifiers) + { + if (modifier.IsKind(SyntaxKind.PublicKeyword) || + modifier.IsKind(SyntaxKind.InternalKeyword) || + modifier.IsKind(SyntaxKind.ProtectedKeyword) || + modifier.IsKind(SyntaxKind.PrivateKeyword)) + { + sb.Append(modifier.ValueText); + sb.Append(' '); + } + } + + sb.Append("partial "); + sb.Append(typeDecl.Keyword.ValueText); + sb.Append(' '); + sb.Append(typeDecl.Identifier.Text); + + if (typeDecl.TypeParameterList != null) + { + sb.Append(typeDecl.TypeParameterList.ToString()); + } + + return new ExtractedContainerDefinition + { + Name = typeDecl.Identifier.Text, + FullDefinition = sb.ToString() + }; + } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs new file mode 100644 index 0000000000..a2b00831e1 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Wraps a SemanticModel for definition extraction +//----------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery +{ + + /// + /// Wraps a SemanticModel for definition extraction, + /// providing helper methods for symbol resolution + /// + internal class DefinitionExtractionContext + { + private readonly SemanticModel _semanticModel; + + /// + /// Create a new DefinitionExtractionContext + /// + /// The Roslyn SemanticModel to use + public DefinitionExtractionContext(SemanticModel semanticModel) + { + _semanticModel = semanticModel; + } + + /// + /// Gets the SemanticModel + /// + public SemanticModel SemanticModel => _semanticModel; + + /// + /// Get the symbol for a syntax node + /// + public ISymbol? GetDeclaredSymbol(SyntaxNode node) + { + return _semanticModel.GetDeclaredSymbol(node); + } + + /// + /// Get the type info for a syntax node + /// + public TypeInfo GetTypeInfo(SyntaxNode node) + { + return _semanticModel.GetTypeInfo(node); + } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs new file mode 100644 index 0000000000..a6dd215c34 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Extract data portal operation methods from a type +//----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery +{ + + /// + /// Extract data portal operation methods from a type declaration + /// + internal static class OperationMethodExtractor + { + /// + /// All CSLA data portal operation attribute fully-qualified names + /// + private static readonly HashSet DataPortalAttributeNames = new HashSet + { + "Csla.CreateAttribute", + "Csla.FetchAttribute", + "Csla.InsertAttribute", + "Csla.UpdateAttribute", + "Csla.ExecuteAttribute", + "Csla.DeleteAttribute", + "Csla.DeleteSelfAttribute", + "Csla.CreateChildAttribute", + "Csla.FetchChildAttribute", + "Csla.InsertChildAttribute", + "Csla.UpdateChildAttribute", + "Csla.DeleteSelfChildAttribute", + "Csla.ExecuteChildAttribute" + }; + + private const string InjectAttributeName = "Csla.InjectAttribute"; + + /// + /// Extract all data portal operation methods from a type declaration + /// + public static IReadOnlyList ExtractOperationMethods( + DefinitionExtractionContext context, + ClassDeclarationSyntax classDeclaration) + { + var methods = new List(); + + foreach (var member in classDeclaration.Members) + { + if (member is MethodDeclarationSyntax methodDecl) + { + var operationMethod = TryExtractOperationMethod(context, methodDecl); + if (operationMethod != null) + { + methods.Add(operationMethod); + } + } + } + + return methods; + } + + private static ExtractedOperationMethod? TryExtractOperationMethod( + DefinitionExtractionContext context, + MethodDeclarationSyntax methodDecl) + { + var semanticModel = context.SemanticModel; + var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl) as IMethodSymbol; + if (methodSymbol == null) + return null; + + var operationAttributes = new List(); + + foreach (var attrData in methodSymbol.GetAttributes()) + { + var attrClassName = attrData.AttributeClass?.ToDisplayString(); + if (attrClassName != null && DataPortalAttributeNames.Contains(attrClassName)) + { + operationAttributes.Add(attrClassName); + } + } + + if (operationAttributes.Count == 0) + return null; + + var result = new ExtractedOperationMethod + { + MethodName = methodSymbol.Name, + IsAsync = IsAsyncMethod(methodSymbol) + }; + + foreach (var attr in operationAttributes) + { + result.OperationAttributeNames.Add(attr); + } + + foreach (var param in methodSymbol.Parameters) + { + var extractedParam = ExtractParameter(param); + if (extractedParam.IsInjected) + { + result.InjectParameters.Add(extractedParam); + } + else + { + result.CriteriaParameters.Add(extractedParam); + } + } + + return result; + } + + private static bool IsAsyncMethod(IMethodSymbol method) + { + if (method.ReturnType is INamedTypeSymbol namedType) + { + var displayName = namedType.ToDisplayString(); + return displayName == "System.Threading.Tasks.Task" || + displayName.StartsWith("System.Threading.Tasks.Task<"); + } + return false; + } + + private static ExtractedOperationParameter ExtractParameter(IParameterSymbol param) + { + var isInjected = false; + var allowNull = false; + + foreach (var attrData in param.GetAttributes()) + { + if (attrData.AttributeClass?.ToDisplayString() == InjectAttributeName) + { + isInjected = true; + + // Check for AllowNull = true + foreach (var namedArg in attrData.NamedArguments) + { + if (namedArg.Key == "AllowNull" && namedArg.Value.Value is bool val) + { + allowNull = val; + } + } + } + } + + return new ExtractedOperationParameter + { + Name = param.Name, + TypeFullName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + TypeDisplayName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + IsInjected = isInjected, + AllowNull = allowNull + }; + } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs new file mode 100644 index 0000000000..0dd95e4703 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Extract the definition of a type for source generation +//----------------------------------------------------------------------- + +using System.Text; +using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery +{ + + /// + /// Extract the definition of a type for which data portal interface + /// source generation is required + /// + internal static class TypeDefinitionExtractor + { + + /// + /// Extract the data needed for source generation from the syntax tree + /// + public static ExtractedTypeDefinition ExtractTypeDefinition( + DefinitionExtractionContext extractionContext, + ClassDeclarationSyntax targetTypeDeclaration) + { + var fullyQualifiedNameBuilder = new StringBuilder(); + + var definition = new ExtractedTypeDefinition + { + TypeName = GetTypeName(targetTypeDeclaration), + TypeParameters = GetTypeParameters(targetTypeDeclaration), + Namespace = GetNamespace(targetTypeDeclaration), + Scope = GetScopeDefinition(targetTypeDeclaration), + IsPartial = targetTypeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) + }; + + foreach (var containerDefinition in ContainerDefinitionsExtractor.GetContainerDefinitions(extractionContext, targetTypeDeclaration)) + { + definition.ContainerDefinitions.Add(containerDefinition); + fullyQualifiedNameBuilder.Append(containerDefinition.Name).Append('.'); + } + + foreach (var operationMethod in OperationMethodExtractor.ExtractOperationMethods(extractionContext, targetTypeDeclaration)) + { + definition.OperationMethods.Add(operationMethod); + } + + fullyQualifiedNameBuilder.Append(definition.TypeName); + definition.FullyQualifiedName = fullyQualifiedNameBuilder.ToString(); + + return definition; + } + + private static string GetNamespace(TypeDeclarationSyntax targetTypeDeclaration) + { + string nameSpace = string.Empty; + var potentialNamespaceParent = targetTypeDeclaration.Parent; + + while (potentialNamespaceParent != null && + potentialNamespaceParent is not NamespaceDeclarationSyntax && + potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) + { + potentialNamespaceParent = potentialNamespaceParent.Parent; + } + + if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) + { + nameSpace = namespaceParent.Name.ToString(); + + while (true) + { + if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) + { + break; + } + + nameSpace = $"{parent.Name}.{nameSpace}"; + namespaceParent = parent; + } + } + + return nameSpace; + } + + private static string GetScopeDefinition(TypeDeclarationSyntax targetTypeDeclaration) + { + var scopeNameBuilder = new StringBuilder(); + + foreach (var modifier in targetTypeDeclaration.Modifiers) + { + if (modifier.IsKind(SyntaxKind.PublicKeyword) || + modifier.IsKind(SyntaxKind.InternalKeyword) || + modifier.IsKind(SyntaxKind.ProtectedKeyword) || + modifier.IsKind(SyntaxKind.PrivateKeyword)) + { + scopeNameBuilder.Append(modifier.ValueText); + scopeNameBuilder.Append(' '); + } + } + + if (scopeNameBuilder.Length < 1) + { + scopeNameBuilder.Append("internal "); + } + + return scopeNameBuilder.ToString().TrimEnd(); + } + + private static string GetTypeName(TypeDeclarationSyntax targetTypeDeclaration) + { + return targetTypeDeclaration.Identifier.ToString(); + } + + private static string GetTypeParameters(TypeDeclarationSyntax targetTypeDeclaration) + { + if (targetTypeDeclaration.TypeParameterList != null) + { + return targetTypeDeclaration.TypeParameterList.ToString(); + } + return string.Empty; + } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs new file mode 100644 index 0000000000..6f769bea7f --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// The definition of a container of a type +//----------------------------------------------------------------------- + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors +{ + + /// + /// The definition of a container of a type, extracted from the syntax tree provided by Roslyn + /// + public class ExtractedContainerDefinition + { + + /// + /// The name of the container + /// + public string Name { get; set; } = string.Empty; + + /// + /// The full definition of the container for use in source generation + /// + public string FullDefinition { get; set; } = string.Empty; + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs new file mode 100644 index 0000000000..b07d894e8c --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Represents a single data portal operation method +//----------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors +{ + + /// + /// Represents a single data portal operation method extracted + /// from the target type + /// + public class ExtractedOperationMethod + { + + /// + /// The name of the method + /// + public string MethodName { get; set; } = string.Empty; + + /// + /// Whether the method returns Task (async) + /// + public bool IsAsync { get; set; } + + /// + /// The fully qualified attribute type names on this method + /// (e.g. "Csla.CreateAttribute", "Csla.FetchAttribute") + /// + public IList OperationAttributeNames { get; } = new List(); + + /// + /// The criteria (non-injected) parameters + /// + public IList CriteriaParameters { get; } = new List(); + + /// + /// The injected parameters (marked with [Inject]) + /// + public IList InjectParameters { get; } = new List(); + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs new file mode 100644 index 0000000000..f3d59fe0bb --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Represents a parameter of a data portal operation method +//----------------------------------------------------------------------- + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors +{ + + /// + /// Represents a parameter of a data portal operation method + /// + public class ExtractedOperationParameter + { + + /// + /// The parameter name + /// + public string Name { get; set; } = string.Empty; + + /// + /// The fully qualified type name of the parameter + /// + public string TypeFullName { get; set; } = string.Empty; + + /// + /// The type name suitable for use in generated code + /// (may include global:: prefix) + /// + public string TypeDisplayName { get; set; } = string.Empty; + + /// + /// Whether this is an injected parameter + /// + public bool IsInjected { get; set; } + + /// + /// For injected parameters, whether null is allowed + /// (maps to [Inject(AllowNull = true)]) + /// + public bool AllowNull { get; set; } + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs new file mode 100644 index 0000000000..fee1ca3294 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// The definition of a type with data portal operations +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors +{ + + /// + /// The definition of a type with data portal operations, + /// extracted from the syntax tree provided by Roslyn + /// + public class ExtractedTypeDefinition : IEquatable + { + + /// + /// The namespace in which the type resides + /// + public string Namespace { get; set; } = string.Empty; + + /// + /// The scope of the class (e.g. "public", "internal") + /// + public string Scope { get; set; } = "public"; + + /// + /// The name of the type, excluding any namespace + /// + public string TypeName { get; set; } = string.Empty; + + /// + /// The type parameters (e.g. "<T>") or empty string + /// + public string TypeParameters { get; set; } = string.Empty; + + /// + /// The fully qualified name of the type, including namespace + /// + public string FullyQualifiedName { get; set; } = string.Empty; + + /// + /// The container definitions for this type + /// + public IList ContainerDefinitions { get; } = new List(); + + /// + /// The data portal operation methods on this type + /// + public IList OperationMethods { get; } = new List(); + + /// + /// Whether the class is partial + /// + public bool IsPartial { get; set; } + + /// + public bool Equals(ExtractedTypeDefinition other) + { + if (other is null) return false; + return FullyQualifiedName == other.FullyQualifiedName; + } + + /// + public override bool Equals(object? obj) => obj is ExtractedTypeDefinition other && Equals(other); + + /// + public override int GetHashCode() => FullyQualifiedName?.GetHashCode() ?? 0; + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs new file mode 100644 index 0000000000..18edc012ab --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// The results of source generation +//----------------------------------------------------------------------- + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors +{ + + /// + /// The results of source generation + /// + public class GenerationResults + { + + /// + /// The fully qualified name of the generated type + /// + public string FullyQualifiedName { get; set; } = string.Empty; + + /// + /// The source code that has been generated by the builder + /// + public string GeneratedSource { get; set; } = string.Empty; + } +} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs new file mode 100644 index 0000000000..644809bdc3 --- /dev/null +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Source generator for data portal operation interface implementations +//----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Csla.Generator.DataPortalInterfaces.CSharp.Discovery; +using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Csla.Generator.DataPortalInterfaces.CSharp +{ + + /// + /// Incremental source generator that creates explicit implementations of + /// IDataPortalOperationMapping for business types with data portal operation methods. + /// + [Generator] + public class IncrementalDataPortalInterfaceGenerator : IIncrementalGenerator + { + /// + /// The short attribute names to look for on methods (syntactic check) + /// + private static readonly HashSet AttributeShortNames = new HashSet + { + "Create", "CreateAttribute", + "Fetch", "FetchAttribute", + "Insert", "InsertAttribute", + "Update", "UpdateAttribute", + "Execute", "ExecuteAttribute", + "Delete", "DeleteAttribute", + "DeleteSelf", "DeleteSelfAttribute", + "CreateChild", "CreateChildAttribute", + "FetchChild", "FetchChildAttribute", + "InsertChild", "InsertChildAttribute", + "UpdateChild", "UpdateChildAttribute", + "DeleteSelfChild", "DeleteSelfChildAttribute", + "ExecuteChild", "ExecuteChildAttribute" + }; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (s, _) => IsCandidateClass(s), + transform: static (ctx, _) => TransformCandidate(ctx) + ).Where(static m => m is not null); + + context.RegisterSourceOutput(classDeclarations, static (spc, typeDefinition) => + { + if (!typeDefinition!.IsPartial) + { + // Emit diagnostic for non-partial class + var diagnostic = Diagnostic.Create( + Diagnostics.NonPartialClassWarning, + Location.None, + typeDefinition.TypeName); + spc.ReportDiagnostic(diagnostic); + return; + } + + var builder = new DataPortalInterfaceBuilder(); + var generationResults = builder.BuildPartialTypeDefinition(typeDefinition); + + spc.AddSource( + $"{generationResults.FullyQualifiedName}.DataPortalOperations.g.cs", + SourceText.From(generationResults.GeneratedSource, Encoding.UTF8)); + }); + } + + private static bool IsCandidateClass(SyntaxNode node) + { + if (node is not ClassDeclarationSyntax classDecl) + return false; + + // Skip abstract classes - they can't be directly instantiated by the data portal + if (classDecl.Modifiers.Any(SyntaxKind.AbstractKeyword)) + return false; + + // Check if any method has a data portal operation attribute + foreach (var member in classDecl.Members) + { + if (member is MethodDeclarationSyntax methodDecl) + { + foreach (var attrList in methodDecl.AttributeLists) + { + foreach (var attr in attrList.Attributes) + { + var name = GetAttributeName(attr); + if (AttributeShortNames.Contains(name)) + return true; + } + } + } + } + + return false; + } + + private static string GetAttributeName(AttributeSyntax attr) + { + switch (attr.Name) + { + case SimpleNameSyntax simpleName: + return simpleName.Identifier.Text; + case QualifiedNameSyntax qualifiedName: + return qualifiedName.Right.Identifier.Text; + default: + return attr.Name.ToString(); + } + } + + private static ExtractedTypeDefinition? TransformCandidate(GeneratorSyntaxContext ctx) + { + var classDecl = (ClassDeclarationSyntax)ctx.Node; + var extractionContext = new DefinitionExtractionContext(ctx.SemanticModel); + var typeDefinition = TypeDefinitionExtractor.ExtractTypeDefinition(extractionContext, classDecl); + + // Verify at least one method actually has a valid CSLA attribute (semantic check) + if (typeDefinition.OperationMethods.Count == 0) + return null; + + return typeDefinition; + } + } + + /// + /// Diagnostic descriptors for the data portal interface generator + /// + internal static class Diagnostics + { + /// + /// Warning when data portal operations are found on a non-partial class + /// + public static readonly DiagnosticDescriptor NonPartialClassWarning = new DiagnosticDescriptor( + id: "CSLADP001", + title: "Data portal operation class should be partial", + messageFormat: "Class '{0}' has data portal operation methods but is not partial. The source generator cannot generate the IDataPortalOperationMapping implementation.", + category: "Csla.DataPortal", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } +} diff --git a/Source/Csla/Csla.csproj b/Source/Csla/Csla.csproj index 22eae4abb3..f36cbf531d 100644 --- a/Source/Csla/Csla.csproj +++ b/Source/Csla/Csla.csproj @@ -136,6 +136,7 @@ + diff --git a/Source/Csla/Server/DataPortalOperationNotSupportedException.cs b/Source/Csla/Server/DataPortalOperationNotSupportedException.cs new file mode 100644 index 0000000000..5a221e0ee1 --- /dev/null +++ b/Source/Csla/Server/DataPortalOperationNotSupportedException.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Exception for unhandled data portal operation dispatch +//----------------------------------------------------------------------- + +namespace Csla.Server +{ + /// + /// Exception thrown by generated IDataPortalOperationMapping code + /// when the operation/criteria combination is not matched. + /// This signals DataPortalTarget to fall back to reflection-based invocation. + /// + public class DataPortalOperationNotSupportedException : Exception + { + /// + /// Creates a new instance of the exception. + /// + /// The operation attribute type that was not matched + /// The criteria that was not matched + public DataPortalOperationNotSupportedException(Type operationType, object?[]? criteria) + : base($"No generated dispatch found for operation {operationType?.Name} with {criteria?.Length ?? 0} criteria parameters.") + { + OperationType = operationType; + Criteria = criteria; + } + + /// + /// Gets the operation attribute type that was not matched. + /// + public Type? OperationType { get; } + + /// + /// Gets the criteria that was not matched. + /// + public object?[]? Criteria { get; } + } +} diff --git a/Source/Csla/Server/DataPortalTarget.cs b/Source/Csla/Server/DataPortalTarget.cs index aa743d3296..c65453d54f 100644 --- a/Source/Csla/Server/DataPortalTarget.cs +++ b/Source/Csla/Server/DataPortalTarget.cs @@ -27,13 +27,17 @@ internal class DataPortalTarget : LateBoundObject #endif private readonly IDataPortalTarget? _target; + private readonly IDataPortalOperationMapping? _operationMapping; + private readonly ApplicationContext _applicationContext; private readonly TimeSpan _waitForIdleTimeout; private readonly DataPortalMethodNames _methodNames; - public DataPortalTarget(object obj, Configuration.CslaOptions cslaOptions) + public DataPortalTarget(object obj, ApplicationContext applicationContext, Configuration.CslaOptions cslaOptions) : base(obj) { _target = obj as IDataPortalTarget; + _operationMapping = obj as IDataPortalOperationMapping; + _applicationContext = applicationContext; _waitForIdleTimeout = TimeSpan.FromSeconds(cslaOptions.DefaultWaitForIdleTimeoutInSeconds); #if NET8_0_OR_GREATER @@ -154,10 +158,46 @@ internal void MarkOld() private async Task InvokeOperationAsync(object criteria, bool isSync) where T : DataPortalOperationAttribute { + if (_operationMapping != null) + { + try + { + var serviceProvider = _applicationContext.CurrentServiceProvider; + await _operationMapping.InvokeOperationAsync( + typeof(T), isSync, DataPortal.GetCriteriaArray(criteria), serviceProvider + ).ConfigureAwait(false); + return; + } + catch (DataPortalOperationNotSupportedException) + { + // Fall through to reflection path + } + } object?[] parameters = DataPortal.GetCriteriaArray(criteria)!; await CallMethodTryAsyncDI(isSync, parameters).ConfigureAwait(false); } + private async Task InvokeChildOperationAsync(object?[]? parameters) + where T : DataPortalChildOperationAttribute + { + if (_operationMapping != null) + { + try + { + var serviceProvider = _applicationContext.CurrentServiceProvider; + await _operationMapping.InvokeOperationAsync( + typeof(T), false, parameters, serviceProvider + ).ConfigureAwait(false); + return; + } + catch (DataPortalOperationNotSupportedException) + { + // Fall through to reflection path + } + } + await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + } + public Task CreateAsync(object criteria, bool isSync) { return InvokeOperationAsync(criteria, isSync); @@ -165,7 +205,7 @@ public Task CreateAsync(object criteria, bool isSync) public Task CreateChildAsync(params object?[]? parameters) { - return CallMethodTryAsyncDI(false, parameters); + return InvokeChildOperationAsync(parameters); } public Task FetchAsync(object criteria, bool isSync) @@ -175,7 +215,7 @@ public Task FetchAsync(object criteria, bool isSync) public Task FetchChildAsync(params object?[]? parameters) { - return CallMethodTryAsyncDI(false, parameters); + return InvokeChildOperationAsync(parameters); } public Task ExecuteAsync(object criteria, bool isSync) @@ -231,7 +271,7 @@ public async Task UpdateChildAsync(params object?[]? parameters) if (!busObj.IsNew) { // tell the object to delete itself - await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + await InvokeChildOperationAsync(parameters).ConfigureAwait(false); MarkNew(); } } @@ -240,12 +280,12 @@ public async Task UpdateChildAsync(params object?[]? parameters) if (busObj.IsNew) { // tell the object to insert itself - await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + await InvokeChildOperationAsync(parameters).ConfigureAwait(false); } else { // tell the object to update itself - await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + await InvokeChildOperationAsync(parameters).ConfigureAwait(false); } MarkOld(); } @@ -254,14 +294,14 @@ public async Task UpdateChildAsync(params object?[]? parameters) else if (Instance is ICommandObject) { // tell the object to update itself - await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + await InvokeChildOperationAsync(parameters).ConfigureAwait(false); } else { // this is an updatable collection or some other // non-BusinessBase type of object // tell the object to update itself - await CallMethodTryAsyncDI(false, parameters).ConfigureAwait(false); + await InvokeChildOperationAsync(parameters).ConfigureAwait(false); MarkOld(); } } diff --git a/Source/Csla/Server/IDataPortalOperationMapping.cs b/Source/Csla/Server/IDataPortalOperationMapping.cs new file mode 100644 index 0000000000..fb6d53ca4e --- /dev/null +++ b/Source/Csla/Server/IDataPortalOperationMapping.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Interface for source-generated data portal operation dispatch +//----------------------------------------------------------------------- + +namespace Csla.Server +{ + /// + /// Implemented by source-generated code to provide explicit + /// data portal operation method invocation without reflection. + /// + public interface IDataPortalOperationMapping + { + /// + /// Invoke a data portal operation method. + /// + /// The type of operation attribute (e.g. typeof(CreateAttribute)) + /// Whether the client is calling synchronously + /// The criteria parameters, or null/empty for no-criteria operations + /// Service provider for resolving injected dependencies + /// A Task representing the async operation + /// + /// Thrown when the operation/criteria combination is not handled by the generated code + /// + Task InvokeOperationAsync(Type operationType, bool isSync, object?[]? criteria, IServiceProvider serviceProvider); + } +} diff --git a/Source/csla.build.sln b/Source/csla.build.sln index 5a11c3c963..193b0aa026 100644 --- a/Source/csla.build.sln +++ b/Source/csla.build.sln @@ -65,6 +65,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoImplemen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.Attributes.CSharp", "Csla.Generators\cs\AutoSerialization\Csla.Generator.AutoSerialization.Attributes.CSharp\Csla.Generator.AutoSerialization.Attributes.CSharp.csproj", "{801AD1EE-6FA3-486F-9921-72342C0D1D9A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Csla.Generators", "Csla.Generators", "{4929775F-66F8-19A3-4CDD-3CBFB3383240}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs", "cs", "{C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataPortalInterfaces", "DataPortalInterfaces", "{42F5FFB7-B37F-9894-5E51-AB6A953DA79E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp", "Csla.Generators\cs\DataPortalInterfaces\Csla.Generator.DataPortalInterfaces.CSharp\Csla.Generator.DataPortalInterfaces.CSharp.csproj", "{0F476655-B155-432F-ADAC-CE9C77881950}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -3569,6 +3577,206 @@ Global {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x64.Build.0 = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x86.ActiveCfg = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Any CPU.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|AnyOS.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|AnyOS.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|ARM.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|ARM.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhone.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhone.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win32.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win32.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win64.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win64.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x64.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x64.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x86.ActiveCfg = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x86.Build.0 = Release|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x86.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Any CPU.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Any CPU.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|AnyOS.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|AnyOS.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|ARM.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|ARM.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhone.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhone.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhoneSimulator.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win32.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win32.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x64.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x64.Build.0 = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x86.ActiveCfg = Debug|Any CPU + {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3594,6 +3802,9 @@ Global {E838D906-6C66-40E7-3604-9CD97AFD47B0} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} {CD5F6C1E-8D50-5A23-2733-ABB305796A22} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} {801AD1EE-6FA3-486F-9921-72342C0D1D9A} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} + {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} = {4929775F-66F8-19A3-4CDD-3CBFB3383240} + {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} = {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} + {0F476655-B155-432F-ADAC-CE9C77881950} = {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9AB8D62-C92D-4E97-A981-46E0A5AF56D1} diff --git a/Source/csla.test.sln b/Source/csla.test.sln index ae904f5734..097426f087 100644 --- a/Source/csla.test.sln +++ b/Source/csla.test.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31612.314 @@ -84,6 +85,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerializ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.CSharp.Tests", "tests\Csla.Generator.AutoSerialization.CSharp.Tests\Csla.Generator.AutoSerialization.CSharp.Tests.csproj", "{32BD631E-431B-95B5-2E49-160A06C1F2EB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Csla.Generators", "Csla.Generators", "{4929775F-66F8-19A3-4CDD-3CBFB3383240}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs", "cs", "{C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataPortalInterfaces", "DataPortalInterfaces", "{42F5FFB7-B37F-9894-5E51-AB6A953DA79E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp", "Csla.Generators\cs\DataPortalInterfaces\Csla.Generator.DataPortalInterfaces.CSharp\Csla.Generator.DataPortalInterfaces.CSharp.csproj", "{D1ED800F-5D7F-43FA-BCAC-A0B244724103}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp.Tests", "tests\Csla.Generator.DataPortalInterfaces.CSharp.Tests\Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj", "{3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1388,6 +1401,86 @@ Global {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x64.Build.0 = Debug|Any CPU {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x86.ActiveCfg = Debug|Any CPU {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x86.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|ARM.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x64.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x86.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|ARM.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x64.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x64.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x86.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x86.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|Any CPU.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|ARM.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|ARM.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x64.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x64.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x86.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x86.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|Any CPU.Build.0 = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|ARM.ActiveCfg = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|ARM.Build.0 = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x64.ActiveCfg = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x64.Build.0 = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x86.ActiveCfg = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x86.Build.0 = Release|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|Any CPU.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|Any CPU.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|ARM.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|ARM.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x64.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x64.Build.0 = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x86.ActiveCfg = Debug|Any CPU + {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x86.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|ARM.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x64.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x64.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x86.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x86.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|ARM.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x64.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x64.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x86.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x86.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|Any CPU.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|ARM.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|ARM.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x64.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x64.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x86.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x86.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|Any CPU.Build.0 = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|ARM.ActiveCfg = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|ARM.Build.0 = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x64.ActiveCfg = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x64.Build.0 = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x86.ActiveCfg = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x86.Build.0 = Release|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|Any CPU.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|Any CPU.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|ARM.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|ARM.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x64.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x64.Build.0 = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x86.ActiveCfg = Debug|Any CPU + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1416,6 +1509,10 @@ Global {916A3B6C-8EFA-DAE5-09A8-7E38A03F033C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {3B24B172-0CD6-FD10-A771-1E90C7E69116} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {32BD631E-431B-95B5-2E49-160A06C1F2EB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} = {4929775F-66F8-19A3-4CDD-3CBFB3383240} + {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} = {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} + {D1ED800F-5D7F-43FA-BCAC-A0B244724103} = {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} + {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46D05EF4-18C0-4E24-A0A3-A11C7FFFFBBA} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj new file mode 100644 index 0000000000..a141599437 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + preview + false + enable + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs new file mode 100644 index 0000000000..fdcecdb727 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs @@ -0,0 +1,312 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using Csla.Generator.DataPortalInterfaces.CSharp.Tests.Helpers; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Tests +{ + [TestClass] + public class DataPortalInterfaceGeneratorTests : VerifyBase + { + [TestMethod("Single Create operation with no parameters")] + public async Task SingleCreateNoParams() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Single Fetch operation with one criteria parameter")] + public async Task SingleFetchWithCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Operation method with Inject parameter")] + public async Task OperationWithInjectParam() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Insert] + private async Task Insert([Inject] IDal dal) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Operation method with Inject AllowNull parameter")] + public async Task OperationWithInjectAllowNull() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface ILogger { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private async Task Fetch(int id, [Inject(AllowNull = true)] ILogger? logger) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Multiple overloads of same Fetch operation")] + public async Task MultipleOverloads() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(string name) { } + + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Method with both root and child attributes")] + public async Task RootAndChildAttributes() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + [CreateChild] + private void Create() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Async method returning Task")] + public async Task AsyncMethod() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private async Task Fetch(int id) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("File-scoped namespace")] + public async Task FileScopedNamespace() + { + var source = """ + using Csla; + + namespace TestApp; + + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Complete CRUD operations")] + public async Task CompleteCrud() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + + [Insert] + private async Task Insert([Inject] IDal dal) { await Task.CompletedTask; } + + [Update] + private async Task Update([Inject] IDal dal) { await Task.CompletedTask; } + + [DeleteSelf] + private async Task DeleteSelf([Inject] IDal dal) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Delete operation with criteria")] + public async Task DeleteWithCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Delete] + private void Delete(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Child operations")] + public async Task ChildOperations() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class LineItem : Csla.BusinessBase + { + [CreateChild] + private void CreateChild() { } + + [FetchChild] + private void FetchChild(int id) { } + + [InsertChild] + private async Task InsertChild([Inject] IDal dal) { await Task.CompletedTask; } + + [UpdateChild] + private async Task UpdateChild([Inject] IDal dal) { await Task.CompletedTask; } + + [DeleteSelfChild] + private async Task DeleteSelfChild([Inject] IDal dal) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Execute operation on command object")] + public async Task ExecuteCommand() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class MyCommand : Csla.CommandBase + { + [Execute] + private void Execute() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Nested class with data portal operations")] + public async Task NestedClass() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class Outer + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + } + """; + + await TestHelperVerify(source); + } + + private static async Task TestHelperVerify(string source, params string[]? additionalSources) + { + await TestHelper.Verify(source, additionalSources); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..20f96a6239 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + await Fetch(p0_int).ConfigureAwait(false); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..4576e85bc2 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs @@ -0,0 +1,58 @@ +//HintName: TestApp.LineItem.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class LineItem : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.CreateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + CreateChild(); + return; + } + } + else if (operationType == typeof(Csla.FetchChildAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + FetchChild(p0_int); + return; + } + } + else if (operationType == typeof(Csla.InsertChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await InsertChild(dal).ConfigureAwait(false); + return; + } + } + else if (operationType == typeof(Csla.UpdateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await UpdateChild(dal).ConfigureAwait(false); + return; + } + } + else if (operationType == typeof(Csla.DeleteSelfChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await DeleteSelfChild(dal).ConfigureAwait(false); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..15127243e7 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,58 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Create(); + return; + } + } + else if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Fetch(p0_int); + return; + } + } + else if (operationType == typeof(Csla.InsertAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await Insert(dal).ConfigureAwait(false); + return; + } + } + else if (operationType == typeof(Csla.UpdateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await Update(dal).ConfigureAwait(false); + return; + } + } + else if (operationType == typeof(Csla.DeleteSelfAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await DeleteSelf(dal).ConfigureAwait(false); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..2fe98089d2 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.DeleteAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Delete(p0_int); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..fc5fa99695 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestApp.MyCommand.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class MyCommand : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.ExecuteAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Execute(); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..2c1504add9 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,31 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Create(); + return; + } + } + else if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Fetch(p0_int); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..76aa316f0f --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is string p0_string) + { + Fetch(p0_string); + return; + } + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Fetch(p0_int); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..a0195aff5a --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: TestApp.Outer.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class Outer + { + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Fetch(p0_int); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..08e5f11c0b --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,24 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + var logger = (global::TestApp.ILogger?)serviceProvider.GetService(typeof(global::TestApp.ILogger)); + await Fetch(p0_int, logger).ConfigureAwait(false); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..ce52056eae --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,24 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.InsertAttribute)) + { + if (criteria is null or { Length: 0 }) + { + var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); + await Insert(dal).ConfigureAwait(false); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..ff998cc8d0 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,31 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Create(); + return; + } + } + else if (operationType == typeof(Csla.CreateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Create(); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..d02f5be2ab --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + Create(); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..2df0544dde --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable + +namespace TestApp +{ + public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping + { + async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + if (operationType == typeof(Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int p0_int) + { + Fetch(p0_int); + return; + } + } + throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs new file mode 100644 index 0000000000..c076969e10 --- /dev/null +++ b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Csla.Generator.DataPortalInterfaces.CSharp.Tests.Helpers +{ + public static class TestHelper where T : IIncrementalGenerator, new() + { + public static Task Verify(string source, IEnumerable? additionalSources = null) + { + var syntaxTrees = new List + { + CSharpSyntaxTree.ParseText(source) + }; + + foreach (var src in additionalSources ?? []) + { + syntaxTrees.Add(CSharpSyntaxTree.ParseText(src)); + } + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Concat([ + MetadataReference.CreateFromFile(typeof(FetchAttribute).Assembly.Location) + ]); + + var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithNullableContextOptions(NullableContextOptions.Enable) + .WithSpecificDiagnosticOptions(new Dictionary { { "CS8019", ReportDiagnostic.Suppress } }); + + CSharpCompilation compilation = CSharpCompilation.Create( + assemblyName: "Tests", + syntaxTrees: syntaxTrees, + references: references, + options: compilationOptions); + + var generator = new T().AsSourceGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); + + using (new AssertionScope()) + { + outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Should().BeEmpty(); + diagnostics.Should().BeEmpty(); + } + + return Verifier.Verify(driver).UseDirectory("Snapshots"); + } + + public static Task VerifyWithDiagnostics(string source) + { + var syntaxTrees = new List + { + CSharpSyntaxTree.ParseText(source) + }; + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Concat([ + MetadataReference.CreateFromFile(typeof(FetchAttribute).Assembly.Location) + ]); + + var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithNullableContextOptions(NullableContextOptions.Enable) + .WithSpecificDiagnosticOptions(new Dictionary { { "CS8019", ReportDiagnostic.Suppress } }); + + CSharpCompilation compilation = CSharpCompilation.Create( + assemblyName: "Tests", + syntaxTrees: syntaxTrees, + references: references, + options: compilationOptions); + + var generator = new T().AsSourceGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); + + return Verifier.Verify(driver).UseDirectory("Snapshots"); + } + } +} From 9b2db9f6a86168533fae9e22ed1d6fdf27ed2ed8 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Fri, 6 Feb 2026 21:49:00 -0600 Subject: [PATCH 3/5] #4359 Make DataPortalInterfaces generator part of Csla NuGet package only Remove standalone NuGet packaging (PackageId, IsPackable=true, Pack items) so the generator is distributed exclusively via the Csla package, not as a separate NuGet package. Co-Authored-By: Claude Opus 4.6 --- ...Csla.Generator.DataPortalInterfaces.CSharp.csproj | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj index effe9ebe2f..30a88ef59a 100644 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj +++ b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj @@ -18,25 +18,15 @@ ..\..\..\..\..\bin\packages\ Csla.Generator.DataPortalInterfaces.CSharp Csla.Generator.DataPortalInterfaces.CSharp - Csla.Generator.DataPortalInterfaces.CSharp CSLA .NET Generator DataPortalInterfaces for CSharp true True - CSLA .NET Generators - CSLA;Roslyn;Generator - - - true - false - true + false - - - all From 165bef3a5a475ebb9cc95ca2a974d736482fec6b Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Fri, 6 Feb 2026 21:49:49 -0600 Subject: [PATCH 4/5] #4359 Add *.swp to .gitignore Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6773cb61a4..82177b9ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,7 @@ Source/Csla.Xaml.Uwp/Csla.Xaml.Uwp.nuget.props Source/Csla.Xaml.Uwp/project.lock.json *.bak +*.swp /Source/Csla.Android/Csla.Android.csproj.bak /Source/Csla.Axml.Android/Csla.Axml.Android.csproj.bak /Source/Csla.Validation.Android/Csla.Validation.Android.csproj.bak From e89001df9cfb0b9a36c04f3a7a398bb8541d3625 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Fri, 6 Feb 2026 22:05:48 -0600 Subject: [PATCH 5/5] #4359 Fix flaky DashboardSuccessCounter test Replace fixed Task.Delay(500) with polling loop that checks dashboard state every 50ms with a 5s timeout, avoiding race with timer callbacks on busy CI servers. Co-Authored-By: Claude Opus 4.6 --- .../csla.netcore.test/DataPortal/DashboardTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/tests/csla.netcore.test/DataPortal/DashboardTests.cs b/Source/tests/csla.netcore.test/DataPortal/DashboardTests.cs index 0680cd945f..d44c9d1e35 100644 --- a/Source/tests/csla.netcore.test/DataPortal/DashboardTests.cs +++ b/Source/tests/csla.netcore.test/DataPortal/DashboardTests.cs @@ -57,7 +57,7 @@ public async Task DashboardSuccessCounter() var obj = CreateSimpleType(serviceProvider); - await Task.Delay(500); + await WaitForDashboard(() => dashboard.CompletedCalls >= 1); Assert.IsTrue(dashboard.FirstCall.Ticks > 0); Assert.AreEqual(1, dashboard.TotalCalls, "total"); @@ -133,6 +133,17 @@ private SimpleType FetchSimpleType(IServiceProvider serviceProvider, string idSt return dataPortal.Fetch(idString); } + private static async Task WaitForDashboard(Func condition, int timeoutMs = 5000) + { + var start = Environment.TickCount; + while (!condition()) + { + if (Environment.TickCount - start > timeoutMs) + Assert.Fail("Dashboard did not reach expected state within timeout"); + await Task.Delay(50); + } + } + private IServiceProvider InitialiseServiceProviderUsingRealDashboard() { ServiceProvider serviceProvider;