References :
https://azure.microsoft.com/en-us/blog/iot-hub-message-routing-now-with-routing-on-message-body/
How to send a json object instead of a string with Azure Client SDK
I can send IOT messages successfully and store these messages in Azure storage.
On reviewing the messages, the body is BASE64 encoded. I want the JSON within the body in the message to be shown as a string so I can process the contents in ADF.
I have added to my message the properties as below, as suggested by the reference posts above:
mes.ContentEncoding = "utf-8";
mes.ContentType = "application/json";
On doing this, I get the message below when I try and send the message. The app compiles ok, it's only when my app sends the message.
Method not found: 'Void Microsoft.Azure.Devices.Client.Message.set_ContentEncoding(System.String)'.
Comment the lines out, and it works fine again.
What am I doing wrong?
private async void SendDeviceToCloudMessagesAsyncDcbIot(DcbIotPayload dataPayload)
{
if (string.IsNullOrWhiteSpace(dataPayload.did))
{
DpmIotLog.LogToFile("no deviceId in data payload", DpmIotLog.LogCondition.High);
return;
}
dataPayload.t = DateTime.Now.ToString();
var messageString = JsonConvert.SerializeObject(dataPayload);
var messageSize = Encoding.ASCII.GetByteCount(messageString);
DpmIotLog.LogToFile($"Message Len {messageSize}", DpmIotLog.LogCondition.Verbose);
DpmIotLog.LogToFile($"Message {messageString}", DpmIotLog.LogCondition.Verbose);
byte[] messageBytes = Encoding.UTF8.GetBytes(messageString);
using (var mes = new Message(messageBytes))
{
// Set message body type and content encoding.
mes.ContentEncoding = "utf-8";
mes.ContentType = "application/json";
try
{
if (messageSize > 7000)
{
var ex = new Exception("PayloadSizeException");
DpmIotLog.LogToFile("payload over 7000 bytes", ex, DpmIotLog.LogCondition.High);
throw ex;
}
DpmIotLog.LogToFile($"Submitting Message to HUB", DpmIotLog.LogCondition.Verbose);
Task t = deviceClient.SendEventAsync(mes);
if (await Task.WhenAny(t, Task.Delay(10000)) == t)
{
MessageSent?.Invoke(true);
DpmIotLog.LogToFile($"Message Sent", DpmIotLog.LogCondition.Verbose);
deviceClient.Dispose();
}
else
{
MessageSent?.Invoke(false);
DpmIotLog.LogToFile($"Message Fail", DpmIotLog.LogCondition.Verbose);
deviceClient.Dispose();
}
}
catch (DeviceNotFoundException)
{
DpmIotLog.LogToFile($"invalid or disabled device ID : {dataPayload.did}", DpmIotLog.LogCondition.Critical);
}
catch (DeviceDisabledException)
{
DpmIotLog.LogToFile($"IOT disabled device ID : {dataPayload.did}", DpmIotLog.LogCondition.Critical);
}
catch (Exception ex)
{
DpmIotLog.LogToFile("task to upload payload to server failure", ex, DpmIotLog.LogCondition.Critical);
}
}
}
I created an entirely new project and updated all the dependences to the latest versions. This resolved the issue. (I was using a old IOT project as a starting point)
Related
I created a .NET core 6 project. I added Azure.Messaging.ServiceBus as the dependency. I am using below code to send message to service bus topic.
// See https://aka.ms/new-console-template for more information
using Azure.Messaging.ServiceBus;
using System.Dynamic;
using System.Net;
using System.Text;
using System.Text.Json;
Console.WriteLine("Hello, World!");
Sender t = new Sender();
Sender.Send();
class Sender
{
public static async Task Send()
{
string connectionString = "Endpoint=sb://sb-test-one.servicebus.windows.net/;SharedAccessKeyName=manage;SharedAccessKey=8e+6SWp3skB3AeDlwH6ufGEainEs45353435JzDywz5DU=;";
string topicName = "topicone";
string subscriptionName = "subone";
// The Service Bus client types are safe to cache and use as a singleton for the lifetime
try
{
await using var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets
});
// create the sender
ServiceBusSender sender = client.CreateSender(topicName);
dynamic data = new ExpandoObject();
data.name = "Abc";
data.age = 6;
// create a message that we can send. UTF-8 encoding is used when providing a string.
var messageBody = JsonSerializer.Serialize(data);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
// send the message
await sender.SendMessageAsync(message);
var s = 10;
}
catch (Exception e)
{
var v = 10;
}
//// create a receiver for our subscription that we can use to receive the message
//ServiceBusReceiver receiver = client.CreateReceiver(topicName, subscriptionName);
//// the received message is a different type as it contains some service set properties
//ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
//// get the message body as a string
//string body = receivedMessage.Body.ToString();
//Console.WriteLine(body);
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
}
}
Issue: When I call await sender.SendMessageAsync(message); after this line get executed, the program is actually terminating. It not awating. The whole execution stops after this line.
System is not throwing any exception and service bus is not receiving any message.
I just noticed that all other samples I saw had a default SharedAccessPolicy called RootManageSharedAccessKey policy available by default in the azure portal. For me, I had to create this policy. To my policy I have given Manage, Send, ReceiveAccess.
Needed to change Sender.Send(); to Sender.Send().GetAwaiter().GetResult();
I am trying to send push notifications through the firebase admin sdk, but the image somehow is not displayed in the push notification.
What's weird is that when I use an invalid key in the notifications object (like image) I get an error. So I assume I got the right keys specified. Documentation for the Notification can be found here.
The following code successfully sends a push notification but there is no image displayed on the users phone:
const admin = require('firebase-admin');
const app = admin.initializeApp({...}); // authenticated with credentials json file
await app.messaging().sendMulticast({
notification: {
title: "hello User",
body: "This is a push notification with an image",
imageUrl: "https://example.com/myPublicImage.png",
},
tokens: ["device_token_1", "device_token_2","..."]
});
Change imageUrl key to image in notification being sent by Firebase Admin SDK. I checked with imageUrl key, it does not work, rather,
it gives null to remoteMessage.getNotification().getImageUrl() in app.
In node.js server, you can create post request to send the Firebase message using Firebase Admin SDK:
Request.post({
"headers": {"Authorization":auth_key_string, "content-type": "application/json" },
"url": "https://fcm.googleapis.com/fcm/send",
"body": JSON.stringify({
"registration_ids" :receiver_token ,
"notification" : {
"title": title,
"body" : message,
"image":imageUrlVal
}
})
}, (error, response, body) => {
if(error) {
return console.dir(error);
}
console.dir(JSON.parse(body));
});
Now, handle this message from FirebaseActivity in Android App code.
In onMessageReceived method add this lines.
if (remoteMessage.getNotification() != null) {
// Since the notification is received directly from
// FCM, the title and the body can be fetched
// directly as below.
Log.d(TAG, "Message Received: " + "YES");
Bitmap bitmap = null;
try {
bitmap = getBitmapfromUrl(remoteMessage.getNotification().getImageUrl().toString());
} catch (Exception e) {
e.printStackTrace(); }
try {
showNotification(
remoteMessage.getNotification().getTitle(),
remoteMessage.getNotification().getBody(),bitmap );
} catch (IOException e) {
e.printStackTrace();
}
}
Define getBitmapfromUrl() as below:
public Bitmap getBitmapfromUrl(String imageUrl) {
try {
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (Exception e) {
Log.e("awesome", "Error in getting notification image: " + e.getLocalizedMessage());
return null;
}
}
showNotification() can be defined as:
public void showNotification(String title,
String message, Bitmap bitmap) throws IOException {
Intent intent
= new Intent(this, NextPageActivity.class);
// Assign channel ID
String channel_id = "notification_channel";
// Here FLAG_ACTIVITY_CLEAR_TOP flag is set to clear
// the activities present in the activity stack,
// on the top of the Activity that is to be launched
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Pass the intent to PendingIntent to start the
// next Activity
PendingIntent pendingIntent
= PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Builder builder
= new NotificationCompat
.Builder(getApplicationContext(), channel_id)
.setSmallIcon(R.drawable.app_icon)
.setAutoCancel(true)
.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000})
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent);
// A customized design for the notification can be
// set only for Android versions 4.1 and above. Thus
// condition for the same is checked here.
if (Build.VERSION.SDK_INT
>= Build.VERSION_CODES.JELLY_BEAN) {
Log.d(TAG, "Higher Version: ");
builder = builder.setContent(
getCustomDesign(title, message));
if (bitmap != null) {
builder.setLargeIcon(bitmap)
.setStyle(
new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null)
.setBigContentTitle(title)
.setSummaryText(message)
);
}
} // If Android Version is lower than Jelly Beans,
// customized layout cannot be used and thus the
// layout is set as follows
else {
Log.d(TAG, "Lower Version: ");
builder = builder.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.app_icon);
}
// Create an object of NotificationManager class to
// notify the
// user of events that happen in the background.
NotificationManager notificationManager
= (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
// Check if the Android Version is greater than Oreo
if (Build.VERSION.SDK_INT
>= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel
= new NotificationChannel(
channel_id, "web_app",
NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(
notificationChannel);
}
notificationManager.notify(0, builder.build());
}
getCustomDesign() can be defined as:
private RemoteViews getCustomDesign(String title,
String message) {
#SuppressLint("RemoteViewLayout") RemoteViews remoteViews =
new RemoteViews(getApplicationContext().getPackageName(),
R.layout.notification);
remoteViews.setTextViewText(R.id.title, title);
remoteViews.setTextViewText(R.id.message, message);
remoteViews.setTextViewText(R.id.note_button, "Reply");
remoteViews.setImageViewResource(R.id.icon, R.drawable.app_icon);
//remoteViews.setImageViewResource(R.id.message_image, R.drawable.app_icon);
return remoteViews;
}
I have used RegisterManager.AddDevicesAsync method when adding a device as device id includes "I (letter)" to Azure IoT hub in my project. And the exception occurred. Has the device id any constraint about this issue?
public async Task AddDeviceAsync(DeviceConfig deviceConfig)
{
try
{
DeviceStatus status;
if (!Enum.TryParse(deviceConfig.Status, true, out status))
{
status = DeviceStatus.Enabled;
}
var d = new Device(deviceConfig.DeviceId)
{
Status = status
};
await this.registryManager.AddDeviceAsync(d);
}
catch (ArgumentException ex)
{
this.logger.LogError(ex.Message);
throw new EVCException(ex.Message);
}
catch (DeviceAlreadyExistsException ex)
{
this.logger.LogInformation(ex.Message);
}
}
await this.deviceManager.AddDeviceAsync(new DeviceConfig { DeviceId = "ILICA"});
AddDeviceAsync() expects a Device object, not a DeviceConfig, you likely want something along the lines of the below, I'm guessing:
await this.deviceManager.AddDeviceAsync(d);
You can try to use DeviceClient.CreateFromConnectionString
using Microsoft.Azure.Devices.Client; // namespace
using DeviceClient device = DeviceClient.CreateFromConnectionString("connectionstring");
When I declare a temporary reply queue to be exclusive (e.g. anonymous queue (exclusive=true, autodelete=true) in rpc-pattern), the response message cannot be posted to the specified reply queue (e.g. message.replyTo="amq.gen-Jg_tv8QYxtEQhq0tF30vAA") because RabbitMqProducer.PublishMessage() tries to redeclare the queue with different parameters (exclusive=false), which understandably results in an error.
Unfortunately, the erroneous call to channel.RegisterQueue(queueName) in RabbitMqProducer.PublishMessage() seems to nack the request message in the incoming queue so that, when ServiceStack.Messaging.MessageHandler.DefaultInExceptionHandler tries to acknowlege the request message (to remove it from the incoming queue), the message just stays on top of the incoming queue and gets processed all over again. This procedure repeats indefinitely and results in one dlq-message per iteration which slowly fills up the dlq.
I am wondering,
if ServiceStack handles the case, when ServiceStack.RabbitMq.RabbitMqProducer cannot declare the response queue, correctly
if ServiceStack.RabbitMq.RabbitMqProducer muss always declare the response queue before publishing the response
if it wouldn't be best to have some configuration flag to omit all exchange and queue declaration calls (outside of the first initialization). The RabbitMqProducer would just assume every queue/exchange to be properly set up and just publish the message.
(At the moment our client just declares its response queue to be exclusive=false and everything works fine. But I'd really like to use rabbitmq's built-in temporary queues.)
MQ-Client Code, requires simple "SayHello" service:
const string INQ_QUEUE_NAME = "mq:SayHello.inq";
const string EXCHANGE_NAME="mx.servicestack";
var factory = new ConnectionFactory() { HostName = "192.168.179.110" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// Create temporary queue and setup bindings
// this works (because "mq:tmp:" stops RabbitMqProducer from redeclaring response queue)
string responseQueueName = "mq:tmp:SayHello_" + Guid.NewGuid().ToString() + ".inq";
channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work (RabbitMqProducer tries to declare queue again => error):
//string responseQueueName = Guid.NewGuid().ToString() + ".inq";
//channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work either (RabbitMqProducer tries to declare queue again => error)
//var responseQueueName = channel.QueueDeclare().QueueName;
// publish simple SayHello-Request to standard servicestack exchange ("mx.servicestack") with routing key "mq:SayHello.inq":
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
channel.BasicPublish(EXCHANGE_NAME, INQ_QUEUE_NAME, props, Encoding.UTF8.GetBytes("{\"ToName\": \"Chris\"}"));
// consume response from response queue
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
// print result: should be "Hello, Chris!"
Console.WriteLine(Encoding.UTF8.GetString(ea.Body));
}
}
Everything seems to work fine when RabbitMqProducer does not try to declare the queues, like that:
public void PublishMessage(string exchange, string routingKey, IBasicProperties basicProperties, byte[] body)
{
const bool MustDeclareQueue = false; // new config parameter??
try
{
if (MustDeclareQueue && !Queues.Contains(routingKey))
{
Channel.RegisterQueueByName(routingKey);
Queues = new HashSet<string>(Queues) { routingKey };
}
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
catch (OperationInterruptedException ex)
{
if (ex.Is404())
{
Channel.RegisterExchangeByName(exchange);
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
throw;
}
}
The issue got adressed in servicestack's version v4.0.32 (fixed in this commit).
The RabbitMqProducer no longer tries to redeclare temporary queues and instead assumes that the reply queue already exist (which solves my problem.)
(The underlying cause of the infinite loop (wrong error handling while publishing response message) probably still exists.)
Edit: Example
The following basic mq-client (which does not use ServiceStackmq client and instead depends directly on rabbitmq's .net-library; it uses ServiceStack.Text for serialization though) can perform generic RPCs:
public class MqClient : IDisposable
{
ConnectionFactory factory = new ConnectionFactory()
{
HostName = "192.168.97.201",
UserName = "guest",
Password = "guest",
//VirtualHost = "test",
Port = AmqpTcpEndpoint.UseDefaultPort,
};
private IConnection connection;
private string exchangeName;
public MqClient(string defaultExchange)
{
this.exchangeName = defaultExchange;
this.connection = factory.CreateConnection();
}
public TResponse RpcCall<TResponse>(IReturn<TResponse> reqDto, string exchange = null)
{
using (var channel = connection.CreateModel())
{
string inq_queue_name = string.Format("mq:{0}.inq", reqDto.GetType().Name);
string responseQueueName = channel.QueueDeclare().QueueName;
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
var message = ServiceStack.Text.JsonSerializer.SerializeToString(reqDto);
channel.BasicPublish(exchange ?? this.exchangeName, inq_queue_name, props, UTF8Encoding.UTF8.GetBytes(message));
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//channel.BasicAck(ea.DeliveryTag, false);
string response = UTF8Encoding.UTF8.GetString(ea.Body);
string responseType = ea.BasicProperties.Type;
Console.WriteLine(" [x] New Message of Type '{1}' Received:{2}{0}", response, responseType, Environment.NewLine);
return ServiceStack.Text.JsonSerializer.DeserializeFromString<TResponse>(response);
}
}
~MqClient()
{
this.Dispose();
}
public void Dispose()
{
if (connection != null)
{
this.connection.Dispose();
this.connection = null;
}
}
}
Key points:
client declares anonymous queue (=with empty queue name) channel.QueueDeclare()
server generates queue and returns queue name (amq.gen*)
client adds queue name to message properties (props.ReplyTo = responseQueueName;)
ServiceStack automatically sends response to temporary queue
client picks up response and deserializes
It can be used like that:
using (var mqClient = new MqClient("mx.servicestack"))
{
var pingResponse = mqClient.RpcCall<PingResponse>(new Ping { });
}
Important: You've got to use servicestack version 4.0.32+.
I am tring to list all the management certificates in a windows azure subcription. And I tried with the following code. But it gives me an exception. And I could find that response is null and the exception message is "The remote server returned an error: (403) Forbidden."
Please help me with this. Msdn doesn't provide an example for this :(
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Xml.Linq;
class ManagemenCertificateViewer
{
public static void Runme()
{
string msVersion = "2012-03-01";
string subscriptionId = "I used the subscription Id here";
try
{
ListManagementCertificates(subscriptionId, msVersion);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: ");
Console.WriteLine(ex.Message);
}
}
private static void ListManagementCertificates(string subscriptionId, string version)
{
string uriFormat = "https://management.core.windows.net/{0}/certificates";
Uri uri = new Uri(string.Format(uriFormat, subscriptionId));
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Method = "GET";
request.Headers.Add("x-ms-version", version);
request.ContentType = "application/xml";
XDocument responseBody = null;
HttpStatusCode statusCode;
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
// GetResponse throws a WebException for 400 and 500 status codes
response = (HttpWebResponse)ex.Response;
}
statusCode = response.StatusCode;
if (response.ContentLength > 0)
{
using (XmlReader reader = XmlReader.Create(response.GetResponseStream()))
{
responseBody = XDocument.Load(reader);
}
}
response.Close();
if (statusCode.Equals(HttpStatusCode.OK))
{
XNamespace wa = "http://schemas.microsoft.com/windowsazure";
XElement storageServices = responseBody.Element(wa + "SubscriptionCertificates");
int mngmntCertificateCount = 0;
foreach (XElement storageService in storageServices.Elements(wa + "SubscriptionCertificate"))
{
string publicKey = storageService.Element(wa + "SubscriptionCertificatePublicKey").Value;
string thumbprint = storageService.Element(wa + "SubscriptionCertificateThumbprint").Value;
string certificateData = storageService.Element(wa + "SubscriptionCertificateData").Value;
string timeCreated = storageService.Element(wa + "TimeCreated").Value;
Console.WriteLine(
"Certificate[{0}]{1} SubscriptionCertificatePublicKey: {2}{1} SubscriptionCertificateThumbprint: {3}{1} certificateData{4}{1} timeCreated{5}{1}",
mngmntCertificateCount++, Environment.NewLine, publicKey, thumbprint, certificateData, timeCreated);
}
}
else
{
Console.WriteLine("List Management certificates returned an error:");
Console.WriteLine("Status Code: {0} ({1}):{2}{3}",
(int)statusCode, statusCode, Environment.NewLine,
responseBody.ToString(SaveOptions.OmitDuplicateNamespaces));
}
return;
}
}
Thanks it's working as I expected. I just add the following line and the Method 'GetCertificate(arg1)'
request.ClientCertificates.Add(GetCertificate(certThumbprint));
One more thing, in Msdn help guide there's a tag in respond body called
<TimeCreated>time-created</TimeCreated>
But the api responds not the TimeCreated its just created.
<Created> ..... </Created>
403 error means something wrong with your management certificate used to authenticate your Service Management API requests. I don't see you attaching a management certificate along with your request in your code. You may find this link useful for authenticating service management API requests: http://msdn.microsoft.com/en-us/library/windowsazure/ee460782.
HTH.