Xamarin read from Azure EventHub - azure

I'm using the frameworks "Microsoft.Azure.EventHubs (2.0.0)" and "Microsoft.Azure.EventHubs.Processor (2.0.1)" to read from the azure notification hub. It works by running the app on the iPhone X iOS 11.2 Simulator but if I try to run the app on my iPhone 6s iOS 11.3 device - the app doesn't connect with the eventhub.
This is my code to connect with the eventhub:
public async Task StartReadingFromHub () {
_eventProcessorHost = new EventProcessorHost (
EhEntityPath,
PartitionReceiver.DefaultConsumerGroupName,
EhConnectionString,
StorageConnectionString,
LeaseContainerName);
var options = new EventProcessorOptions () {
MaxBatchSize = 10
};
await _eventProcessorHost.RegisterEventProcessorAsync<SimpleEventProcessor> (options);
}
This is my EventProcessor:
public class SimpleEventProcessor : IEventProcessor {
public Task CloseAsync (PartitionContext context, CloseReason reason) {
return Task.CompletedTask;
}
public Task OpenAsync (PartitionContext context) {
return Task.CompletedTask;
}
public Task ProcessErrorAsync (PartitionContext context, Exception error) {
return Task.CompletedTask;
}
public Task ProcessEventsAsync (PartitionContext context, IEnumerable<EventData> messages) {
foreach (var eventData in messages) {
var data = Encoding.UTF8.GetString (eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
var alert = JsonConvert.DeserializeObject<Alert> (data);
HubMessages.receivedMessages.Add (alert);
}
return context.CheckpointAsync ();
}
}
And the OpenAsync Function will never enter.

Related

Can I use a basic Web App in Azure to just define a SignalR hub with AZ SignalR Service and use other apps as clients to communicate between them

I have been struggling with this for few days so I am asking whether what I am trying to do is possible.
Basically I am experimenting things with signalR in Azure. I am using the latest version of SignalR NuGet.
I have a small Web App defining a simple signalR hub I want to access via a Azure SignalR Service.
I then have a couple of apps which communicate via hub in the signalR service.
I have various issues.
Getting the hub to load on the AZ SignalR Service. It does not work all the time. Complaining about Serverless/Default settings...
2023-02-16T14:05:25.034917571Z info: Microsoft.Azure.SignalR.Connections.Client.Internal.WebSocketsTransport[1]
2023-02-16T14:05:25.034960072Z Starting transport. Transfer mode: Binary. Url: 'wss://nmg-opus-common-inventory-dev-sigr.service.signalr.net/server/?hub=myhub&cid=9e64b380-b752-4175-8fd2-215a2b62139d'.
2023-02-16T14:05:25.057819457Z info: Microsoft.Azure.SignalR.Connections.Client.Internal.WebSocketsTransport[11]
2023-02-16T14:05:25.057859557Z WebSocket closed by the server. Close status NormalClosure.
2023-02-16T14:05:25.057865857Z info: Microsoft.Azure.SignalR.ServiceConnection[24]
2023-02-16T14:05:25.057870457Z Connection to service '(Primary)https://nmg-opus-common-inventory-dev-sigr.service.signalr.net(hub=MyHub)' handshake failed, probably caused by network instability or service restart. Will retry after the back off period. Error detail: Azure SignalR Service is in serverless mode, server connection is not allowed.. Id: 9e64b380-b752-4175-8fd2-215a2b62139d
2023-02-16T14:05:25.057875657Z info: Microsoft.Azure.SignalR.Connections.Client.Internal.WebSocketsTransport[6]
2023-02-16T14:05:25.057880257Z Transport is stopping.
2
On the client side, it seems that the connection succeeds if the service is set to Serverless mode.
Once connected, my call to join a group just hang and never returns. The connection happens after I click an element (div) on a blazor page.
So I am not sure why something so simple does not work.
here is my very simple hub :
public class MyHub : Hub
{
public async Task SendMessage(string groupName, string message)
{
await Group(groupName).SendAsync("ReceiveMessage", message);
}
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public override async Task OnConnectedAsync()
{
await Clients.Client(Context.ConnectionId).SendAsync("Connected", Context.ConnectionId);
}
}
here is the classic startup method for the app defining hub
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAzureAppConfiguration();
services.AddSignalR(e => { e.MaximumReceiveMessageSize = 10240000; });
string SignalRURLHub = Configuration.GetValue<string>("Resources:SignalR:Inventory:ConnectionString");
services.AddSignalR().AddAzureSignalR(SignalRURLHub);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAzureAppConfiguration();
app.UseRouting();
var SignalRHubName = $"/{Configuration.GetValue<string>("Resources:SignalR:HubName")}";
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>(SignalRHubName);
});
}
}
Code from the client applications Initializing the connection and starting it.
protected HubConnection _connection;
private readonly string groupName;
protected ClientHandler(string connectionString, string hubName, string groupName, string userId = null)
{
var serviceUtils = new ServiceUtils(connectionString);
var url = GetClientUrl(serviceUtils.Endpoint, hubName);
_connection = new HubConnectionBuilder()
.WithUrl(url
, option =>
{
option.AccessTokenProvider = () =>
{
return Task.FromResult(serviceUtils.GenerateAccessToken(url, userId));
};
})
.WithAutomaticReconnect(new AlwaysRetryPolicy())
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Trace);
logging.AddConsole();
})
.Build();
this.groupName = groupName;
_connection.Reconnected += JoinGroup;
_connection.On("Connected", new[] { typeof(string) }, OnInitialConnection);
}
private string GetClientUrl(string endpoint, string hubName)
{
return $"{endpoint}/client/?hub={hubName}";
}
private async Task JoinGroup(string contextId)
{
try
{
await _connection.InvokeAsync("JoinGroup", groupName); //JoinGroup is C# method name
}
catch (Exception e)
{
}
}
// Called separately after the ClientHandler object is created.
public async Task StartAsyncWithRetry(CancellationToken cancellationToken = default)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await _connection.StartAsync()
.ContinueWith(res =>
{
_connection.InvokeAsync("JoinGroup", groupName);
});
if (_connection.State == HubConnectionState.Disconnected)
{
await Task.Delay(GetRandomDelayMilliseconds());
continue;
}
return;
}
catch (Exception e)
{
//throw;
//cancellationToken.
await Task.Delay(GetRandomDelayMilliseconds());
}
}
}
I have tried tweaking various setting in Azure, in my apps, changed my code from synch to async...
I must be missing something ... I've found lot of post but many of them were out of date. Also tried my luck with ChatGPT ...

DeviceClient.SetConnectionStatusChangesHandler is NOT invoked after device is 1+ hour offline

The code below works if the device is online and offline wtihin minutes. However, when it is offline more than 1 hour, and is switched to online, the SetConnectionStatusChangesHandler's handler is not invoked.
public class IotHubService
{
Microsoft.Azure.Devices.Client.DeviceClient _deviceClient;
public void InitDeviceClient()
{
_deviceClient = DeviceClient.Create(_iotHubEndpoint,
new DeviceAuthenticationWithToken(deviceId, sasToken),
TransportType.Mqtt_WebSocket_Only);
var retryPolicy = new MyExponentialBackOff(...);
_deviceClient.SetRetryPolicy(retryPolicy);
// Set handlers and callbacks
_deviceClient.SetConnectionStatusChangesHandler(ConnectionStatusChanged);
_iotHubCancellationTokenSource = new CancellationTokenSource();
await _deviceClient.OpenAsync(_iotHubCancellationTokenSource.Token);
}
//this does NOT get invoked if the device is offline for more than one hour, and switched to online
public async void ConnectionStatusChanged(ConnectionStatus status, ConnectionStatusChangeReason reason)
{
}
}
public class MyExponentialBackOff : IRetryPolicy
{
public bool ShouldRetry(int currentRetryCount, Exception lastException, out TimeSpan retryInterval)
{
if (currentRetryCount < _retryCount) //_retryCounty is int.MaxValue
{
retryInterval = TimeSpan.FromMilliseconds(10000);
return true;
}
retryInterval = TimeSpan.Zero;
return false;
}
}
Microsoft.Azure.Devices.Client: Version="1.21.0"
Xamarin Android Lollipop

How to communicate with parent application while it's in background?

I'm trying to develop watchOS 3 with Xamarin. My watch is communicating with parent application while it's active. When iOS app is killed or in background state, my watch doesn't receive any updated data. I'm sending request from the watch every 10 seconds in order to get updated data. I'm using WCSession for connection. The question is: is it possible to activate parent application from watch extension?
My functions for connectivity:
public void StartSession()
{
if (session != null)
{
session.Delegate = this;
session.ActivateSession();
Console.WriteLine($"Started Watch Connectivity Session on {Device}");
}
}
public override void SessionReachabilityDidChange(WCSession session)
{
Console.WriteLine($"Watch connectivity Reachable:{(session.Reachable ? '✓' : '✗')} from {Device}");
// handle session reachability change
if (session.Reachable)
{
// great! continue on with Interactive Messaging
}
else {
// 😥 prompt the user to unlock their iOS device
}
}
#region Application Context Methods
public void UpdateApplicationContext(Dictionary<string, object> applicationContext)
{
// Application context doesnt need the watch to be reachable, it will be received when opened
if (validSession != null)
{
try
{
var NSValues = applicationContext.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = applicationContext.Keys.Select(x => new NSString(x)).ToArray();
var NSApplicationContext = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
NSError error;
var sendSuccessfully = validSession.UpdateApplicationContext(NSApplicationContext, out error);
if (sendSuccessfully)
{
Console.WriteLine($"Sent App Context from {Device} \nPayLoad: {NSApplicationContext.ToString()} \n");
}
else
{
Console.WriteLine($"Error Updating Application Context: {error.LocalizedDescription}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception Updating Application Context: {ex.Message}");
}
}
}
public override void DidReceiveApplicationContext(WCSession session, NSDictionary<NSString, NSObject> applicationContext)
{
Console.WriteLine($"Receiving Message on {Device}");
if (ApplicationContextUpdated != null)
{
var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray();
var values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString())).ToArray();
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
ApplicationContextUpdated(session, dictionary);
}
}
#endregion

LeaseLostException in single instance of EventProcessorHost for IOT Hub

I have created a simple event processor host which reads events from Azure IOT hub configured with the default options for lease management. But at regular intervals, I get following exception even though I am running single instance of the EventProcessorHost:
ReceiverDisconnectedException: New receiver with higher epoch is created hence current receiver with epoch is getting disconnected. Reason: LeaseLost
The client re-acquires the lease and process messages further.
Here is the initialization of the EventProcessorHost
EventProcessorHost eventProcessorHost = new EventProcessorHost(hostName, eventHubName, EventHubConsumerGroup.DefaultGroupName,
eventHubConnectionString, storageAccountConnectionString);
var options = new EventProcessorOptions();
options.ExceptionReceived += (sender, e) =>
{
Console.WriteLine(e.Exception);
};
eventProcessorHost.PartitionManagerOptions = PartitionManagerOptions.DefaultOptions;
eventProcessorHost.RegisterEventProcessorAsync<SimpleEventProcessor>(options).Wait();
Is this behaviour normal or I need to do something to avoid it for a single instance?
Below is my EventProcessor:
public class SimpleEventProcessor : IEventProcessor
{
Stopwatch checkpointStopWatch;
public async Task CloseAsync(PartitionContext context, CloseReason reason)
{
Console.WriteLine("Processor Shutting Down. Partition '{0}', Reason: '{1}'.", context.Lease.PartitionId, reason);
await context.CheckpointAsync();
}
public Task OpenAsync(PartitionContext context)
{
Console.WriteLine("SimpleEventProcessor initialized. Partition: '{0}', Offset: '{1}'", context.Lease.PartitionId, context.Lease.Offset);
this.checkpointStopWatch = new Stopwatch();
this.checkpointStopWatch.Start();
this.rawDataService = new RawDataService();
return Task.FromResult<object>(null);
}
public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (EventData eventData in messages)
{
string data = Encoding.UTF8.GetString(eventData.GetBytes());
Console.WriteLine(string.Format("Message received. Partition: '{0}', Data: '{1}'", context.Lease.PartitionId, data));
}
}
}

Any Example of WebJob using EventHub?

I've tried to come up with something from the example in the WebJobsSDK gitHub
var eventHubConfig = new EventHubConfiguration();
string eventHubName = "MyHubName";
eventHubConfig.AddSender(eventHubName,"Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=SendRule;SharedAccessKey=xxxxxxxx");
eventHubConfig.AddReceiver(eventHubName, "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=ReceiveRule;SharedAccessKey=yyyyyyy");
config.UseEventHub(eventHubConfig);
JobHost host = new JobHost(config);
But I'm afraid that's not far enough for someone of my limited "skillset"!
I can find no instance of JobHostConfiguration that has a UseEventHub property (using the v1.2.0-alpha-10291 version of the Microsoft.AzureWebJobs package), so I can't pass the EventHubConfiguration to the JobHost.
I've used EventHub before, not within the WebJob context. I don't see if the EventHostProcessor is still required if using the WebJob triggering...or does the WebJob trigger essentially act as the EventHostProcessor?
Anyway, if anyone has a more complete example for a simpleton like me that would be really sweet! Thanks
From the documentation here, you should have all the information you need.
What you are missing is a reference of the Microsoft.Azure.WebJobs.ServiceBus.1.2.0-alpha-10291 nuget package.
The UseEventHub is an extension method that is declared in this package.
Otherwise your configuration seems ok.
Here is an example on how to receive or send messages from/to an EventHub:
public class BasicTest
{
public class Payload
{
public int Counter { get; set; }
}
public static void SendEvents([EventHub("MyHubName")] out Payload x)
{
x = new Payload { Counter = 100 };
}
public static void Trigger(
[EventHubTrigger("MyHubName")] Payload x,
[EventHub("MyHubName")] out Payload y)
{
x.Counter++;
y = x;
}
}
EventProcessorHost is still required, as the WebJob just provides the hosting environment for running it. As far as I know, EventProcessorHost is not integrated so deeply into WebJob, so its triggering mechanism cannot be used for processing EventHub messages. I use WebJob for running EventProcessorHost continuously:
public static void Main()
{
RunAsync().Wait();
}
private static async Task RunAsync()
{
try
{
using (var shutdownWatcher = new WebJobsShutdownWatcher())
{
await Console.Out.WriteLineAsync("Initializing...");
var eventProcessorHostName = "eventProcessorHostName";
var eventHubName = ConfigurationManager.AppSettings["eventHubName"];
var consumerGroupName = ConfigurationManager.AppSettings["eventHubConsumerGroupName"];
var eventHubConnectionString = ConfigurationManager.ConnectionStrings["EventHub"].ConnectionString;
var storageConnectionString = ConfigurationManager.ConnectionStrings["EventHubStorage"].ConnectionString;
var eventProcessorHost = new EventProcessorHost(eventProcessorHostName, eventHubName, consumerGroupName, eventHubConnectionString, storageConnectionString);
await Console.Out.WriteLineAsync("Registering event processors...");
var processorOptions = new EventProcessorOptions();
processorOptions.ExceptionReceived += ProcessorOptions_ExceptionReceived;
await eventProcessorHost.RegisterEventProcessorAsync<CustomEventProcessor>(processorOptions);
await Console.Out.WriteLineAsync("Processing...");
await Task.Delay(Timeout.Infinite, shutdownWatcher.Token);
await Console.Out.WriteLineAsync("Unregistering event processors...");
await eventProcessorHost.UnregisterEventProcessorAsync();
await Console.Out.WriteLineAsync("Finished.");
}
catch (Exception ex)
{
await HandleErrorAsync(ex);
}
}
}
private static async void ProcessorOptions_ExceptionReceived(object sender, ExceptionReceivedEventArgs e)
{
await HandleErrorAsync(e.Exception);
}
private static async Task HandleErrorAsync(Exception ex)
{
await Console.Error.WriteLineAsync($"Critical error occured: {ex.Message}{ex.StackTrace}");
}

Resources