Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .claude/settings.local.json

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,4 @@ Source/Csla.Xaml.Uwp/project.lock.json
.vscode
.idea
Samples/ProjectTracker/ProjectTracker.Blazor/ProjectTracker.Blazor/PTracker.db
.claude/settings.local.json
3 changes: 3 additions & 0 deletions Source/Csla.Channels.Grpc/GrpcPortal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public async Task<DataPortalResponse> Create(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Create(objectType, criteria, context, true);

Expand Down Expand Up @@ -216,6 +217,7 @@ public async Task<DataPortalResponse> Fetch(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Fetch(objectType, criteria, context, true);

Expand Down Expand Up @@ -307,6 +309,7 @@ public async Task<DataPortalResponse> Delete(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Delete(objectType, criteria, context, true);

Expand Down
5 changes: 4 additions & 1 deletion Source/Csla.Channels.RabbitMq/RabbitMqPortal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,12 @@ private async Task<DataPortalResponse> Create(CriteriaRequest request)

var objectType = Reflection.MethodCaller.GetType(AssemblyNameTranslator.GetAssemblyQualifiedName(request.TypeName));
var context = new DataPortalContext(
_applicationContext, Deserialize<IPrincipal>(request.Principal),
_applicationContext, Deserialize<IPrincipal>(request.Principal),
true,
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await _dataPortalServer.Create(objectType, criteria, context, true);

Expand Down Expand Up @@ -207,6 +208,7 @@ private async Task<DataPortalResponse> Fetch(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await _dataPortalServer.Fetch(objectType, criteria, context, true);

Expand Down Expand Up @@ -290,6 +292,7 @@ private async Task<DataPortalResponse> Delete(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
Deserialize<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await _dataPortalServer.Delete(objectType, criteria, context, true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ internal GenerationResults BuildPartialTypeDefinition(ExtractedTypeDefinition ty
AppendBlockStart(textWriter);

AppendInvokeOperationAsyncMethod(textWriter, typeDefinition);
textWriter.WriteLine();
AppendInvokeNamedOperationAsyncMethod(textWriter, typeDefinition);

AppendBlockEnd(textWriter);
AppendContainerDefinitionClosures(textWriter, typeDefinition);
Expand Down Expand Up @@ -85,7 +87,7 @@ private void AppendTypeDefinition(IndentedTextWriter textWriter, ExtractedTypeDe
textWriter.Write(" partial class ");
textWriter.Write(typeDefinition.TypeName);
textWriter.Write(typeDefinition.TypeParameters);
textWriter.WriteLine(" : Csla.Server.IDataPortalOperationMapping");
textWriter.WriteLine(" : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping");
}

private void AppendInvokeOperationAsyncMethod(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition)
Expand Down Expand Up @@ -233,6 +235,42 @@ private static Dictionary<string, List<ExtractedOperationMethod>> GroupMethodsBy
return result;
}

private void AppendInvokeNamedOperationAsyncMethod(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition)
{
textWriter.WriteLine("async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync(");
textWriter.Indent++;
textWriter.WriteLine("string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)");
textWriter.Indent--;
AppendBlockStart(textWriter);

textWriter.WriteLine("switch (operationName)");
AppendBlockStart(textWriter);

// Collect all unique operation names across all methods
var emittedNames = new HashSet<string>();
foreach (var method in typeDefinition.OperationMethods)
{
foreach (var attr in method.OperationAttributeNames)
{
var operationName = method.GetOperationName(attr);
if (emittedNames.Add(operationName))
{
textWriter.WriteLine($"case \"{operationName}\":");
textWriter.Indent++;
AppendMethodDispatch(textWriter, method);
textWriter.WriteLine("break;");
textWriter.Indent--;
}
}
}

AppendBlockEnd(textWriter);

textWriter.WriteLine("throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria);");

AppendBlockEnd(textWriter);
}

private static string SanitizeTypeName(string typeName)
{
// Remove global:: prefix and dots, convert to safe identifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,49 @@ private static ExtractedOperationParameter ExtractParameter(IParameterSymbol par
Name = param.Name,
TypeFullName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
TypeDisplayName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
TypeMetadataName = GetOperationTypeKey(param.Type),
IsInjected = isInjected,
AllowNull = allowNull
};
}

/// <summary>
/// Computes a deterministic type key for use in operation names.
/// Arrays: elementType + "Array" (e.g. "Int32Array")
/// Generic types: MetadataName with backtick replaced, then type args (e.g. "List_1_Int32")
/// Simple types: MetadataName (e.g. "Int32", "String")
/// </summary>
internal static string GetOperationTypeKey(ITypeSymbol typeSymbol)
{
if (typeSymbol is IArrayTypeSymbol arrayType)
{
return GetOperationTypeKey(arrayType.ElementType) + "Array";
}

if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType)
{
var baseName = namedType.MetadataName.Replace('`', '_');
var typeArgs = string.Join("_", namedType.TypeArguments.Select(GetOperationTypeKey));
return baseName + "_" + typeArgs;
}

return typeSymbol.MetadataName;
}

/// <summary>
/// Gets the base operation name from an attribute fully-qualified name.
/// Strips namespace and "Attribute" suffix
/// (e.g. "Csla.FetchAttribute" -> "Fetch")
/// </summary>
internal static string GetBaseOperationName(string attributeFullName)
{
var name = attributeFullName;
var dotIndex = name.LastIndexOf('.');
if (dotIndex >= 0)
name = name.Substring(dotIndex + 1);
if (name.EndsWith("Attribute"))
name = name.Substring(0, name.Length - "Attribute".Length);
return name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//-----------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;
using Csla.Generator.DataPortalInterfaces.CSharp.Discovery;

namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors
{
Expand Down Expand Up @@ -43,5 +45,18 @@ public class ExtractedOperationMethod
/// The injected parameters (marked with [Inject])
/// </summary>
public IList<ExtractedOperationParameter> InjectParameters { get; } = new List<ExtractedOperationParameter>();

/// <summary>
/// Computes the deterministic operation name for a given attribute.
/// Format: "OperationType" for no criteria, "OperationType__Type1_Type2" for criteria.
/// </summary>
public string GetOperationName(string attributeFullName)
{
var baseName = OperationMethodExtractor.GetBaseOperationName(attributeFullName);
if (CriteriaParameters.Count == 0)
return baseName;
var typeKeys = string.Join("_", CriteriaParameters.Select(p => p.TypeMetadataName));
return baseName + "__" + typeKeys;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public class ExtractedOperationParameter
/// </summary>
public string TypeDisplayName { get; set; } = string.Empty;

/// <summary>
/// The type's metadata name used for operation name computation
/// (e.g. "Int32", "String", "List_1_Int32")
/// </summary>
public string TypeMetadataName { get; set; } = string.Empty;

/// <summary>
/// Whether this is an injected parameter
/// </summary>
Expand Down
5 changes: 4 additions & 1 deletion Source/Csla.Web.Mvc.Shared/Server/Hosts/HttpPortal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public async Task<DataPortalResponse> Create(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
DeserializeRequired<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Create(objectType, criteria, context, true);

Expand Down Expand Up @@ -112,6 +113,7 @@ public async Task<DataPortalResponse> Fetch(CriteriaRequest request)
request.ClientCulture,
request.ClientUICulture,
DeserializeRequired<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Fetch(objectType, criteria, context, true);

Expand Down Expand Up @@ -197,12 +199,13 @@ public async Task<DataPortalResponse> Delete(CriteriaRequest request)

var objectType = Reflection.MethodCaller.GetType(AssemblyNameTranslator.GetAssemblyQualifiedName(request.TypeName));
var context = new DataPortalContext(
_applicationContext,
_applicationContext,
Deserialize<IPrincipal?>(request.Principal),
true,
request.ClientCulture,
request.ClientUICulture,
DeserializeRequired<IContextDictionary>(request.ClientContext));
context.OperationName = request.OperationName;

var dpr = await dataPortalServer.Delete(objectType, criteria, context, true);

Expand Down
5 changes: 4 additions & 1 deletion Source/Csla/DataPortalClient/DataPortalProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public async virtual Task<DataPortalResult> Create([DynamicallyAccessedMembers(D
{
var request = GetBaseCriteriaRequest(criteria);
request.TypeName = AssemblyNameTranslator.GetAssemblyQualifiedName(objectType.AssemblyQualifiedName!);

request.OperationName = context.OperationName;

request = ConvertRequest(request);
var serialized = ApplicationContext.GetRequiredService<ISerializationFormatter>().Serialize(request);
serialized = await CallDataPortalServer(serialized, "create", GetRoutingToken(objectType), isSync).ConfigureAwait(false);
Expand Down Expand Up @@ -114,6 +115,7 @@ public async virtual Task<DataPortalResult> Fetch([DynamicallyAccessedMembers(Dy
{
var request = GetBaseCriteriaRequest(criteria);
request.TypeName = AssemblyNameTranslator.GetAssemblyQualifiedName(objectType.AssemblyQualifiedName!);
request.OperationName = context.OperationName;
request = ConvertRequest(request);

var serialized = ApplicationContext.GetRequiredService<ISerializationFormatter>().Serialize(request);
Expand Down Expand Up @@ -212,6 +214,7 @@ public async virtual Task<DataPortalResult> Delete([DynamicallyAccessedMembers(D
{
var request = GetBaseCriteriaRequest(criteria);
request.TypeName = AssemblyNameTranslator.GetAssemblyQualifiedName(objectType.AssemblyQualifiedName!);
request.OperationName = context.OperationName;
request = ConvertRequest(request);

var serialized = ApplicationContext.GetRequiredService<ISerializationFormatter>().Serialize(request);
Expand Down
8 changes: 8 additions & 0 deletions Source/Csla/DataPortalT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private async Task<object> DoCreateAsync([DynamicallyAccessedMembers(Dynamically
var proxy = GetDataPortalProxy(method);

var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote);
if (method != null)
dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName<CreateAttribute>(method.MethodInfo);

Server.DataPortalResult result = default!;
try
Expand Down Expand Up @@ -190,6 +192,8 @@ private async Task<object> DoFetchAsync([DynamicallyAccessedMembers(DynamicallyA

var proxy = GetDataPortalProxy(method);
var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote);
if (method != null)
dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName<FetchAttribute>(method.MethodInfo);

Server.DataPortalResult result = default!;
try
Expand Down Expand Up @@ -225,6 +229,8 @@ private async Task<object> DoExecuteAsync([DynamicallyAccessedMembers(Dynamicall
_ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor<ExecuteAttribute>(objectType, criteria, out var method);
var proxy = GetDataPortalProxy(method);
var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote);
if (method != null)
dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName<ExecuteAttribute>(method.MethodInfo);

Server.DataPortalResult result = default!;
try
Expand Down Expand Up @@ -510,6 +516,8 @@ private async Task DoDeleteAsync([DynamicallyAccessedMembers(DynamicallyAccessed
var proxy = GetDataPortalProxy(method);

var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote);
if (method != null)
dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName<DeleteAttribute>(method.MethodInfo);
try
{
var result = await _cache.GetDataPortalResultAsync(objectType, criteria, DataPortalOperations.Delete,
Expand Down
8 changes: 8 additions & 0 deletions Source/Csla/Server/DataPortalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class DataPortalContext : Serialization.Mobile.IMobileObject, IUseApplica
/// </summary>
public string ClientUICulture { get; private set; }

/// <summary>
/// Gets or sets the operation name for name-based dispatch.
/// Null when sent from legacy clients.
/// </summary>
public string? OperationName { get; set; }

internal IContextDictionary? ClientContext { get; private set; }

/// <summary>
Expand Down Expand Up @@ -148,6 +154,7 @@ void Serialization.Mobile.IMobileObject.GetState(Serialization.Mobile.Serializat
info.AddValue("clientCulture", ClientCulture);
info.AddValue("clientUICulture", ClientUICulture);
info.AddValue("isRemotePortal", IsRemotePortal);
info.AddValue("operationName", OperationName);
}

void Serialization.Mobile.IMobileObject.GetChildren(Serialization.Mobile.SerializationInfo info, Serialization.Mobile.MobileFormatter formatter)
Expand All @@ -161,6 +168,7 @@ void Serialization.Mobile.IMobileObject.SetState(Serialization.Mobile.Serializat
ClientCulture = info.GetValue<string>("clientCulture")!;
ClientUICulture = info.GetValue<string>("clientUICulture")!;
IsRemotePortal = info.GetValue<bool>("isRemotePortal");
OperationName = info.GetValue<string>("operationName");
}

void Serialization.Mobile.IMobileObject.SetChildren(Serialization.Mobile.SerializationInfo info, Serialization.Mobile.MobileFormatter formatter)
Expand Down
Loading
Loading