0

I am changing the way my code receives messages from an Azure Service Bus Subscription.

Previously I was using the SDK classes and now I am changing to http REST calls.

In order to create a rule for a subscription and set a filter on this rule, I always receive http 400 as a return.

It seems the way I am creating the body is not correct:

            var rule = $"https://{serviceBusNamespace}.servicebus.windows.net/{topicPath}/subscriptions/{subscriptionName}/rules/{ruleName}";

            var content = new StringContent(@"<RuleDescription xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"">
             < Filter i: type = ""SqlFilter"" >
              < SqlExpression > type = 'REPLY' AND username = 'blabla@contoso.com' </ SqlExpression >
             </ Filter >
            </ RuleDescription >
            ", Encoding.UTF8, "application/xml");

            var requestResponse = await _httpClient.PutAsync(rule, content, new System.Threading.CancellationToken());

I am also setting the following headers:

_httpClient.DefaultRequestHeaders.Add("Authorization", _token);
                _httpClient.DefaultRequestHeaders.Add("ContentType", "application/atom+xml");
                _httpClient.DefaultRequestHeaders.Add("Accept", "application/atom+xml");

Any ideas about what is missing?

Igor Kondrasovas
  • 1,479
  • 4
  • 19
  • 36
  • Do you have any update about this SO thread? If it is useful, please mark it as an answer. – Tom Sun - MSFT Sep 25 '18 at 09:31
  • Hi. I am still working on that. Soon I will give more updates/mark as answer. Thank you. – Igor Kondrasovas Sep 26 '18 at 19:12
  • If have any confusion about it, please let me know. – Tom Sun - MSFT Sep 28 '18 at 01:34
  • Thing is I am still using the classic API approach, so I have something stable and can think about using the management API in the next improvement. For now I am stuck with the SqlFilter for the rule, where no matter what I do I receive all messages, where I should receive only the ones that are on the filter criteria... – Igor Kondrasovas Oct 01 '18 at 21:02
  • It seems it is another issue, you could accept it to close this SO thread. And you could post another SO thread to add more detail information to descript it. If I have free time I will look into that and you also could get the help from other communities. – Tom Sun - MSFT Oct 02 '18 at 01:20
  • 1
    The issue with the rule filter was not actually an issue. Accordingly to this thread (https://stackoverflow.com/questions/11639437/sqlfilter-on-azure-servicebus-topic-subscription-not-filtering/11659401#11659401), when you create a subscription a filter that "accepts everything" is created automatically. So all I had to do was to delete it after subscription creation. – Igor Kondrasovas Oct 02 '18 at 06:41

1 Answers1

1

According to the error information it means that request parameters error. I am not familar with mentioned api, you could share the link if possible.

But I recommand that you could use the Rules - Create Or Update. It is easy for us to use. For more information about service bus api, please refer to this document.

PUT https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ServiceBus/namespaces/{namespaceName}/topics/{topicName}/subscriptions/{subscriptionName}/rules/{ruleName}?api-version=2017-04-01

I also do a demo for it.

1) Get the access token

private static async Task<string> GetToken(string tenantId, string clientId, string secretKey)
        {
            var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
            ClientCredential clientCredential = new ClientCredential(clientId, secretKey);
            var tokenResponse = await context.AcquireTokenAsync("https://management.azure.com/", clientCredential);
            var accessToken = tokenResponse.AccessToken;
            return accessToken;
        }

2) About how to get the tenantId ,clientId and secret key please refer to this tutorial. And don't forget assign to role the application.

var tenantId = "tenantId";
var clientId = "clientId";
var secretkey = "sercret Key";
var subscriptionId = "subscription Id";
var resurceGroup = "resourceGroup";
var nameSpace = "servicebus namespace";
var topicName = "topicName";
var subscription = "service subscription name";
var ruleName = "rule name";
var token = GetToken(tenantId,clientId,secretkey).Result;
using (var httpClient = new HttpClient())
{
   httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
   httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json"));
   var body = "{\"properties\": { \"filterType\": \"SqlFilter\"},\"sqlExpression\": { \"sqlExpression\": \"myproperty=test\"}}";
   HttpContent content = new StringContent(body);
   var url = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resurceGroup}/providers/Microsoft.ServiceBus/namespaces/{nameSpace}/topics/{topicName}/subscriptions/{subscription}/rules/{ruleName}?api-version=2017-04-01";
   var response = httpClient.PutAsync(url, content).Result;
 }

Test result

enter image description here

Update:

It seems the way I am creating the body is not correct:

Yes, you are right. According to your mentioned API document, we could know the body is xml format. But you xml code string is not xml format, you could vaild with xml validator online. There should be no space between charater </ > and tags. For example < Filter i: type = ""SqlFilter""> should be <Filter i: type = ""SqlFilter"">

But it is a classic rest api.

We’re no longer updating this content regularly. Check the Microsoft Product Lifecycle for information about how this product, service, technology, or API is supported.

I recommand that you could use the Azure manmagement API, we also could get access token with api.

public static string GenerateAccessToken(string resource, string tenantId, string clientId,string secretKey)
        {
            var url = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
            var body = $"grant_type=client_credentials&client_id={clientId}&client_secret={secretKey}&resource={resource}";
            HttpClient client = new HttpClient
            {
                BaseAddress = new Uri(url)
            };
            StringContent content = new StringContent(body);
            content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
            var result = client.PostAsync(url, content).Result;
            var json = JObject.Parse (result.Content.ReadAsStringAsync().Result);
            return json["access_token"].ToString();
        }

If you still want to use the classic api, I also do a demo. please hava a try with following code.

1.Get sastoken code

public static string GetSasToken(string resourceUri, string keyName, string key, TimeSpan ttl)
  {
         var expiry = GetExpiry(ttl);
         string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
         HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
         var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
         var sasToken = string.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
         HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
         return sasToken;
 }

private static string GetExpiry(TimeSpan ttl)
{
  TimeSpan expirySinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1) + ttl;
   return Convert.ToString((int)expirySinceEpoch.TotalSeconds);
 }

2.Create a rule with c# code.

var serviceBusNamespace = "serviceBusNameSpace";
var topicPath = "topicPath";
var subscriptionName = "subscription name";
var ruleName = "testrule2"; // rule name
var sharedAccessKeyName = "xxxSharedAccessKey",
var key = "xxxxxxM2Xf8uTRcphtbY=";
var queueUrl = $"https://{serviceBusNamespace}.servicebus.windows.net/{topicPath}/subscriptions/{subscriptionName}/rules/{ruleName}";
var token = GetSasToken(queueUrl, sharedAccessKeyName,key ,TimeSpan.FromDays(1));
            var body = @"<entry xmlns=""http://www.w3.org/2005/Atom"">
   <content type =""application/xml"" >
   <RuleDescription xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"">
           <Filter i:type=""SqlFilter"">
               <SqlExpression> type = 'REPLY' AND username = 'blabla@contoso.com' </SqlExpression>
             </Filter>
           </RuleDescription>
         </content>
       </entry>";
            var length = body.Length.ToString();
            var content = new StringContent(body, Encoding.UTF8, "application/xml");
            var _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add("Authorization", token);
            _httpClient.DefaultRequestHeaders.Add("ContentType", "application/atom+xml");
            _httpClient.DefaultRequestHeaders.Add("Accept", "application/atom+xml");
            content.Headers.Add("Content-Length", length);
            var requestResponse =  _httpClient.PutAsync(queueUrl, content, new System.Threading.CancellationToken()).Result;

Test Result:

enter image description here

Tom Sun - MSFT
  • 24,161
  • 3
  • 30
  • 47
  • The sample code does not work for me. For creating the access token I need some class libraries not supported by .NET 4.0 (my target .net framework version). The API I am using can be found at https://learn.microsoft.com/en-us/previous-versions/azure/reference/hh780755%28v%3dazure.100%29 I am creating the token and already using to make some service-bus related operations, like send and receive messages. My problem now is to setup the SQL filter. – Igor Kondrasovas Sep 14 '18 at 21:48
  • The API I am trying to use contains information on how to create a new rule. using is: https://learn.microsoft.com/en-us/previous-versions/azure/reference/hh780774(v%3dazure.100) The body of the request should be created like this: https://learn.microsoft.com/en-us/previous-versions/azure/reference/hh780755%28v%3dazure.100%29 I believe my problem is to create the body request. – Igor Kondrasovas Sep 15 '18 at 09:27
  • `I believe my problem is to create the body request`. Yes you are right. The body string of the xml is not well format. I update the answer with demo xml string. And I also add the way to get access token **without** .net library. I see that you are using classic API. I recommand that you could use the [Azure management API](https://learn.microsoft.com/en-us/previous-versions/azure/reference/hh780774(v%3dazure.100)). – Tom Sun - MSFT Sep 17 '18 at 06:22