subscribing to Blazor AuthenticationStateChanged - blazor-webassembly

I could not find any example of how to use the AuthenticationStateChanged in blazor.
My intention is that any page where i want to react to user login or logout i will use these
code. I could not find any example on how to implement the event. the one that i tried just keeps on firing for infinite times.
_CustomAuthProvider.AuthenticationStateChanged += AuhtenticationStateChanged;
private async void AuhtenticationStateChanged(Task<AuthenticationState> task)
{
//This keeps on executing in in loop.
}

I know this is old, but I would have liked an answer when I found it...
This is the code I use on a Blazor web assembly (dotnet 6.0). This is part of a scoped service that I can access through dependency injection from anywhere else in my application.
Notice the await(task) to retrieve the state in the event handler:
public AuthenticationService(AuthenticationStateProvider authenticationProvider, IProfileService profileService)
{
_profileService = profileService;
_authenticationProvider = authenticationProvider;
_authenticationProvider.AuthenticationStateChanged += AuthenticationStateChangedHandler;
// perform initial call into the event handler
AuthenticationStateChangedHandler(_authenticationProvider.GetAuthenticationStateAsync());
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_authenticationProvider.AuthenticationStateChanged -= AuthenticationStateChangedHandler;
}
}
public event AuthenticationChanged? AuthenticationChanged;
public AuthenticationState? AuthenticationState { get; private set; }
private async void AuthenticationStateChangedHandler(Task<AuthenticationState> task)
{
AuthenticationState = await (task);
if (IsAuthenticated)
{
// first load profile
await _profileService.LoadProfile(UserName!);
}
else
{
await _profileService.EmptyProfile();
}
// then update all listening clients, invoke the event
AuthenticationChanged?.Invoke(AuthenticationState);
}

Related

Blazor WebAssembly SignalR HubConnection causing javascript error on reload

I have a SignalR HubConnection within my Blazor WebAssembly application and whilst it works most of the time, if I reload the page (via the browser reload) then I often am getting the following error in the console and the connection is not made:
Uncaught Error: The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd.
at Object.invoke_delegate (dotnet.5.0.4.js:1)
at WebSocket. (dotnet.5.0.4.js:1)
Here's a rough, simplified view of the code where I create the HubConnection (and dispose it).
#inherits LayoutBase
#attribute [Authorize]
<AuthorizeView>
<Authorized>
//...
</Authorized>
<NotAuthorized>
//...
</NotAuthorized>
</AuthorizeView>
public class LayoutBase : LayoutComponentBase, IAsyncDisposable
{
[Inject] public IAccessTokenProvider AccessTokenProvider { get; set; }
private readonly HubConnection _hubConnection;
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.AddNewtonsoftJsonProtocol(c =>
{
//...
})
.WithUrl(notificationHubUrl, option => option.AccessTokenProvider = GetAccessToken)
.WithAutomaticReconnect()
.Build();
_hubConnection.Closed += HubConnectionOnClosed;
_hubConnection.Reconnected += HubConnectionOnReconnected;
_hubConnection.Reconnecting += HubConnectionOnReconnecting;
await _hubConnection.StartAsync()
await base.OnInitializedAsync();
}
private async Task<string> GetAccessToken()
{
var tokenResult = await AccessTokenProvider.RequestAccessToken(...)
// etc...
}
// .. Event Handlers
public ValueTask DisposeAsync()
{
_logger.LogInformation($"Disposing Hub: {_hubConnection.ConnectionId}");
_hubConnection.Closed -= HubConnectionOnClosed;
_hubConnection.Reconnected -= HubConnectionOnReconnected;
_hubConnection.Reconnecting -= HubConnectionOnReconnecting;
return _hubConnection.DisposeAsync();
}
}
Previously I had it as an injected service but I eventually simplified it to this structure but it continues to get this error on reload. It's not every time I reload but most times.
I have tried changing the dispose pattern without success. I can't find any information on the error anywhere else.
Any ideas?
I don't have a definitive answer as to the underlying reason but I suspect that this is a bug somewhere in the SignalR/dotnet framework resulting in the GCing of a delegate because something drops a reference to it.
One way I've managed to provoke this error reasonably consistently is to have a handler returning just a Task, e.g.
_hubConnection.On<TEvent>(eventType.Name, OnEvent);
where OnEvent looks like this:
// THIS IS THE BROKEN SIGNATURE - DO NOT USE
private async Task OnEvent<TEvent>(TEvent e)
{
}
A workaround which appears to have fixed it for me is to make the handler actually return something. This seems to make something deeper in the framework hold a reference for longer so that it doesn't get GC'ed. E.g.
// WORKS ON MY MACHINE - Note the return type of Task<object>
private async Task<object> OnEvent<TEvent>(TEvent e)
{
// ... Do stuff
return null;
}

resultCode is 0 for all requests in Application Insights

I have a function app connected with an application insights instance.
When I look at the requests on application insights, all entries have a resultCode of 0, regardless of whether it was successful or not. How can I have the resultCode showing properly?
If I get it correctly, my function app is running at the version "3.0.14916.0".
Here is my startup:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging(loggingBuilder =>
{
var key = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
loggingBuilder.AddApplicationInsights(key);
});
builder.Services.AddSingleton(sp =>
{
var key = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
return new TelemetryConfiguration(key);
});
(...)
}
}
Edit 1:
In the comments it was asked why I am adding logging in Startup. I do it because, as far as I could verify, ILogger < MyClass > only logs to AI if I add logging in Startup.
Following is an example of an injected class. Note that this class is also used in other projects.
public class CosmosDbService : ICosmosDbService
{
private readonly IDocumentClient _documentClient;
private readonly ILogger _logger;
public CosmosDbService(IDocumentClient documentClient, ILogger<CosmosDbService> logger)
{
_logger = logger;
_documentClient = documentClient;
}
public async Task<UserData> GetUserAsync()
{
try
{
// Getting user here
// (...)
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching user.");
throw;
}
}
}
This class is injected as:
builder.Services.AddSingleton<IDocumentClient>(sp =>
{
// This does not really matter for this question
var configuration = sp.GetService<IConfiguration>();
var connectionString = configuration.GetValue<string>("COSMOS_DB_CONNECTION");
var cosmosDbConnectionString = new CosmosDbConnectionString(connectionString);
return new DocumentClient(cosmosDbConnectionString.ServiceEndpoint, cosmosDbConnectionString.AuthKey);
});
builder.Services.AddSingleton<ICosmosDbService, CosmosDbService>();
This answer from #PeterBons helped me fixing the wrong resultCode as well.
Basically I was importing the wrong package: Microsoft.Extensions.Logging.ApplicationInsights
I changed it to Microsoft.Azure.WebJobs.Logging.ApplicationInsights and removed the code in Startup. Now I got the resultCode properly filled in again.

Application Insights Telemetry filtering is not working

I have already followed the guide here. I have tried both the config and "in code" approach of initializing and registering our telemetry processor. My goal is to filter out some HTTP responses so that those don't make their way to the sampled data. I haven't had any success. While our processor is initialized on app start, the Process method is never hit. Also, I already made sure that there is an InstrumentationKey in the config and that I'm using the correct key. What else am I missing?
This is what I have:
public class MyTelemetryProcessor : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
// You can pass values from .config
public string MyParamFromConfigFile { get; set; }
// Link processors to each other in a chain.
public MyTelemetryProcessor(ITelemetryProcessor next)
{
this.Next = next; <-- this is always hit indicating this processor is active
}
public void Process(ITelemetry item)
{
// To filter out an item, just return
if (!OKtoSend(item)) { return; } <-- breakpoint here is never hit
// Modify the item if required
ModifyItem(item);
this.Next.Process(item);
}
private bool OKtoSend(ITelemetry item) <-- and consequently this method is never hit
{
var request = item as RequestTelemetry; <-- breakpoint here is never hit
// some more code goes here
return request.Success.GetValueOrDefault(false);
}
// Example: replace with your own modifiers.
private void ModifyItem(ITelemetry item)
{
item.Context.Properties.Add("app-version", "1." + MyParamFromConfigFile);
}
}
And this is how it is registered. I can see this being hit during debugging when the app starts up:
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new MyTelemetryProcessor (next));
builder.Build();
In aspnetcore, my solution was to use :
services.AddApplicationInsightsTelemetryProcessor(typeof(BasicTelemetryFilter));
(using the regular CreateWebHostBuilder :
WebHost.CreateDefaultBuilder(args)
.UseApplicationInsights()
.UseStartup<Startup>();
)

WF 4 OnUnhandledException not hit

I've created a custom activity which contains as a Body another Activity.
[Browsable(false)]
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
}
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
throw new Exception(propagatedException.Message);
}
When an exception is thrown during the execution of the Body, ma handler for the OnFaulted is hit.
My execution starts with a call to static method Run of the WorkflowApplication class. My WorkflowApplication instance has a handler associated for the OnUnhandledException event.
instance.OnUnhandledException +=
delegate(WorkflowApplicationUnhandledExceptionEventArgs args)
{
Console.WriteLine(args.ExceptionSource);
waitEvent.Set();
return UnhandledExceptionAction.Cancel;
};
But regardless of what happens when the Activity hosted in the Body is executed, i never reach the handler defined above. I thought that if i throw an exception from the OnFaulted, i will be able to redirect the flow to the OnUnhandledException but i was wrong. Any ideas ?
I need this in order to centralize my errors, check them and display messages accordingly. Also i need a way to stop the execution and so on and i don't want to define handlers all over the application. Is there any way to accomplish this ?
As Will suggested, i will post what i did to handle my scenario.
Basically, in my custom activity i have hosted an Assign :
[Browsable(false)]
public Activity Body { get; set; }
Activity System.Activities.Presentation.IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
{
return new Assignment()
{
Body = new Assign() { DisplayName = "" }
};
}
I've added this code to my Execute method :
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete), OnFaulted);
I was trying to run this Assignment by giving an array a negative value as index and and an exception was thrown. This, somehow ended my execution but no handler for the events of my WorkflowApplication instance were hit.
Here is the method given as a callback when executing the body ( in our case the Assign activity ) :
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
faultContext.HandleFault();
CommunicationExtension ce = faultContext.GetExtension<CommunicationExtension>();
ITextExpression toTextExpression = (propagatedFrom.Activity as Assign).To.Expression as ITextExpression;
string valueTextExpression = string.Empty;
if ((propagatedFrom.Activity as Assign).Value != null)
{
if ((propagatedFrom.Activity as Assign).Value.Expression != null)
valueTextExpression = (propagatedFrom.Activity as Assign).Value.Expression.ToString();
}
if (ce != null)
{
ce.AddData(string.Format("{0} found on Assignment definition [{1} = {2}]", propagatedException.Message, toTextExpression.ExpressionText, valueTextExpression));
}
}
The trick was to call :
faultContext.HandleFault();
and use CommunicationExtension to allow me to to display the erros in the GUI.
The code for this class is trivial :
public class CommunicationExtension
{
public List<string> Messages { get; set; }
public CommunicationExtension()
{
Messages = new List<string>();
}
public void AddData(string message)
{
if (string.IsNullOrEmpty(message))
return;
Messages.Add(message);
}
}
Use this to add the extension:
CommunicationExtension ce = new CommunicationExtension();
instance.Extensions.Add(ce);
where instance is my WorkflowApplication instance.
I understood that for each instance of the workflow application we have one instance of its extension class. So i can send messages like this from all my custom activities in order to display their status.
I hope this scenario can help other people too.

Self host SignalR with Cross domain ASP.Net Client callback fail

I have a WPF application which use SignalR to achieve publish/subscribe model.
When I used a WPF client to connect to the above application, the publish and callback worked successfully.
Then I created a ASP.Net client. I use a cross domain property of SignalR to connect to above WPF application.
It could connect to the application and call the method provided in the hub successfully.
However, when the WPF application call the method in the ASP.Net Client, it seems that that call cannot be reached to the client browser
(viewed in Firefox, the long polling does not return; break point cannot be reached even I have set the break point in the javascript callback function, and nothing could be displayed in the broswer).
I have included the following script in html
<script src="#Url.Content("~/Scripts/jquery-1.6.4.min.js")" type="text/javascript</script>
<script src="/Scripts/jquery.signalR-1.0.0-rc2.min.js" type="text/javascript"></script>
<script src="http://localhost:9999/signalr/hubs" type="text/javascript"></script>
The following is the javascript that I have used.
jQuery.support.cors = true;
myHub = $.connection.subscriberHub;
myHub.client.addMessage = function (msg, time) {
$("#message").prepend("<div>" + time + " " + msg + "</div>");
};
$.connection.hub.url = 'http://localhost:9999/signalr';
$.connection.hub.start();
The below is the server code in the WPF application:
public partial class App : Application
{
private IDisposable app;
private void Application_Startup(object sender, StartupEventArgs e)
{
string url = "http://localhost:9999";
app = WebApplication.Start<Startup>(url);
}
private void Application_Exit(object sender, ExitEventArgs e)
{
if (app != null)
{
app.Dispose();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapHubs();
}
}
And I send the message when the WPF application clicked a button:
private void btn_sendMsg_Click(object sender, RoutedEventArgs e)
{
var context = GlobalHost.ConnectionManager.GetHubContext<SubscriberHub>();
DateTime sentTime = DateTime.Now;
context.Clients.Group("subscriber").addMessage(tb_message.Text, sentTime);
MessageList.Insert(0,string.Format("{0:yyyy-MM-dd HH:mm:ss} {1}", sentTime, tb_message.Text));
}
The following is the hub that I have defined:
public class SubscriberHub : Hub
{
string group = "subscriber";
public Task Subscribe()
{
return Groups.Add(Context.ConnectionId, group);
}
public Task Unsubscribe()
{
return Groups.Remove(Context.ConnectionId, group);
}
}
Is there any problem in the above code?
Don't you need to have the JavaScript client call Subscribe?
$.connection.hub.start().done(function () {
myHub.server.subscribe();
});
Alternatively you could modify SubscriberHub:
public class SubscriberHub : Hub
{
string group = "subscriber";
public override Task OnConnected()
{
return Groups.Add(Context.ConnectionId, group);
}
// ...
}

Resources