I am trying to register my FCM service with Azure Notification hub.
I am getting a valid FCM token using my Instance Id Service which extends FirebaseMessagingService().
Below is my InstanceIdService which is returning a valid token and starts my registrationIntentService
class FCMInstanceIdService : FirebaseMessagingService() {
companion object {
private const val TAG = "FCMInstanceIDService"
}
init {
Log.i(TAG, "init")
}
override fun onNewToken(refreshedToken: String?) {
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult ->
instanceIdResult.token
// Log the event
Log.i(TAG, "Refreshing GCM Registration Token")
// Declare our intent to start the service
val intent = Intent(this, FCMRegistrationIntentService::class.java)
// Start the service!
startService(intent)
}
}
}
Below is the portion of code where-in I try to register this token with my notification hub.
// Declare the azure notification hub
val notificationHub = NotificationHub(
// Provide our notification hub name
CentreMK.FCM_NOTIFICATION_HUB_NAME,
// Provide our notification hub connection string
CentreMK.FCM_NOTIFICATION_HUB_LISTEN_CONNECTION_STRING,
// The context of this service
this)
// Log the event
Log.i(TAG, "Attempting to register with NH using token : $token")
// Update the registration id by registering our token with the notification hub
// This provides us with the registration id
regID = notificationHub.register(token).registrationId
This is the exception I get :
Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
Any help is appreciated cause I dont know how I could possibly get a null pointer exception when I've configured everything correctly.
In your initialisation of the String HubListenConnectionStringin class:
public class NotificationSettings {
public static String SenderId = "<Your project number>";
public static String HubName = "<Your HubName>";
public static String HubListenConnectionString = "<Enter your DefaultListenSharedAccessSignature connection string>";
}
you did not add "Endpoint=sb://"to the beginning of the string (i.e: public static String HubListenConnectionString ="Endpoint=sb://......". After which you might encounter a "Resource not found" exception error. That's where I am stuck at now.
Related
Is there any way to setup email alerts for when individual Azure Container Instances succeed or fail (or basically change state)? I have some run-once containers I kick off periodically, and would like to be notified when they complete and the status of the completion. Bonus if the email can include logs from the container, as well. Is this possible using the built-in alerts?
So far I haven't been able to make it work with the provided signals.
Dont believe there is an automatic way so what I did was created a small timer based function that runs every 5 minutes and gets a list of all containers and checks the status. If any are in say a failed state it then uses SendGrid to sent an alert email.
UPDATE for Dan
I use Managed Service Identity in my function so I have a container tasks class like the below, can't remember exactly where I got the help to generate the GetAzure function as obviously when doing local debug you can't use the MSI credentials and the local account on Visual Studio that is meant to work doesn't appear to. However I think it might have been here - https://github.com/Azure/azure-sdk-for-net/issues/4968
public static class ContainerTasks
{
private static readonly IAzure azure = GetAzure();
private static IAzure GetAzure()
{
var tenantId = Environment.GetEnvironmentVariable("DevLocalDbgTenantId");
var clientId = Environment.GetEnvironmentVariable("DevLocalDbgClientId");
var clientSecret = Environment.GetEnvironmentVariable("DevLocalDbgClientSecret");
AzureCredentials credentials;
if (!string.IsNullOrEmpty(tenantId) &&
!string.IsNullOrEmpty(clientId) &&
!string.IsNullOrEmpty(clientSecret))
{
var sp = new ServicePrincipalLoginInformation
{
ClientId = clientId,
ClientSecret = clientSecret
};
credentials = new AzureCredentials(sp, tenantId, AzureEnvironment.AzureGlobalCloud);
}
else
{
credentials = SdkContext
.AzureCredentialsFactory
.FromMSI(new MSILoginInformation(MSIResourceType.AppService), AzureEnvironment.AzureGlobalCloud);
}
var authenticatedAzure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(credentials);
var subscriptionId = Environment.GetEnvironmentVariable("DevLocalDbgSubscriptionId");
if (!string.IsNullOrEmpty(subscriptionId))
return authenticatedAzure.WithSubscription(subscriptionId);
return authenticatedAzure.WithDefaultSubscription();
}
public static IEnumerable<IContainerGroup> ListTaskContainers(string resourceGroupName, ILogger log)
{
log.LogInformation($"Getting a list of all container groups in Resource Group '{resourceGroupName}'");
return azure.ContainerGroups.ListByResourceGroup(resourceGroupName);
}
}
Then my monitor function is simply
public static class MonitorACIs
{
[FunctionName("MonitorACIs")]
public static void Run([TimerTrigger("%TimerSchedule%")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"MonitorACIs Timer trigger function executed at: {DateTime.Now}");
foreach(var containerGroup in ContainerTasks.ListTaskContainers(Environment.GetEnvironmentVariable("ResourceGroupName"), log))
{
if(String.Equals(containerGroup.State, "Failed"))
{
log.LogInformation($"Container Group {containerGroup.Name} has failed please investigate");
Notifications.FailedTaskACI(containerGroup.Name, log);
}
}
}
}
Notifications.FailedTaskACI is just a class method that sends an email to one of our Teams channels
It's not perfect but it does the job for now!
Hi i have the following code, that reads the message to the device. I have azure portal running. But i get the following exception when running on a command prompt;
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Microsoft.Azure.EventHubs;
using System.Threading.Tasks;
using System.Threading;
using System.Text;
using System.Collections.Generic;
namespace read_d2c_messages
{
class ReadDeviceToCloudMessages
{
// Event Hub-compatible endpoint
// az iot hub show --query properties.eventHubEndpoints.events.endpoint --name {your IoT Hub name}
private readonly static string s_eventHubsCompatibleEndpoint ="sb://iothub-ns-mydeviceco......";
// Event Hub-compatible name
// az iot hub show --query properties.eventHubEndpoints.events.path --name {your IoT Hub name}
private readonly static string s_eventHubsCompatiblePath = "mydeviceconnection";
// az iot hub policy show --name service --query primaryKey --hub-name {your IoT Hub name}
private readonly static string s_iotHubSasKey = "";
private readonly static string s_iotHubSasKeyName = "service";
private static EventHubClient s_eventHubClient;
// Asynchronously create a PartitionReceiver for a partition and then start
// reading any messages sent from the simulated client.
private static async Task ReceiveMessagesFromDeviceAsync(string partition, CancellationToken ct)
{
// Create the receiver using the default consumer group.
// For the purposes of this sample, read only messages sent since
// the time the receiver is created. Typically, you don't want to skip any messages.
var eventHubReceiver = s_eventHubClient.CreateReceiver("$Default", partition, EventPosition.FromEnqueuedTime(DateTime.Now));
Console.WriteLine("Create receiver on partition: " + partition);
while (true)
{
if (ct.IsCancellationRequested) break;
Console.WriteLine("Listening for messages on: " + partition);
// Check for EventData - this methods times out if there is nothing to retrieve.
var events = await eventHubReceiver.ReceiveAsync(100);
// If there is data in the batch, process it.
if (events == null) continue;
foreach(EventData eventData in events)
{
string data = Encoding.UTF8.GetString(eventData.Body.Array);
Console.WriteLine("Message received on partition {0}:", partition);
Console.WriteLine(" {0}:", data);
Console.WriteLine("Application properties (set by device):");
foreach (var prop in eventData.Properties)
{
Console.WriteLine(" {0}: {1}", prop.Key, prop.Value);
}
Console.WriteLine("System properties (set by IoT Hub):");
foreach (var prop in eventData.SystemProperties)
{
Console.WriteLine(" {0}: {1}", prop.Key, prop.Value);
}
}
}
}
private static async Task Main(string[] args)
{
Console.WriteLine("IoT Hub Quickstarts - Read device to cloud messages. Ctrl-C to exit.\n");
// Create an EventHubClient instance to connect to the
// IoT Hub Event Hubs-compatible endpoint.
var connectionString = new EventHubsConnectionStringBuilder(new Uri(s_eventHubsCompatibleEndpoint), s_eventHubsCompatiblePath, s_iotHubSasKeyName, s_iotHubSasKey);
s_eventHubClient = EventHubClient.CreateFromConnectionString(connectionString.ToString());
// Create a PartitionReciever for each partition on the hub.
var runtimeInfo = await s_eventHubClient.GetRuntimeInformationAsync();
var d2cPartitions = runtimeInfo.PartitionIds;
CancellationTokenSource cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
cts.Cancel();
Console.WriteLine("Exiting...");
};
var tasks = new List<Task>();
foreach (string partition in d2cPartitions)
{
tasks.Add(ReceiveMessagesFromDeviceAsync(partition, cts.Token));
}
// Wait for all the PartitionReceivers to finsih.
Task.WaitAll(tasks.ToArray());
}
}
}
On my command prompt, the exception is System.Net.Sockets.SocketException:A connection attempt failed because the connected party did not properly respond after a period of time, or established failed because host has failed to respond at Microsoft.Azure.EventHub.Amqp.AmqpHubClient.CreateConnectionAsync(TimeSpan, Timeout); What kind of error is this? Is had to do with firewall connectivity issue on my connection string not to receive message? Or hence i am using Free Trail cant be able to create Service-Bus-Messsage on my EndPoint?
I think the way you construct your connection string is not quite correct. When you copy the connection string from your IoT Hub in the full format, this should already work:
s_eventHubClient = EventHubClient.CreateFromConnectionString("Endpoint=sb://iothub-xxxxxx-1180347-e18a7c8824.servicebus.windows.net/;SharedAccessKeyName=iothubowner;SharedAccessKey=qya62bOiN0********gIyEQ=;EntityPath=myiothubname");
I have an ASP .Net Core 2.2 Web API with a SignalR hub.
When the API receives a message form the client, it needs to save this message to the database. It does this as follows:
The SignalR Hub:
public class ChatHub : Hub
{
public async Task SendMessageToGroup(int clientId, int groupName, string message)
{
await SaveMessage(clientId, groupName, message);
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
private async Task<bool> SaveMessage(int clientId, string groupName, string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();
Message newMessage = new Message()
{
Message = message,
GroupName = groupName,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(pwMessage);
dbContext.SaveChanges();
}
return true;
}
}
All would be well except for the fact that this is a multi-tenant application. Normally, when the client calls the API's controller methods using HTTP requests, the client sends through a "TenantId" header with each request. I then have middleware that intercepts this request, grabs the TenantId from the header, calls a service to retrieve this Tenant using the tenantId, and saves the Tenant object in the HttpContext. Then, on the DbContext's OnConfiguring() method, I use this Tenant Object (stored in the HttpContext) to set the connectionString of the dbContext to whatever database this tenant uses. So:
Middleware:
public class TenantIdentifier
{
private readonly RequestDelegate _next;
public TenantIdentifier(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
Tenant tenant = await GetTenant(tenantId);
httpContext.Items["Tenant"] = tenant;
await _next.Invoke(httpContext);
}
}
DbContext.cs:
public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}
public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
optionsBuilder.UseMySql(connectionString);
}
Now, when the client calls the SignalR hub, and I create a new scope in the hub and request the DbContext, it's connection string is null. This appears to be because, unlike an HTTP request, calling a SignalR hub doesn't trigger the middleware (which is responsible fro identifying the tenant)
How can I, when requesting a DbContext from the scope, manually pass it the connection string, instead of relying on it to try and generate the connectionString in the OnConfiguring() event (which won't work)
Hope this makes sense :/ Thank you
If you add the IHttpContextAccessor to your Hub class constructor - are you able to access the current context (and headers) there?
public class ChatHub : Hub
{
private IHttpContextAccessor currentContext;
public ChatHub(IHttpContextAccessor currentContext)
{
this.currentContext = currentContext;
}
}
Of course, remembering to register the HttpContextAccessor in the DI too:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}
Using methods below, I previously (until last week) could create a service bus with a "mixed" type and then could add a notification hub. However, it suddenly stopped working. It creates a service bus with "Messaging" type and when it tries to create the notification hub, I get the error below:
Enity xxxxxx, create notification hub failed with error forbidden
public ServiceBusNamespaceResponse CreateServiceBus(SubscriptionCloudCredentials credentials, string regoin)
{
var serviceBushubClient = CloudContext.Clients.CreateServiceBusManagementClient(credentials);
var checkserviceBusResponse = serviceBushubClient.Namespaces.CheckAvailability(_deploymentName);
if (checkserviceBusResponse.IsAvailable)
{
try
{
var serviceBusClientResponse = serviceBushubClient.Namespaces.Create(_deploymentName, regoin);
_serviceBusEndpoint = serviceBusClientResponse.Namespace.ServiceBusEndpoint.ToString();
return serviceBusClientResponse;
}
catch (CloudException ex)
{
Console.WriteLine(ex.ErrorMessage);
return null;
}
}
return null;
}
and
public bool CreateNotificationHub(SubscriptionCloudCredentials credentials)
{
SBNotificationHubManager notificationHub;
EntityDescription servesBus = new EntityDescription();
servesBus.Name =_deploymentID;
var des = new MyNotificationHubDescription("push-hub-" + TenantID, servesBus);
notificationHub =
ResourceFactory.Get( _subscriptionID,
new X509Certificate2(Convert.FromBase64String(RowData._base64EncodedCert)),
SBRestResourceType.NHub, des) as SBNotificationHubManager;
if (notificationHub != null)
{
// Console.WriteLine("Created Notification Hub: {0}{1}", Environment.NewLine, notificationHub.LookUp().ToString());
if (notificationHub.Create())
{
Console.WriteLine("Created Notification Hub: {0}{1}", Environment.NewLine, notificationHub.LookUp().ToString());
notificationHub.WaitUntillActive();
return true;
}
}
return false;
}
I was wondering if something recently changed in azure? Can anyone please advise how to define the type of service bus (messaging/notification hub) when creating it in c#. The default with the above method is messaging. I need mixed, but notification hub type also works fine in my case.
Thank you
Now you must explicitly select Messaging (for topics/queues/event hubs) or NotificationHub (for notification hubs). That is idea.
About implementation. Observing source codes I can see overload which accepts ServiceBusNamespaceCreateParameters argument:
Task<ServiceBusNamespaceResponse> CreateNamespaceAsync(string namespaceName, ServiceBusNamespaceCreateParameters namespaceEntity, CancellationToken cancellationToken);
That class has public property of NamespaceType
public NamespaceType NamespaceType{...
So you probably could set it to NamespaceType.NotificationHub and then go ahead to create namespace.
i have a simple EventHub listener sample code trying to listen to event hub
public class Program
{
private const string EventHubConnectionString = "Endpoint=sb://fake.servicebus.windows.net/;SharedAccessKeyName=SendETB;SharedAccessKey=JcvVeX5KsGHfJkPNmdns5jvNYVpB9Wc05jDuMaV3NW8=";
private const string EventHubName = "myhub";
private const string StorageContainerName = "my-own-container";
private const string StorageAccountName = "mystorage";
private const string StorageAccountKey = "fakeZn8WUV1mcsh0MVbmea/ypxDs+No2tzrhr0kUmjxvA0a0jUxfZ29hHoY/yopVvGLEn/stEQbBEAyjYMX9g==";
private static readonly string StorageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", StorageAccountName, StorageAccountKey);
public static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
private static async Task MainAsync(string[] args)
{
Console.WriteLine("Registering EventProcessor...");
var eventProcessorHost = new EventProcessorHost(
EventHubName,
PartitionReceiver.DefaultConsumerGroupName,
EventHubConnectionString,
StorageConnectionString,
StorageContainerName);
// Registers the Event Processor Host and starts receiving messages
await eventProcessorHost.RegisterEventProcessorAsync<SimpleEventProcessor>();
Console.WriteLine("Receiving. Press enter key to stop worker.");
Console.ReadLine();
// Disposes of the Event Processor Host
await eventProcessorHost.UnregisterEventProcessorAsync();
}
}
}
with the above mentioned code i get an error.
Error on Partition: 0, Error: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://fake.servicebus.windows.net/etbhub/consumergroups/$default/partitions/0'. TrackingId:fakef417d94238ba36d41d32b83341_G9, SystemTracker:gateway5, Timestamp:2020-01-27T22:06:49
Error message is pretty self-descriptive. Make sure SharedAccessKeyName SendETB allows 'Listen' permission. You can check it on the portal like below.
Make sure you have added a Shared Access Policy for Listen for your Event Hub Namespace.
For Event Hub it should be Send
For Event Hub Namespace it should have all three Manage, Sen, Listen as per the need.