Skip to content

Commit 36ca794

Browse files
b-higginbothamBobby HigginbothamCopilotrockfordlhotka
authored
Implement WCF Data Portal Channel (#4851)
* Added WCF Data Portal Channel * Fix a typo in XML comment respose -> response Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix typo in XML comment poral -> portal Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix typos in readme.md * remove Console.WriteLine that was used for debugging * Add WCF portal integration tests * Replace ChannelFactory with ClientBase. * Revert VisualStudioVersion change in csla.test.sln Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix typos in XML comments * Fix client not being closed after sync call * Add missing file header * Add support for data portal routing * Remove test that cannot fail * Remove unnecessary wrapping of return value * Remove WCF sample that isn't ready yet --------- Co-authored-by: Bobby Higginbotham <robert.l.higginbotham22.ctr@army.mil> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Rockford Lhotka <rocky@lhotka.net>
1 parent 2121809 commit 36ca794

19 files changed

Lines changed: 1821 additions & 1 deletion
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="IWcfPortal.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using System.ServiceModel;
10+
11+
namespace Csla.Channels.Wcf.Client
12+
{
13+
/// <summary>
14+
/// Represents the WCF service contract that is used for communication between the data portal client and server.
15+
/// </summary>
16+
[ServiceContract]
17+
public interface IWcfPortal
18+
{
19+
/// <summary>
20+
/// Asynchronously invokes an operation on the remote data portal.
21+
/// </summary>
22+
/// <param name="request">
23+
/// The request that contains the name and parameters necessary to invoke the data portal operation.
24+
/// </param>
25+
/// <returns>
26+
/// A task containing the response from the remote data portal.
27+
/// </returns>
28+
[OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
29+
Task<WcfResponse> InvokeAsync(WcfRequest request);
30+
31+
/// <summary>
32+
/// Synchronously invokes an operation on the remote data portal.
33+
/// </summary>
34+
/// <param name="request">
35+
/// The request that contains the name and parameters necessary to invoke the data portal operation.
36+
/// </param>
37+
/// <returns>
38+
/// The response from the remote data portal.
39+
/// </returns>
40+
[OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
41+
WcfResponse Invoke(WcfRequest request);
42+
}
43+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="WcfClientExtensions.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using Csla.Configuration;
10+
using Csla.DataPortalClient;
11+
using Microsoft.Extensions.DependencyInjection;
12+
13+
namespace Csla.Channels.Wcf.Client
14+
{
15+
/// <summary>
16+
/// Provides extension methods that can be used to configure the client side data portal to use the WCF channel.
17+
/// </summary>
18+
public static class WcfClientExtensions
19+
{
20+
/// <summary>
21+
/// Configures the client side data portal to use the WCF channel.
22+
/// </summary>
23+
/// <param name="config">
24+
/// The client side data portal options that the WCF configuration will be applied to.
25+
/// </param>
26+
/// <param name="setOptions">
27+
/// An optional action that will be invoked to set options for the <see cref="WcfProxy"/>.
28+
/// </param>
29+
/// <returns>
30+
/// The <paramref name="config"/> with the WCF configuration applied.
31+
/// </returns>
32+
/// <exception cref="ArgumentNullException">
33+
/// <paramref name="config"/> is <see langword="null"/>.
34+
/// </exception>
35+
public static DataPortalClientOptions UseWcfProxy(this DataPortalClientOptions config, Action<WcfProxyOptions>? setOptions)
36+
{
37+
if (config is null)
38+
throw new ArgumentNullException(nameof(config));
39+
40+
var proxyOptions = new WcfProxyOptions();
41+
42+
setOptions?.Invoke(proxyOptions);
43+
44+
config.Services.AddSingleton(_ => proxyOptions);
45+
config.Services.AddTransient<IDataPortalProxy>(provider =>
46+
{
47+
var applicationContext = provider.GetRequiredService<ApplicationContext>();
48+
var options = provider.GetRequiredService<WcfProxyOptions>();
49+
var dataPortalOptions = provider.GetRequiredService<DataPortalOptions>();
50+
var wcfProxy = new WcfProxy(applicationContext, options, dataPortalOptions);
51+
return wcfProxy;
52+
});
53+
54+
return config;
55+
}
56+
57+
/// <summary>
58+
/// Configures the client side data portal to use the WCF channel.
59+
/// </summary>
60+
/// <param name="config">
61+
/// The client side data portal options that the WCF configuration will be applied to.
62+
/// </param>
63+
/// <returns>
64+
/// The <paramref name="config"/> with the WCF configuration applied.
65+
/// </returns>
66+
/// <exception cref="ArgumentNullException">
67+
/// <paramref name="config"/> is <see langword="null"/>.
68+
/// </exception>
69+
public static DataPortalClientOptions UseWcfProxy(this DataPortalClientOptions config) => config.UseWcfProxy(null);
70+
}
71+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="WcfPortalClient.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using System.ServiceModel;
10+
using System.ServiceModel.Channels;
11+
12+
namespace Csla.Channels.Wcf.Client
13+
{
14+
/// <summary>
15+
/// Represents a WCF client that is used by the <see cref="WcfProxy"/> to communicate with a remote data portal.
16+
/// </summary>
17+
/// <param name="binding">
18+
/// The WCF binding that is used to communicate with the remote WCF data portal service.
19+
/// </param>
20+
/// <param name="address">
21+
/// The endpoint address that is used to communicate with the remote WCF data portal service.
22+
/// </param>
23+
internal class WcfPortalClient(Binding binding, EndpointAddress address) : ClientBase<IWcfPortal>(binding, address), IWcfPortal
24+
{
25+
/// <summary>
26+
/// Asynchronously invokes an operation on the remote data portal.
27+
/// </summary>
28+
/// <param name="request">
29+
/// The request that contains the name and parameters necessary to invoke the data portal operation.
30+
/// </param>
31+
/// <returns>
32+
/// A task containing the response from the remote data portal.
33+
/// </returns>
34+
public Task<WcfResponse> InvokeAsync(WcfRequest request) => Channel.InvokeAsync(request);
35+
36+
/// <summary>
37+
/// Synchronously invokes an operation on the remote data portal.
38+
/// </summary>
39+
/// <param name="request">
40+
/// The request that contains the name and parameters necessary to invoke the data portal operation.
41+
/// </param>
42+
/// <returns>
43+
/// The response from the remote data portal.
44+
/// </returns>
45+
public WcfResponse Invoke(WcfRequest request) => Channel.Invoke(request);
46+
}
47+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="WcfProxy.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using System.ServiceModel;
10+
using Csla.Configuration;
11+
using Csla.DataPortalClient;
12+
13+
namespace Csla.Channels.Wcf.Client
14+
{
15+
/// <summary>
16+
/// Represents a <see cref="DataPortalProxy"/> that communicates with a remote data portal using WCF.
17+
/// </summary>
18+
/// <param name="applicationContext">
19+
/// The client side context for the data portal.
20+
/// </param>
21+
/// <param name="wcfProxyOptions">
22+
/// The options that are used to configure the data portal proxy.
23+
/// </param>
24+
/// <param name="dataPortalOptions">
25+
/// The options that are used to configure the client side data portal.
26+
/// </param>
27+
/// <exception cref="ArgumentNullException">
28+
/// <paramref name="wcfProxyOptions"/> is <see langword="null"/>.
29+
/// </exception>
30+
public class WcfProxy(ApplicationContext applicationContext, WcfProxyOptions wcfProxyOptions, DataPortalOptions dataPortalOptions) : DataPortalProxy(applicationContext)
31+
{
32+
protected WcfProxyOptions _options = wcfProxyOptions ?? throw new ArgumentNullException(nameof(wcfProxyOptions));
33+
protected string? _versionRoutingTag = dataPortalOptions.VersionRoutingTag;
34+
35+
/// <summary>
36+
/// Gets the URL address for the data portal server used by this proxy instance.
37+
/// </summary>
38+
public override string DataPortalUrl => _options.DataPortalUrl;
39+
40+
protected override async Task<byte[]> CallDataPortalServer(byte[] serialized, string operation, string? routingToken, bool isSync)
41+
{
42+
var client = new WcfPortalClient(_options.Binding, new EndpointAddress(_options.DataPortalUrl));
43+
44+
var wcfRequest = new WcfRequest
45+
{
46+
Operation = CreateOperationTag(operation, _versionRoutingTag, routingToken),
47+
Body = serialized
48+
};
49+
50+
try
51+
{
52+
var response = isSync ? client.Invoke(wcfRequest) : await client.InvokeAsync(wcfRequest);
53+
54+
client.Close();
55+
56+
return response.Body;
57+
}
58+
catch (Exception)
59+
{
60+
client.Abort();
61+
throw;
62+
}
63+
}
64+
65+
internal async Task<WcfResponse> RouteMessage(WcfRequest request)
66+
{
67+
var client = new WcfPortalClient(_options.Binding, new EndpointAddress(_options.DataPortalUrl));
68+
69+
try
70+
{
71+
var response = await client.InvokeAsync(request);
72+
73+
client.Close();
74+
75+
return response;
76+
}
77+
catch (Exception)
78+
{
79+
client.Abort();
80+
throw;
81+
}
82+
}
83+
84+
private string CreateOperationTag(string operation, string? versionToken, string? routingToken)
85+
{
86+
if (!string.IsNullOrWhiteSpace(versionToken) || !string.IsNullOrWhiteSpace(routingToken))
87+
return $"{operation}/{routingToken}-{versionToken}";
88+
return operation;
89+
}
90+
}
91+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="WcfProxyOptions.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using System.ServiceModel;
10+
using System.ServiceModel.Channels;
11+
12+
namespace Csla.Channels.Wcf.Client
13+
{
14+
/// <summary>
15+
/// Represents options that are used to configure a WCF data portal proxy.
16+
/// </summary>
17+
public class WcfProxyOptions
18+
{
19+
/// <summary>
20+
/// Gets or sets the WCF binding that should be used to communicate with the remote WCF data portal service.
21+
/// </summary>
22+
public Binding Binding { get; set; } = new BasicHttpBinding();
23+
24+
/// <summary>
25+
/// Gets or sets the URL that should be used to communicate with the remote WCF data portal service.
26+
/// </summary>
27+
public string DataPortalUrl { get; set; } = "http://localhost";
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net462;net472;net48;net8.0;net9.0;net10.0</TargetFrameworks>
5+
<LangVersion>Latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\Csla\Csla.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup Condition="$(TargetFramework.StartsWith(&quot;net4&quot;))">
15+
<Reference Include="System.ServiceModel" />
16+
<Reference Include="System.ServiceModel.Activation" />
17+
</ItemGroup>
18+
19+
<!-- CoreWCF libraries are only needed for modern .net targets. -->
20+
<ItemGroup Condition="'$(TargetFramework)'=='net8.0' or '$(TargetFramework)'=='net9.0' or '$(TargetFramework)'=='net10.0'">
21+
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
22+
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
23+
</ItemGroup>
24+
25+
<!-- The supported target frameworks for the client side System.ServiceModel packages are not compatible between
26+
versions 8.x and 10.x so they must be included under separate conditions. There is no 9.x version. -->
27+
<ItemGroup Condition="'$(TargetFramework)'=='net8.0' or '$(TargetFramework)'=='net9.0'">
28+
<PackageReference Include="System.ServiceModel.Primitives" Version="8.1.2" />
29+
<PackageReference Include="System.ServiceModel.Http" Version="8.1.2" />
30+
</ItemGroup>
31+
32+
<ItemGroup Condition="'$(TargetFramework)'=='net10.0'">
33+
<PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" />
34+
<PackageReference Include="System.ServiceModel.Http" Version="10.0.652802" />
35+
</ItemGroup>
36+
37+
</Project>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="IWcfPortalServer.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Provides consistent context information between the client</summary>
7+
//-----------------------------------------------------------------------
8+
9+
#if NETFRAMEWORK
10+
using System.ServiceModel;
11+
#else
12+
using CoreWCF;
13+
#endif
14+
15+
namespace Csla.Channels.Wcf.Server
16+
{
17+
/// <summary>
18+
/// Represents the WCF service contract that is used for communication between the data portal client and server.
19+
/// </summary>
20+
[ServiceContract]
21+
public interface IWcfPortalServer
22+
{
23+
/// <summary>
24+
/// Asynchronously invokes an operation on the remote data portal.
25+
/// </summary>
26+
/// <param name="request">
27+
/// The request that contains the name and parameters necessary to invoke the data portal operation.
28+
/// </param>
29+
/// <returns>
30+
/// As task containing the response from the remote data portal.
31+
/// </returns>
32+
[OperationContract(Action = "https://cslanet.com/IwcfPortal/Invoke", ReplyAction = "https://cslanet.com/IwcfPortal/InvokeResponse")]
33+
Task<WcfResponse> InvokeAsync(WcfRequest request);
34+
}
35+
}

0 commit comments

Comments
 (0)