Skip to content

Commit

Permalink
New Middleware demo in C# (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardnormier authored Feb 13, 2025
1 parent 62318e1 commit b3640d9
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 0 deletions.
20 changes: 20 additions & 0 deletions csharp/Ice/Middleware/Client/Client.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Copy the PDBs from the NuGet packages to get file names and line numbers in stack traces. -->
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
</PropertyGroup>
<ItemGroup>
<SliceCompile Include="../slice/Greeter.ice" />
<PackageReference Include="zeroc.ice.net" Version="3.8.0-alpha0" />
<PackageReference Include="zeroc.icebuilder.msbuild" Version="5.0.9" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
35 changes: 35 additions & 0 deletions csharp/Ice/Middleware/Client/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) ZeroC, Inc.

// Slice module VisitorCenter in Greeter.ice maps to C# namespace VisitorCenter.
using VisitorCenter;

// Create an Ice communicator to initialize the Ice runtime.
using Ice.Communicator communicator = Ice.Util.initialize(ref args);

// Create a Greeter proxy. If you run the server on a different computer, replace localhost in the string below with
// the server's hostname or IP address.
GreeterPrx greeter = GreeterPrxHelper.createProxy(communicator, "greeter:tcp -h localhost -p 4061");

// We hardcode the tokens in this demo, for simplicity. A real application would obtain the token from a secure source.
string validToken = "iced tea";

// Send a request to the remote object with an invalid token in the request context.
try
{
string unexpected = await greeter.GreetAsync(
Environment.UserName,
context: new Dictionary<string, string> { ["token"] = "pineapple" });

Console.WriteLine($"Received unexpected greeting: {unexpected}");
}
catch (Ice.ObjectNotExistException)
{
// Expected with an invalid (or missing) token. See AuthorizationMiddleware.
}

// Send a request with the correct token in the request context.
string greeting = await greeter.GreetAsync(
Environment.UserName,
context: new Dictionary<string, string> { ["token"] = validToken });

Console.WriteLine(greeting);
36 changes: 36 additions & 0 deletions csharp/Ice/Middleware/Middleware.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33122.133
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{527EEA4D-77B9-4252-A2CD-C641A25CAD53}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FBDAF448-665A-4595-98D3-4538C56A666D}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Release|Any CPU.Build.0 = Release|Any CPU
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B36F1E1-0592-4A15-9981-67BC4A653EC4}
EndGlobalSection
EndGlobal
37 changes: 37 additions & 0 deletions csharp/Ice/Middleware/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Middleware

The Middleware demo shows how to write a simple middleware and how to add this middleware to an object adapter.

Each object adapter maintains a dispatch pipeline:

```mermaid
flowchart LR
client[Client] -- request --> am -- response --> client
subgraph pipeline [Dispatch pipeline]
direction LR
am[Authorization <br>middleware] --> om[Other <br>middleware] --> Servant --> om --> am
end
```

> [!NOTE]
> Other middleware is just an example, and is not provided by this demo.
You can build the client and server applications with:

``` shell
dotnet build
```

First start the Server program:

```shell
cd Server
dotnet run
```

In a separate terminal, start the Client program:

```shell
cd Client
dotnet run
```
40 changes: 40 additions & 0 deletions csharp/Ice/Middleware/Server/AuthorizationMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) ZeroC, Inc.

namespace Server;

/// <summary>The AuthorizationMiddleware intercepts incoming requests and checks the token entry in their contexts.
/// </summary>
internal class AuthorizationMiddleware : Ice.Object
{
private readonly Ice.Object _next;
private readonly string _validToken;

/// <inheritdoc />
public ValueTask<Ice.OutgoingResponse> dispatchAsync(Ice.IncomingRequest request)
{
// Check if the request has a valid token in its context.
if (!request.current.ctx.TryGetValue("token", out string? token) || token != _validToken)
{
Console.WriteLine($"Rejecting request with token '{token}'");

// ObjectNotExistException is one of the 6 Ice local exceptions that Ice transmits "over the wire".
// It usually means "no servant was found for this identity". Here, we reuse it to indicate an authorization
// failure without leaking information to the client.
throw new Ice.ObjectNotExistException();
}

Console.WriteLine($"Accepting request with token '{token}'");

// Continue the dispatch pipeline.
return _next.dispatchAsync(request);
}

/// <summary>Constructs an AuthorizationMiddleware.</summary>
/// <param name="next">The next Ice.Object in the dispatch pipeline.</param>
/// <param name="validToken">The value to check against.</param>
internal AuthorizationMiddleware(Ice.Object next, string validToken)
{
_next = next;
_validToken = validToken;
}
}
17 changes: 17 additions & 0 deletions csharp/Ice/Middleware/Server/Chatbot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) ZeroC, Inc.

using VisitorCenter;

namespace Server;

/// <summary>Chatbot is an Ice servant that implements Slice interface Greeter.</summary>
internal class Chatbot : GreeterDisp_
{
/// <inheritdoc />
/// // Implements the abstract method Greet from the GreeterDisp_ class generated by the Slice compiler.
public override string Greet(string name, Ice.Current current)
{
Console.WriteLine($"Dispatching greet request {{ name = '{name}' }}");
return $"Hello, {name}!";
}
}
21 changes: 21 additions & 0 deletions csharp/Ice/Middleware/Server/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) ZeroC, Inc.

// Create an Ice communicator to initialize the Ice runtime.
using Ice.Communicator communicator = Ice.Util.initialize(ref args);

// Create an object adapter that listens for incoming requests and dispatches them to servants.
Ice.ObjectAdapter adapter = communicator.createObjectAdapterWithEndpoints("GreeterAdapter", "tcp -p 4061");

// Install the Authorization middleware in the object adapter.
adapter.use(next => new Server.AuthorizationMiddleware(next, validToken: "iced tea"));

// Register the Chatbot servant with the adapter.
adapter.add(new Server.Chatbot(), Ice.Util.stringToIdentity("greeter"));

// Start dispatching requests.
adapter.activate();
Console.WriteLine("Listening on port 4061...");

// Wait until the user presses Ctrl+C.
await CancelKeyPressed;
Console.WriteLine("Caught Ctrl+C, exiting...");
21 changes: 21 additions & 0 deletions csharp/Ice/Middleware/Server/Server.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Copy the PDBs from the NuGet packages to get file names and line numbers in stack traces. -->
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
</PropertyGroup>
<ItemGroup>
<SliceCompile Include="../slice/Greeter.ice" />
<PackageReference Include="zeroc.ice.net" Version="3.8.0-alpha0" />
<PackageReference Include="zeroc.icebuilder.msbuild" Version="5.0.9" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<Compile Include="../../../Common/Program.CancelKeyPressed.cs" Link="Program.CancelKeyPressed.cs" />
</ItemGroup>
</Project>
16 changes: 16 additions & 0 deletions csharp/Ice/Middleware/slice/Greeter.ice
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) ZeroC, Inc.

#pragma once

module VisitorCenter
{
/// Represents a simple greeter.
interface Greeter
{
/// Creates a personalized greeting.
/// @param name The name of the person to greet.
/// @return The greeting.
["cs:identifier:Greet"] // We prefer PascalCase for C# methods.
string greet(string name);
}
}

0 comments on commit b3640d9

Please sign in to comment.