Skip to content

Commit

Permalink
Merge pull request #35 from SeanFeldman/develop
Browse files Browse the repository at this point in the history
release-2.2.0
  • Loading branch information
SeanFeldman authored Mar 12, 2018
2 parents 1b4fa71 + 3f9266d commit e128416
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 31 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

Allows sending messages that exceed maximum size by implementing [Claim Check pattern](http://www.enterpriseintegrationpatterns.com/patterns/messaging/StoreInLibrary.html) with Azure Storage.

### Nuget package [![NuGet Status](https://buildstats.info/nuget/ServiceBus.AttachmentPlugin?includePreReleases=true)](https://www.nuget.org/packages/ServiceBus.AttachmentPlugin/) [![Build Status](https://img.shields.io/appveyor/ci/seanfeldman/ServiceBus-AttachmentPlugin/master.svg?style=flat-square)](https://ci.appveyor.com/project/seanfeldman/ServiceBus-AttachmentPlugin) [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin/blob/master/LICENSE) [![Issues](https://img.shields.io/github/issues-raw/badges/shields/website.svg)](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin/blob/master/LICENSE)
[![develop build status](https://ci.appveyor.com/api/projects/status/kpw7nfmr4femj29y/branch/develop.svg?style=flat-square&pendingText=develop%20%E2%80%A3%20pending&failingText=develop%20%E2%80%A3%20failing&passingText=develop%20%E2%80%A3%20passing)](https://ci.appveyor.com/project/seanfeldman/ServiceBus-AttachmentPlugin)
[![master build status](https://ci.appveyor.com/api/projects/status/kpw7nfmr4femj29y/branch/master.svg?style=flat-square&pendingText=master%20%E2%80%A3%20pending&failingText=master%20%E2%80%A3%20failing&passingText=master%20%E2%80%A3%20passing)](https://ci.appveyor.com/project/seanfeldman/ServiceBus-AttachmentPlugin)
[![opened issues](https://img.shields.io/github/issues-raw/badges/shields/website.svg)](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin)

### Nuget package

[![NuGet Status](https://buildstats.info/nuget/ServiceBus.AttachmentPlugin?includePreReleases=true)](https://www.nuget.org/packages/ServiceBus.AttachmentPlugin/)

Available here http://nuget.org/packages/ServiceBus.AttachmentPlugin

Expand Down Expand Up @@ -114,12 +121,23 @@ Default is to convert any body to attachment.
new AzureStorageAttachmentConfiguration(storageConnectionString, message => message.Body.Length > 200 * 1024);
```

### Configuring connection string provider

When Storage connection string needs to be retrieved rather than passed in as a plain text, `AzureStorageAttachmentConfiguration` accepts implementation of `IProvideStorageConnectionString`.
The plugin comes with a `PlainTextConnectionStringProvider` and can be used in the following way.

```c#
var provider = new PlainTextConnectionStringProvider("connectionString");
var config = new AzureStorageAttachmentConfiguration(provider);
```

## Who's trusting this add-in in production

![Codit](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin/blob/master/images/using/Codit.png)
![RISC Software](https://github.com/SeanFeldman/ServiceBus.AttachmentPlugin/blob/develop/images/using/RISC.software.png)

Proudly list your company here if use this add-in in production

## Icon

Created by Dinosoft Labs from the Noun Project.
Created by Dinosoft Labs from the Noun Project.
Binary file added images/using/RISC.software.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
namespace ServiceBus.AttachmentPlugin.Tests
{
using System;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
using Xunit;

public class AzureStorageAttachmentConfigurationTests
{
[Fact]
public void Should_apply_defaults_for_missing_arguments()
public async Task Should_apply_defaults_for_missing_arguments()
{
var configuration = new AzureStorageAttachmentConfiguration("connectionString")
var configuration = new AzureStorageAttachmentConfiguration(new PlainTextConnectionStringProvider("connectionString"))
.WithSasUri();
Assert.Equal("connectionString", configuration.ConnectionString);
Assert.Equal("connectionString", await configuration.ConnectionStringProvider.GetConnectionString());
Assert.NotEmpty(configuration.ContainerName);
Assert.NotEmpty(configuration.MessagePropertyToIdentifyAttachmentBlob);
Assert.Equal(AzureStorageAttachmentConfigurationExtensions.DefaultSasTokenValidationTime.Days, configuration.SasTokenValidationTime.Value.Days);
Expand All @@ -22,7 +23,8 @@ public void Should_apply_defaults_for_missing_arguments()
[Fact]
public void Should_not_accept_negative_token_validation_time()
{
Assert.Throws<ArgumentException>(() => new AzureStorageAttachmentConfiguration("connectionString").WithSasUri(sasTokenValidationTime: TimeSpan.FromHours(-4)));
Assert.Throws<ArgumentException>(() =>
new AzureStorageAttachmentConfiguration(new PlainTextConnectionStringProvider("connectionString")).WithSasUri(sasTokenValidationTime: TimeSpan.FromHours(-4)));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
namespace ServiceBus.AttachmentPlugin.Tests
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;

public class AzureStorageEmulatorFixture
{
public static IProvideStorageConnectionString ConnectionStringProvider = new PlainTextConnectionStringProvider("UseDevelopmentStorage=true");

public AzureStorageEmulatorFixture()
{
AzureStorageEmulatorManager.StartStorageEmulator();

// Emulator is not started fast enough on AppVeyor
// Microsoft.WindowsAzure.Storage.StorageException : Unable to connect to the remote server
Thread.Sleep(1000);

var properties = IPGlobalProperties.GetIPGlobalProperties();
bool emulatorStarted;

var stopwatch = Stopwatch.StartNew();

do
{
var endpoints = properties.GetActiveTcpListeners();
emulatorStarted = endpoints.Any(x => x.Port == 10000 && Equals(x.Address, IPAddress.Loopback));
Console.WriteLine("waiting for emulator to start...");
Thread.Sleep(100);
} while (emulatorStarted == false && stopwatch.Elapsed < TimeSpan.FromSeconds(60));

if (emulatorStarted == false)
{
throw new Exception("Storage emulator failed to start after 10 seconds.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace ServiceBus.AttachmentPlugin.Tests

public class When_receiving_message : IClassFixture<AzureStorageEmulatorFixture>
{

[Fact]
public async Task Should_throw_exception_with_blob_path_for_found_that_cant_be_found()
{
Expand All @@ -21,15 +20,15 @@ public async Task Should_throw_exception_with_blob_path_for_found_that_cant_be_f
};

var sendingPlugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true", containerName: "attachments"));
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName: "attachments"));
await sendingPlugin.BeforeMessageSend(message);

var receivingPlugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true", containerName: "attachments-wrong-containers"));
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName: "attachments-wrong-containers"));

var exception = await Assert.ThrowsAsync<Exception>(() => receivingPlugin.AfterMessageReceive(message));
Assert.Contains("attachments-wrong-containers", exception.Message);
Assert.Contains(message.UserProperties["$attachment.blob"].ToString(), exception.Message);
Assert.Contains("attachments-wrong-containers", actualString: exception.Message);
Assert.Contains(message.UserProperties["$attachment.blob"].ToString(), actualString: exception.Message);
}
}
}
12 changes: 6 additions & 6 deletions src/ServiceBus.AttachmentPlugin.Tests/When_sending_message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task Should_nullify_body_when_body_should_be_sent_as_attachment()
MessageId = Guid.NewGuid().ToString(),
};
var plugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString:"UseDevelopmentStorage=true", containerName:"attachments", messagePropertyToIdentifyAttachmentBlob:"attachment-id"));
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName:"attachments", messagePropertyToIdentifyAttachmentBlob:"attachment-id"));
var result = await plugin.BeforeMessageSend(message);

Assert.Null(result.Body);
Expand All @@ -37,7 +37,7 @@ public async Task Should_leave_body_as_is_for_message_not_exceeding_max_size()
TimeToLive = TimeSpan.FromHours(1)
};
var plugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString:"UseDevelopmentStorage=true", containerName:"attachments", messagePropertyToIdentifyAttachmentBlob:"attachment-id",
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName:"attachments", messagePropertyToIdentifyAttachmentBlob:"attachment-id",
messageMaxSizeReachedCriteria:msg => msg.Body.Length > 100));
var result = await plugin.BeforeMessageSend(message);

Expand All @@ -56,13 +56,13 @@ public async Task Should_set_valid_until_datetime_on_blob_same_as_message_TTL()
TimeToLive = TimeSpan.FromHours(1)
};
var dateTimeNowUtc = new DateTime(2017, 1, 2);
var configuration = new AzureStorageAttachmentConfiguration(connectionString:"UseDevelopmentStorage=true", containerName:"attachments",
var configuration = new AzureStorageAttachmentConfiguration(connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName:"attachments",
messagePropertyToIdentifyAttachmentBlob:"attachment-id");
AzureStorageAttachment.DateTimeFunc = () => dateTimeNowUtc;
var plugin = new AzureStorageAttachment(configuration);
await plugin.BeforeMessageSend(message);

var account = CloudStorageAccount.Parse(configuration.ConnectionString);
var account = CloudStorageAccount.Parse(await configuration.ConnectionStringProvider.GetConnectionString());
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference(configuration.ContainerName);
var blobName = (string)message.UserProperties[configuration.MessagePropertyToIdentifyAttachmentBlob];
Expand All @@ -79,7 +79,7 @@ public async Task Should_receive_it()
var bytes = Encoding.UTF8.GetBytes(payload);
var message = new Message(bytes);
var configuration = new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true", containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id");
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id");

var plugin = new AzureStorageAttachment(configuration);
await plugin.BeforeMessageSend(message);
Expand All @@ -101,7 +101,7 @@ public async Task Should_not_set_sas_uri_by_default()
MessageId = Guid.NewGuid().ToString(),
};
var plugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true", containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id"));
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id"));
var result = await plugin.BeforeMessageSend(message);

Assert.Null(result.Body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task Should_set_sas_uri_when_specified()
MessageId = Guid.NewGuid().ToString(),
};
var plugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true", containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id")
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider, containerName: "attachments", messagePropertyToIdentifyAttachmentBlob: "attachment-id")
.WithSasUri(sasTokenValidationTime: TimeSpan.FromHours(4), messagePropertyToIdentifySasUri: "mySasUriProperty"));
var result = await plugin.BeforeMessageSend(message);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public async Task Should_download_attachment_using_provided_from_sas_uri()
MessageId = Guid.NewGuid().ToString(),
};
var plugin = new AzureStorageAttachment(new AzureStorageAttachmentConfiguration(
connectionString: "UseDevelopmentStorage=true",
connectionStringProvider: AzureStorageEmulatorFixture.ConnectionStringProvider,
containerName: "attachments",
messagePropertyToIdentifyAttachmentBlob:
"attachment-id")
Expand Down
40 changes: 35 additions & 5 deletions src/ServiceBus.AttachmentPlugin/AzureStorageAttachment.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace ServiceBus.AttachmentPlugin
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
using Microsoft.Azure.ServiceBus.Core;
Expand All @@ -9,18 +10,17 @@

class AzureStorageAttachment : ServiceBusPlugin
{
SemaphoreSlim semaphore= new SemaphoreSlim(1);
const string MessageId = "_MessageId";
internal const string ValidUntilUtc = "_ValidUntilUtc";
internal const string DateFormat = "yyyy-MM-dd HH:mm:ss:ffffff Z";

Lazy<CloudBlobClient> client;
CloudBlobClient client;
AzureStorageAttachmentConfiguration configuration;

public AzureStorageAttachment(AzureStorageAttachmentConfiguration configuration)
{
Guard.AgainstNull(nameof(configuration), configuration);
var account = CloudStorageAccount.Parse(configuration.ConnectionString);
client = new Lazy<CloudBlobClient>(() => account.CreateCloudBlobClient());
this.configuration = configuration;
}

Expand All @@ -30,12 +30,14 @@ public AzureStorageAttachment(AzureStorageAttachmentConfiguration configuration)

public override async Task<Message> BeforeMessageSend(Message message)
{
await InitializeClient().ConfigureAwait(false);

if (!configuration.MessageMaxSizeReachedCriteria(message))
{
return message;
}

var container = client.Value.GetContainerReference(configuration.ContainerName);
var container = client.GetContainerReference(configuration.ContainerName);
await container.CreateIfNotExistsAsync().ConfigureAwait(false);
var blob = container.GetBlockBlobReference(Guid.NewGuid().ToString());

Expand All @@ -57,6 +59,32 @@ public override async Task<Message> BeforeMessageSend(Message message)
return message;
}

async Task InitializeClient()
{
if (client != null)
{
return;
}

await semaphore.WaitAsync().ConfigureAwait(false);

if (client != null)
{
return;
}

try
{
var connectionString = await configuration.ConnectionStringProvider.GetConnectionString().ConfigureAwait(false);
var account = CloudStorageAccount.Parse(connectionString);
client = account.CreateCloudBlobClient();
}
finally
{
semaphore.Release();
}
}

static void SetValidMessageId(ICloudBlob blob, string messageId)
{
if (!string.IsNullOrWhiteSpace(messageId))
Expand Down Expand Up @@ -93,7 +121,9 @@ public override async Task<Message> AfterMessageReceive(Message message)
}
else
{
var container = client.Value.GetContainerReference(configuration.ContainerName);
await InitializeClient().ConfigureAwait(false);

var container = client.GetContainerReference(configuration.ContainerName);
await container.CreateIfNotExistsAsync().ConfigureAwait(false);
var blobName = (string)userProperties[configuration.MessagePropertyToIdentifyAttachmentBlob];
blob = container.GetBlockBlobReference(blobName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@ public AzureStorageAttachmentConfiguration(
string connectionString,
string containerName = "attachments",
string messagePropertyToIdentifyAttachmentBlob = "$attachment.blob",
Func<Message, bool> messageMaxSizeReachedCriteria = null)
: this(new PlainTextConnectionStringProvider(connectionString), containerName, messagePropertyToIdentifyAttachmentBlob, messageMaxSizeReachedCriteria)
{
}

/// <summary>Constructor to create new configuration object.</summary>
/// <param name="connectionStringProvider">Provider to retrieve connection string such as <see cref="PlainTextConnectionStringProvider"/></param>
/// <param name="containerName">Storage container name</param>
/// <param name="messagePropertyToIdentifyAttachmentBlob">Message user property to use for blob URI</param>
/// <param name="messageMaxSizeReachedCriteria">Default is always use attachments</param>
public AzureStorageAttachmentConfiguration(
IProvideStorageConnectionString connectionStringProvider,
string containerName = "attachments",
string messagePropertyToIdentifyAttachmentBlob = "$attachment.blob",
Func<Message, bool> messageMaxSizeReachedCriteria = null)
{
Guard.AgainstEmpty(nameof(containerName), containerName);
Guard.AgainstEmpty(nameof(messagePropertyToIdentifyAttachmentBlob), messagePropertyToIdentifyAttachmentBlob);
ConnectionString = connectionString;
ConnectionStringProvider = connectionStringProvider;
ContainerName = containerName;
MessagePropertyToIdentifyAttachmentBlob = messagePropertyToIdentifyAttachmentBlob;
MessageMaxSizeReachedCriteria = GetMessageMaxSizeReachedCriteria(messageMaxSizeReachedCriteria);
Expand All @@ -44,7 +58,7 @@ Func<Message, bool> GetMessageMaxSizeReachedCriteria(Func<Message, bool> message
};
}

internal string ConnectionString { get; }
internal IProvideStorageConnectionString ConnectionStringProvider { get; }

internal string ContainerName { get; }

Expand Down
6 changes: 3 additions & 3 deletions src/ServiceBus.AttachmentPlugin/Guard.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;

namespace ServiceBus.AttachmentPlugin
namespace ServiceBus.AttachmentPlugin
{
using System;

static class Guard
{
public static void AgainstEmpty(string argumentName, string value)
Expand Down
15 changes: 15 additions & 0 deletions src/ServiceBus.AttachmentPlugin/IProvideStorageConnectionString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace ServiceBus.AttachmentPlugin
{
using System.Threading.Tasks;

/// <summary>
/// Storage account connection string provider.
/// </summary>
public interface IProvideStorageConnectionString
{
/// <summary>
/// Connection string for storage account to be used.
/// </summary>
Task<string> GetConnectionString();
}
}
Loading

0 comments on commit e128416

Please sign in to comment.