0

Currently I have an IoT Hub and I am trying to get the device connection events and the lifecycle events from my IoT hub to a function app. This while using the event hub instead of the event grid.

The reason for this is that this is cheaper, since the event grid also has some cost to it. In case this is not possible I'll have to use the event hub for all the telemetry data and the event grid for the connection/lifecycle events.

Compare message routing and Event Grid for IoT Hub

This question seems related to Subscribe to Azure IoT Hub Device Events but here the event grid is used.

From what I found on the internet this should be possible. https://sandervandevelde.wordpress.com/2022/01/16/azure-iot-device-lifecycle-events/. However I am afraid that with going from Microsoft.Azure.EventHubs.EventData to Azure.Messaging.EventHubs.EventData this option seems to got lost on the way.

So my set-up: As an IoT device I used https://azure-samples.github.io/raspberry-pi-web-simulator/ (I did have to comment out all the LEDPin lines)

In my IoT Hub I added the following routes. IoT hub routes

Next I made a function app (C# 6, non-isloated, windows) with the following (default) code.

using System;

public static void Run(string myIoTHubMessage, ILogger log)
{
    log.LogInformation($"C# IoT Hub trigger function processed a message: {myIoTHubMessage}");
}

Note on when trying this. In "function.json" set eventHubName to the correct value. The function won't like it if you don't (runtime crashes).

That gives the following result.

2023-04-20T14:49:38Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=dfc5bf56-b24d-4007-8ce7-dc53368844a6)
2023-04-20T14:49:38Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129561056, EnqueueTimeUtc: 2023-04-20T14:49:37.9070000+00:00, SequenceNumber: 3096, Count: 1
2023-04-20T14:49:38Z   [Information]   C# IoT Hub trigger function processed a message: {"deviceId":"AzureIotSim","etag":"AAAAAAAAAAE=","version":2,"properties":{"desired":{"$metadata":{"$lastUpdated":"2023-04-20T14:49:37.6237465Z"},"$version":1},"reported":{"$metadata":{"$lastUpdated":"2023-04-20T14:49:37.6237465Z"},"$version":1}}}
2023-04-20T14:49:38Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=dfc5bf56-b24d-4007-8ce7-dc53368844a6, Duration=1ms)
2023-04-20T14:51:16Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=1dd9de7f-9782-4431-b2ee-2bce3e238f51)
2023-04-20T14:51:16Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129561728, EnqueueTimeUtc: 2023-04-20T14:51:15.9050000+00:00, SequenceNumber: 3097, Count: 1
2023-04-20T14:51:16Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":1,"deviceId":"Raspberry Pi Web Client","temperature":23.38174622864487,"humidity":64.11401901408449}
2023-04-20T14:51:16Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=1dd9de7f-9782-4431-b2ee-2bce3e238f51, Duration=1ms)
2023-04-20T14:51:18Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=1a82745b-458a-40aa-90d1-5337c80ff0b4)
2023-04-20T14:51:18Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129562216, EnqueueTimeUtc: 2023-04-20T14:51:17.8280000+00:00, SequenceNumber: 3098, Count: 1
2023-04-20T14:51:18Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":2,"deviceId":"Raspberry Pi Web Client","temperature":24.456039635710496,"humidity":60.665519892235665}
2023-04-20T14:51:18Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=1a82745b-458a-40aa-90d1-5337c80ff0b4, Duration=0ms)
2023-04-20T14:51:20Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=f0c8cc5c-bec2-4835-8b86-af017ad68ce6)
2023-04-20T14:51:20Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129562704, EnqueueTimeUtc: 2023-04-20T14:51:19.8130000+00:00, SequenceNumber: 3099, Count: 1
2023-04-20T14:51:20Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":3,"deviceId":"Raspberry Pi Web Client","temperature":23.476940209551135,"humidity":73.32736250962085}
2023-04-20T14:51:20Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=f0c8cc5c-bec2-4835-8b86-af017ad68ce6, Duration=1ms)
2023-04-20T14:51:22Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=99bf995b-9965-4ef8-8f46-5a11df269351)
2023-04-20T14:51:22Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129563192, EnqueueTimeUtc: 2023-04-20T14:51:21.7990000+00:00, SequenceNumber: 3100, Count: 1
2023-04-20T14:51:22Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":4,"deviceId":"Raspberry Pi Web Client","temperature":21.78412119821204,"humidity":76.14936941122197}
2023-04-20T14:51:22Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=99bf995b-9965-4ef8-8f46-5a11df269351, Duration=1ms)
2023-04-20T14:51:24Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=52a4220e-4b44-4723-b4c4-278da9547922)
2023-04-20T14:51:24Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129563680, EnqueueTimeUtc: 2023-04-20T14:51:23.8930000+00:00, SequenceNumber: 3101, Count: 1
2023-04-20T14:51:24Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":5,"deviceId":"Raspberry Pi Web Client","temperature":24.420409860121747,"humidity":74.58519698742455}
2023-04-20T14:51:24Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=52a4220e-4b44-4723-b4c4-278da9547922, Duration=1ms)
2023-04-20T14:51:26Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=56614130-1bd2-428f-b164-7fc9b370f946)
2023-04-20T14:51:26Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129564168, EnqueueTimeUtc: 2023-04-20T14:51:25.8770000+00:00, SequenceNumber: 3102, Count: 1
2023-04-20T14:51:26Z   [Information]   C# IoT Hub trigger function processed a message: {"messageId":6,"deviceId":"Raspberry Pi Web Client","temperature":23.216916301025233,"humidity":68.84637049944389}
2023-04-20T14:51:26Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=56614130-1bd2-428f-b164-7fc9b370f946, Duration=0ms)
2023-04-20T14:51:49Z   [Information]   Executing 'Functions.IoTHub_EventHub1' (Reason='(null)', Id=0bc20f6f-56ce-4144-bf98-75f014497087)
2023-04-20T14:51:49Z   [Information]   Trigger Details: PartionId: 1, Offset: 60129564656, EnqueueTimeUtc: 2023-04-20T14:51:49.3160000+00:00, SequenceNumber: 3103, Count: 1
2023-04-20T14:51:49Z   [Information]   C# IoT Hub trigger function processed a message: {"sequenceNumber":"000000000000000001D923FE9CF3452400000014000000000000000000000004"}
2023-04-20T14:51:49Z   [Information]   Executed 'Functions.IoTHub_EventHub1' (Succeeded, Id=0bc20f6f-56ce-4144-bf98-75f014497087, Duration=0ms)

The 2 most important parts are in my opinion.

{"deviceId":"AzureIotSim","etag":"AAAAAAAAAAE=","version":2,"properties":{"desired":{"$metadata":{"$lastUpdated":"2023-04-20T14:49:37.6237465Z"},"$version":1},"reported":{"$metadata":{"$lastUpdated":"2023-04-20T14:49:37.6237465Z"},"$version":1}}}
{"sequenceNumber":"000000000000000001D923FE9CF3452400000014000000000000000000000004"}

These 2 are, from what I can tell the "Created" and "Disconnected" messages.

Ok, that works, now does Azure.Messaging.EventHubs.EventData work as expected?

So I created an C# function app (in visual studio) (C# 7, isolated, windows) with the following code.

using System;
using Azure.Messaging.EventHubs;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace EventHubsFunctionTest
{
    public class ParseHubMessage
    {
        private readonly ILogger _logger;

        public ParseHubMessage(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<ParseHubMessage>();
        }

        [Function("ParseHubMessage")]
        public void Run([EventHubTrigger("HUBNAME", Connection = "CONNECTIONNAME", IsBatched = false)] EventData data)
        {
            _logger.LogInformation("Got ContentType {ContentType}", data.ContentType);
            _logger.LogInformation("Got CorrelationId {CorrelationId}", data.CorrelationId);
            _logger.LogInformation("Got EnqueuedTime {EnqueuedTime}", data.EnqueuedTime);
            _logger.LogInformation("Got EventBody {EventBody}", data.EventBody);
            _logger.LogInformation("Got MessageId {MessageId}", data.MessageId);
            _logger.LogInformation("Got PartitionKey {PartitionKey}", data.PartitionKey);
            _logger.LogInformation("Got SequenceNumber {SequenceNumber}", data.SequenceNumber);

            foreach(var property in data.Properties)
            {
                _logger.LogInformation("Got property {Key} with value {Value}", property.Key, property.Value);
            }

            foreach (var systemProperty in data.SystemProperties)
            {
                _logger.LogInformation("Got system property {Key} with value {Value}", systemProperty.Key, systemProperty.Value);
            }
        }
    }
}

If I now deploy this, the Azure function is going to spit errors back at me. (repeated twice)

Exception while executing function: Functions.ParseHubMessage Result: Failure
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'ParseHubMessage': Cannot convert input parameter 'data' to type 'Azure.Messaging.EventHubs.EventData' from type 'System.String'. Error:System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.messageId | LineNumber: 0 | BytePositionInLine: 14.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Number' as a string.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType)
   at System.Text.Json.Utf8JsonReader.GetString()
   at System.Text.Json.Serialization.Converters.StringConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo, ReadStack& state)
   at System.Text.Json.JsonSerializer.ContinueDeserialize[TValue](ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.ReadFromStreamAsync[TValue](Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
   at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 106
   at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 42
   at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
   at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request, WorkerOptions workerOptions) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 84
Stack:    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 106
   at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 42
   at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
   at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request, WorkerOptions workerOptions) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 84 
Result: Function 'ParseHubMessage', Invocation id '285058fc-38f2-4a8d-ac73-77b1bc863521': An exception was thrown by the invocation.
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'ParseHubMessage': Cannot convert input parameter 'data' to type 'Azure.Messaging.EventHubs.EventData' from type 'System.String'. Error:System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.messageId | LineNumber: 0 | BytePositionInLine: 14.
 ---> System.InvalidOperationException: Cannot get the value of a token type 'Number' as a string.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType)
   at System.Text.Json.Utf8JsonReader.GetString()
   at System.Text.Json.Serialization.Converters.StringConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo, ReadStack& state)
   at System.Text.Json.JsonSerializer.ContinueDeserialize[TValue](ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.ReadFromStreamAsync[TValue](Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
   at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 106
   at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 42
   at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
Stack:    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 106
   at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 42
   at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77 

Any suggestions on how to get this to work (via the event hub, NOT the event grid).

As for (other) related questions.
Azure IoT Hub, EventHub and Functions Gives me more questions than answers. Links to this github issue. Than the question becomes, how can I bind to "important ServiceBus and EventHub message/event properties."
Azure function missing IoT hub trigger messages Has the question, not the answer.
Get device ID and payload in Azure Function trigered from IoT Hub Looks like it could do the job, however Microsoft.Azure.ServiceBus has been deprecated.

[Edit]

Switching to .net 6 did the trick. However keep in mind that if you don't start with a fresh project (.NET 6, LTS, non-isolated) in Visual Studio. Otherwise the updated project will still be generating errors.

user2986756
  • 89
  • 1
  • 9

1 Answers1

2

It is not currently possible to bind to EventData with the isolated model for Azure Functions. To do so, you'll need to use the in-process model. Examples can be found here.

The isolated model requires passing data across process boundaries, which needs additional infrastructure support in the host. This is an area that is being worked on, and there is preview-level support for some triggers currently - but Event Hubs is not among them. More details can be found here.

Jesse Squire
  • 6,107
  • 1
  • 27
  • 30
  • Ok, so If I want to use EventData I need to develop my app in C# 6 and next choose the .Net 6 (non-isolated) environment when creating my Azure function. Correct? – user2986756 Apr 21 '23 at 13:45
  • Also, how do I know when the moment is there that I can use EventData with the isolated model? – user2986756 Apr 21 '23 at 14:01
  • 1
    Yes, you need to choose the non-isolated template for the Function. I'd suggest watching the Azure SDK blog release anouncements; isolated support for Event Hubs would appear there. https://devblogs.microsoft.com/azure-sdk/ – Jesse Squire Apr 21 '23 at 15:00
  • I just created a new App function with the .net 6 non-isolated template. However if I (try) to run my original code I still get exceptions [1](https://pastebin.com/tFf93MnZ)(2x) [2](https://pastebin.com/K3bYVb3W) – user2986756 Apr 24 '23 at 06:58
  • Can you share your .csproj file so that we can see the packages and versions that you're referencing? – Jesse Squire Apr 24 '23 at 13:04
  • Also, if you're seeing exceptions that reference the Functions worker, that would generally indicate that you're still using an isolated model function. – Jesse Squire Apr 24 '23 at 13:05
  • The csproj can be found [here](https://pastebin.com/Cr0cmyXv), a screenshot of the settings I used when creating the app are [here](https://i.imgur.com/wTXdMM8.png) – user2986756 Apr 25 '23 at 11:36
  • I'm not able to repro what you're seeing using the information available. I'd advise opening an issue in the Azure SDK for .NET repository with a small packaged repro that we can use to investigate further. https://github.com/Azure/azure-sdk-for-net/issues – Jesse Squire Apr 26 '23 at 13:33
  • 1
    For future reference: The github issue is [here](https://github.com/Azure/azure-sdk-for-net/issues/35866). See my edit for the solution. – user2986756 Apr 28 '23 at 09:11