SignalR C# Client Hubconnection.On not fired - c#

I have a ASPNet.Core WebApi, with signalR. I have angular app, that consumes the webAPI, and I want to replace it with a Blazor Webassembly app. I have a problem with signalR in the Blazor app.
I create a hubconnection, set it up, and when the server sends data, the Hubconnection.On method is not invoked. Here's my code:
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:45299/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.AccessTokenProvider = () => Task.FromResult(token);
cfg.Transports = HttpTransportType.WebSockets;
})
.Build();
_hubConnection.On<IEnumerable<AccountResponse>>("accountschanged", (accounts) =>
{
foreach(var account in accounts)
{
Console.WriteLine(account.Name);
}
});
await _hubConnection.StartAsync();
}
In the network tab, I see that the connection is ok, I receive new data, but the method in hubconnection.On doesn't get fired. I double checked the method name, and it is the same. In the angular app it works fine and as data gets send from the server, I don't there's any problem with the server code.
I use Fluxor for state management, and I fire an action in the 'On' method, I just replaced is with a single Console.WriteLine for simplicity.
Edit: Added server code, and the message received
Here's the server code, 'AccountsChanged' is called when an account is changed:
public class AccountHub : Hub, IAccountHub
{
private readonly IHubContext<AccountHub> _accHub;
private readonly IAggregateMapper _mapper;
public AccountHub(IHubContext<AccountHub> accHub, IAggregateMapper mapper)
{
_accHub = accHub;
_mapper = mapper;
}
public async Task AccountsChanged(Guid userId, IEnumerable<Account> accounts)
{
var mapped = _mapper.MapAll<Account, AccountResponse>(accounts);
await _accHub.Clients.User(userId.ToString()).SendAsync("accountschanged", mapped);
}
}
And here's the message I receive (I make a request from Postman), copied from the network tab (I removed additional properties of accounts to keep it simple):
{
"type":1,
"target":"accountschanged",
"arguments":[
[
{
"id":1,
"name":"bank account 1"
},
{
"id":2,
"name":"wallet 1"
}
]
]
}

I finally found the problem. It was about serializing the received json message. I had to add .AddJsonProtocol(), and set it up, here is the final code:
_hubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:59225/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.Transports = HttpTransportType.WebSockets;
cfg.AccessTokenProvider = () => Task.FromResult(token);
})
.AddJsonProtocol(cfg =>
{
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
jsonOptions.Converters.Add(new JsonStringEnumConverter());
cfg.PayloadSerializerOptions = jsonOptions;
})
.Build();
I find it strange that I didn't get any error message btw.

Related

MQTTNet: How to change value in topic for active client?

I have a device that has a built-in mqtt client that subscribes to a broker server and displays topic 0 or 1;
0 - disabled;
1 - enabled;
MQTT Broker (ASP.NET WEB API) .Net 6
BUILDER
var optionBuilder = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(5000))
.Build();
builder.Services
.AddHostedMqttServer(optionBuilder)
.AddMqttConnectionHandler()
.AddConnections()
.AddMqttTcpServerAdapter();
builder.Services.AddMqttConnectionHandler();
builder.Services.AddMqttWebSocketServerAdapter();
APP
app.UseMqttServer(server =>
{
});
After connecting the device to the server, I want to see the status of this client on the server and send a parameter to change the topic attribute.
In version 3.. - I used the IMqttServer interface
private readonly IMqttServer _mqttServer;
public MqttBrokerService(IMqttServer mqttServer)
{
_mqttServer = mqttServer ?? throw new ArgumentNullException(nameof(mqttServer));
}
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
{
return _mqttServer.GetClientStatusAsync();
}
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
{
return _mqttServer.GetSessionStatusAsync();
}
public Task ClearRetainedApplicationMessagesAsync()
{
return _mqttServer.ClearRetainedApplicationMessagesAsync();
}
public Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync()
{
return _mqttServer.GetRetainedApplicationMessagesAsync();
}
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
return _mqttServer.PublishAsync(applicationMessage);
}
But in version 4.. - this interface was removed and now I don't understand how I can build messages for the client and get detailed statistics.
there is MQTTnet.Extensions.ManagedClient, but I still could not connect to the active session of my client.
var options = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(new MqttClientOptionsBuilder()
.WithClientId("Client1")
.WithTcpServer("192.168.1.1")
.WithTls().Build())
.Build();
var mqttClient = new MqttFactory().CreateManagedMqttClient();
await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("my/topic").Build());
await mqttClient.StartAsync(options);
I will be very grateful for your help

How to disable ASP.NET Core activities but continue endpoints tracing?

In our application we set our traceId. When ASP.NET Core creates its Activity it has no children and will never have. And its Activity always stands alone in Jaeger because of different traceId. We want to somehow remove this Activities.
Firstly, I had this code:
builder.Services.AddOpenTelemetryTracing(b =>
{
b
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName: TracingConstants.ApplicationName, serviceVersion: TracingConstants.ApplicationVersion))
.AddSource(TracingConstants.EndpointsSourceName)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddJaegerExporter()
;
});
It creates ASP.NET Core Activities that i want to remove.
My first try was to remove .AddAspNetCoreInstrumentation(). ASP.NET core Activities disappeared but also disappeared Activities of endpoints. I thought that AlwaysOnSampler can help but it did not.
Next try was to implement Filter in .AddAspNetCoreInstrumentation():
builder.Services.AddOpenTelemetryTracing(b =>
{
b
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName: TracingConstants.ApplicationName, serviceVersion: TracingConstants.ApplicationVersion))
.AddSource(TracingConstants.EndpointsSourceName)
.AddAspNetCoreInstrumentation(o =>
{
o.Filter = context =>
{
return context.Request.Path != "/endpoint";
};
})
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddJaegerExporter()
;
});
But it had same result as just remove .AddAspNetCoreInstrumentation().
Now I don't know what to try. Please, help.
Sample code that reproduce my problem:
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetryTracing(b =>
{
b
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName: TracingConstants.ApplicationName, serviceVersion: TracingConstants.ApplicationVersion))
.AddSource(TracingConstants.EndpointsSourceName)
.AddAspNetCoreInstrumentation(o =>
{
o.Filter = context =>
{
return context.Request.Path != "/hello";
};
})
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddJaegerExporter()
;
});
var app = builder.Build();
var myActivitySource = new ActivitySource(TracingConstants.EndpointsSourceName);
app.MapGet("/hello", () =>
{
using var activity = myActivitySource.StartActivity("SayHello");
activity?.SetTag("hello", "world");
return $"Hello, World!";
});
await app.RunAsync();
public static class TracingConstants
{
public const string ApplicationName = "SampleCode";
public const string ApplicationVersion = "1.0.0";
public const string EndpointsSourceName = "Endpoints";
}
UPDATE 26.05.2022
I have found out that AlwaysOnSampler can help but I have to set it with .SetSampler(new AlwaysOnSampler()) instead of .SetSampler<AlwaysOnSampler>() as I did in one of my previous tries.
Here is code that turn off ASP.NET Core Activities but continue endpoint tracing:
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetryTracing(b =>
{
b
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName: TracingConstants.ApplicationName, serviceVersion: TracingConstants.ApplicationVersion))
.AddSource(TracingConstants.EndpointsSourceName)
// Because of default OpenTelemetry Sampler is ParentBased(root=AlwaysOn)
// and we turned off ASP.NET Core Activities (parent Activities
// will not be recorded, so will ours by default Sampler),
// we need AlwaysOnSampler to record our Activities anyway
.SetSampler(new AlwaysOnSampler())
.AddAspNetCoreInstrumentation(o =>
{
o.Filter = context =>
{
return context.Request.Path != "/hello";
};
})
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddJaegerExporter()
;
});
var app = builder.Build();
var myActivitySource = new ActivitySource(TracingConstants.EndpointsSourceName);
app.MapGet("/hello", () =>
{
using var activity = myActivitySource.StartActivity("SayHello");
activity?.SetTag("hello", "world");
return $"Hello, World!";
});
await app.RunAsync();
public static class TracingConstants
{
public const string ApplicationName = "SampleCode";
public const string ApplicationVersion = "1.0.0";
public const string EndpointsSourceName = "Endpoints";
}
Why different overloads of SetSampler works differently??
By the way, approach implemented in code above have issues. Because ASP.NET Cores Activity will not be recorded and exported, created by my endpoint Activities will have invalid parentSpanId and Jaeger will show warning. Also I think that trace will be interrupted because one Activity (ASP.NET Cores Activity) is always lost.

Enable/ disable a button in Client when an event on server is finished

I am developing a .Net core project with Angular on Frontend and c# on backend. I am new to web development and i am looking for ideas or little help on achieving a task.
I am connecting my Angular frontend to an External server using .net web server. I am able to post data to External server successfully using Http services. After i receive response from external server the results are queued in my controller. Before i send response to client i want to enable a download button on Client, only if i receive response from the External server and the results are queued in my controller successfully. If there is no response from External server I do not want to enable download button.
Appcomponent.html
<button mat-button id="getdoc" (click) = "getdoc()" [disabled] = "disabled" >Download</button>
Appcomponent.ts
getdoc() {
this.download.downloadDoc()
.subscribe(res => {
this.datadownload = new Blob([res], { type: 'application/txt' });
// { this.disabled = false };
saveAs(this.datadownload);
console.log(this.datadownload);
}, error => {
console.log('error while downloading', error)
})
}
Appservice.ts
export class DownloadService {
constructor(private http: HttpClient) { }
downloadDoc(): Observable<any> {
return this.http.get('URL', {/* headers, */ reportProgress: true, responseType : "blob"});
}
}
Controller
namespace ang.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class DoController : Controller
{
private void OnProvideData(DataAndHeader dat)
{
CommHeader header = (CommHeader)dat.Header;
switch (header.Type)
{
case CommHelpers.CommType.ServerToClientReceived:
break;
case CommHelpers.CommType.ServerToClientFinished:
string ClientID = header.ClientID;
ConcurrentQueue<DataAndHeader> queueResultsForClient = null;
if (!dicResults.TryGetValue(ClientID, out queueResultsForClient))
{
queueResultsForClient = new ConcurrentQueue<DataAndHeader>();
if (!dicResults.TryAdd(ClientID, queueResultsForClient))
{
}
}
queueResultsForClient.Enqueue(dat);
break;
}
}
[HttpGet]
// [Route("download")]
public byte[] download(string ClientID)
{
// some logic
return new byte[0];
}
}
}
I want to wait till i get the Response from the External Server and loaded into my Controller after that i want to enable the button. After the button is enabled and clicked by Client he should be able to download file.
The "getdoc()" works only when i click download button. but initially the download button is in disable state. I want to programatically enable it from my controller not from my Client
Thanks in Advance.
Try the following code:
getdoc() {
this.download.downloadDoc()
.subscribe(res => {
this.datadownload = new Blob([res], { type: 'application/txt' });
saveAs(this.datadownload);
console.log(this.datadownload);
this.disabled = false; // This is the change
}, error => {
this.disabled = true;
console.log('error while downloading', error)
})
}

Angular 2 Signalr

So I have a C# WebApi Signalr webservice with a Angular 2 Signalr Client.
The WebService is supposed to update the client when a new message comes in from a post method that other services may call.
[HttpPost]
public void NotificationMessage([FromBody]ServiceInformation serviceInformation)
{
Messages.notificationMessage = serviceInformation;
notificationMessage.BroadCastNotificationMessage();
}
Another service posts to this method and this method sets a static variable in the Messages class and then calls the NotificationMessage Hub.
public class NotificationMessage : Hub<IClient>
{
public void BroadCastNotificationMessage()
{
Clients.All.serviceInfoBroadCast(JsonConvert.SerializeObject(Messages.notificationMessage));
}
}
The notification hub then calls Clients.All to broadcast the new message to all clients.
Angular Site:
Service:
constructor() {
this.connectionEstablished = new EventEmitter<Boolean>();
this.messageReceived = new EventEmitter<ServiceInformationObject>();
this.connectionExists = false;
this.connection = $.hubConnection(CONFIGURATION.baseUrls.server);
this.proxy = this.connection.createHubProxy(this.proxyName);
this.registerOnServerEvents();
this.startConnection();
}
private startConnection(): void {
this.connection.start({ jsonp: true, waitForPageLoad: false}).done((data: any) => {
console.log('Now connected ' + data.transport.name + ', connection ID= ' + data.id);
this.connectionEstablished.emit(true);
this.connectionExists = true;
//this.proxy.invoke('BroadCastNotificationMessage');
}).fail((error: any) => {
console.log('Could not connect ' + error);
this.connectionEstablished.emit(false);
});
}
private registerOnServerEvents(): void {
this.proxy.on('serviceInfoBroadCast', (data: string) => {
console.log('received in SignalRService: ' + JSON.stringify(data));
let jsonData = JSON.parse(data);
let newData = new ServiceInformationObject(jsonData.ServiceName, jsonData.Message, jsonData.State, jsonData.MachineName, Date.now());
this.messageReceived.emit(newData);
});
}
I setup the hub connection followed by the hub proxy. I call the proxy.on method connecting to the Clients.All dynamic method from the web service. Then I start the connection.
Component to display data on view:
private subscribeToEvents(): void {
this._signalRService.connectionEstablished.subscribe(() => {
this.canSendMessage = true;
});
this._signalRService.messageReceived.subscribe((message: ServiceInformationObject) => {
this._ngZone.run(() => {
this.testArray.push(message);
});
});
}
The issue:
If I leave the invoke call in the startConnection method, it will pull data down from the webservice, but it will never update ever again.
If I don't use the invoke nothing happens.
I'm not sure why the webservice is not pushing the information when Clients.All is called. I know the post method is being called from my own interal logging and I know the object is not empty.
Any ideas on why the webservice is not pushing the information? Or why the client is not displaying it?

Using Cognito Federated Identities from Xamarin

I guess my question, Understanding Cognito Identities, wasn't specific enough. I still can't figure out how to use a federated identity from a Xamarin app. Here's what I'm trying, but it's really quite random because I can't find any sample code for this task out there. I tried putting a breakpoint on the AddLogin line, and it never gets hit, even though breakpoint two lines up does get hit. There are too many new-to-me technologies in this code for me to know where to begin on tracking down the problem. (I x'd out the Identity pool ID in the code below, but a real one is there.) At this point I'm just trying to get evidence that I can uniquely identify/validate an Amazon account, and maybe add it to my user pool. But I can't even get the code to entirely execute or report an error.
Login().ContinueWith(t => { if (t.Exception != null)
Toast.MakeText(ApplicationContext, t.Exception.ToString(), ToastLength.Long).Show(); });
public async Task Login()
{
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"us-east-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
var client = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(credentials);
var request = new Amazon.SecurityToken.Model.GetFederationTokenRequest("myamazonid#gmail.com");
var response = await client.GetFederationTokenAsync(request);
credentials.AddLogin("www.amazon.com", response.Credentials.SessionToken);
}
It took a good deal of searching, but I think I figured it out. Setting up the services and getting the client ID is not too hard (is well documented) compared to working out the code, so this answer will focus on the code. Google is particularly tricky because of changes made to their OAuth implementation that prevents some forms of authentication from working. In order for Google identities to work with Cognito, APIs need to be up-to-date. Use NuGet to reference the following API versions or later:
Xamarin.Auth 1.5.0.3
Xamarin.Android.Support.v4 25.4.0.2
Xamarin.Android.Support.CustomTabs 25.4.0.2
AWSSDK.CognitoIdentity 3.3.2.14
AWSSDK.Core 3.3.17.8
Validation 2.4.15
Xamarin.Android.Support.Annotations 25.4.0.2
This code is in the main activity:
protected override void OnCreate(Bundle savedInstanceState)
{
// (etc)
credentials = new CognitoAWSCredentials(
"us-east-2:00000000-0000-0000-0000-000000000000", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
// (etc)
}
private void ShowMessage(string message)
{
AlertDialog dlgAlert = new AlertDialog.Builder(this).Create();
dlgAlert.SetMessage(message);
dlgAlert.SetButton("Close", (s, args) => { dlgAlert.Dismiss(); });
dlgAlert.Show();
}
public void Logout()
{
credentials.Clear();
}
public void Login()
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
ShowMessage(string.Format("I still remember you're {0} ", credentials.GetIdentityId()));
bDidLogin = true;
return;
}
bDidLogin = true;
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.mynamespace.myapp:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var http = new System.Net.Http.HttpClient();
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:00000000-0000-0000-0000-000000000000";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
else
ShowMessage(task.Exception.InnerException!=null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
Then there's another activity to handle the callback from the redirect URL in the Google authentication process:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.mynamespace.myapp" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}

Categories

Resources