Skip to content

Commit 19566d9

Browse files
authored
Add default AddFromIncoming overload and test (#797)
Introduce a convenience overload of IOutgoingAttachments.AddFromIncoming that uses the "default" incoming and outgoing attachment name. Add AddFromIncomingDefaultNameTests to validate a round-trip scenario where an incoming default-named attachment is transformed (to uppercase) and returned under the default name, ensuring the new overload correctly reads the default incoming attachment and registers the outgoing one.
1 parent ba278f5 commit 19566d9

3 files changed

Lines changed: 110 additions & 1 deletion

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
[NotInParallel]
4+
public class AddFromIncomingDefaultNameTests
5+
{
6+
static ManualResetEvent resetEvent = new(false);
7+
static byte[]? receivedBytes;
8+
9+
[Test]
10+
public async Task RoundTripsWithDefaultName()
11+
{
12+
receivedBytes = null;
13+
resetEvent.Reset();
14+
15+
await using var database = await Connection.SqlInstance.Build("AddFromIncomingDefaultName");
16+
var connectionString = database.ConnectionString;
17+
var databaseName = new SqlConnectionStringBuilder(connectionString).InitialCatalog;
18+
19+
var configuration = new EndpointConfiguration("SqlAddFromIncomingDefaultName");
20+
SqlConnection NewConnection() => new(connectionString);
21+
var attachments = configuration.EnableAttachments(NewConnection, TimeToKeep.Default, database: databaseName, table: "Attachments");
22+
configuration.UseSerialization<SystemJsonSerializer>();
23+
configuration.UsePersistence<LearningPersistence>();
24+
configuration.DisableRetries();
25+
configuration.EnableInstallers();
26+
configuration.PurgeOnStartup(true);
27+
attachments.DisableCleanupTask();
28+
configuration.RegisterComponents(_ => _.AddSingleton(resetEvent));
29+
var transport = configuration.UseTransport<LearningTransport>();
30+
transport.StorageDirectory(Path.Combine(Path.GetTempPath(), "AddFromIncomingDefaultName"));
31+
transport.Transactions(TransportTransactionMode.SendsAtomicWithReceive);
32+
33+
var endpoint = await Endpoint.Start(configuration);
34+
35+
// Sender writes the incoming attachment under the default name.
36+
var sendOptions = new SendOptions();
37+
sendOptions.RouteToThisEndpoint();
38+
var outgoing = sendOptions.Attachments();
39+
outgoing.Add(BuildStream("hello"));
40+
await endpoint.Send(new InMessage(), sendOptions);
41+
42+
if (!resetEvent.WaitOne(TimeSpan.FromSeconds(20)))
43+
{
44+
await endpoint.Stop();
45+
throw new("TimedOut");
46+
}
47+
48+
await endpoint.Stop();
49+
50+
await Assert.That(Encoding.UTF8.GetString(receivedBytes!)).IsEqualTo("HELLO");
51+
}
52+
53+
static MemoryStream BuildStream(string content) =>
54+
new(Encoding.UTF8.GetBytes(content));
55+
56+
class InMessage :
57+
IMessage;
58+
59+
class OutMessage :
60+
IMessage;
61+
62+
class InHandler :
63+
IHandleMessages<InMessage>
64+
{
65+
public Task Handle(InMessage message, HandlerContext context)
66+
{
67+
var replyOptions = new ReplyOptions();
68+
var outgoing = replyOptions.Attachments();
69+
// No-name overload reads the default-named incoming and registers the result under the default name.
70+
outgoing.AddFromIncoming(transform: async (source, sink, cancel) =>
71+
{
72+
using var reader = new StreamReader(source, leaveOpen: true);
73+
var content = await reader.ReadToEndAsync(cancel);
74+
await using var writer = new StreamWriter(sink, leaveOpen: true);
75+
await writer.WriteAsync(content.ToUpperInvariant());
76+
});
77+
return context.Reply(new OutMessage(), replyOptions);
78+
}
79+
}
80+
81+
class OutHandler :
82+
IHandleMessages<OutMessage>
83+
{
84+
public async Task Handle(OutMessage message, HandlerContext context)
85+
{
86+
await using var memoryStream = new MemoryStream();
87+
var incoming = context.Attachments();
88+
// No-name CopyTo reads the default-named attachment.
89+
await incoming.CopyTo(memoryStream, context.CancellationToken);
90+
receivedBytes = memoryStream.ToArray();
91+
resetEvent.Set();
92+
}
93+
}
94+
}

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
<Project>
33
<PropertyGroup>
4-
<Version>19.4.0</Version>
4+
<Version>19.5.0</Version>
55
<LangVersion>preview</LangVersion>
66
<AssemblyVersion>1.0.0</AssemblyVersion>
77
<PackageTags>NServiceBus, Attachments, DataBus</PackageTags>

src/Shared/Outgoing/OutgoingAttachmentsExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,19 @@ public static void AddFromIncoming(
4949
Action? cleanup = null,
5050
IReadOnlyDictionary<string, string>? metadata = null) =>
5151
attachments.AddFromIncoming(name, name, transform, bufferSource, bufferSink, timeToKeep, cleanup, metadata);
52+
53+
/// <summary>
54+
/// Add an outgoing attachment whose data is produced by transforming the default-named incoming attachment
55+
/// of the current message. The produced attachment is registered with the default name.
56+
/// See <see cref="IOutgoingAttachments.AddFromIncoming"/>.
57+
/// </summary>
58+
public static void AddFromIncoming(
59+
this IOutgoingAttachments attachments,
60+
Func<Stream, Stream, Cancel, Task> transform,
61+
bool bufferSource = false,
62+
bool bufferSink = false,
63+
GetTimeToKeep? timeToKeep = null,
64+
Action? cleanup = null,
65+
IReadOnlyDictionary<string, string>? metadata = null) =>
66+
attachments.AddFromIncoming("default", "default", transform, bufferSource, bufferSink, timeToKeep, cleanup, metadata);
5267
}

0 commit comments

Comments
 (0)