Is there is a way to connect devices from Azure IoT Hub to Azure IoT Central? - azure-iot-hub

i see so many devices that can easily connect to Azure IoT hub via MQTT. But it is NOT as easy to connect those same devices to Azure IoT Central. Is there a way to send those data from Azure IoT Hub to Azure IoT Central?

In the case of sending only a telemetry data to the Azure IoT Central App, you can use the Azure Event Grid integrator, where the device telemetry message is published via the Azure IoT Hub routing feature:
The following code snippet is an example of the webhook subscriber implementation (HttpTrigger Function) for handling all needs such as DPS, etc.
function.json file:
{
"bindings": [
{
"name": "eventGridEvent",
"authLevel": "function",
"methods": [
"post",
"options"
],
"direction": "in",
"type": "httpTrigger"
},
{
"name": "$return",
"type": "http",
"direction": "out"
}
]
}
run.csx file:
#r "Newtonsoft.Json"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Net;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public static async Task<ActionResult> Run(JObject eventGridEvent, HttpRequest req, ILogger log)
{
if (req.Method == HttpMethod.Options.ToString())
{
log.LogInformation("CloudEventSchema validation");
req.HttpContext.Response.Headers.Add("Webhook-Allowed-Origin", req.Headers["WebHook-Request-Origin"].FirstOrDefault()?.Trim());
return (ActionResult)new OkResult();
}
// consumer of telemetry (iot central)
uint sasTokenTTLInHrs = 1;
string iotcScopeId = req.Headers["iotc-scopeId"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_scopeId");
string iotcSasToken = req.Headers["iotc-sasToken"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_sasToken");
log.LogInformation($"CloudEvent_Id = {eventGridEvent["id"]}");
log.LogInformation($"AzureIoT_scopeId = {iotcScopeId}");
// mandatory properties
string source = eventGridEvent["data"]?["systemProperties"]?["iothub-message-source"]?.Value<string>();
string deviceId = eventGridEvent["data"]?["systemProperties"]?["iothub-connection-device-id"]?.Value<string>();
if (source == "Telemetry" && !string.IsNullOrEmpty(deviceId) && Regex.IsMatch(deviceId, #"^[a-z0-9\-]+$"))
{
var sysProp = eventGridEvent["data"]["systemProperties"];
var appProp = eventGridEvent["data"]["properties"];
// device model
var component = appProp?["iothub-app-component-name"]?.Value<string>() ?? sysProp["dt-subject"]?.Value<string>() ?? "";
var modelId = appProp?["iothub-app-model-id"]?.Value<string>() ?? sysProp["dt-dataschema"]?.Value<string>();
// creation time
var enqueuedtime = sysProp["iothub-enqueuedtime"]?.Value<DateTime>().ToString("o");
var ctime = appProp?["iothub-creation-time-utc"]?.Value<DateTime>().ToString("o");
// device group (device prefix)
var deviceGroup = appProp?["iothub-app-device-group"]?.Value<string>();
deviceId = $"{(deviceGroup == null ? "" : deviceGroup + "-")}{deviceId}";
// remove sysprop
((JObject)eventGridEvent["data"]).Remove("systemProperties");
try
{
var info = await Connectivity.GetConnectionInfo(deviceId, modelId, iotcScopeId, iotcSasToken, log, sasTokenTTLInHrs);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", info.SasToken);
client.DefaultRequestHeaders.Add("dt-subject", component);
client.DefaultRequestHeaders.Add("iothub-app-iothub-creation-time-utc", ctime ?? enqueuedtime);
var response = await client.PostAsJsonAsync(info.RequestUri, eventGridEvent["data"]);
response.EnsureSuccessStatusCode();
}
log.LogInformation($"POST: {info.RequestUri}\r\n{eventGridEvent["data"]}");
}
catch(Exception ex)
{
log.LogError(ex.InnerException == null ? ex.Message : ex.InnerException.Message);
Connectivity.RemoveDevice(deviceId);
throw ex; // for retrying and deadlettering undeliverable message
}
}
else
{
log.LogWarning($"Wrong event message:\r\n{eventGridEvent}");
}
return (ActionResult)new OkResult();
}
class ConnectivityInfo
{
public string IoTHubName { get; set; }
public string RequestUri { get; set; }
public string SasToken { get; set; }
public ulong SaSExpiry { get; set; }
public string ModelId { get; set; }
public string DeviceConnectionString { get; set; }
}
static class Connectivity
{
static Dictionary<string, ConnectivityInfo> devices = new Dictionary<string, ConnectivityInfo>();
public static async Task<ConnectivityInfo> GetConnectionInfo(string deviceId, string modelId, string iotcScopeId, string iotcSasToken, ILogger log, uint sasTokenTTLInHrs = 24, int retryCounter = 10, int pollingTimeInSeconds = 3)
{
if (devices.ContainsKey(deviceId))
{
if (!string.IsNullOrEmpty(modelId) && devices[deviceId].ModelId != modelId)
{
log.LogWarning($"Reprovissiong device with new model");
devices.Remove(deviceId);
}
else
{
if (!SharedAccessSignatureBuilder.IsValidExpiry(devices[deviceId].SaSExpiry, 100))
{
log.LogWarning($"Refreshing sasToken");
devices[deviceId].SasToken = SharedAccessSignatureBuilder.GetSASTokenFromConnectionString(devices[deviceId].DeviceConnectionString, sasTokenTTLInHrs);
devices[deviceId].SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));
}
return devices[deviceId];
}
}
string deviceKey = SharedAccessSignatureBuilder.ComputeSignature(iotcSasToken, deviceId);
string address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/register?api-version=2021-06-01";
string sas = SharedAccessSignatureBuilder.GetSASToken($"{iotcScopeId}/registrations/{deviceId}", deviceKey, "registration", 1);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", sas);
client.DefaultRequestHeaders.Add("accept", "application/json");
string jsontext = string.IsNullOrEmpty(modelId) ? null : $"{{ \"modelId\":\"{modelId}\" }}";
var response = await client.PutAsync(address, new StringContent(JsonConvert.SerializeObject(new { registrationId = deviceId, payload = jsontext }), Encoding.UTF8, "application/json"));
var atype = new { errorCode = "", message = "", operationId = "", status = "", registrationState = new JObject() };
do
{
dynamic operationStatus = JsonConvert.DeserializeAnonymousType(await response.Content.ReadAsStringAsync(), atype);
if (!string.IsNullOrEmpty(operationStatus.errorCode))
{
throw new Exception($"{operationStatus.errorCode} - {operationStatus.message}");
}
response.EnsureSuccessStatusCode();
if (operationStatus.status == "assigning")
{
Task.Delay(TimeSpan.FromSeconds(pollingTimeInSeconds)).Wait();
address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/operations/{operationStatus.operationId}?api-version=2021-06-01";
response = await client.GetAsync(address);
}
else if (operationStatus.status == "assigned")
{
var cinfo = new ConnectivityInfo();
cinfo.ModelId = modelId;
cinfo.IoTHubName = operationStatus.registrationState.assignedHub;
cinfo.DeviceConnectionString = $"HostName={cinfo.IoTHubName};DeviceId={deviceId};SharedAccessKey={deviceKey}";
cinfo.RequestUri = $"https://{cinfo.IoTHubName}/devices/{deviceId}/messages/events?api-version=2021-04-12";
cinfo.SasToken = SharedAccessSignatureBuilder.GetSASToken($"{cinfo.IoTHubName}/{deviceId}", deviceKey, null, sasTokenTTLInHrs);
cinfo.SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));
devices.Add(deviceId, cinfo);
log.LogInformation($"DeviceConnectionString: {cinfo.DeviceConnectionString}");
return cinfo;
}
else
{
throw new Exception($"{operationStatus.registrationState.status}: {operationStatus.registrationState.errorCode} - {operationStatus.registrationState.errorMessage}");
}
} while (--retryCounter > 0);
throw new Exception("Registration device status retry timeout exprired, try again.");
}
}
public static void RemoveDevice(string deviceId)
{
if (devices.ContainsKey(deviceId))
devices.Remove(deviceId);
}
}
public sealed class SharedAccessSignatureBuilder
{
public static string GetHostNameNamespaceFromConnectionString(string connectionString)
{
return GetPartsFromConnectionString(connectionString)["HostName"].Split('.').FirstOrDefault();
}
public static string GetSASTokenFromConnectionString(string connectionString, uint hours = 24)
{
var parts = GetPartsFromConnectionString(connectionString);
if (parts.ContainsKey("HostName") && parts.ContainsKey("SharedAccessKey"))
return GetSASToken(parts["HostName"], parts["SharedAccessKey"], parts.Keys.Contains("SharedAccessKeyName") ? parts["SharedAccessKeyName"] : null, hours);
else
return string.Empty;
}
public static string GetSASToken(string resourceUri, string key, string keyName = null, uint hours = 24)
{
try
{
var expiry = GetExpiry(hours);
string stringToSign = System.Web.HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
var signature = SharedAccessSignatureBuilder.ComputeSignature(key, stringToSign);
var sasToken = keyName == null ?
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry) :
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
catch
{
return string.Empty;
}
}
#region Helpers
public static string ComputeSignature(string key, string stringToSign)
{
using (HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)))
{
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
}
}
public static Dictionary<string, string> GetPartsFromConnectionString(string connectionString)
{
return connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Split(new[] { '=' }, 2)).ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase);
}
// default expiring = 24 hours
public static string GetExpiry(uint hours = 24)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToString((ulong)sinceEpoch.TotalSeconds + 3600 * hours);
}
public static DateTime GetDateTimeUtcFromExpiry(ulong expiry)
{
return (new DateTime(1970, 1, 1)).AddSeconds(expiry);
}
public static bool IsValidExpiry(ulong expiry, ulong toleranceInSeconds = 0)
{
return GetDateTimeUtcFromExpiry(expiry) - TimeSpan.FromSeconds(toleranceInSeconds) > DateTime.UtcNow;
}
#endregion
}
The following screen snippet shows part of the subscription for passing requested headers for webhook subscriber:
Note, that the mapping feature can be used at the Azure IoT Central App on the input side, based on the device model.
As the above first picture shows, this solution is based on using the Azure Event Grid feature, where the Azure IoT Hub represents a publisher of the device telemetry data and the Azure IoT Central app is its consumer.
The logical connectivity between the Azure IoT Hub and Azure IoT Central is done via the AEG Subscription with a webhook destination handler such as the HttpTrigger Function (see the above implementation).
Note, that this subscription is configured for delivering an event message (device telemetry data) in the CloudEventSchema.

Device that provisions itself in IoTHub via DPS will work with IoT Central with no change other than ID Scope sent by device during provisioning which identifies DPS service instance. One ID Scope will point it specific IoT Hub configured in DPS enrollment group, while other will point it to an internal IoT Hub in IoT Central application (IoT Central spins additional internal IoT Hubs as needed for auto scaling, which is why it has its own internal DPS).
Use of DPS allows provisioning of the device to specific IoTHub at the first call and subsequently the change can be triggered explicitly for reprovisioning to different IoTHub or IoT Central, which can be used to move device if needed. This functionality allows scenarios where you can force a device to connect to an IoT Hub or IoT Central by implementing ID Scope change direct method and triggering reprovisioning. Use of DPS is highly recommended as it simplifies provisioning and provides this flexibility.
Reprovisioning should be part of retry logic on the device in case if it fails to connect to IoTHub for certain amount of time in addition to on-demand change described above.

What makes you think "But it is NOT as easy to connect those same devices to Azure IoT Central"?
Any device connecting to IoTHub can also connect to IoTCentral, you just need to provision the device using DPS, it will get the IoTHub hostname and everything else will work the same way.

Related

Are there advantages of using .NET Core's middleware "Health checks" over just a ping in a controller's route?

I'm reading a "Asp.net Core 3 and Angular 9" book and there is an example usage of .NET Core Health check.
It's also described on Microsoft website: https://learn.microsoft.com/en-US/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.0
I can't find a reason to actually use it over just creating a route in some controller which will ping external addresses.
Code in a book goes like this:
Add this in Configure (Startup.cs) method:
app.UseHealthChecks("/hc", new CustomHealthCheckOptions());
ConfigureServices method:
services.AddHealthChecks()
.AddCheck("ICMP_01", new ICMPHealthCheck("www.ryadel.com", 100))
.AddCheck("ICMP_02", new ICMPHealthCheck("www.google.com", 100))
.AddCheck("ICMP_03", new ICMPHealthCheck("www.does-notexist.com", 100));
Create ICMPHealthCheck.cs file:
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
namespace HealthCheck
{
public class ICMPHealthCheck : IHealthCheck
{
private string Host { get; set; }
private int Timeout { get; set; }
public ICMPHealthCheck(string host, int timeout)
{
Host = host;
Timeout = timeout;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(Host);
switch (reply.Status)
{
case IPStatus.Success:
var msg = String.Format(
"IMCP to {0} took {1} ms.",
Host,
reply.RoundtripTime);
return (reply.RoundtripTime > Timeout)
? HealthCheckResult.Degraded(msg)
: HealthCheckResult.Healthy(msg);
default:
var err = String.Format(
"IMCP to {0} failed: {1}",
Host,
reply.Status);
return HealthCheckResult.Unhealthy(err);
}
}
}
catch (Exception e)
{
var err = String.Format(
"IMCP to {0} failed: {1}",
Host,
e.Message);
return HealthCheckResult.Unhealthy(err);
}
}
}
}
Create CustomHealthCheckOptions.cs file:
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
namespace HealthCheck
{
public class CustomHealthCheckOptions : HealthCheckOptions
{
public CustomHealthCheckOptions() : base()
{
var jsonSerializerOptions = new JsonSerializerOptions()
{
WriteIndented = true
};
ResponseWriter = async (c, r) =>
{
c.Response.ContentType =
MediaTypeNames.Application.Json;
c.Response.StatusCode = StatusCodes.Status200OK;
var result = JsonSerializer.Serialize(new
{
checks = r.Entries.Select(e => new
{
name = e.Key,
responseTime = e.Value.Duration.TotalMilliseconds,
status = e.Value.Status.ToString(),
description = e.Value.Description
}),
totalStatus = r.Status,
totalResponseTime =
r.TotalDuration.TotalMilliseconds,
}, jsonSerializerOptions);
await c.Response.WriteAsync(result);
};
}
}
}
So it just pings 3 addresses and I can't see advantages of using Microsoft.AspNetCore.Diagnostics.HealthChecks library. Is that wrong example?

Unable to connect Azure Function App with Database (using .net core 2.1)

Please note (Environment):
Function App: Version 2,
Target Framework: .Net Core 2.1
I am developing a Function App, that will work like Web Api. This Function App will return the data from database tables, also it'll manipulate files in Azure storage(Blob). But I am stuck as I am unable to create ConnectionString from local.settings.json file. Ideally the connection string should be created by default as I followed some tutorials & no where mentioned any extra steps to create default connectionstring value, just need to create it in local.settings.json file.
following is my local.settings.json file content:-
{
"ConnectionStrings": {
"mycs": "data source=servername;initial catalog=dbname;user id=XXXX;password=XXXX;MultipleActiveResultSets=True;"
},
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"mycs": "data source=servername;initial catalog=dbname;user id=XXXX;password=XXXX;MultipleActiveResultSets=True;"
}
}
following is my HttpTrigger file:
namespace my_api
{
public class myDataContext : DbContext
{
public myDataContext() : base(GetConnectionString()) { }
private static string GetConnectionString()
{
const string providerName = "System.Data.SqlClient";
const string metadata = #"res://*/MYDB.csdl|res://*/MYDB.ssdl|res://*/MYDB.msl";
try
{
string connectString = ConfigurationManager.ConnectionStrings["mycs"].ToString();
// Initialize the connection string builder for the
// underlying provider.
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder(connectString);
// Set the properties for the data source.
//sqlBuilder.IntegratedSecurity = true;
sqlBuilder.MultipleActiveResultSets = true;
// Build the SqlConnection connection string.
string providerString = sqlBuilder.ToString();
// Initialize the EntityConnectionStringBuilder.
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
//Set the provider name.
entityBuilder.Provider = providerName;
// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = providerString;
// Set the Metadata location.
entityBuilder.Metadata = metadata;
return entityBuilder.ConnectionString;
}
catch { }
var connectionstring = Environment.GetEnvironmentVariable("mycs");
return connectionstring;
}
public DbSet<flowerset> flowersets
{
get;
set;
}
}
}
Following is the code for :
namespace my_api
{
public static class helpService
{
[FunctionName("helpService_get")]
public static async Task> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log, ExecutionContext context)
{
log.LogInformation("C# HTTP trigger function processed a request helpService_get).");
try {
int page = 0;
int pageSize = 20;
myDataContext entity = new myDataContext();
if (page == 0 && pageSize == 0)
{
return entity.helpsets.ToList();
}
if (pageSize <= 0) { pageSize = 20; }
entity.helpsets.OrderByDescending(x => x.id).Skip((page - 1) * pageSize).Take(pageSize).ToList();
}
catch (Exception exx) {
log.LogInformation("Exception changed (helpService_get): "+exx.Message);
}
return null;
}
}//End of Class
}//End of Namespace
I am getting following error on line entity.helpsets.OrderByDescending(x => x.id).Skip((page - 1) * pageSize).Take(pageSize).ToList();:
Unable to determine the provider name for provider factory of type 'System.Data.SqlClient.SqlClientFactory'. Make sure that the ADO.NET provider is installed or registered in the application config.
According to my test, we can use System.Data.SqlClient to connect Azure SQL in Azure function V2.0. For example
Create an Azure Function with Visual Studio 2019
Install System.Data.SqlClient package(the version I sue is 4.5.1)
Develop the function
local.settings.json file content
"ConnectionStrings": {
"mycs": "Data Source="";Initial Catalog=DotNetAppSqlDb20190826105048_db;User Id="";Password="" "
},
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
Code
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
try
{
var connectionstring = System.Environment.GetEnvironmentVariable($"ConnectionStrings:mycs"); ;
using (SqlConnection connection = new SqlConnection(connectionstring))
{
connection.Open();
log.LogInformation(" sql login success");
StringBuilder sb = new StringBuilder();
sb.Append("select * from dbo.Todoes");
String sql = sb.ToString();
using (SqlCommand command = new SqlCommand(sql, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
log.LogInformation("{0} {1}", reader.GetInt32(0), reader.GetString(1));
}
}
}
connection.Close();
}
}
catch (SqlException e)
{
Console.WriteLine(e.ToString());
}
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
For more details, please refer to the document

ASP.NET Core 2.2 - SignalR How to join a group and send message from the server?

I have a set up a basic solution for SignalR where I can send and receive messages to all clients through the browser.
Required solution:
I need to implement the use of groups, BUT with the ability to join the group and send a message from a background service class that is running on the server.
Currently I have only been successful in sending a message to ALL clients from the server.
My background service class (basically a comms server) will be running multiple instances, so each instance will have a unique name such as CommsServer 1, CommsServer 2 etc. Each instance of the comms server will need to output messages to a specific SignalR group of recipients.
In the browser, the user will select which SignalR group they wish to join from a dropdown list that is pulled from the server. The server also has knowledge of this same list and therefore each comms server instance will represent an item from the list.
My code so far:
MessageHub Class:
public class MessageHub : Hub
{
public Task SendMessageToAll(string message)
{
return Clients.All.SendAsync("ReceiveMessage", message);
}
}
Message.js File:
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/messages")
.build();
connection.on("ReceiveMessage", function (message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var div = document.createElement("div");
div.innerHTML = msg + "<hr/>";
document.getElementById("messages").appendChild(div);
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var message = document.getElementById("message").value;
connection.invoke("SendMessageToAll", message).catch(function (err) {
return console.error(err.toString());
});
e.preventDefault();
});
Razor Page:
<textarea name="message" id="message"></textarea>
<input type="button" id="sendButton" value="Send Message" />
<div id="messages"></div>
<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script src="~/js/message.js"></script>
Comms server class:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
public string inboundIpAddress = "127.0.0.1";
public int inboundLocalPortNumber = 10001;
public void TcpServerIN(string serverName)
{
Task.Run(() =>
{
IPAddress localAddrIN = IPAddress.Parse(inboundIpAddress);
TcpListener listener = new TcpListener(localAddrIN, inboundLocalPortNumber);
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream(); // Networkstream is used to send/receive messages
//Buffer for reading data
Byte[] bytes = new Byte[4096];
String data = null;
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
_hubContext.Clients.All.SendAsync(data);
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
stream.Write(msg, 0, data.Length);
}
// Shutdown and end connection
client.Close();
}
});
}
}
I need to select a specific SignalR group name e.g. "CommsServer 1" this will form the name of the SignalR group that the user can select from a dropdown list in the browser so they can monitor events from just this one particular server instance. Other user may wish to monitor events from a different server instance.
One option I was considering is simply send events from all servers instances to ALL browser connected clients and then somehow filter the events on the client side, but this would seem an inefficient way of handling things and not good for network traffic.
If you want your client to decide which group to join (by selecting from a list of dropdowns), then your client should probably notify the server of its subscription to messages aimed at the group via the hub:
Hub:
public class MessageHub : Hub
{
public async Task SendMessageToAll(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
public async Task Subscribe(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
Background service:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
private readonly string _serviceInstanceName;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext, string serviceInstanceName)
{
_hubContext = hubContext;
_serviceInstanceName = serviceInstanceName;
}
public async Task SendMessageToGroup(string message)
{
await _hubContext.Clients.Group(_serviceInstanceName).SendAsync("ReceiveMessage", message);
}
}
And somewhere in your client, perhaps after user chooses an option from your dropdown:
connection.invoke("subscribe", "CommsServer 1, or whatever").catch(function (err) {
return console.error(err.toString());
});

SEC_ERROR_REVOKED_CERTIFICATE error while accesing the url in browser

Hi i am getting below error while trying to access the website which is hosted in IIS 8 for which the SSL certificate had got expired and i installed the new SSL certificate provided by GoDaddy, it was all working fine for 2 days and now it shows the below error. Let me know if anyone can figure out what is the issue
using Microsoft.CognitiveServices.Speech;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace SPT
{
class Program
{
public static async Task RecognizeSpeechAsync()
{
// Creates an instance of a speech config with specified subscription key and service region.
// Replace with your own subscription key // and service region (e.g., "westus").
var config = SpeechConfig.FromSubscription(" 7cf359266c964dc789960abe063cc65b", "westus");
// Creates a speech recognizer.
using (var recognizer = new SpeechRecognizer(config))
{
Console.WriteLine("Say something...");
// Starts speech recognition, and returns after a single utterance is recognized. The end of a
// single utterance is determined by listening for silence at the end or until a maximum of 15
// seconds of audio is processed. The task returns the recognition text as result.
// Note: Since RecognizeOnceAsync() returns only a single utterance, it is suitable only for single
// shot recognition like command or query.
// For long-running multi-utterance recognition, use StartContinuousRecognitionAsync() instead.
var result = await recognizer.RecognizeOnceAsync();
// Checks result.
if (result.Reason == ResultReason.RecognizedSpeech)
{
Console.WriteLine($"We recognized: {result.Text}");
}
else if (result.Reason == ResultReason.NoMatch)
{
Console.WriteLine($"NOMATCH: Speech could not be recognized.");
}
else if (result.Reason == ResultReason.Canceled)
{
var cancellation = CancellationDetails.FromResult(result);
Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
if (cancellation.Reason == CancellationReason.Error)
{
Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
Console.WriteLine($"CANCELED: Did you update the subscription info?");
}
}
}
}
public static async Task SynthesisToSpeakerAsync()
{
// Creates an instance of a speech config with specified subscription key and service region.
// Replace with your own subscription key and service region (e.g., "westus").
// The default language is "en-us".
var config = SpeechConfig.FromSubscription("7cf359266c964dc789960abe063cc65b", "westus");
// Creates a speech synthesizer using speaker as audio output.
using (var synthesizer = new SpeechSynthesizer(config))
{
// Receive a text from console input and synthesize it to speaker.
Console.WriteLine("Type some text that you want to speak...");
Console.Write("> ");
string text = Console.ReadLine();
using (var result = await synthesizer.SpeakTextAsync(text))
{
if (result.Reason == ResultReason.SynthesizingAudioCompleted)
{
Console.WriteLine($"Speech synthesized to speaker for text [{text}]");
}
else if (result.Reason == ResultReason.Canceled)
{
var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");
if (cancellation.Reason == CancellationReason.Error)
{
Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
Console.WriteLine($"CANCELED: ErrorDetails=[{cancellation.ErrorDetails}]");
Console.WriteLine($"CANCELED: Did you update the subscription info?");
}
}
}
// This is to give some time for the speaker to finish playing back the audio
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
public static async Task SynthesisToVideoAsync()
{
var apiUrl = "https://api.videoindexer.ai";
var accountId = "56fbb8f8-b9a8-4119-b46a-fa5fb6668ddd";
var location = "westus2";
var apiKey = "6f354f730bc141f9bc3e57e73c6001b0";
System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol | System.Net.SecurityProtocolType.Tls12;
// create the http client
var handler = new HttpClientHandler();
handler.AllowAutoRedirect = false;
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
// obtain account access token
var accountAccessTokenRequestResult = client.GetAsync($"{apiUrl}/auth/{location}/Accounts/{accountId}/AccessToken?allowEdit=true").Result;
var accountAccessToken = accountAccessTokenRequestResult.Content.ReadAsStringAsync().Result.Replace("\"", "");
client.DefaultRequestHeaders.Remove("Ocp-Apim-Subscription-Key");
// upload a video
var content = new MultipartFormDataContent();
Debug.WriteLine("Uploading...");
// get the video from URL
var videoUrl = "VIDEO_URL"; // replace with the video URL
// as an alternative to specifying video URL, you can upload a file.
// remove the videoUrl parameter from the query string below and add the following lines:
//FileStream video =File.OpenRead(Globals.VIDEOFILE_PATH);
//byte[] buffer =newbyte[video.Length];
//video.Read(buffer, 0, buffer.Length);
//content.Add(newByteArrayContent(buffer));
var uploadRequestResult = client.PostAsync($"{apiUrl}/{location}/Accounts/{accountId}/Videos?accessToken={accountAccessToken}&name=some_name&description=some_description&privacy=private&partition=some_partition&videoUrl={videoUrl}", content).Result;
var uploadResult = uploadRequestResult.Content.ReadAsStringAsync().Result;
// get the video id from the upload result
var videoId = JsonConvert.DeserializeObject<dynamic>(uploadResult)["id"];
Debug.WriteLine("Uploaded");
Debug.WriteLine("Video ID: " + videoId);
// obtain video access token
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
var videoTokenRequestResult = client.GetAsync($"{apiUrl}/auth/{location}/Accounts/{accountId}/Videos/{videoId}/AccessToken?allowEdit=true").Result;
var videoAccessToken = videoTokenRequestResult.Content.ReadAsStringAsync().Result.Replace("\"", "");
client.DefaultRequestHeaders.Remove("Ocp-Apim-Subscription-Key");
// wait for the video index to finish
while (true)
{
Thread.Sleep(10000);
var videoGetIndexRequestResult = client.GetAsync($"{apiUrl}/{location}/Accounts/{accountId}/Videos/{videoId}/Index?accessToken={videoAccessToken}&language=English").Result;
var videoGetIndexResult = videoGetIndexRequestResult.Content.ReadAsStringAsync().Result;
var processingState = JsonConvert.DeserializeObject<dynamic>(videoGetIndexResult)["state"];
Debug.WriteLine("");
Debug.WriteLine("State:");
Debug.WriteLine(processingState);
// job is finished
if (processingState != "Uploaded" && processingState != "Processing")
{
Debug.WriteLine("");
Debug.WriteLine("Full JSON:");
Debug.WriteLine(videoGetIndexResult);
break;
}
}
// search for the video
var searchRequestResult = client.GetAsync($"{apiUrl}/{location}/Accounts/{accountId}/Videos/Search?accessToken={accountAccessToken}&id={videoId}").Result;
var searchResult = searchRequestResult.Content.ReadAsStringAsync().Result;
Debug.WriteLine("");
Debug.WriteLine("Search:");
Debug.WriteLine(searchResult);
// get insights widget url
var insightsWidgetRequestResult = client.GetAsync($"{apiUrl}/{location}/Accounts/{accountId}/Videos/{videoId}/InsightsWidget?accessToken={videoAccessToken}&widgetType=Keywords&allowEdit=true").Result;
var insightsWidgetLink = insightsWidgetRequestResult.Headers.Location;
Debug.WriteLine("Insights Widget url:");
Debug.WriteLine(insightsWidgetLink);
// get player widget url
var playerWidgetRequestResult = client.GetAsync($"{apiUrl}/{location}/Accounts/{accountId}/Videos/{videoId}/PlayerWidget?accessToken={videoAccessToken}").Result;
var playerWidgetLink = playerWidgetRequestResult.Headers.Location;
Debug.WriteLine("");
Debug.WriteLine("Player Widget url:");
Debug.WriteLine(playerWidgetLink);
}
static void Main()
{
RecognizeSpeechAsync().Wait();
SynthesisToSpeakerAsync().Wait();
SynthesisToVideoAsync().Wait();
Console.WriteLine("Please press a key to continue.");
Console.ReadLine();
}
}
}

Sharepoint 2010 Web Part Communication - How to make consumer wait for the provider

I have a series of web parts I need to implement in SharePoint 2010. The data provider web part uses an UpdatePanel and asynchronously makes a web service call which can potentially be slow. To keep it simple, I've put a single consumer web part on the page (Chart) which will use the consumer as its data provider.
My problem is that I can't get the consumer to wait for the provider - I get a variety of errors but all basically come back to "There is no data available". This may be because it is a Chart web part but the question also applies to the other custom parts I will be developing as they will pull the same data.
The question is: how do I either push data to my consumers when my provider is ready or somehow let them wait for my provider to have data (via polling or whatever).
Note: this is just a prototype, I haven't added error handling, etc yet.
Code is below:
[ToolboxItem(true)]
public partial class ClarityProjectGeneral : System.Web.UI.WebControls.WebParts.WebPart , IWebPartTable
{
public DataTable ProjectVitals = new DataTable(); For web part communication
// bunch of properties
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
InitializeControl();
// For web part communication
// Initialize our datatable so the chart doesn't barf
DataColumn col = new DataColumn();
col.DataType = typeof(string);
col.ColumnName = "Name";
this.ProjectVitals.Columns.Add(col);
col = new DataColumn();
col.DataType = typeof(DateTime);
col.ColumnName = "Start";
this.ProjectVitals.Columns.Add(col);
col = new DataColumn();
col.DataType = typeof(DateTime);
col.ColumnName = "End";
this.ProjectVitals.Columns.Add(col);
}
protected void Page_Load(object sender, EventArgs e)
{
loading.Visible = true;
content.Visible = false;
}
public ClarityObjectClasses.Projects GetProject(string projectID)
{
Clarity.ClarityAbstractorProject ca = new Clarity.ClarityAbstractorProject(this.Username, this.Password);
Dictionary<string, string> queryParams = new Dictionary<string, string>();
queryParams.Add("projectID", projectID);
// Class for making web service call
ClarityObjectClasses.Projects response = new ClarityObjectClasses.Projects();
response = ca.GetProject(queryParams);
return response;
}
protected void Timer1_Tick(object sender, EventArgs e)
{
if (this.ProjectID == null || this.Username == null || this.Password == null)
{
lblConfigError.Visible = true;
lblConfigError.Text = "One or more required configuration values are not set. Please check the web part configuration.";
panelProjectDetails.Visible = false;
}
else
{
loading.Visible = true;
content.Visible = false;
panelProjectDetails.Visible = true;
ClarityObjectClasses.Projects projects = GetProject(this.ProjectID);
//Assign a bunch of values
// For web part communication
LoadTable(projects.Project[0]);
Timer1.Enabled = false;
loading.Visible = false;
content.Visible = true;
}
}
/* Interface functions for Graph Chart communication */
For web part communication
protected void LoadTable(ClarityObjectClasses.Project project)
{
DataRow row = ProjectVitals.NewRow();
row["Name"] = project.name;
row["Start"] = project.start;
row["End"] = project.finish;
this.ProjectVitals.Rows.Add(row);
}
public PropertyDescriptorCollection Schema
{
get
{
return TypeDescriptor.GetProperties(ProjectVitals.DefaultView[0]);
}
}
public void GetTableData(TableCallback callback)
{
callback(ProjectVitals.Rows);
}
public bool ConnectionPointEnabled
{
get
{
object o = ViewState["ConnectionPointEnabled"];
return (o != null) ? (bool)o : true;
}
set
{
ViewState["ConnectionPointEnabled"] = value;
}
}
[ConnectionProvider("Table", typeof(TableProviderConnectionPoint), AllowsMultipleConnections = true)]
public IWebPartTable GetConnectionInterface()
{
return this;
}
public class TableProviderConnectionPoint : ProviderConnectionPoint
{
public TableProviderConnectionPoint(MethodInfo callbackMethod, Type interfaceType, Type controlType, string name, string id, bool allowsMultipleConnections)
: base(callbackMethod, interfaceType, controlType, name, id, allowsMultipleConnections)
{
}
public override bool GetEnabled(Control control)
{
return ((ClarityProjectGeneral)control).ConnectionPointEnabled;
}
}
}
Do not quite understand, but if it helps
You may not use "connectable" web-parts inside UpdatePanel,
because of lack of corresponding events to bind data on asynchronous callback.
I just stumbled across this. I had exactly the same problem trying to implement a custom webpart just as a proof to myself. I applied filters to both my webpart and a list, and then let a chart consume them. What I found was that my webpart sent the wrong data, but the list webpart worked as expected.
So I reflected the XsltListViewWebPart (or whatever it's exact name is) and I discovered that there is an IConnectionData interface. This allows you to specify the dependencies and get the correct delay binding you need. GetRequiresData indicates that there are still more connections to be consumed before the data can be requested.