I have a Signaler client in worker service. developed Signaler server in .net core web API.
Here everything is working but when I am calling.
await _hub.Clients.All.SendAsync("calldscclient", desUserList.FirstOrDefault().DscConnectionId);
From the server, it's not sending a message to the client
connection.On<string>("calldscclient", (message) => {}
I am writing my client code here.
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using WorkerService1.Properties;
namespace WorkerService1
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
public HubConnection connection;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
//while (!stoppingToken.IsCancellationRequested)
{
using (StreamWriter writer = new
StreamWriter(#"C:\Users\devma\source\repos\WorkerService1\WorkerService1\TextFile1.txt"))
{
writer.WriteLine("bigin");
}
var ip = GetLocalIPAddress();
ip = "localhost";
connection = new HubConnectionBuilder()
//.WithUrl(new Uri("https://localhost:5001/dschub"))
//.WithAutomaticReconnect(new RandomRetryPolicy())
//.Build();
.WithUrl("https://localhost:44339/dschub"
,
options =>
{
options.WebSocketConfiguration = conf =>
{
conf.RemoteCertificateValidationCallback = (message, cert, chain, errors) =>
{ return true; };
};
options.HttpMessageHandlerFactory = factory => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{ return true; }
};
}
)
.Build();
await connection.StartAsync();
await connection.InvokeAsync("dscRegister", ip);
connection.On<string>("calldscclient", (message) =>
{
using (StreamWriter writer = new
StreamWriter(#"C:\Users\devma\source\repos\WorkerService1\WorkerService1\TextFile1.txt"))
{
writer.WriteLine("Monica Rathbun");
}
});
}
}
public static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
return ip.ToString();
}
}
throw new Exception("No network adapters with an IPv4 address in the system!");
}
}
}
For calling
connection.On<string>("calldscclient", (message) => {}
I am using web API on the server.
My WebAPI code is here.
[HttpGet("servedsc")]
public async Task<ActionResult> ServeDsc()
{
var remoteIpAddress = HttpContext.Request.Host.Host;
var desUserList = await _dscService.GetDscUser(remoteIpAddress);
await _hub.Clients.All.SendAsync("calldscclient",
desUserList.FirstOrDefault().DscConnectionId);
//await
_hub.Clients.User(desUserList.FirstOrDefault().DscConnectionId).SendAsync("calldscclient");
return Ok(desUserList);
}
Here everything is working fine like Signaler Hub is executing property when the client making a connection.
But the problem is when the server is sending messages to the client it's not getting.
You should generally register your .On method handlers before starting the connection. In your case, you're awaiting InvokeAsync which will invoke the server method and wait for the server method to finish. Then you are registering your .On method which will be too late at that point.
Related
I am trying to build a test app so that my HTML page can send a message to an long running method on an Azure function via SignalR, and the long running function can use SignalR to report progress back to the HTML page.
Does anyone know how to do this? I am using .NET Core 3.1 for the function and the Azure SignalR service in serverless mode. I have looked around on the web but it all seems to be about chat whereas I would have thought this is quite a common requirement.
This is what I have so far...
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace AzureSignalRFunctionTest
{
public static class Function1
{
// Input Binding
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req,
[SignalRConnectionInfo(HubName = "TestHub")] SignalRConnectionInfo connectionInfo)
{
return connectionInfo;
}
// Trigger Binding
[FunctionName("longrunningtask")]
public static void LongRunningTask([SignalRTrigger("longrunningtask", "messages", "SendMessage")] InvocationContext invocationContext, [SignalRParameter] string message, ILogger logger)
{
logger.LogInformation($"Receive {message} from {invocationContext.ConnectionId}.");
// what to put here?
//var clients = invocationContext.GetClientsAsync().Result;
//ReportProgress(invocationContext.ConnectionId, "Progress", ...);
// Simulate Long running task
System.Threading.Thread.Sleep(30000);
// ReportProgress etc
}
// Output Binding
[FunctionName("reportprogress")]
public static Task ReportProgress(string connectionId, string message,
[SignalR(HubName = "TestHub")] IAsyncCollector<SignalRMessage> signalRMessages)
{
return signalRMessages.AddAsync(
new SignalRMessage
{
ConnectionId = connectionId,
Target = "reportProgress",
Arguments = new[] { message }
});
}
}
}
The answer was to do this:
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace MyAzureFunction
{
public class TestHub : ServerlessHub
{
// Input Binding
[FunctionName("negotiate")]
public SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req,
[SignalRConnectionInfo(HubName="testhub")]SignalRConnectionInfo connectionInfo, ILogger logger)
{
logger.LogInformation($"ConnectionInfo: {connectionInfo.Url} {connectionInfo.AccessToken} {req.Path} {req.Query}");
return connectionInfo;
}
// Trigger Binding
[FunctionName("longrunningtask")]
public async Task LongRunningTask(
[SignalRTrigger] InvocationContext invocationContext,
string message, ILogger logger)
{
logger.LogInformation($"Receive {message} from {invocationContext.ConnectionId}.");
await Clients.Client(invocationContext.ConnectionId).SendAsync("reportProgress", message + " has started.");
System.Threading.Thread.Sleep(10000);
await Clients.Client(invocationContext.ConnectionId).SendAsync("reportProgress", message + " has ended.");
}
}
}
The Html looked like this:
<h2>SignalR</h2>
<div id="messages"></div>
<form>
<button type="submit" id="submitbtn">Submit</button>
</form>
#section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.19/signalr.min.js"></script>
<script>
let messages = document.querySelector('#messages');
const apiBaseUrl = 'https://myazurefunction.azurewebsites.net';
const connection = new signalR.HubConnectionBuilder()
.withUrl(apiBaseUrl + '/api')
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('reportProgress', (message) => {
document.getElementById("messages").innerHTML = message;
});
function onConnected(connection) {
document.getElementById('submitbtn').addEventListener('click', function (event) {
connection.send('longrunningtask', 'this is my message');
event.preventDefault();
});
}
connection.start()
.then(function () {
onConnected(connection)
})
.catch(console.error);
</script>
}
If you move away from the calling page, and return while the function app is running, it does not show the end result. If this is a requirement rather than just a simple progress bar, then I think the answer is to authentication via a user id, and then send messages to that user id.
I have created an HTTP trigger azure function, which holds the code (below) to upload a video to YouTube automatically. Source: (https://developers.google.com/youtube/v3/docs/videos/insert).
When I try to run the app locally using visual studio, I am getting the following error:
Executed 'Function1' (Failed, Id=d601d64a-2f2c-4f8a-8053-a2f33ca21dbc)
System.Private.CoreLib: Exception while executing function: Function1.
Google.Apis.Auth: At least one client secrets (Installed or Web)
should be set
It looks like a Google Authentication error, but I am unsure as to how to get this fixed and I see that YouTube API does not support Service account? How can this issue be fixed, is there a get around to this? Thanks in advance.
C# Code:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3.Data;
using System.Reflection;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using System.Threading;
namespace UploadVideo
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
log.LogInformation("YouTube Data API: Upload Video");
log.LogInformation("==============================");
try
{
await Run();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
log.LogInformation("Error: " + e.Message);
}
}
return new OkObjectResult($"Video Processed..");
}
private static async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"C:\Users\Peter\Desktop\audio\test.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
private static void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
private static void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}
It looks like you were trying to use the service account to do the OAuth2 web server flow, which wont work. The correct code form creating a service account credential is as follows.
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
Note
as i have mentioned in your other questions the YouTube API does NOT support service account authentication. You must use Oauth2 and i am not convinced this can be done inside of azure functions. As there is no way to spawn the web browser window to request authorization of the user.
I have a netcore console app which is accessing the Azure's Text analysis API's using the Client library from the Microsoft.Azure.CognitiveServices.Language.TextAnalytics Nuget package.
When trying to access the API, I receive the following HttpException:
Unauthorized. Access token is missing, invalid, audience is incorrect (https://cognitiveservices.azure.com), or have expired.
Unhandled exception. System.AggregateException: One or more errors occurred. (Operation returned an invalid status code 'Unauthorized')
When accessing the same API using exactly the same code which is hosted on Azure Functions - everything works as expected. I was unable to find any info in the docs or anywhere else.
Try the .net core console app code below to use TextAnalytics SDK :
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;
namespace TextAnalysis
{
class Program
{
private static readonly string key = "<your text analyisis service key>";
private static readonly string endpoint = "<your text analyisis service endpoint>";
static void Main(string[] args)
{
var client = authenticateClient();
sentimentAnalysisExample(client);
languageDetectionExample(client);
entityRecognitionExample(client);
keyPhraseExtractionExample(client);
Console.Write("Press any key to exit.");
Console.ReadKey();
}
static TextAnalyticsClient authenticateClient()
{
ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
TextAnalyticsClient client = new TextAnalyticsClient(credentials)
{
Endpoint = endpoint
};
return client;
}
class ApiKeyServiceClientCredentials : ServiceClientCredentials
{
private readonly string apiKey;
public ApiKeyServiceClientCredentials(string apiKey)
{
this.apiKey = apiKey;
}
public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
request.Headers.Add("Ocp-Apim-Subscription-Key", this.apiKey);
return base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
static void sentimentAnalysisExample(ITextAnalyticsClient client)
{
var result = client.Sentiment("I had the best day of my life.", "en");
Console.WriteLine($"Sentiment Score: {result.Score:0.00}");
}
static void languageDetectionExample(ITextAnalyticsClient client)
{
var result = client.DetectLanguage("This is a document written in English.","us");
Console.WriteLine($"Language: {result.DetectedLanguages[0].Name}");
}
static void entityRecognitionExample(ITextAnalyticsClient client)
{
var result = client.Entities("Microsoft was founded by Bill Gates and Paul Allen on April 4, 1975, to develop and sell BASIC interpreters for the Altair 8800.");
Console.WriteLine("Entities:");
foreach (var entity in result.Entities)
{
Console.WriteLine($"\tName: {entity.Name},\tType: {entity.Type ?? "N/A"},\tSub-Type: {entity.SubType ?? "N/A"}");
foreach (var match in entity.Matches)
{
Console.WriteLine($"\t\tOffset: {match.Offset},\tLength: {match.Length},\tScore: {match.EntityTypeScore:F3}");
}
}
}
static void keyPhraseExtractionExample(TextAnalyticsClient client)
{
var result = client.KeyPhrases("My cat might need to see a veterinarian.");
// Printing key phrases
Console.WriteLine("Key phrases:");
foreach (string keyphrase in result.KeyPhrases)
{
Console.WriteLine($"\t{keyphrase}");
}
}
}
}
You can find your key and endpoint here on Azure portal :
Result :
I'm literally going crazy trying to fix this but nothing I do seems to make a difference. When I navigate to localhost:3978/api/callback it throws
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:3978/api/callback'.
</Message>
<MessageDetail>
No action was found on the controller 'Callback' that matches the request.
</MessageDetail>
</Error>
This is the controller in my Controllers/CallbackController.cs
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Autofac;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace VSTF_RD_Bot.Controllers
{
public class CallbackController : ApiController
{
[HttpGet]
[Route("api/Callback")]
public async Task<HttpResponseMessage> Callback([FromUri] string state, [FromUri] string code)
{
//parse out the userId and convoId from states parameter
string[] states = state.Split(new[] { "," }, StringSplitOptions.None);
string userId = states[0];
string conversationId = states[1];
// Check if the bot is running against emulator
var connectorType = HttpContext.Current.Request.IsLocal ? ConnectorType.Emulator : ConnectorType.Cloud;
// Exchange the Facebook Auth code with Access toekn
var token = await AdHelpers.ExchangeCodeForAccessToken(userId, conversationId, code, "redirect_uri");
// Create the message that is send to conversation to resume the login flow
var msg = new Message
{
Text = $"token:{token}",
From = new ChannelAccount { Id = userId },
To = new ChannelAccount { Id = Constants.botId },
ConversationId = conversationId
};
var reply = await Conversation.ResumeAsync(Constants.botId, userId, conversationId, msg, connectorType: connectorType);
// Remove the pending message because login flow is complete
IBotData dataBag = new JObjectBotData(reply);
PendingMessage pending;
if (dataBag.PerUserInConversationData.TryGetValue("pendingMessage", out pending))
{
dataBag.PerUserInConversationData.RemoveValue("pendingMessage");
var pendingMessage = pending.GetMessage();
reply.To = pendingMessage.From;
reply.From = pendingMessage.To;
// Send the login success asynchronously to user
var client = Conversation.ResumeContainer.Resolve<IConnectorClient>(TypedParameter.From(connectorType));
await client.Messages.SendMessageAsync(reply);
return Request.CreateResponse("You are now logged in! Continue talking to the bot.");
}
else
{
// Callback is called with no pending message as a result the login flow cannot be resumed.
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, new InvalidOperationException("Cannot resume!"));
}
}
}
}
What am I missing here?
This is my webApiconfig.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace VSTF_RD_Bot
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Json settings
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Make your controller
[HttpGet]
[Route("api/Callback/{state}/{code}")]
public async Task<HttpResponseMessage> Callback(string state, string code)
{
and request url
"localhost:3978/api/Callback/samplestate/samplecode"
I want to send data to my console application wich have a connection to my "someHub". I tried to do as described in example from a link but got no result.
Server side code:
[HubName("somehub")]
public class SomeHub : Hub
{
public override Task OnConnected()
{
//Here I want to send "hello" on my sonsole application
Clients.Caller.sendSomeData("hello");
return base.OnConnected();
}
}
Clien side code:
public class Provider
{
protected HubConnection Connection;
private IHubProxy _someHub;
public Provider()
{
Connection = new HubConnection("http://localhost:4702/");
_someHub = Connection.CreateHubProxy("somehub");
Init();
}
private void Init()
{
_someHub.On<string>("sendSomeData", s =>
{
//This code is not reachable
Console.WriteLine("Some data from server({0})", s);
});
Connection.Start().Wait();
}
}
What is the best solution for implementing this and what is the reason why i am not able to invoke the client method?
Are you trying to talk to clients outside of Hub? If yes then you will have to get a HubContext outside of Hub. And then you can talk all the clients.
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
SignalR Server using Owin Self Host
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8081/";
using (WebApplication.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(3000);
context.Clients.All.addMessage("Current integer value : " + i.ToString());
}
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
// Turn cross domain on
var config = new HubConfiguration { EnableCrossDomain = true };
config.EnableJavaScriptProxies = true;
// This will map out to http://localhost:8081/signalr by default
app.MapHubs(config);
}
}
[HubName("MyHub")]
public class MyHub : Hub
{
public void Chatter(string message)
{
Clients.All.addMessage(message);
}
}
Signalr Client Console Application consuming Signalr Hubs.
class Program
{
static void Main(string[] args)
{
var connection = new HubConnection("http://localhost:8081/");
var myHub = connection.CreateHubProxy("MyHub");
connection.Start().Wait();
// Static type
myHub.On<string>("addMessage", myString =>
{
Console.WriteLine("This is client getting messages from server :{0}", myString);
});
myHub.Invoke("Chatter",System.DateTime.Now.ToString()).Wait();
Console.Read();
}
}
To run this code, create two separate applications, then first run server application and then client console application, then just hit key on server console and it will start sending messages to the client.