I have a simple Asp.Net Core 5 website. I added a hub and SignalR to the website. However, when I pushed the website to production they said the website slowed down significantly. This is the summary of what I did:
1.Added the Microsoft.AspNetCore.SignalR.Core package version 1.1.0
2.Created an empty hub:
public class NotificationHub : Hub
{
}
3.In the code called it like so:
public class NotificationService
{
public NotificationService(ApplicationDbContext db, IHubContext<NotificationHub> hub)
{
this.db = db;
this.hub = hub;
}
public async Task AlertAllUsersAsync(Order order, string content, string icon)
{
//...
await hub.Clients
.Users(users.Select(u => u.Id.ToString()).ToList())
.SendAsync("sendToUser", icon, content, link);
}
}
4.In Startup.cs added services.AddSignalR(); and endpoints.MapHub<NotificationHub>("/NotificationHub");
5.And in JS:
var connection = new signalR.HubConnectionBuilder()
.withAutomaticReconnect()
.withUrl("/NotificationHub")
.build();
connection.on("sendToUser", (icon, content, link) => {
showToast('info', content);
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
I tested on the server. Each page takes at least 1 minute to load. Sometimes even much longer. However, and as soon as I remove these lines of JS code, it works fast. I have never had any issues with SignalR, although this is the first time I'm using it in Asp.Net Core.
Related
I'm currently designing an Angular SPA web client, backed with .NET5 REST. It's all in the same Visual Studio project, and it builds / runs fine.
I'm now investigating the possibility of distributing this as a windows desktop application. I was able to get Electron.NET to work, but it seems like a round-about solution (Node?!). I also didn't particularly like that the resources were visible/changeable in the distributed app.
This led me to investigate using WebView2 within WPF (Microsoft seems to be making a similar transition with MSTeams.) I've found some examples, but they only use:
solely remote content ("www.bing.com")
local content, but only img / html / etc
postmessage, etc to communicate using custom objects.
None of these is what I want. Well, that's not entirely true. I need #2 to load the Angular SPA, but when the WebView2-hosted Angular invokes HttpClient, I'd like to intercept that request in the host application and Route it to my REST Controllers. This would allow me to keep nearly all of my code intact, and presumably ship a smaller, more obfuscated exe.
Is this possible? obvious? Is my desire fundamentally flawed? (wouldn't be the first time)
Chromium.AspNetCore.Bridge offers a solution to the problem. It uses owin to host the server-side code in memory, and provides a RequestInterceptor to cleanly relay all requests to the "server" code.
The link above has working examples, but briefly:
App.xaml.cs:
private IWebHost _host;
private AppFunc _appFunc;
public AppFunc AppFunc
{
get { return _appFunc; }
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
_ = Task.Run(async () =>
{
var builder = new WebHostBuilder();
builder.ConfigureServices(services =>
{
var server = new OwinServer();
server.UseOwin(appFunc =>
{
_appFunc = appFunc;
});
services.AddSingleton<IServer>(server);
});
_host = builder
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.Build();
await _host.RunAsync();
});
}
MainWindow.xaml.cs
private AppFunc _appFunc;
public MainWindow()
{
InitializeComponent();
Browser.CoreWebView2InitializationCompleted += Browser_CoreWebView2InitializationCompleted;
}
private void Browser_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
_appFunc = ((App)Application.Current).AppFunc;
Browser.CoreWebView2.WebResourceRequested += BrowserWebResourceRequestedAsync;
Browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
}
}
private async void BrowserWebResourceRequestedAsync(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
var deferral = e.GetDeferral();
var request = new ResourceRequest(e.Request.Uri, e.Request.Method, e.Request.Headers, e.Request.Content);
var response = await RequestInterceptor.ProcessRequest(_appFunc, request);
var coreWebView2 = (CoreWebView2)sender;
e.Response = coreWebView2.Environment.CreateWebResourceResponse(response.Stream, response.StatusCode, response.ReasonPhrase, response.GetHeaderString());
deferral.Complete();
}
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;
}
We are in the process of migrating a .net framework web jobs implementation to dotnet core. I'm using the documented extension method (IHostBuilder.ConfigureServices) on IHostBuilder to register dependencies with the scopes that seem fit, i.e., scoped, because most of the time I want an instance per web job invocation.
In the unit of work implementation that we use, the Entity Framework DbContext is disposed when the unit of work completes. In local development, and this is the issue that leads to this question, I bump into the issue that a second trigger (the web job is triggered via a ServiceBusTrigger) reuses the same instances of my dependencies, while they are properly registered on the IServiceCollection via the regular AddScoped<,> API. In my scenario, this manifests itself a DisposedObjectException on the DbContext.
While investigating this, I found that all scoped services are reused over invocations, which leads to the question whether you have to do the scoping differently in Azure Webjobs? Is this a local development thing only?
So, in pseudo code, this is how stuff is implemented:
// Program.cs
public static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureLogging((ctx, loggingBuilder) => { /* ... */});
builder.ConfigureWebJobs(webJobsBuilder => {
// DO STUFF
webJobsBuilder.AddServiceBus(options => { /* ... */ });
});
builder.ConfigureServices(services => {
services.AddScoped<IService, ServiceImplementation>();
// ...
services.AddScoped<IContextFactory, ContextFactoryImplementation>();
// ...
});
var host = builder.Build();
using(host)
{
await host.RunAsync();
}
}
And the unit of work is basically:
public class UnitOfWork: IUnitOfWork
{
public UnitOfWork(DbContext context)
{
// ...
}
public void Commit()
{
dbContext.SaveChanges();
}
public void Dispose()
{
...
}
public void Dispose(bool disposing)
{
...
dbContext?.Dispose();
dbContext = null;
}
}
Thanks!
Ok guys, sorry to waste your time, it turns out that a particular service was incorrectly registered as a singleton. I have to admit I might have jumped to conclusions, given that recently we bumped into the issues op scoped services in combination with usage of HttpClient(Factory) in Azure Functions (which is a real problem).
I need to add some code to a Blazor WASM app that run as the application is starting up. I want to make a call to an API to get some settings to use during the rest of the application's lifetime.
I have verified that the API is configured correctly and that it returns data.
I've tried adding both MainLayout.razor.cs as well as App.razor.cs in order to make the call.
Neither of these worked. However when I add the SAME code to one of my other components (below), it works fine.
public class ViewMenuModel : ComponentBase
{
[Inject] HttpClient Http { get; set; }
[Inject] AppState AppState { get; set; }
protected override async Task OnInitializedAsync()
{
Settings = await Http.GetJsonAsync<List<Settings>>("settings");
UpdateSettings(Settings);
}
protected void UpdateSettings(List<Settings> settings)
{
AppState.SetSettings(settings);
}
}
Is it possible that I'm just missing something? Is this kind of thing supposed to work from either MainLayout or App?? If so, what's the trick?
It's been some time since I asked this question initially, but I think it might be valuable for future people....
When I started, I think we were on .Net core 3.1, since then, migrating to .net 6, there's actual Microsoft documentation on how to add these types of configurations
https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/configuration?view=aspnetcore-6.0
In Program.cs
var http = new HttpClient()
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};
builder.Services.AddScoped(sp => http);
using var response = await http.GetAsync("cars.json");
using var stream = await response.Content.ReadAsStreamAsync();
builder.Configuration.AddJsonStream(stream);
Hope everyone is well.
I'm using MVC C# , AspNet.Identities and have a fully functional Account controller.
I recently introduced a basic SignalR Hub to the project, I want to log the user out once he disconnects from the Hub. My idea is to call the LogOff method from the Account Controller.
The Hub is really simple, infact I've taken this from a video by Scott Hanselman if I'm not mistaken. Just the hitCounter part...Now I'm trying to add in the logoff() functionality.
Here's what I've got so far.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Welcome", "Home");
}//this is in my account controller
[Authorize]
[HubName("hitCounter")]
public class GameplayHub : Hub
{
private static int intCounter = 0;
public void RecordHit()
{
intCounter += 1;
this.Clients.All.onHitRecorded(intCounter);
}
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
Final.Controllers.AccountController obj = new Final.Controllers.AccountController();
intCounter -= 1;
this.Clients.All.onHitRecorded(intCounter);
this.Clients.Caller.onHitRecorded(obj.LogOff());
obj.LogOff();
return base.OnDisconnected(stopCalled);
}
}//this is the hub
<div id="hitCount" style="font-size:50px;"></div>
<script src="~/Scripts/jquery-1.6.4.js"></script>
<script src="~/Scripts/jquery.signalR-2.1.1.js"></script>
<script src="/signalr/hubs"></script>
<script>
$(function() {
var con=$.hubConnection();
var hub=con.createHubProxy('hitCounter');
hub.on('onHitRecorded', function (i) {
$('#hitCount').text(i);
});
con.start(function() {
hub.invoke('recordHit');
});
})
</script>//My view...just shows how many people active at that point...
The amount of people active, shows correctly. What I'm trying to do in my app is, if a user in on 2 tabs, and he closes one, he must be logged off on both.
Any help will be appreciated! Thanks :)
Unfortuntelny it is not going to work. Logging off user is done by removing cookie which has to be done by browser. SignalR runs your disconnected handler after tab was already closed, so you can not tell browser to delete cookie.
The solution that could work is to force redirect to log off page on the second tab opened by user.
Pseudocode:
In SignalR hub:
Task OnDisconnected()
{
var otherTab = FindOtherTabWithUser();
otherTab.forceLogOff();
}
In JavaScript:
forceLogOff = function() { window.location.href = '/logoff'; }