From 394ecef24a54df2785974ed615dbaae138dfbdd9 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Thu, 26 Mar 2026 11:51:32 -0500 Subject: [PATCH 01/15] Added WCF Data Portal Channel --- Source/Csla.Channels.Wcf/Client/IWcfPortal.cs | 43 +++ .../Client/WcfClientExtensions.cs | 70 ++++ Source/Csla.Channels.Wcf/Client/WcfProxy.cs | 60 ++++ .../Client/WcfProxyOptions.cs | 29 ++ .../Csla.Channels.Wcf.csproj | 37 ++ .../Server/IWcfPortalServer.cs | 35 ++ Source/Csla.Channels.Wcf/Server/WcfPortal.cs | 316 ++++++++++++++++++ .../Csla.Channels.Wcf/Server/WcfPortalHost.cs | 46 +++ .../Server/WcfPortalInstanceProvider.cs | 89 +++++ .../Server/WcfPortalOptions.cs | 39 +++ .../Server/WcfServerExtensions.cs | 81 +++++ Source/Csla.Channels.Wcf/WcfRequest.cs | 32 ++ Source/Csla.Channels.Wcf/WcfResponse.cs | 26 ++ Source/Csla.Channels.Wcf/readme.md | 81 +++++ Source/csla.build.sln | 203 +++++++++++ 15 files changed, 1187 insertions(+) create mode 100644 Source/Csla.Channels.Wcf/Client/IWcfPortal.cs create mode 100644 Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs create mode 100644 Source/Csla.Channels.Wcf/Client/WcfProxy.cs create mode 100644 Source/Csla.Channels.Wcf/Client/WcfProxyOptions.cs create mode 100644 Source/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj create mode 100644 Source/Csla.Channels.Wcf/Server/IWcfPortalServer.cs create mode 100644 Source/Csla.Channels.Wcf/Server/WcfPortal.cs create mode 100644 Source/Csla.Channels.Wcf/Server/WcfPortalHost.cs create mode 100644 Source/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs create mode 100644 Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs create mode 100644 Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs create mode 100644 Source/Csla.Channels.Wcf/WcfRequest.cs create mode 100644 Source/Csla.Channels.Wcf/WcfResponse.cs create mode 100644 Source/Csla.Channels.Wcf/readme.md diff --git a/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs b/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs new file mode 100644 index 0000000000..3b91c2da64 --- /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. + /// + /// + /// 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); + + /// + /// 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..97ac7b7918 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// +// 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 wcfProxy = new WcfProxy(applicationContext, options); + 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/WcfProxy.cs b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs new file mode 100644 index 0000000000..3a54f38df0 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Provides consistent context information between the client +//----------------------------------------------------------------------- + +using System.ServiceModel; +using Csla.DataPortalClient; + +namespace Csla.Channels.Wcf.Client +{ + /// + /// Represents a that communicates with a remote data poral using WCF. + /// + /// + /// The client side context for the data portal. + /// + /// + /// The options that are used to configure the data portal proxy. + /// + /// + /// is . + /// + public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions options) : DataPortalProxy(applicationContext) + { + protected WcfProxyOptions _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + /// 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 channelFactory = new ChannelFactory(_options.Binding); + + var client = channelFactory.CreateChannel(new EndpointAddress(_options.DataPortalUrl)); + + var wcfRequest = new WcfRequest + { + Operation = operation, + Body = serialized + }; + + //Note: I'm not sure if we need to support routing here. + + // This implementation is following the pattern used in the gRPC channel. I'm not sure it is necessary for the WCF portal. + if (isSync) + { + return client.Invoke(wcfRequest).Body; + } + + var response = await client.InvokeAsync(wcfRequest); + + return response.Body; + } + } +} 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..c9261d4510 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs @@ -0,0 +1,316 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Provides consistent context information between the 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)); + + /// + /// 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; + return await InvokePortal(operation, request.Body).ConfigureAwait(false); + } + + private async Task InvokePortal(string operation, byte[] requestData) + { + var result = new DataPortalResponse(); + try + { + var request = DeserializeRequired(requestData); + var callResult = await CallPortal(operation, request); + result.ObjectData = callResult.ObjectData; + } + catch (Exception ex) + { + result.ErrorData = _applicationContext.CreateInstanceDI(ex); + } + + 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..e0dae6d256 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// +// 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(); + + /// + /// 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..8a19c4e307 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// +// 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); + host.Opened += (s, e) => Console.WriteLine($"WCF Data portal host now listening at {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..96a3e3730a --- /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 respose 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..148770be69 --- /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 difference 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 a 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 Frameowrk 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} From b85bf0d63dab437f3073650a4eb06f37a9c8aa5e Mon Sep 17 00:00:00 2001 From: b-higginbotham <40719377+b-higginbotham@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:16:07 -0500 Subject: [PATCH 02/15] Fix a typo in XML comment respose -> response Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Source/Csla.Channels.Wcf/WcfResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Csla.Channels.Wcf/WcfResponse.cs b/Source/Csla.Channels.Wcf/WcfResponse.cs index 96a3e3730a..0d247ad6db 100644 --- a/Source/Csla.Channels.Wcf/WcfResponse.cs +++ b/Source/Csla.Channels.Wcf/WcfResponse.cs @@ -11,7 +11,7 @@ namespace Csla.Channels.Wcf { /// - /// Represents a respose message from a data portal operation that is returned by the data portal server through a + /// Represents a response message from a data portal operation that is returned by the data portal server through a /// WCF channel. /// [DataContract] From 980b31b0f44e3ac3ba5836dd9c7e52a9b8dc1b14 Mon Sep 17 00:00:00 2001 From: b-higginbotham <40719377+b-higginbotham@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:24:38 -0500 Subject: [PATCH 03/15] Fix typo in XML comment poral -> portal Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Source/Csla.Channels.Wcf/Client/WcfProxy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs index 3a54f38df0..823e3a9a4c 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs @@ -12,7 +12,7 @@ namespace Csla.Channels.Wcf.Client { /// - /// Represents a that communicates with a remote data poral using WCF. + /// Represents a that communicates with a remote data portal using WCF. /// /// /// The client side context for the data portal. From 74fa936c364146d77c7a5d3550adfe5782fa0c50 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Thu, 26 Mar 2026 16:42:51 -0500 Subject: [PATCH 04/15] Fix typos in readme.md --- Source/Csla.Channels.Wcf/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Csla.Channels.Wcf/readme.md b/Source/Csla.Channels.Wcf/readme.md index 148770be69..e16ba31e99 100644 --- a/Source/Csla.Channels.Wcf/readme.md +++ b/Source/Csla.Channels.Wcf/readme.md @@ -6,13 +6,13 @@ WCF itself allows for the use of a wide array of communication protocols for dif 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 difference 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. +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 a ASP.NET Core hosted data portal: +Here is an example Program.cs file to demonstrate an ASP.NET Core hosted data portal: ```c# using CoreWCF.Configuration; using CoreWCF.Description; @@ -42,7 +42,7 @@ app.UseServiceModel(builder => app.Run(); ``` -## .NET Frameowrk Hosting +## .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. From 24259d14d1ccb55b422aedb7c55526894dd356c8 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Thu, 26 Mar 2026 16:51:57 -0500 Subject: [PATCH 05/15] remove Console.WriteLine that was used for debugging --- Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs b/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs index 8a19c4e307..92a2f9cca3 100644 --- a/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs +++ b/Source/Csla.Channels.Wcf/Server/WcfServerExtensions.cs @@ -55,7 +55,6 @@ public static DataPortalServerOptions UseWcfPortal(this DataPortalServerOptions var options = provider.GetRequiredService(); var host = new WcfPortalHost(dataPortal, applicationContext, typeof(WcfPortal)); host.AddServiceEndpoint(typeof(IWcfPortalServer), options.Binding, options.DataPortalUrl); - host.Opened += (s, e) => Console.WriteLine($"WCF Data portal host now listening at {options.DataPortalUrl}."); return host; }); #else From d975371fc75db3744b8d6da5e3e145992511f011 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Thu, 26 Mar 2026 17:11:39 -0500 Subject: [PATCH 06/15] Add WCF portal integration tests --- Source/csla.test.sln | 47 +- .../Channels/Wcf/WcfPortalIntegrationTests.cs | 471 ++++++++++++++++++ Source/tests/Csla.test/Csla.Tests.csproj | 1 + 3 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs diff --git a/Source/csla.test.sln b/Source/csla.test.sln index 097426f087..139e0a3378 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 +# Visual Studio Version 18 +VisualStudioVersion = 18.4.11620.152 stable 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..08f87a6abe --- /dev/null +++ b/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs @@ -0,0 +1,471 @@ +//----------------------------------------------------------------------- +// +// 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 Context Tests + + [TestMethod] + public async Task Operation_PreservesCultureSettings() + { + // Arrange + var expectedCulture = "de-DE"; + var context = new DataPortalContext( + _applicationContext, + null, + true, + expectedCulture, + "de-DE", + _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.Create(request); + + // Assert + result.Should().NotBeNull(); + context.ClientCulture.Should().Be(expectedCulture); + } + + #endregion Context 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 @@ + From 1ab43d8a5be43ce244c400cbd4023408d049edb9 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 09:47:40 -0500 Subject: [PATCH 07/15] Replace ChannelFactory with ClientBase. --- .../Client/WcfPortalClient.cs | 39 +++++++++++++++++++ Source/Csla.Channels.Wcf/Client/WcfProxy.cs | 26 ++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs diff --git a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs new file mode 100644 index 0000000000..d63c70d8a5 --- /dev/null +++ b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs @@ -0,0 +1,39 @@ +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 + { + /// + /// 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 Task InvokeAsync(WcfRequest request) => Channel.InvokeAsync(request); + + /// + /// 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 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 index 823e3a9a4c..c70f8411f3 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs @@ -34,9 +34,7 @@ public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions opt protected override async Task CallDataPortalServer(byte[] serialized, string operation, string? routingToken, bool isSync) { - var channelFactory = new ChannelFactory(_options.Binding); - - var client = channelFactory.CreateChannel(new EndpointAddress(_options.DataPortalUrl)); + var client = new WcfPortalClient(_options.Binding, new EndpointAddress(_options.DataPortalUrl)); var wcfRequest = new WcfRequest { @@ -46,15 +44,25 @@ protected override async Task CallDataPortalServer(byte[] serialized, st //Note: I'm not sure if we need to support routing here. - // This implementation is following the pattern used in the gRPC channel. I'm not sure it is necessary for the WCF portal. - if (isSync) + try { - return client.Invoke(wcfRequest).Body; - } + // This implementation is following the pattern used in the gRPC channel. I'm not sure it is necessary for the WCF portal. + if (isSync) + { + return client.Invoke(wcfRequest).Body; + } + + var response = await client.InvokeAsync(wcfRequest); - var response = await client.InvokeAsync(wcfRequest); + client.Close(); - return response.Body; + return response.Body; + } + catch (Exception) + { + client.Abort(); + throw; + } } } } From f28e27d24d93692c8669285da320cdc1e61df057 Mon Sep 17 00:00:00 2001 From: b-higginbotham <40719377+b-higginbotham@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:15:32 -0500 Subject: [PATCH 08/15] Revert VisualStudioVersion change in csla.test.sln Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Source/csla.test.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/csla.test.sln b/Source/csla.test.sln index 139e0a3378..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 18 -VisualStudioVersion = 18.4.11620.152 stable +# Visual Studio Version 17 +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 From 03b0a9484dac054c63aa65dbe44f6849a67d222f Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 13:22:01 -0500 Subject: [PATCH 09/15] Fix typos in XML comments --- Source/Csla.Channels.Wcf/Client/IWcfPortal.cs | 2 +- Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs b/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs index 3b91c2da64..3769fe8a76 100644 --- a/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs +++ b/Source/Csla.Channels.Wcf/Client/IWcfPortal.cs @@ -23,7 +23,7 @@ public interface IWcfPortal /// 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. + /// 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); diff --git a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs index d63c70d8a5..aa2e8958e4 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs @@ -15,24 +15,24 @@ namespace Csla.Channels.Wcf.Client internal class WcfPortalClient(Binding binding, EndpointAddress address) : ClientBase(binding, address), IWcfPortal { /// - /// Synchronously invokes an operation on the remote data portal. + /// Asynchronously 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. + /// A task containing the response from the remote data portal. /// public Task InvokeAsync(WcfRequest request) => Channel.InvokeAsync(request); /// - /// Asynchronously invokes an operation on the remote data portal. + /// Synchronously 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. + /// The response from the remote data portal. /// public WcfResponse Invoke(WcfRequest request) => Channel.Invoke(request); } From 71964e3bb954fc8bdb5a71879598863a87c3dc1a Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 13:31:35 -0500 Subject: [PATCH 10/15] Fix client not being closed after sync call --- Source/Csla.Channels.Wcf/Client/WcfProxy.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs index c70f8411f3..f3e606bce6 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs @@ -46,13 +46,7 @@ protected override async Task CallDataPortalServer(byte[] serialized, st try { - // This implementation is following the pattern used in the gRPC channel. I'm not sure it is necessary for the WCF portal. - if (isSync) - { - return client.Invoke(wcfRequest).Body; - } - - var response = await client.InvokeAsync(wcfRequest); + var response = isSync ? client.Invoke(wcfRequest) : await client.InvokeAsync(wcfRequest); client.Close(); From 69c9d903866651ad2feded615a97ac21adb5a246 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 13:58:01 -0500 Subject: [PATCH 11/15] Add missing file header --- Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs index aa2e8958e4..0a7faba146 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfPortalClient.cs @@ -1,4 +1,12 @@ -using System.ServiceModel; +//----------------------------------------------------------------------- +// +// 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 From 942edea8029b67555f033cd2b693ef31b9044bf2 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 15:36:17 -0500 Subject: [PATCH 12/15] Add support for data portal routing --- .../Client/WcfClientExtensions.cs | 3 +- Source/Csla.Channels.Wcf/Client/WcfProxy.cs | 43 +++++++++-- Source/Csla.Channels.Wcf/Server/WcfPortal.cs | 76 ++++++++++++++++++- .../Server/WcfPortalOptions.cs | 17 +++++ 4 files changed, 130 insertions(+), 9 deletions(-) diff --git a/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs b/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs index 97ac7b7918..30be9a2501 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfClientExtensions.cs @@ -46,7 +46,8 @@ public static DataPortalClientOptions UseWcfProxy(this DataPortalClientOptions c { var applicationContext = provider.GetRequiredService(); var options = provider.GetRequiredService(); - var wcfProxy = new WcfProxy(applicationContext, options); + var dataPortalOptions = provider.GetRequiredService(); + var wcfProxy = new WcfProxy(applicationContext, options, dataPortalOptions); return wcfProxy; }); diff --git a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs index f3e606bce6..d9c28c3e66 100644 --- a/Source/Csla.Channels.Wcf/Client/WcfProxy.cs +++ b/Source/Csla.Channels.Wcf/Client/WcfProxy.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using System.ServiceModel; +using Csla.Configuration; using Csla.DataPortalClient; namespace Csla.Channels.Wcf.Client @@ -17,15 +18,19 @@ namespace Csla.Channels.Wcf.Client /// /// 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 . + /// is . /// - public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions options) : DataPortalProxy(applicationContext) + public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions wcfProxyOptions, DataPortalOptions dataPortalOptions) : DataPortalProxy(applicationContext) { - protected WcfProxyOptions _options = options ?? throw new ArgumentNullException(nameof(options)); + 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. @@ -38,12 +43,10 @@ protected override async Task CallDataPortalServer(byte[] serialized, st var wcfRequest = new WcfRequest { - Operation = operation, + Operation = CreateOperationTag(operation, _versionRoutingTag, routingToken), Body = serialized }; - //Note: I'm not sure if we need to support routing here. - try { var response = isSync ? client.Invoke(wcfRequest) : await client.InvokeAsync(wcfRequest); @@ -58,5 +61,31 @@ protected override async Task CallDataPortalServer(byte[] serialized, st 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/Server/WcfPortal.cs b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs index c9261d4510..2da6cfcb13 100644 --- a/Source/Csla.Channels.Wcf/Server/WcfPortal.cs +++ b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs @@ -6,6 +6,7 @@ // Provides consistent context information between the client //----------------------------------------------------------------------- +using Csla.Channels.Wcf.Client; using Csla.Core; using Csla.Properties; using Csla.Serialization; @@ -41,6 +42,13 @@ public class WcfPortal(IDataPortalServer dataPortal, ApplicationContext applicat 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. /// @@ -56,7 +64,73 @@ public async Task InvokeAsync(WcfRequest request) throw new ArgumentNullException(nameof(request)); var operation = request.Operation; - return await InvokePortal(operation, request.Body).ConfigureAwait(false); + 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) diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs b/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs index e0dae6d256..390823a76f 100644 --- a/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs +++ b/Source/Csla.Channels.Wcf/Server/WcfPortalOptions.cs @@ -26,6 +26,23 @@ public class WcfPortalOptions /// 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. /// From 7628b764e69c35a5f7cde039fd3b81a0aff8b8b9 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Fri, 27 Mar 2026 15:49:18 -0500 Subject: [PATCH 13/15] Remove test that cannot fail --- .../BusinessLibrary/BusinessLibrary.csproj | 15 + Samples/WcfExample/BusinessLibrary/Person.cs | 13 + .../BusinessLibrary/PersonFactory.cs | 23 ++ .../Csla.Channels.Wcf/Client/IWcfPortal.cs | 43 ++ .../Client/WcfClientExtensions.cs | 71 ++++ .../Client/WcfPortalClient.cs | 47 +++ .../Csla.Channels.Wcf/Client/WcfProxy.cs | 91 +++++ .../Client/WcfProxyOptions.cs | 29 ++ .../Csla.Channels.Wcf.csproj | 37 ++ .../Server/IWcfPortalServer.cs | 35 ++ .../Csla.Channels.Wcf/Server/WcfPortal.cs | 369 ++++++++++++++++++ .../Csla.Channels.Wcf/Server/WcfPortalHost.cs | 46 +++ .../Server/WcfPortalInstanceProvider.cs | 89 +++++ .../Server/WcfPortalOptions.cs | 52 +++ .../Server/WcfServerExtensions.cs | 80 ++++ .../Csla.Channels.Wcf/WcfRequest.cs | 32 ++ .../Csla.Channels.Wcf/WcfResponse.cs | 26 ++ .../WcfAspNetCoreExample/Program.cs | 26 ++ .../Properties/launchSettings.json | 14 + .../WcfAspNetCoreExample.csproj | 18 + .../appsettings.Development.json | 8 + .../WcfAspNetCoreExample/appsettings.json | 8 + .../WcfExample/WcfClientExample/Program.cs | 24 ++ .../WcfClientExample/WcfClientExample.csproj | 19 + Samples/WcfExample/WcfExample.slnx | 7 + .../WcfNetFrameworkExample/Program.cs | 19 + .../WcfNetFrameworkExample.csproj | 20 + .../Channels/Wcf/WcfPortalIntegrationTests.cs | 42 -- 28 files changed, 1261 insertions(+), 42 deletions(-) create mode 100644 Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj create mode 100644 Samples/WcfExample/BusinessLibrary/Person.cs create mode 100644 Samples/WcfExample/BusinessLibrary/PersonFactory.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs create mode 100644 Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs create mode 100644 Samples/WcfExample/WcfAspNetCoreExample/Program.cs create mode 100644 Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json create mode 100644 Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj create mode 100644 Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json create mode 100644 Samples/WcfExample/WcfAspNetCoreExample/appsettings.json create mode 100644 Samples/WcfExample/WcfClientExample/Program.cs create mode 100644 Samples/WcfExample/WcfClientExample/WcfClientExample.csproj create mode 100644 Samples/WcfExample/WcfExample.slnx create mode 100644 Samples/WcfExample/WcfNetFrameworkExample/Program.cs create mode 100644 Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj diff --git a/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj b/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj new file mode 100644 index 0000000000..4dbe987bc0 --- /dev/null +++ b/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + enable + enable + Latest + + + + + + + + diff --git a/Samples/WcfExample/BusinessLibrary/Person.cs b/Samples/WcfExample/BusinessLibrary/Person.cs new file mode 100644 index 0000000000..4babc31b70 --- /dev/null +++ b/Samples/WcfExample/BusinessLibrary/Person.cs @@ -0,0 +1,13 @@ +using Csla; +using Csla.Server; + +namespace BusinessLibrary +{ + [ObjectFactory(typeof(PersonFactory))] + [CslaImplementProperties] + public partial class Person : BusinessBase + { + public partial int Id { get; set; } + public partial string Name { get; set; } + } +} diff --git a/Samples/WcfExample/BusinessLibrary/PersonFactory.cs b/Samples/WcfExample/BusinessLibrary/PersonFactory.cs new file mode 100644 index 0000000000..7ae464c92e --- /dev/null +++ b/Samples/WcfExample/BusinessLibrary/PersonFactory.cs @@ -0,0 +1,23 @@ +using Csla; +using Csla.Server; + +namespace BusinessLibrary +{ + public class PersonFactory(ApplicationContext applicationContext) : ObjectFactory(applicationContext) + { + public Person Fetch(int id) + { + var person = ApplicationContext.CreateInstanceDI(); + + using (BypassPropertyChecks(person)) + { + person.Id = id; + person.Name = "Clark Kent"; + } + + MarkOld(person); + + return person; + } + } +} \ No newline at end of file diff --git a/Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs new file mode 100644 index 0000000000..3769fe8a76 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs new file mode 100644 index 0000000000..30be9a2501 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs new file mode 100644 index 0000000000..0a7faba146 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs new file mode 100644 index 0000000000..d9c28c3e66 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs new file mode 100644 index 0000000000..7bdd92586d --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj b/Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj new file mode 100644 index 0000000000..b5a2fc543c --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs new file mode 100644 index 0000000000..f9ed26ec53 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs new file mode 100644 index 0000000000..513665c25b --- /dev/null +++ b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs @@ -0,0 +1,369 @@ +//----------------------------------------------------------------------- +// +// 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); + } + } + + 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 result = new DataPortalResponse(); + try + { + var request = DeserializeRequired(requestData); + var callResult = await CallPortal(operation, request); + result.ObjectData = callResult.ObjectData; + } + catch (Exception ex) + { + result.ErrorData = _applicationContext.CreateInstanceDI(ex); + } + + 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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs new file mode 100644 index 0000000000..671082d4e3 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs new file mode 100644 index 0000000000..ea297d6365 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs new file mode 100644 index 0000000000..95279c56c7 --- /dev/null +++ b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// +// 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(); + +#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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs new file mode 100644 index 0000000000..92a2f9cca3 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs b/Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs new file mode 100644 index 0000000000..e36d25d8e6 --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs b/Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs new file mode 100644 index 0000000000..0d247ad6db --- /dev/null +++ b/Samples/WcfExample/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/Samples/WcfExample/WcfAspNetCoreExample/Program.cs b/Samples/WcfExample/WcfAspNetCoreExample/Program.cs new file mode 100644 index 0000000000..9e3e3b27f6 --- /dev/null +++ b/Samples/WcfExample/WcfAspNetCoreExample/Program.cs @@ -0,0 +1,26 @@ +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(); diff --git a/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json b/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json new file mode 100644 index 0000000000..e833a374e0 --- /dev/null +++ b/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj b/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj new file mode 100644 index 0000000000..2adc698daf --- /dev/null +++ b/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Samples/WcfExample/WcfClientExample/Program.cs b/Samples/WcfExample/WcfClientExample/Program.cs new file mode 100644 index 0000000000..4f794095c5 --- /dev/null +++ b/Samples/WcfExample/WcfClientExample/Program.cs @@ -0,0 +1,24 @@ +using BusinessLibrary; +using Csla; +using Csla.Channels.Wcf.Client; +using Csla.Configuration; +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); +services + .AddCsla(csla => csla + .DataPortal(dp => dp + .AddClientSideDataPortal(c => c + .UseWcfProxy()))); + +var provider = services.BuildServiceProvider(); +var applicationContext = provider.GetRequiredService(); +var portal = applicationContext.GetRequiredService>(); + +var user = applicationContext.User; + +int personId = 1; +var p = portal.Fetch(personId); + +Console.WriteLine(p.Name); +Console.ReadLine(); diff --git a/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj b/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj new file mode 100644 index 0000000000..4155e463fb --- /dev/null +++ b/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/Samples/WcfExample/WcfExample.slnx b/Samples/WcfExample/WcfExample.slnx new file mode 100644 index 0000000000..a693bb39b0 --- /dev/null +++ b/Samples/WcfExample/WcfExample.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Samples/WcfExample/WcfNetFrameworkExample/Program.cs b/Samples/WcfExample/WcfNetFrameworkExample/Program.cs new file mode 100644 index 0000000000..5c02a308ff --- /dev/null +++ b/Samples/WcfExample/WcfNetFrameworkExample/Program.cs @@ -0,0 +1,19 @@ +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.Opened += (o, e) => Console.WriteLine($"WCF Data Portal now listening on {wcfHost.Description.Endpoints.First().Address.Uri}"); + +wcfHost.Open(); + +Console.ReadLine(); \ No newline at end of file diff --git a/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj b/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj new file mode 100644 index 0000000000..ad61cfbbc5 --- /dev/null +++ b/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj @@ -0,0 +1,20 @@ + + + + Exe + net48 + enable + enable + Latest + + + + + + + + + + + + diff --git a/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs b/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs index 08f87a6abe..ca3cfc6faa 100644 --- a/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs +++ b/Source/tests/Csla.test/Channels/Wcf/WcfPortalIntegrationTests.cs @@ -365,48 +365,6 @@ public async Task Delete_WithError_ReturnsErrorResult() #endregion Delete Operation Tests - #region Context Tests - - [TestMethod] - public async Task Operation_PreservesCultureSettings() - { - // Arrange - var expectedCulture = "de-DE"; - var context = new DataPortalContext( - _applicationContext, - null, - true, - expectedCulture, - "de-DE", - _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.Create(request); - - // Assert - result.Should().NotBeNull(); - context.ClientCulture.Should().Be(expectedCulture); - } - - #endregion Context Tests - #region Helper Classes private class FakeDataPortalServer : IDataPortalServer From 1973c69de044e044a0423d894a7581d64127c5a5 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Mon, 30 Mar 2026 10:47:25 -0500 Subject: [PATCH 14/15] Remove unnecessary wrapping of return value --- Source/Csla.Channels.Wcf/Server/WcfPortal.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Source/Csla.Channels.Wcf/Server/WcfPortal.cs b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs index 2da6cfcb13..fff17de1bf 100644 --- a/Source/Csla.Channels.Wcf/Server/WcfPortal.cs +++ b/Source/Csla.Channels.Wcf/Server/WcfPortal.cs @@ -135,19 +135,10 @@ protected virtual async Task RouteMessage(string operation, string private async Task InvokePortal(string operation, byte[] requestData) { - var result = new DataPortalResponse(); - try - { - var request = DeserializeRequired(requestData); - var callResult = await CallPortal(operation, request); - result.ObjectData = callResult.ObjectData; - } - catch (Exception ex) - { - result.ErrorData = _applicationContext.CreateInstanceDI(ex); - } - + var request = DeserializeRequired(requestData); + var result = await CallPortal(operation, request); var buffer = _applicationContext.GetRequiredService().Serialize(result); + return new WcfResponse { Body = buffer }; } From 4c92ea5dd780d09b936a999cb46b2722e03318b6 Mon Sep 17 00:00:00 2001 From: Bobby Higginbotham Date: Mon, 30 Mar 2026 10:55:12 -0500 Subject: [PATCH 15/15] Remove WCF sample that isn't ready yet --- .../BusinessLibrary/BusinessLibrary.csproj | 15 - Samples/WcfExample/BusinessLibrary/Person.cs | 13 - .../BusinessLibrary/PersonFactory.cs | 23 -- .../Csla.Channels.Wcf/Client/IWcfPortal.cs | 43 -- .../Client/WcfClientExtensions.cs | 71 ---- .../Client/WcfPortalClient.cs | 47 --- .../Csla.Channels.Wcf/Client/WcfProxy.cs | 91 ----- .../Client/WcfProxyOptions.cs | 29 -- .../Csla.Channels.Wcf.csproj | 37 -- .../Server/IWcfPortalServer.cs | 35 -- .../Csla.Channels.Wcf/Server/WcfPortal.cs | 369 ------------------ .../Csla.Channels.Wcf/Server/WcfPortalHost.cs | 46 --- .../Server/WcfPortalInstanceProvider.cs | 89 ----- .../Server/WcfPortalOptions.cs | 52 --- .../Server/WcfServerExtensions.cs | 80 ---- .../Csla.Channels.Wcf/WcfRequest.cs | 32 -- .../Csla.Channels.Wcf/WcfResponse.cs | 26 -- .../WcfAspNetCoreExample/Program.cs | 26 -- .../Properties/launchSettings.json | 14 - .../WcfAspNetCoreExample.csproj | 18 - .../appsettings.Development.json | 8 - .../WcfAspNetCoreExample/appsettings.json | 8 - .../WcfExample/WcfClientExample/Program.cs | 24 -- .../WcfClientExample/WcfClientExample.csproj | 19 - Samples/WcfExample/WcfExample.slnx | 7 - .../WcfNetFrameworkExample/Program.cs | 19 - .../WcfNetFrameworkExample.csproj | 20 - 27 files changed, 1261 deletions(-) delete mode 100644 Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj delete mode 100644 Samples/WcfExample/BusinessLibrary/Person.cs delete mode 100644 Samples/WcfExample/BusinessLibrary/PersonFactory.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs delete mode 100644 Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs delete mode 100644 Samples/WcfExample/WcfAspNetCoreExample/Program.cs delete mode 100644 Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json delete mode 100644 Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj delete mode 100644 Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json delete mode 100644 Samples/WcfExample/WcfAspNetCoreExample/appsettings.json delete mode 100644 Samples/WcfExample/WcfClientExample/Program.cs delete mode 100644 Samples/WcfExample/WcfClientExample/WcfClientExample.csproj delete mode 100644 Samples/WcfExample/WcfExample.slnx delete mode 100644 Samples/WcfExample/WcfNetFrameworkExample/Program.cs delete mode 100644 Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj diff --git a/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj b/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj deleted file mode 100644 index 4dbe987bc0..0000000000 --- a/Samples/WcfExample/BusinessLibrary/BusinessLibrary.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netstandard2.0 - enable - enable - Latest - - - - - - - - diff --git a/Samples/WcfExample/BusinessLibrary/Person.cs b/Samples/WcfExample/BusinessLibrary/Person.cs deleted file mode 100644 index 4babc31b70..0000000000 --- a/Samples/WcfExample/BusinessLibrary/Person.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Csla; -using Csla.Server; - -namespace BusinessLibrary -{ - [ObjectFactory(typeof(PersonFactory))] - [CslaImplementProperties] - public partial class Person : BusinessBase - { - public partial int Id { get; set; } - public partial string Name { get; set; } - } -} diff --git a/Samples/WcfExample/BusinessLibrary/PersonFactory.cs b/Samples/WcfExample/BusinessLibrary/PersonFactory.cs deleted file mode 100644 index 7ae464c92e..0000000000 --- a/Samples/WcfExample/BusinessLibrary/PersonFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Csla; -using Csla.Server; - -namespace BusinessLibrary -{ - public class PersonFactory(ApplicationContext applicationContext) : ObjectFactory(applicationContext) - { - public Person Fetch(int id) - { - var person = ApplicationContext.CreateInstanceDI(); - - using (BypassPropertyChecks(person)) - { - person.Id = id; - person.Name = "Clark Kent"; - } - - MarkOld(person); - - return person; - } - } -} \ No newline at end of file diff --git a/Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs deleted file mode 100644 index 3769fe8a76..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Client/IWcfPortal.cs +++ /dev/null @@ -1,43 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs deleted file mode 100644 index 30be9a2501..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfClientExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs deleted file mode 100644 index 0a7faba146..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfPortalClient.cs +++ /dev/null @@ -1,47 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs deleted file mode 100644 index d9c28c3e66..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxy.cs +++ /dev/null @@ -1,91 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs deleted file mode 100644 index 7bdd92586d..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Client/WcfProxyOptions.cs +++ /dev/null @@ -1,29 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj b/Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj deleted file mode 100644 index b5a2fc543c..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Csla.Channels.Wcf.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - net462;net472;net48;net8.0;net9.0;net10.0 - Latest - enable - enable - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs deleted file mode 100644 index f9ed26ec53..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/IWcfPortalServer.cs +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs deleted file mode 100644 index 513665c25b..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortal.cs +++ /dev/null @@ -1,369 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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); - } - } - - 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 result = new DataPortalResponse(); - try - { - var request = DeserializeRequired(requestData); - var callResult = await CallPortal(operation, request); - result.ObjectData = callResult.ObjectData; - } - catch (Exception ex) - { - result.ErrorData = _applicationContext.CreateInstanceDI(ex); - } - - 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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs deleted file mode 100644 index 671082d4e3..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalHost.cs +++ /dev/null @@ -1,46 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs deleted file mode 100644 index ea297d6365..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalInstanceProvider.cs +++ /dev/null @@ -1,89 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs deleted file mode 100644 index 95279c56c7..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfPortalOptions.cs +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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(); - -#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/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs b/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs deleted file mode 100644 index 92a2f9cca3..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/Server/WcfServerExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs b/Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs deleted file mode 100644 index e36d25d8e6..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/WcfRequest.cs +++ /dev/null @@ -1,32 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs b/Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs deleted file mode 100644 index 0d247ad6db..0000000000 --- a/Samples/WcfExample/Csla.Channels.Wcf/WcfResponse.cs +++ /dev/null @@ -1,26 +0,0 @@ -//----------------------------------------------------------------------- -// -// 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/Samples/WcfExample/WcfAspNetCoreExample/Program.cs b/Samples/WcfExample/WcfAspNetCoreExample/Program.cs deleted file mode 100644 index 9e3e3b27f6..0000000000 --- a/Samples/WcfExample/WcfAspNetCoreExample/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -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(); diff --git a/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json b/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json deleted file mode 100644 index e833a374e0..0000000000 --- a/Samples/WcfExample/WcfAspNetCoreExample/Properties/launchSettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj b/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj deleted file mode 100644 index 2adc698daf..0000000000 --- a/Samples/WcfExample/WcfAspNetCoreExample/WcfAspNetCoreExample.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net10.0 - enable - enable - - - - - - - - - - - - diff --git a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json deleted file mode 100644 index 0c208ae918..0000000000 --- a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json b/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json deleted file mode 100644 index 0c208ae918..0000000000 --- a/Samples/WcfExample/WcfAspNetCoreExample/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Samples/WcfExample/WcfClientExample/Program.cs b/Samples/WcfExample/WcfClientExample/Program.cs deleted file mode 100644 index 4f794095c5..0000000000 --- a/Samples/WcfExample/WcfClientExample/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using BusinessLibrary; -using Csla; -using Csla.Channels.Wcf.Client; -using Csla.Configuration; -using Microsoft.Extensions.DependencyInjection; - -var services = new ServiceCollection(); -services - .AddCsla(csla => csla - .DataPortal(dp => dp - .AddClientSideDataPortal(c => c - .UseWcfProxy()))); - -var provider = services.BuildServiceProvider(); -var applicationContext = provider.GetRequiredService(); -var portal = applicationContext.GetRequiredService>(); - -var user = applicationContext.User; - -int personId = 1; -var p = portal.Fetch(personId); - -Console.WriteLine(p.Name); -Console.ReadLine(); diff --git a/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj b/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj deleted file mode 100644 index 4155e463fb..0000000000 --- a/Samples/WcfExample/WcfClientExample/WcfClientExample.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net10.0 - enable - enable - - - - - - - - - - - - diff --git a/Samples/WcfExample/WcfExample.slnx b/Samples/WcfExample/WcfExample.slnx deleted file mode 100644 index a693bb39b0..0000000000 --- a/Samples/WcfExample/WcfExample.slnx +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Samples/WcfExample/WcfNetFrameworkExample/Program.cs b/Samples/WcfExample/WcfNetFrameworkExample/Program.cs deleted file mode 100644 index 5c02a308ff..0000000000 --- a/Samples/WcfExample/WcfNetFrameworkExample/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -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.Opened += (o, e) => Console.WriteLine($"WCF Data Portal now listening on {wcfHost.Description.Endpoints.First().Address.Uri}"); - -wcfHost.Open(); - -Console.ReadLine(); \ No newline at end of file diff --git a/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj b/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj deleted file mode 100644 index ad61cfbbc5..0000000000 --- a/Samples/WcfExample/WcfNetFrameworkExample/WcfNetFrameworkExample.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Exe - net48 - enable - enable - Latest - - - - - - - - - - - -