Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create filtered Service Bus subscription using .NET Core SDKs

I want to use Azure Service Bus from inside an .NET Core 2.1 application. I'm familiar with the SDK coming with the Nuget package Microsoft.Azure.ServiceBus and using this I'm currently writing to a topic and receiving messages from there.

What I want now is to create subscriptions on this topic for every client. The point is that I want to create a filtered subscription using the SqlFilter. This means, I have to create the subscription by code.

BTW: As far as I've seen it the only other way then doing it in code is using ARM template for the deployment because the portal will not allow me to create a filtered subscription.

I understood, that Microsoft.Azure.ServiceBus is not able to create ressources and so I went to the Nuget package Microsoft.Azure.Management.ServiceBus. So I now can create a new subscription from code now like this:

private static async Task CreateSubscriptionAsync()
{
    var context = new AuthenticationContext($"https://login.microsoftonline.com/{Configuration["AppSettings:AzureManagement:TenantId"]}");
    var token = await context.AcquireTokenAsync(
        "https://management.core.windows.net/",
        new ClientCredential(Configuration["AppSettings:AzureManagement:ClientId"], Configuration["AppSettings:AzureManagement:ClientSecret"]));
    var creds = new TokenCredentials(token.AccessToken);
    using (var sbClient = new ServiceBusManagementClient(creds)
    {
        SubscriptionId = VariableHelper.Configuration["AppSettings:AzureManagement:AzureSubscriptionId"]
    })
    {                
        var queueParams = new SBSubscription
        {
            LockDuration = TimeSpan.FromSeconds(10)
        };          
        await sbClient.Subscriptions.CreateOrUpdateAsync(
            Configuration["AppSettings:ServiceBus:ResourceGroup"],
            Configuration["AppSettings:ServiceBus:Namespace"],
            Configuration["AppSettings:ServiceBus:TopicName"],
            "mysub",
            queueParams);                
    }
}

This works so I have a new subscription which I'm also able to delete. But now where can I define the filter? The SBSubscription-type contains no option to define a filter and there is no method-overload in sbClient.Subscriptions.CreateOrUpdateAsync.

The only way I found to do this in code is based on the old Nuget WindowsAzure.ServiceBus which is not available for .NET Core. So there I would use a NamespaceManager like this:

var manager = NamespaceManager.CreateFromConnectionString("MyConnectionString");            
var rule = new RuleDescription("property > 4");
var sub = manager.CreateSubscriptionAsync(new SubscriptionDescription("mytopic", "mysub"), rule); 

Without this feature the complete topic-thing seems to be very useless to me and I can't believe that this means that Azure Service Bus is simply not ready for .NET Core.

Edit: Solution as suggested by Arunprabhu

I just wanted to present the solution in complete.

  1. Add reference to NuGet Microsoft.Azure.Management.ServiceBus.
  2. Add reference to NuGet Microsoft.Azure.ServiceBus.
  3. Create the sub as follows:
private static async Task CreateSubscriptionAsync()
{
    // this is to show that it works with any subscription-name
    var subName = Guid.NewGuid().ToString("N");     
    // create the subscription using Azure Management API
    var context = new AuthenticationContext($"https://login.microsoftonline.com/{Configuration["AppSettings:AzureManagement:TenantId"]}");
    var token = await context.AcquireTokenAsync(
        "https://management.core.windows.net/",
        new ClientCredential(Configuration["AppSettings:AzureManagement:ClientId"], Configuration["AppSettings:AzureManagement:ClientSecret"]));
    var creds = new TokenCredentials(token.AccessToken);
    using (var sbClient = new ServiceBusManagementClient(creds)
    {
        SubscriptionId = VariableHelper.Configuration["AppSettings:AzureManagement:AzureSubscriptionId"]
    })
    {                
        var queueParams = new SBSubscription
        {
            LockDuration = TimeSpan.FromSeconds(10)
        };          
        await sbClient.Subscriptions.CreateOrUpdateAsync(
            Configuration["AppSettings:ServiceBus:ResourceGroup"],
            Configuration["AppSettings:ServiceBus:Namespace"],
            Configuration["AppSettings:ServiceBus:TopicName"],
            subName,
            queueParams);                
    }
    // add filter-rule using Azure ServiceBus API
    var client = new SubscriptionClient(ServiceBusConnectionString, Configuration["AppSettings:ServiceBus:TopicName"], subName);
    await client.AddRuleAsync("default", new SqlFilter("1=1"));
    // That's it        
}
like image 313
Alexander Schmidt Avatar asked Aug 01 '18 11:08

Alexander Schmidt


4 Answers

I understood, that Microsoft.Azure.ServiceBus is not able to create ressources

As of version 3.1.0-preview of Microsoft.Azure.ServiceBus you can create entities using ManagementClient without the need in the Microsoft.Azure.Management.ServiceBus library. It has an overload of CreateSubscriptionAsync() that takes in RuleDescription to be created as the default rule. Alternatively, providing no RuleDescription will set up a default rule for you automatically.

like image 181
Sean Feldman Avatar answered Oct 08 '22 08:10

Sean Feldman


There are similar methods available for rules management under SubscriptionClient in Microsoft.Azure.ServiceBus. Look here for samples.

like image 20
Arunprabhu Avatar answered Oct 08 '22 07:10

Arunprabhu


Expounding on Sean Feldman's answer since his example link is now broken, you can do something like this:

public static async Task CreateSubscription(
    string connection,
    string topicPath,
    string subscriptionName,
    string matchExpression,
    string ruleDescription,
    bool checkForExisting = false,
    CancellationToken cancellationToken = default(CancellationToken)
)
{
    if (checkForExisting)
    {
        var exists = (await new ManagementClient(connection)
            .GetSubscriptionsAsync(topicPath, cancellationToken: cancellationToken))
            .Any(sub => sub.SubscriptionName == subscriptionName);

        if (exists) return;
    }

    var subscriptionDescription = new SubscriptionDescription(topicPath, subscriptionName);
    var rule = new RuleDescription(ruleDescription, new SqlFilter(matchExpression));
    await new ManagementClient(connection).CreateSubscriptionAsync(subscriptionDescription, rule, cancellationToken);
}

For simplicity I've removed the obligatory input validations like empty strings or non-valid characters. I've also included a way to check whether the subscription already exists. Ideally you'd only perform that check once, then keep a running tab in memory so you're not performing that API call each time.

For your matchExpression you'd define the filter for that subscription in SQLish syntax, based on user properties that were added to the message when it was published. The implementation would look something like:

await CreateSubscription(
    Configuration.GetConnectionString("MyServiceBusConnectionString"),
    "myTopicName",
    "mySubscription",
    "MyUserProperty='MyValue'",
    "MyRule",
    true
)
like image 1
Chad Hedgcock Avatar answered Oct 08 '22 09:10

Chad Hedgcock


You can create filters with the Microsoft.Azure.Management.ServiceBus package, they're part of Rules.

await client.Rules.CreateOrUpdateAsync(resourceGroupName, namespaceName, 
    topicName, subscriptionName, filterName, new Rule {
    SqlFilter = new SqlFilter(sql)
});

You see the filter name and SQL string in the screenshot below from the Azure Portal:

enter image description here

like image 1
Glorfindel Avatar answered Oct 08 '22 09:10

Glorfindel