diff --git a/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs b/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs
new file mode 100644
index 0000000000..3769fe8a76
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.ServiceModel;
+
+namespace Csla.Channels.Wcf.Client
+{
+ ///
+ /// Represents the WCF service contract that is used for communication between the data portal client and server.
+ ///
+ [ServiceContract]
+ public interface IWcfPortal
+ {
+ ///
+ /// Asynchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// A task containing the response from the remote data portal.
+ ///
+ [OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
+ Task InvokeAsync(WcfRequest request);
+
+ ///
+ /// Synchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// The response from the remote data portal.
+ ///
+ [OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
+ WcfResponse Invoke(WcfRequest request);
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs b/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs
new file mode 100644
index 0000000000..30be9a2501
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using Csla.Configuration;
+using Csla.DataPortalClient;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Csla.Channels.Wcf.Client
+{
+ ///
+ /// Provides extension methods that can be used to configure the client side data portal to use the WCF channel.
+ ///
+ public static class WcfClientExtensions
+ {
+ ///
+ /// Configures the client side data portal to use the WCF channel.
+ ///
+ ///
+ /// The client side data portal options that the WCF configuration will be applied to.
+ ///
+ ///
+ /// An optional action that will be invoked to set options for the .
+ ///
+ ///
+ /// The with the WCF configuration applied.
+ ///
+ ///
+ /// is .
+ ///
+ public static DataPortalClientOptions UseWcfProxy(this DataPortalClientOptions config, Action? setOptions)
+ {
+ if (config is null)
+ throw new ArgumentNullException(nameof(config));
+
+ var proxyOptions = new WcfProxyOptions();
+
+ setOptions?.Invoke(proxyOptions);
+
+ config.Services.AddSingleton(_ => proxyOptions);
+ config.Services.AddTransient(provider =>
+ {
+ var applicationContext = provider.GetRequiredService();
+ var options = provider.GetRequiredService();
+ var dataPortalOptions = provider.GetRequiredService();
+ var wcfProxy = new WcfProxy(applicationContext, options, dataPortalOptions);
+ return wcfProxy;
+ });
+
+ return config;
+ }
+
+ ///
+ /// Configures the client side data portal to use the WCF channel.
+ ///
+ ///
+ /// The client side data portal options that the WCF configuration will be applied to.
+ ///
+ ///
+ /// The with the WCF configuration applied.
+ ///
+ ///
+ /// is .
+ ///
+ public static DataPortalClientOptions UseWcfProxy(this DataPortalClientOptions config) => config.UseWcfProxy(null);
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs
new file mode 100644
index 0000000000..0a7faba146
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.ServiceModel;
+using System.ServiceModel.Channels;
+
+namespace Csla.Channels.Wcf.Client
+{
+ ///
+ /// Represents a WCF client that is used by the to communicate with a remote data portal.
+ ///
+ ///
+ /// The WCF binding that is used to communicate with the remote WCF data portal service.
+ ///
+ ///
+ /// The endpoint address that is used to communicate with the remote WCF data portal service.
+ ///
+ internal class WcfPortalClient(Binding binding, EndpointAddress address) : ClientBase(binding, address), IWcfPortal
+ {
+ ///
+ /// Asynchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// A task containing the response from the remote data portal.
+ ///
+ public Task InvokeAsync(WcfRequest request) => Channel.InvokeAsync(request);
+
+ ///
+ /// Synchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// The response from the remote data portal.
+ ///
+ public WcfResponse Invoke(WcfRequest request) => Channel.Invoke(request);
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs
new file mode 100644
index 0000000000..d9c28c3e66
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.ServiceModel;
+using Csla.Configuration;
+using Csla.DataPortalClient;
+
+namespace Csla.Channels.Wcf.Client
+{
+ ///
+ /// Represents a that communicates with a remote data portal using WCF.
+ ///
+ ///
+ /// The client side context for the data portal.
+ ///
+ ///
+ /// The options that are used to configure the data portal proxy.
+ ///
+ ///
+ /// The options that are used to configure the client side data portal.
+ ///
+ ///
+ /// is .
+ ///
+ public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions wcfProxyOptions, DataPortalOptions dataPortalOptions) : DataPortalProxy(applicationContext)
+ {
+ protected WcfProxyOptions _options = wcfProxyOptions ?? throw new ArgumentNullException(nameof(wcfProxyOptions));
+ protected string? _versionRoutingTag = dataPortalOptions.VersionRoutingTag;
+
+ ///
+ /// Gets the URL address for the data portal server used by this proxy instance.
+ ///
+ public override string DataPortalUrl => _options.DataPortalUrl;
+
+ protected override async Task CallDataPortalServer(byte[] serialized, string operation, string? routingToken, bool isSync)
+ {
+ var client = new WcfPortalClient(_options.Binding, new EndpointAddress(_options.DataPortalUrl));
+
+ var wcfRequest = new WcfRequest
+ {
+ Operation = CreateOperationTag(operation, _versionRoutingTag, routingToken),
+ Body = serialized
+ };
+
+ try
+ {
+ var response = isSync ? client.Invoke(wcfRequest) : await client.InvokeAsync(wcfRequest);
+
+ client.Close();
+
+ return response.Body;
+ }
+ catch (Exception)
+ {
+ client.Abort();
+ throw;
+ }
+ }
+
+ internal async Task RouteMessage(WcfRequest request)
+ {
+ var client = new WcfPortalClient(_options.Binding, new EndpointAddress(_options.DataPortalUrl));
+
+ try
+ {
+ var response = await client.InvokeAsync(request);
+
+ client.Close();
+
+ return response;
+ }
+ catch (Exception)
+ {
+ client.Abort();
+ throw;
+ }
+ }
+
+ private string CreateOperationTag(string operation, string? versionToken, string? routingToken)
+ {
+ if (!string.IsNullOrWhiteSpace(versionToken) || !string.IsNullOrWhiteSpace(routingToken))
+ return $"{operation}/{routingToken}-{versionToken}";
+ return operation;
+ }
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Client/WcfProxyOptions.cs b/Source/Csla.Channels.Wcf/Client/WcfProxyOptions.cs
new file mode 100644
index 0000000000..7bdd92586d
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Client/WcfProxyOptions.cs
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.ServiceModel;
+using System.ServiceModel.Channels;
+
+namespace Csla.Channels.Wcf.Client
+{
+ ///
+ /// Represents options that are used to configure a WCF data portal proxy.
+ ///
+ public class WcfProxyOptions
+ {
+ ///
+ /// Gets or sets the WCF binding that should be used to communicate with the remote WCF data portal service.
+ ///
+ public Binding Binding { get; set; } = new BasicHttpBinding();
+
+ ///
+ /// Gets or sets the URL that should be used to communicate with the remote WCF data portal service.
+ ///
+ public string DataPortalUrl { get; set; } = "http://localhost";
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj b/Source/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj
new file mode 100644
index 0000000000..a1df404297
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net462;net472;net48;net8.0;net9.0;net10.0
+ Latest
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Csla.Channels.Wcf/Server/IWcfPortalServer.cs b/Source/Csla.Channels.Wcf/Server/IWcfPortalServer.cs
new file mode 100644
index 0000000000..f9ed26ec53
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/IWcfPortalServer.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+#if NETFRAMEWORK
+using System.ServiceModel;
+#else
+using CoreWCF;
+#endif
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Represents the WCF service contract that is used for communication between the data portal client and server.
+ ///
+ [ServiceContract]
+ public interface IWcfPortalServer
+ {
+ ///
+ /// Asynchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// As task containing the response from the remote data portal.
+ ///
+ [OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
+ Task InvokeAsync(WcfRequest request);
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortal.cs b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs
new file mode 100644
index 0000000000..fff17de1bf
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs
@@ -0,0 +1,381 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using Csla.Channels.Wcf.Client;
+using Csla.Core;
+using Csla.Properties;
+using Csla.Serialization;
+using Csla.Serialization.Mobile;
+using Csla.Server;
+using Csla.Server.Hosts.DataPortalChannel;
+using System.Runtime.Serialization;
+using System.Security.Principal;
+
+#if NETFRAMEWORK
+using System.ServiceModel;
+#else
+using CoreWCF;
+#endif
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Represents a server side data portal that is called via WCF.
+ ///
+ ///
+ /// The server side data portal that processes the data portal requests.
+ ///
+ ///
+ /// The server side context for the data portal.
+ ///
+ ///
+ /// or is .
+ ///
+ [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
+ public class WcfPortal(IDataPortalServer dataPortal, ApplicationContext applicationContext) : IWcfPortalServer
+ {
+ private readonly IDataPortalServer _dataPortalServer = dataPortal ?? throw new ArgumentNullException(nameof(dataPortal));
+ private readonly ApplicationContext _applicationContext = applicationContext ?? throw new ArgumentNullException(nameof(applicationContext));
+
+ ///
+ /// Gets a dictionary containing the URLs for each
+ /// data portal route, where each key is the
+ /// routing tag identifying the route URL.
+ ///
+ protected static Dictionary RoutingTagUrls = [];
+
+ ///
+ /// Asynchronously invokes an operation on the remote data portal.
+ ///
+ ///
+ /// The request that contains the name and parameters necessary to invoke the data portal operation.
+ ///
+ ///
+ /// As task containing the response from the remote data portal.
+ ///
+ public async Task InvokeAsync(WcfRequest request)
+ {
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ var operation = request.Operation;
+ if (operation.Contains("/"))
+ {
+ var temp = operation.Split('/');
+ return await RouteMessage(temp[0], temp[1], request);
+ }
+ else
+ {
+ return await InvokePortal(operation, request.Body).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Routes a message using tag based data portal operations.
+ ///
+ ///
+ /// Name of the data portal operation to perform
+ ///
+ ///
+ /// Routing tag from caller
+ ///
+ ///
+ /// Request message
+ ///
+ ///
+ /// or is .
+ ///
+ ///
+ /// is , empty or only consists of white spaces.
+ ///
+ ///
+ /// is .
+ ///
+ protected virtual async Task RouteMessage(string operation, string routingTag, WcfRequest request)
+ {
+ if (string.IsNullOrWhiteSpace(operation))
+ throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operation)), nameof(operation));
+ if (routingTag is null)
+ throw new ArgumentNullException(nameof(routingTag));
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ if (RoutingTagUrls.TryGetValue(routingTag, out string? route) && route != "localhost")
+ {
+ var portalOptions = _applicationContext.GetRequiredService();
+
+ var routerBinding = portalOptions.RouterBinding ?? throw new InvalidOperationException($"The {nameof(WcfPortalOptions)}.{nameof(WcfPortalOptions.RouterBinding)} property must not be null in order to use data portal routing.");
+
+ var proxyOptions = new WcfProxyOptions
+ {
+ Binding = routerBinding,
+ DataPortalUrl = $"{route}?operation={operation}",
+ };
+
+ var dataPortalOptions = _applicationContext.GetRequiredService();
+ var proxy = new WcfProxy(_applicationContext, proxyOptions, dataPortalOptions);
+ var clientRequest = new WcfRequest
+ {
+ Body = request.Body,
+ Operation = operation
+ };
+
+ return await proxy.RouteMessage(clientRequest);
+ }
+ else
+ {
+ return await InvokePortal(operation, request.Body).ConfigureAwait(false);
+ }
+ }
+
+ private async Task InvokePortal(string operation, byte[] requestData)
+ {
+ var request = DeserializeRequired(requestData);
+ var result = await CallPortal(operation, request);
+ var buffer = _applicationContext.GetRequiredService().Serialize(result);
+
+ return new WcfResponse { Body = buffer };
+ }
+
+ private async Task CallPortal(string operation, object request)
+ {
+ return operation switch
+ {
+ "create" => await Create((CriteriaRequest)request).ConfigureAwait(false),
+ "fetch" => await Fetch((CriteriaRequest)request).ConfigureAwait(false),
+ "update" => await Update((UpdateRequest)request).ConfigureAwait(false),
+ "delete" => await Delete((CriteriaRequest)request).ConfigureAwait(false),
+ _ => throw new InvalidOperationException(operation)
+ };
+ }
+
+ ///
+ /// Create and initialize an existing business object.
+ ///
+ /// The request parameter object.
+ /// is .
+ public async Task Create(CriteriaRequest request)
+ {
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ var result = new DataPortalResponse();
+ try
+ {
+ request = ConvertRequest(request);
+
+ // unpack criteria data into object
+ var criteria = GetCriteria(_applicationContext, request.CriteriaData);
+ if (criteria is DataPortalClient.PrimitiveCriteria primitiveCriteria)
+ {
+ criteria = primitiveCriteria.Value;
+ }
+
+ var objectType = Reflection.MethodCaller.GetType(AssemblyNameTranslator.GetAssemblyQualifiedName(request.TypeName));
+ var context = new DataPortalContext(
+ _applicationContext, Deserialize(request.Principal),
+ true,
+ request.ClientCulture,
+ request.ClientUICulture,
+ DeserializeRequired(request.ClientContext));
+ context.OperationName = request.OperationName;
+
+ var dpr = await _dataPortalServer.Create(objectType, criteria, context, true);
+
+ if (dpr.Error != null)
+ result.ErrorData = _applicationContext.CreateInstanceDI(dpr.Error);
+ result.ObjectData = _applicationContext.GetRequiredService().Serialize(dpr.ReturnObject);
+ }
+ finally
+ {
+ result = ConvertResponse(result);
+ }
+ return result;
+ }
+
+ ///
+ /// Get an existing business object.
+ ///
+ /// The request parameter object.
+ /// is .
+ public async Task Fetch(CriteriaRequest request)
+ {
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ var result = new DataPortalResponse();
+ try
+ {
+ request = ConvertRequest(request);
+
+ // unpack criteria data into object
+ var criteria = GetCriteria(_applicationContext, request.CriteriaData);
+ if (criteria is DataPortalClient.PrimitiveCriteria primitiveCriteria)
+ {
+ criteria = primitiveCriteria.Value;
+ }
+
+ var objectType = Reflection.MethodCaller.GetType(AssemblyNameTranslator.GetAssemblyQualifiedName(request.TypeName));
+ var context = new DataPortalContext(
+ _applicationContext, Deserialize(request.Principal),
+ true,
+ request.ClientCulture,
+ request.ClientUICulture,
+ DeserializeRequired(request.ClientContext));
+ context.OperationName = request.OperationName;
+
+ var dpr = await _dataPortalServer.Fetch(objectType, criteria, context, true);
+
+ if (dpr.Error != null)
+ result.ErrorData = _applicationContext.CreateInstanceDI(dpr.Error);
+ result.ObjectData = _applicationContext.GetRequiredService().Serialize(dpr.ReturnObject);
+ }
+ finally
+ {
+ result = ConvertResponse(result);
+ }
+ return result;
+ }
+
+ ///
+ /// Update a business object.
+ ///
+ /// The request parameter object.
+ /// is .
+ public async Task Update(UpdateRequest request)
+ {
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ var result = new DataPortalResponse();
+ try
+ {
+ request = ConvertRequest(request);
+ // unpack object
+ var obj = GetCriteria(_applicationContext, request.ObjectData) ?? throw new InvalidOperationException(Resources.ObjectToBeUpdatedCouldNotBeDeserialized);
+
+ var context = new DataPortalContext(
+ _applicationContext, Deserialize(request.Principal),
+ true,
+ request.ClientCulture,
+ request.ClientUICulture,
+ DeserializeRequired(request.ClientContext));
+
+ var dpr = await _dataPortalServer.Update((ICslaObject)obj, context, true);
+
+ if (dpr.Error != null)
+ result.ErrorData = _applicationContext.CreateInstanceDI(dpr.Error);
+
+ result.ObjectData = _applicationContext.GetRequiredService().Serialize(dpr.ReturnObject);
+ }
+ finally
+ {
+ result = ConvertResponse(result);
+ }
+ return result;
+ }
+
+ ///
+ /// Delete a business object.
+ ///
+ /// The request parameter object.
+ /// is .
+ public async Task Delete(CriteriaRequest request)
+ {
+ if (request is null)
+ throw new ArgumentNullException(nameof(request));
+
+ var result = new DataPortalResponse();
+ try
+ {
+ request = ConvertRequest(request);
+
+ // unpack criteria data into object
+ var criteria = GetCriteria(_applicationContext, request.CriteriaData);
+ if (criteria is DataPortalClient.PrimitiveCriteria primitiveCriteria)
+ {
+ criteria = primitiveCriteria.Value;
+ }
+
+ var objectType = Reflection.MethodCaller.GetType(AssemblyNameTranslator.GetAssemblyQualifiedName(request.TypeName));
+ var context = new DataPortalContext(
+ _applicationContext, Deserialize(request.Principal),
+ true,
+ request.ClientCulture,
+ request.ClientUICulture,
+ DeserializeRequired(request.ClientContext));
+ context.OperationName = request.OperationName;
+
+ var dpr = await _dataPortalServer.Delete(objectType, criteria, context, true);
+
+ if (dpr.Error != null)
+ result.ErrorData = _applicationContext.CreateInstanceDI(dpr.Error);
+ result.ObjectData = _applicationContext.GetRequiredService().Serialize(dpr.ReturnObject);
+ }
+ finally
+ {
+ result = ConvertResponse(result);
+ }
+ return result;
+ }
+
+ #region Criteria
+
+ private static object GetCriteria(ApplicationContext applicationContext, byte[] criteriaData)
+ {
+ return applicationContext.GetRequiredService().Deserialize(criteriaData) ?? throw new SerializationException(Resources.ServerSideDataPortalRequestDeserializationFailed);
+ }
+
+ #endregion Criteria
+
+ #region Extension Method for Requests
+
+ ///
+ /// Override to convert the request data before it
+ /// is transferred over the network.
+ ///
+ /// Request object.
+ protected virtual UpdateRequest ConvertRequest(UpdateRequest request)
+ {
+ return request;
+ }
+
+ ///
+ /// Override to convert the request data before it
+ /// is transferred over the network.
+ ///
+ /// Request object.
+ protected virtual CriteriaRequest ConvertRequest(CriteriaRequest request)
+ {
+ return request;
+ }
+
+ ///
+ /// Override to convert the response data after it
+ /// comes back from the network.
+ ///
+ /// Response object.
+ protected virtual DataPortalResponse ConvertResponse(DataPortalResponse response)
+ {
+ return response;
+ }
+
+ #endregion Extension Method for Requests
+
+ private T? Deserialize(byte[] data)
+ {
+ var deserializedData = _applicationContext.GetRequiredService().Deserialize(data);
+ return (T?)deserializedData;
+ }
+
+ private T DeserializeRequired(byte[] data)
+ {
+ return Deserialize(data) ?? throw new SerializationException(Resources.ServerSideDataPortalRequestDeserializationFailed);
+ }
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortalHost.cs b/Source/Csla.Channels.Wcf/Server/WcfPortalHost.cs
new file mode 100644
index 0000000000..671082d4e3
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/WcfPortalHost.cs
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+#if NETFRAMEWORK
+using Csla.Server;
+using System.ServiceModel;
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Represents a custom that is used to provide WCF configuration to the server side WCF
+ /// data portal via dependency injection.
+ ///
+ public class WcfPortalHost : ServiceHost
+ {
+ ///
+ /// Creates an instance of .
+ ///
+ ///
+ /// The server side data portal that processes the data portal requests.
+ ///
+ ///
+ /// The server side context for the data portal.
+ ///
+ ///
+ /// The type that implements the WCF service contract that is being hosted.
+ ///
+ ///
+ /// The base address where the WCF service is being hosted.
+ ///
+ public WcfPortalHost(IDataPortalServer dataPortal, ApplicationContext applicationContext, Type serviceType, params Uri[] baseAddresses)
+ : base(serviceType, baseAddresses)
+ {
+ foreach (var cd in ImplementedContracts.Values)
+ {
+ cd.Behaviors.Add(new WcfPortalInstanceProvider(dataPortal, applicationContext));
+ }
+ }
+ }
+}
+#endif
diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs b/Source/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs
new file mode 100644
index 0000000000..ea297d6365
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+#if NETFRAMEWORK
+using Csla.Server;
+using System.ServiceModel;
+using System.ServiceModel.Channels;
+using System.ServiceModel.Description;
+using System.ServiceModel.Dispatcher;
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Represents an object that is used to pass context from a dependency injection container into an instance of
+ /// .
+ ///
+ ///
+ /// The service side data portal that processes the data portal requests.
+ ///
+ ///
+ /// The server side context for the data portal.
+ ///
+ internal class WcfPortalInstanceProvider(IDataPortalServer dataPortal, ApplicationContext applicationContext) : IInstanceProvider, IContractBehavior
+ {
+ #region Implementation of IInstanceProvider
+
+ ///
+ /// Creates a new instance of .
+ ///
+ ///
+ /// The context information for a WCF service instance.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public object GetInstance(InstanceContext instanceContext) => new WcfPortal(dataPortal, applicationContext);
+
+ ///
+ /// Creates a new instance of .
+ ///
+ ///
+ /// The context information for a WCF service instance.
+ ///
+ ///
+ /// The message that triggered the creation of a service object.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public object GetInstance(InstanceContext instanceContext, Message message) => GetInstance(instanceContext);
+
+ #region Unused
+ public void ReleaseInstance(InstanceContext instanceContext, object instance) { }
+ #endregion
+
+ #endregion
+
+ #region IContractBehavior
+
+ ///
+ /// Sets the to this instance of .
+ ///
+ ///
+ /// The contract description to be modified.
+ ///
+ ///
+ /// The endpoint to modify.
+ ///
+ ///
+ /// The dispatch runtime that controls service execution.
+ ///
+ public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) =>
+ dispatchRuntime.InstanceProvider = this;
+
+ #region Unused
+ public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
+ public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
+ public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) { }
+ #endregion
+
+ #endregion
+ }
+}
+#endif
diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs b/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs
new file mode 100644
index 0000000000..390823a76f
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+#if NETFRAMEWORK
+using System.ServiceModel;
+using System.ServiceModel.Channels;
+#else
+using CoreWCF;
+using CoreWCF.Channels;
+#endif
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Represents options that are used to configure a WCF data portal server.
+ ///
+ public class WcfPortalOptions
+ {
+ ///
+ /// Gets or sets the WCF binding that should be used to host the remote WCF data portal service.
+ ///
+ public Binding Binding { get; set; } = new BasicHttpBinding();
+
+ // The Binding property for server configuration will be System.ServiceModel.Channels.Binding for .NET Framework
+ // target frameworks or CoreWCF.Channels.Binding for other target frameworks. However, the client binding is always
+ // System.ServiceModel.Channels.Binding which means the binding used for routing must be explicitly set in a
+ // different property for modern .net targets.
+#if NETFRAMEWORK
+ internal Binding? RouterBinding => Binding;
+#else
+ ///
+ ///
+ /// Gets or sets the WCF client binding that will be used to route calls to other data portal services.
+ ///
+ ///
+ /// This property is by default, but is required in order to enable routing.
+ ///
+ ///
+ public System.ServiceModel.Channels.Binding? RouterBinding { get; set; }
+#endif
+ ///
+ /// Gets or sets the URL that should be used to host the remote WCF data portal service.
+ ///
+ public string DataPortalUrl { get; set; }
+#if NETFRAMEWORK
+ = "http://localhost";
+#else
+ ="/";
+#endif
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs b/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs
new file mode 100644
index 0000000000..92a2f9cca3
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using Csla.Configuration;
+using Csla.Server;
+using Microsoft.Extensions.DependencyInjection;
+
+#if !NETFRAMEWORK
+using CoreWCF.Configuration;
+#endif
+
+namespace Csla.Channels.Wcf.Server
+{
+ ///
+ /// Provides extension methods that can be used to configure the server side data portal to use the WCF channel.
+ ///
+ public static class WcfServerExtensions
+ {
+ ///
+ /// Configures the server side data portal to use the WCF channel.
+ ///
+ ///
+ /// The server side data portal options that the WCF configuration will be applied to.
+ ///
+ ///
+ /// An optional action that will be invoked to set options for the .
+ ///
+ ///
+ /// The with the WCF configuration applied.
+ ///
+ ///
+ /// is .
+ ///
+ public static DataPortalServerOptions UseWcfPortal(this DataPortalServerOptions config, Action? setOptions)
+ {
+ if (config is null)
+ throw new ArgumentNullException(nameof(config));
+
+ var portalOptions = new WcfPortalOptions();
+
+ setOptions?.Invoke(portalOptions);
+
+ config.Services.AddSingleton(_ => portalOptions);
+
+#if NETFRAMEWORK
+ config.Services.AddSingleton(provider =>
+ {
+ var applicationContext = provider.GetRequiredService();
+ var dataPortal = provider.GetRequiredService();
+ var options = provider.GetRequiredService();
+ var host = new WcfPortalHost(dataPortal, applicationContext, typeof(WcfPortal));
+ host.AddServiceEndpoint(typeof(IWcfPortalServer), options.Binding, options.DataPortalUrl);
+ return host;
+ });
+#else
+ config.Services.AddTransient();
+#endif
+ return config;
+ }
+
+ ///
+ /// Configures the server side data portal to use the WCF channel.
+ ///
+ ///
+ /// The server side data portal options that the WCF configuration will be applied to.
+ ///
+ ///
+ /// The with the WCF configuration applied.
+ ///
+ ///
+ /// is .
+ ///
+ public static DataPortalServerOptions UseWcfPortal(this DataPortalServerOptions config) => config.UseWcfPortal(null);
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/WcfRequest.cs b/Source/Csla.Channels.Wcf/WcfRequest.cs
new file mode 100644
index 0000000000..e36d25d8e6
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/WcfRequest.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.Runtime.Serialization;
+using Csla.Core;
+
+namespace Csla.Channels.Wcf
+{
+ ///
+ /// Represents a request message that is used to invoke a remote data portal operation through a WCF channel.
+ ///
+ [DataContract]
+ public class WcfRequest : MobileObject
+ {
+ ///
+ /// Gets or sets the name of the data portal operation to invoke.
+ ///
+ [DataMember]
+ public string Operation { get; set; } = "";
+
+ ///
+ /// Gets or sets the request body that contains the criteria for the data portal operation.
+ ///
+ [DataMember]
+ public byte[] Body { get; set; } = [];
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/WcfResponse.cs b/Source/Csla.Channels.Wcf/WcfResponse.cs
new file mode 100644
index 0000000000..0d247ad6db
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/WcfResponse.cs
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Provides consistent context information between the client
+//-----------------------------------------------------------------------
+
+using System.Runtime.Serialization;
+
+namespace Csla.Channels.Wcf
+{
+ ///
+ /// Represents a response message from a data portal operation that is returned by the data portal server through a
+ /// WCF channel.
+ ///
+ [DataContract]
+ public class WcfResponse
+ {
+ ///
+ /// Gets or sets the body of the response that contains the result of the data portal operation that was called.
+ ///
+ [DataMember]
+ public byte[] Body { get; set; } = [];
+ }
+}
diff --git a/Source/Csla.Channels.Wcf/readme.md b/Source/Csla.Channels.Wcf/readme.md
new file mode 100644
index 0000000000..e16ba31e99
--- /dev/null
+++ b/Source/Csla.Channels.Wcf/readme.md
@@ -0,0 +1,81 @@
+# Windows Communication Foundation (WCF) Data Portal Channel
+
+This data portal channel uses WCF as a means of configuring communication between data portal clients and servers.
+
+WCF itself allows for the use of a wide array of communication protocols for different network topologies and this data portal channel can be configured to support any protocol that WCF supports.
+
+The client is implemented using the System.ServiceModel libraries that are included in .NET Framework and can be included via package references in modern .net.
+
+The server is implemented using the System.Servicemodel libraries in .NET Framework when using a .NET Framework target (i.e. net462, net472, net48). When targeting modern .net (i.e. net8.0+) the server is implemented using the CoreWCF packages that are designed to bring legacy WCF functionality to modern ASP.NET Core. Because there are fundamental differences between how WCF is hosted on the .NET Framework and how CoreWCF extends ASP.NET Core to host WCF services, a WCF data portal server must be hosted differently depending on the target framework of your server side project.
+
+## Modern .net Hosting
+
+CoreWCF allows for the hosting of WCF endpoints along side other elements of web applications and is configured in a similar way.
+
+Here is an example Program.cs file to demonstrate an ASP.NET Core hosted data portal:
+```c#
+using CoreWCF.Configuration;
+using CoreWCF.Description;
+using Csla.Configuration;
+using Csla.Channels.Wcf.Server;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddServiceModelServices();
+builder.Services.AddSingleton();
+builder.Services
+ .AddCsla(csla => csla
+ .DataPortal(dp => dp
+ .AddServerSideDataPortal(p => p
+ .UseWcfPortal())));
+
+var app = builder.Build();
+
+var wcfPortalOptions = app.Services.GetRequiredService();
+
+app.UseServiceModel(builder =>
+{
+ builder.AddService()
+ .AddServiceEndpoint(wcfPortalOptions.Binding, wcfPortalOptions.DataPortalUrl);
+});
+
+app.Run();
+```
+
+## .NET Framework Hosting
+
+WCF Services in .NET Framework can be self hosted via the System.ServiceModel.ServiceHost class or through IIS via a .svc file. Self hosted services offer the most flexibility and are the default way to host the data portal service. It is likely that the service can be hosted via IIS by using a [ServiceHostFactory](https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/extending-hosting-using-servicehostfactory) to handle dependency injection though.
+
+Here is an example Program.cs file to demonstrate a self hosted WCF data portal:
+```c#
+using Csla.Channels.Wcf.Server;
+using Csla.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+var services = new ServiceCollection();
+services
+ .AddCsla(csla => csla
+ .DataPortal(dp => dp
+ .AddServerSideDataPortal(p => p
+ .UseWcfPortal())));
+
+var provider = services.BuildServiceProvider();
+
+var wcfHost = provider.GetRequiredService();
+wcfHost.Open();
+
+Console.ReadLine();
+```
+
+## Client Configuration
+
+The client configuration is the same regardless of target framework. Additionally, the client is agnostic of the server's target framework. In other words, a .NET Framework client can communicate with a ASP.NET Core hosted server and a modern .net client can communicate with a .NET Framework hosted server.
+
+Here is an example client configuration:
+```c#
+services
+ .AddCsla(csla => csla
+ .DataPortal(dp => dp
+ .AddClientSideDataPortal(c => c
+ .UseWcfProxy())));
+```
diff --git a/Source/csla.build.sln b/Source/csla.build.sln
index 193b0aa026..fc8f729bbf 100644
--- a/Source/csla.build.sln
+++ b/Source/csla.build.sln
@@ -73,6 +73,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataPortalInterfaces", "Dat
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
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Channels.Wcf", "Csla.Channels.Wcf\Csla.Channels.Wcf.csproj", "{50A9114F-5D22-43EF-43CF-559234473966}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -3777,6 +3779,206 @@ Global
{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
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.AppStore|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.CD_ROM|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|AnyOS.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|AnyOS.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|ARM.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Win32.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Win32.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Win64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Win64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x86.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.DVD-5|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|AnyOS.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|AnyOS.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|iPhone.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|iPhone.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|iPhoneSimulator.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Mixed Platforms.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Win32.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Win32.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Win64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|Win64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.SingleImage|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Any CPU.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Any CPU.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|AnyOS.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|AnyOS.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|ARM.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|ARM.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|iPhone.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|iPhone.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Win32.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Win32.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Win64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Win64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x86.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3805,6 +4007,7 @@ Global
{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}
+ {50A9114F-5D22-43EF-43CF-559234473966} = {580424DE-C982-4E80-A948-EF42FC3AB110}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9AB8D62-C92D-4E97-A981-46E0A5AF56D1}
diff --git a/Source/csla.test.sln b/Source/csla.test.sln
index 097426f087..e21ca56c88 100644
--- a/Source/csla.test.sln
+++ b/Source/csla.test.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.0.31612.314
+VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{207BA2D1-EB2E-45A0-9F82-4B7975EDD8C6}"
ProjectSection(SolutionItems) = preProject
@@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05
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
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Channels.Wcf", "Csla.Channels.Wcf\Csla.Channels.Wcf.csproj", "{50A9114F-5D22-43EF-43CF-559234473966}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1481,6 +1483,46 @@ Global
{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
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|ARM.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Debug|x86.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Debug|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Mono Release|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|Any CPU.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|ARM.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|ARM.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x64.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x64.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x86.ActiveCfg = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Release|x86.Build.0 = Release|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Any CPU.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|Any CPU.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|ARM.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|ARM.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x64.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x64.Build.0 = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x86.ActiveCfg = Debug|Any CPU
+ {50A9114F-5D22-43EF-43CF-559234473966}.Testing|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1513,6 +1555,7 @@ Global
{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}
+ {50A9114F-5D22-43EF-43CF-559234473966} = {01AA1A57-727E-4665-93FF-6E8BDD316ADE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46D05EF4-18C0-4E24-A0A3-A11C7FFFFBBA}
diff --git a/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs b/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs
new file mode 100644
index 0000000000..ca3cfc6faa
--- /dev/null
+++ b/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs
@@ -0,0 +1,429 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// Integration tests for WcfPortal class validation
+//-----------------------------------------------------------------------
+
+using System.Security.Claims;
+using Csla.Channels.Wcf.Server;
+using Csla.Core;
+using Csla.Serialization;
+using Csla.Server;
+using Csla.Server.Hosts.DataPortalChannel;
+using Csla.TestHelpers;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Csla.Test.Channels.Wcf;
+
+///
+/// Integration tests for the WcfPortal class.
+/// These tests validate the core data portal operations
+/// that are performed through the WCF channel.
+///
+/// Note: Full WCF testing would require the Csla.Channels.Wcf
+/// assembly reference, which is added when integrating with
+/// actual WCF server implementations.
+///
+[TestClass]
+public class WcfPortalIntegrationTests
+{
+ private TestDIContext _testDIContext;
+ private ApplicationContext _applicationContext;
+ private FakeDataPortalServer _fakeDataPortalServer;
+ private WcfPortal _wcfPortal;
+
+
+ [TestInitialize]
+ public void Setup()
+ {
+ _testDIContext = TestDIContextFactory.CreateDefaultContext(services =>
+ {
+ services.AddScoped();
+ });
+ _applicationContext = _testDIContext.CreateTestApplicationContext();
+ _fakeDataPortalServer = new FakeDataPortalServer();
+ _wcfPortal = new WcfPortal(_fakeDataPortalServer, _applicationContext);
+ }
+
+ #region Create Operation Tests
+
+ [TestMethod]
+ public async Task Create_WithValidCriteria_ReturnsCreatedObject()
+ {
+ // Arrange
+ var serializer = _applicationContext.GetRequiredService();
+ var criteria = new object();
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var expectedReturnObject = new object();
+ _fakeDataPortalServer.SetReturnValue(expectedReturnObject);
+
+ // Act
+ var result = await _fakeDataPortalServer.Create(
+ typeof(object),
+ criteria,
+ context,
+ true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ReturnObject.Should().Be(expectedReturnObject);
+ result.Error.Should().BeNull();
+ }
+
+ [TestMethod]
+ public async Task Create_WithError_ReturnsErrorResult()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var testException = new InvalidOperationException("Create operation failed");
+ _fakeDataPortalServer.SetErrorValue(testException);
+
+ // Act
+ var result = await _fakeDataPortalServer.Create(
+ typeof(object),
+ null,
+ context,
+ true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Error.Should().NotBeNull();
+ result.Error.Should().Be(testException);
+ result.ReturnObject.Should().BeNull();
+ }
+
+ [TestMethod]
+ public async Task Create_WithPrimitiveCriteria_HandledCorrectly()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var primitiveValue = 42;
+ var expectedReturnObject = new object();
+ _fakeDataPortalServer.SetReturnValue(expectedReturnObject);
+
+ // Act
+ var result = await _fakeDataPortalServer.Create(
+ typeof(object),
+ primitiveValue,
+ context,
+ true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ReturnObject.Should().Be(expectedReturnObject);
+ }
+
+ #endregion Create Operation Tests
+
+ #region Fetch Operation Tests
+
+ [TestMethod]
+ public async Task Fetch_WithValidCriteria_ReturnsFetchedObject()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var expectedReturnObject = new object();
+ _fakeDataPortalServer.SetReturnValue(expectedReturnObject);
+
+ // Act
+ var result = await _fakeDataPortalServer.Fetch(
+ typeof(object),
+ new object(),
+ context,
+ true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ReturnObject.Should().Be(expectedReturnObject);
+ result.Error.Should().BeNull();
+ }
+
+ [TestMethod]
+ public async Task Fetch_WithError_ReturnsErrorResult()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var testException = new InvalidOperationException("Fetch operation failed");
+ _fakeDataPortalServer.SetErrorValue(testException);
+ var serializer = _applicationContext.GetRequiredService();
+ var criteriaData = serializer.Serialize(new TestCslaObject());
+ var principalData = serializer.Serialize(new ClaimsPrincipal());
+ var contextData = serializer.Serialize(_applicationContext.GetRequiredService());
+
+ var request = new CriteriaRequest(
+ _applicationContext,
+ principalData,
+ contextData,
+ "en-US",
+ "en-US",
+ criteriaData)
+ {
+ TypeName = typeof(object).AssemblyQualifiedName
+ };
+ // Act
+ var result = await _wcfPortal.Fetch(request);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ErrorData.Should().NotBeNull();
+ }
+
+ #endregion Fetch Operation Tests
+
+ #region Update Operation Tests
+
+ [TestMethod]
+ public async Task Update_WithValidObject_ReturnsUpdatedObject()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var businessObject = new TestCslaObject();
+ _fakeDataPortalServer.SetReturnValue(businessObject);
+ var serializer = _applicationContext.GetRequiredService();
+ var criteriaData = serializer.Serialize(new TestCslaObject());
+ var principalData = serializer.Serialize(new ClaimsPrincipal());
+ var contextData = serializer.Serialize(_applicationContext.GetRequiredService());
+
+ var request = new UpdateRequest(
+ principalData,
+ contextData,
+ "en-US",
+ "en-US",
+ criteriaData)
+ {
+
+ };
+ // Act
+ var result = await _wcfPortal.Update(request);
+
+ // Assert
+ result.Should().NotBeNull();
+
+ result.ErrorData.Should().BeNull();
+ }
+
+ [TestMethod]
+ public async Task Update_WithError_ReturnsErrorResult()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var businessObject = new TestCslaObject();
+ var testException = new InvalidOperationException("Update operation failed");
+ _fakeDataPortalServer.SetErrorValue(testException);
+
+ var serializer = _applicationContext.GetRequiredService();
+ var criteriaData = serializer.Serialize(new TestCslaObject());
+ var principalData = serializer.Serialize(new ClaimsPrincipal());
+ var contextData = serializer.Serialize(_applicationContext.GetRequiredService());
+
+ var request = new UpdateRequest(
+ principalData,
+ contextData,
+ "en-US",
+ "en-US",
+ criteriaData)
+ {
+
+ };
+ // Act
+ var result = await _wcfPortal.Update(request);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ErrorData.Should().NotBeNull();
+ }
+
+ #endregion Update Operation Tests
+
+ #region Delete Operation Tests
+
+ [TestMethod]
+ public async Task Delete_WithValidCriteria_DeletesObjectSuccessfully()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ _fakeDataPortalServer.SetReturnValue(new TestCslaObject());
+ var serializer = _applicationContext.GetRequiredService();
+ var criteriaData = serializer.Serialize(new TestCslaObject());
+ var principalData = serializer.Serialize(new ClaimsPrincipal());
+ var contextData = serializer.Serialize(_applicationContext.GetRequiredService());
+
+ var request = new CriteriaRequest(
+ _applicationContext,
+ principalData,
+ contextData,
+ "en-US",
+ "en-US",
+ criteriaData)
+ {
+ TypeName = typeof(object).AssemblyQualifiedName
+ };
+ // Act
+ var result = await _wcfPortal.Delete(request);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ErrorData.Should().BeNull();
+ }
+
+ [TestMethod]
+ public async Task Delete_WithError_ReturnsErrorResult()
+ {
+ // Arrange
+ var context = new DataPortalContext(
+ _applicationContext,
+ null,
+ true,
+ "en-US",
+ "en-US",
+ _applicationContext.GetRequiredService());
+
+ var testException = new InvalidOperationException("Delete operation failed");
+ _fakeDataPortalServer.SetErrorValue(testException);
+ var serializer = _applicationContext.GetRequiredService();
+ var criteriaData = serializer.Serialize(new TestCslaObject());
+ var principalData = serializer.Serialize(new ClaimsPrincipal());
+ var contextData = serializer.Serialize(_applicationContext.GetRequiredService());
+
+ var request = new CriteriaRequest(
+ _applicationContext,
+ principalData,
+ contextData,
+ "en-US",
+ "en-US",
+ criteriaData)
+ {
+ TypeName = typeof(object).AssemblyQualifiedName
+ };
+ // Act
+ var result = await _wcfPortal.Delete(request);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.ErrorData.Should().NotBeNull();
+ }
+
+ #endregion Delete Operation Tests
+
+ #region Helper Classes
+
+ private class FakeDataPortalServer : IDataPortalServer
+ {
+ private object? _returnValue;
+ private Exception? _errorValue;
+
+ public void SetReturnValue(object? returnValue)
+ {
+ _returnValue = returnValue;
+ _errorValue = null;
+ }
+
+ public void SetErrorValue(Exception errorValue)
+ {
+ _errorValue = errorValue;
+ _returnValue = null;
+ }
+
+ public Task Create(Type objectType, object? criteria, DataPortalContext context, bool isSync)
+ {
+ var appContext = ((IUseApplicationContext)context).ApplicationContext;
+ var result = _errorValue != null
+ ? new DataPortalResult(appContext, null, _errorValue)
+ : new DataPortalResult(appContext, _returnValue);
+ return Task.FromResult(result);
+ }
+
+ public Task Fetch(Type objectType, object? criteria, DataPortalContext context, bool isSync)
+ {
+ var appContext = ((IUseApplicationContext)context).ApplicationContext;
+ var result = _errorValue != null
+ ? new DataPortalResult(appContext, null, _errorValue)
+ : new DataPortalResult(appContext, _returnValue);
+ return Task.FromResult(result);
+ }
+
+ public Task Update(ICslaObject businessObject, DataPortalContext context, bool isSync)
+ {
+ var appContext = ((IUseApplicationContext)context).ApplicationContext;
+ var result = _errorValue != null
+ ? new DataPortalResult(appContext, null, _errorValue)
+ : new DataPortalResult(appContext, businessObject);
+ return Task.FromResult(result);
+ }
+
+ public Task Delete(Type objectType, object? criteria, DataPortalContext context, bool isSync)
+ {
+ var appContext = ((IUseApplicationContext)context).ApplicationContext;
+ var result = _errorValue != null
+ ? new DataPortalResult(appContext, null, _errorValue)
+ : new DataPortalResult(appContext, _returnValue);
+ return Task.FromResult(result);
+ }
+ }
+
+ private class TestCslaObject : MobileObject, ICslaObject
+ {
+ }
+
+ #endregion Helper Classes
+}
diff --git a/Source/tests/Csla.test/Csla.Tests.csproj b/Source/tests/Csla.test/Csla.Tests.csproj
index 99b4854c8a..57534a5fa8 100644
--- a/Source/tests/Csla.test/Csla.Tests.csproj
+++ b/Source/tests/Csla.test/Csla.Tests.csproj
@@ -67,6 +67,7 @@
+