1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
using System;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table; // same SDK as your sample
using Microsoft.Azure.Devices; // Install-Package Microsoft.Azure.Devices
using Microsoft.Azure.Devices.Common.Exceptions;
namespace Tango.MachineService.Telemetry
{
internal sealed class TelemetryDeviceRegistrationEntity : TableEntity
{
public TelemetryDeviceRegistrationEntity() { }
public TelemetryDeviceRegistrationEntity(string machineGuid, string serialNumber)
{
PartitionKey = machineGuid;
RowKey = SanitizeRowKey(serialNumber);
}
public String Environment { get; set; }
public string SerialNumber { get; set; }
public string DeviceId { get; set; }
public string ConnectionString { get; set; }
public DateTime CreatedUtc { get; set; }
public DateTime UpdatedUtc { get; set; }
internal static string SanitizeRowKey(string value) =>
(value ?? string.Empty)
.Replace("/", "_").Replace("\\", "_")
.Replace("#", "_").Replace("?", "_");
}
/// <summary>
/// Creates/gets IoT Hub device identities and caches the per-device connection string
/// in Azure Table Storage ("TelemetryDeviceRegistrations").
/// PartitionKey = MachineGuid, RowKey = SerialNumber (sanitized).
/// </summary>
public sealed class TelemetryDeviceRegistrationManager
{
private readonly string _iotHubServiceConnectionString;
private readonly string _iotHubHostName;
private readonly CloudTable _table;
private readonly object _locker = new object();
public TelemetryDeviceRegistrationManager(
string storageAccountConnectionString,
string iotHubServiceConnectionString,
string tableName = "TelemetryDeviceRegistrations")
{
if (string.IsNullOrWhiteSpace(storageAccountConnectionString))
throw new ArgumentNullException(nameof(storageAccountConnectionString));
if (string.IsNullOrWhiteSpace(iotHubServiceConnectionString))
throw new ArgumentNullException(nameof(iotHubServiceConnectionString));
if (string.IsNullOrWhiteSpace(tableName))
throw new ArgumentNullException(nameof(tableName));
_iotHubServiceConnectionString = iotHubServiceConnectionString;
_iotHubHostName = ExtractHostName(iotHubServiceConnectionString);
var storageAccount = CloudStorageAccount.Parse(storageAccountConnectionString);
var tableClient = storageAccount.CreateCloudTableClient();
_table = tableClient.GetTableReference(tableName);
_table.CreateIfNotExists(); // sync, as in your sample
}
/// <summary>
/// Returns the per-device IoT Hub connection string for (machineGuid, serialNumber).
/// Creates the IoT Hub device (DeviceId = serialNumber) if needed, persists the connection string,
/// then returns the cached value next time.
/// </summary>
public string GetOrCreateDeviceConnectionString(string machineGuid, string serialNumber)
{
if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
if (string.IsNullOrWhiteSpace(serialNumber)) throw new ArgumentNullException(nameof(serialNumber));
// 1) Try table cache
var cached = TryGetFromTable(machineGuid, serialNumber);
if (!string.IsNullOrEmpty(cached))
return cached;
// 2) Create/get in IoT Hub (guard against concurrent callers)
lock (_locker)
{
// double-check after lock
cached = TryGetFromTable(machineGuid, serialNumber);
if (!string.IsNullOrEmpty(cached))
return cached;
var registry = RegistryManager.CreateFromConnectionString(_iotHubServiceConnectionString);
Device device;
try
{
device = registry.AddDeviceAsync(new Device(serialNumber)).GetAwaiter().GetResult();
}
catch (DeviceAlreadyExistsException)
{
device = registry.GetDeviceAsync(serialNumber).GetAwaiter().GetResult();
}
if (device == null)
throw new InvalidOperationException("Failed to get or create IoT Hub device.");
var primaryKey = device.Authentication?.SymmetricKey?.PrimaryKey;
if (string.IsNullOrEmpty(primaryKey))
throw new InvalidOperationException("Device has no symmetric key.");
var deviceConnectionString =
$"HostName={_iotHubHostName};DeviceId={serialNumber};SharedAccessKey={primaryKey}";
UpsertToTable(machineGuid, serialNumber, deviceConnectionString);
return deviceConnectionString;
}
}
// ---- Table helpers (sync) ----
private string TryGetFromTable(string machineGuid, string serialNumber)
{
var rowKey = TelemetryDeviceRegistrationEntity.SanitizeRowKey(serialNumber);
var retrieve = TableOperation.Retrieve<TelemetryDeviceRegistrationEntity>(machineGuid, rowKey);
var result = _table.Execute(retrieve);
var entity = result.Result as TelemetryDeviceRegistrationEntity;
return entity?.ConnectionString;
}
private void UpsertToTable(string machineGuid, string serialNumber, string connectionString)
{
var entity = new TelemetryDeviceRegistrationEntity(machineGuid, serialNumber)
{
SerialNumber = serialNumber,
DeviceId = serialNumber,
Environment = MachineServiceConfig.DEPLOYMENT_SLOT.ToString(),
ConnectionString = connectionString,
CreatedUtc = DateTime.UtcNow,
UpdatedUtc = DateTime.UtcNow
};
var op = TableOperation.InsertOrReplace(entity);
_table.Execute(op);
}
// ---- Utility ----
private static string ExtractHostName(string iotHubServiceConnectionString)
{
var parts = iotHubServiceConnectionString.Split(';');
foreach (var p in parts)
if (p.StartsWith("HostName=", StringComparison.OrdinalIgnoreCase))
return p.Substring("HostName=".Length);
throw new ArgumentException("IoT Hub service connection string is missing HostName=…");
}
}
}
|