I'm creating an ASP.NET Core Application running on 4.7 with an Angular front-end. As I need to notify the users about status changes I'm trying to add SignalR. I'm using following versions:
AspNetCore 2.0.1
Angular 5.x
SignalR 1.0.0-alpha2-final
Below you can see my code. If I run this app I can see in the logs that both hub connections get started. If I open a second browser tab with the same URL, only one connection is successful. For the second hub I always get this error:
Error: Not Found
at XMLHttpRequest.xhr.onload [as __zone_symbol__ON_PROPERTYload] (http://localhost:51616/dist/estructuring_main.js?v=fBx34vrzWnqBIPjAm71NaWQz0TEeQZWcx2ixKnjADWI:36168:28)
at XMLHttpRequest.wrapFn (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120085:39)
at ZoneDelegate.invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119340:31)
at Object.onInvokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:5013:33)
at ZoneDelegate.invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119339:36)
at Zone.runTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119107:47)
at ZoneTask.invokeTask [as invoke] (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119415:34)
at invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120436:14)
at XMLHttpRequest.globalZoneAwareCallback (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120462:17)
The error says 404 not found even though I can see the request in my request-middleware.
What do I need to change that the hub connections also work with multiple tabs?
Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
app.UseSignalR(routes =>
{
routes.MapHub<StatusHub>("status", (options) =>
{
options.Transports = TransportType.LongPolling;
});
routes.MapHub<AutopricerHub>("autopricer", (options) =>
{
options.Transports = TransportType.LongPolling;
});
});
}
InMemoryUserTracker.cs
public class InMemoryUserTracker<THub> : IUserTracker<THub>
{
private readonly ConcurrentDictionary<HubConnectionContext, UserDetails> usersOnline
= new ConcurrentDictionary<HubConnectionContext, UserDetails>();
public IEnumerable<UserDetails> UsersOnline()
=> usersOnline.Values.AsEnumerable();
public Task AddUser(HubConnectionContext connection, UserDetails userDetails)
{
usersOnline.TryAdd(connection, userDetails);
return Task.CompletedTask;
}
public Task RemoveUser(HubConnectionContext connection)
{
usersOnline.TryRemove(connection, out var userDetails);
return Task.CompletedTask;
}
}
BaseHubWithPresence.cs
public abstract class BaseHubWithPresence : Hub
{
protected readonly IUserTracker<BaseHubWithPresence> userTracker;
protected BaseHubWithPresence(IUserTracker<BaseHubWithPresence> userTracker)
{
this.userTracker = userTracker;
}
public virtual async Task Join(long userId)
{
await userTracker.AddUser(Context.Connection, new UserDetails(Context.ConnectionId, userId));
await Task.CompletedTask;
}
public virtual async Task Leave()
{
await userTracker.RemoveUser(Context.Connection);
await Task.CompletedTask;
}
}
AutopricerHub.cs
public class AutopricerHub : BaseHubWithPresence
{
public AutopricerHub(IUserTracker<AutopricerHub> userTracker)
: base(userTracker) {}
public async Task SendToAll()
{
await Clients.All.InvokeAsync("All", "data");
}
public async Task SendUpdateAsync(long userId)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == userId))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
}
StatusHub.cs
public class StatusHub : BaseHubWithPresence
{
public StatusHub(IUserTracker<BaseHubWithPresence> userTracker)
: base(userTracker)
{
}
public async Task SendSingleAsync(long listener)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == listener))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
public async Task SendMultipleAsync(IReadOnlyCollection<long> listeners)
{
foreach (var listener in listeners)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == listener))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
}
}
price-notification.service.ts
#Injectable()
export class PriceNotificationService {
private hubConnection: HubConnection;
private destroy$ = new Subject<boolean>();
constructor(
private appContextService: AppContextService,
private messageService: MessageService) {
this.init();
}
private init(): void {
this.hubConnection = new HubConnection('/autopricer', { transport: TransportType.LongPolling });
this.hubConnection.start()
.then(() => {
this.appContextService.getAppContext().takeUntil(this.destroy$).subscribe(appContext => {
this.hubConnection.invoke('join', appContext.FirmuserId);
});
DEBUG.init && debug('Price Notification Hub connection started');
})
.catch(err => {
DEBUG.init && debug('Error while establishing Price Notification Hub connection');
});
this.hubConnection.onclose = e => {
DEBUG.functionCalls && debug('On close Price Notification Hub connection');
this.hubConnection.invoke('leave');
};
this.hubConnection.off('leave', null);
}
private showUpdateNotification(data: string) {
}
private showAllNotification(data: string) {
}
}
status-notification.service.ts
#Injectable()
export class StatusNotificationService {
private hubConnection: HubConnection;
private destroy$ = new Subject<boolean>();
constructor(
private appContextService: AppContextService,
private messageService: MessageService) {
this.init();
}
private init(): void {
this.hubConnection = new HubConnection('/status', { transport: TransportType.LongPolling });
this.hubConnection.start()
.then(() => {
this.appContextService.getAppContext().takeUntil(this.destroy$).subscribe(appContext => {
this.hubConnection.invoke('join', appContext.FirmuserId);
});
DEBUG.init && debug('Status Notification Hub connection started');
})
.catch(err => {
DEBUG.init && debug('Error while establishing Status Notification Hub connection');
});
this.hubConnection.onclose = e => {
DEBUG.functionCalls && debug('On close Status Notification Hub connection');
this.hubConnection.invoke('leave');
};
this.hubConnection.off('leave', null);
}
private showUpdateNotification(data: string) {
}
}
The service provider for each of these Angular service is in a separate component.
Browser network tab:
Related
I am getting this error occasionally from a Blazor server side .NET Core project.
Unable to read data from the transport connection: An existing
connection was forcibly closed by the remote host..
After reload the page or retry to execute same function call one more time there will be no errors and results will be returned
The DbContext definition looks like below:
public class ADPortalDbContext:DbContext
{
public DbSet<Company> Companies { get; set; }
public ADPortalDbContext(DbContextOptions<ADPortalDbContext> options)
: base(options) { }
}
The Service for returning the results from database looks like below:
public class CompanyService : ICompanyService
{
private readonly ADPortalDbContext context;
public CompanyService(ADPortalDbContext context)
{
this.context = context;
}
public async Task<IEnumerable<Company>> GetCompaniesSearchText(string searchText)
{
try
{
return await context.Companies
.Where(i => EF.Functions.Like(i.Name.ToLower(), $"%{searchText.ToLower()}%"))
.ToListAsync()
.ConfigureAwait(false);
}
catch(Exception ex)
{
throw new InvalidOperationException("Unable to return results " + ex.Message);
}
}
Startup.cs of Blazor app is like below:
services.AddTransient<ICompanyService, CompanyService>();
services.AddDbContext<ADPortalDbContext>(options =>
options.UseMySql(connStr, srvVersion, x =>
{
x.MigrationsAssembly("DCPortal.Infrastructre");
}),
contextLifetime: ServiceLifetime.Transient);
The Blazor page calling the service is like below:
#inject ICompanyService CompanyService
private async Task<IEnumerable<Company>> SearchCompanies(string searchText)
{
try
{
IEnumerable<Company> companies_ListDb = await CompanyService.GetCompaniesSearchText(searchText);
}
catch(Exception ex)
{
//Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
}
}
The general crux of the issue is the lifetime of the service - a DB context should be pretty much single-use; so in the background the connection is being closed at some point (resulting in your error).
The only real way I've found to get around it is to implement a context factory.
So, in your case, you'll want to create a file in the same area as your context:
public class ADPortalDbContextFactory : IDbContextFactory<ADPortalDbContext>
{
private readonly DbContextOptions<ADPortalDbContext> options;
public ADPortalDbContextFactory(DbContextOptions<ADPortalDbContext> contextOptions)
{
options = contextOptions;
}
public ADPortalDbContext CreateDbContext()
{
return new ADPortalDbContext(options);
}
}
From there, you can put a simple tweak into your startup:
services.AddDbContext<ADPortalDbContext>(options =>
options.UseMySql(connStr, srvVersion, x =>
{
x.MigrationsAssembly("DCPortal.Infrastructre");
}));
services.AddDbContextFactory<ADPortalDbContext, ADPortalDbContextFactory>(options =>
options.UseMySql(connStr, srvVersion, x =>
{
x.MigrationsAssembly("DCPortal.Infrastructre");
}), ServiceLifetime.Scoped);
services.AddScoped<ICompanyService, CompanyService>();
Your service then becomes:
public class CompanyService : ICompanyService
{
private readonly IDbContextFactory<ADPortalDbContext> contextFactory;
public CompanyService(IDbContextFactory<ADPortalDbContext> context)
{
this.contextFactory = context;
}
public async Task<IEnumerable<Company>> GetCompaniesSearchText(string searchText)
{
try
{
using (var context = contextFactory.CreateDbContext())
{
return await context.Companies
.Where(i => EF.Functions.Like(i.Name.ToLower(), $"%{searchText.ToLower()}%"))
.ToListAsync()
.ConfigureAwait(false);
}
}
catch(Exception ex)
{
throw new InvalidOperationException("Unable to return results " + ex.Message);
}
}
}
I am trying to configure Automatonymous worker implementation with EF Core as persistence. I publish event via api and process it in hosted service using RabbitMq as a transport. Unfortunately database does not store state of machine. I applied migrations and I see table OrderState, but there is no data in it after I publish OrderSubmmited event. It gets processed properly, because I see a log in a console, but database table remains empty. What am I missing?
This is my code:
Event:
public interface OrderSubmitted : CorrelatedBy<Guid>
{
}
Consumer:
public class OrderSubmittedConsumer : IConsumer<OrderSubmitted>
{
public Task Consume(ConsumeContext<OrderSubmitted> context)
{
Console.Out.WriteLine($"Order with id {context.Message.CorrelationId} has been submitted.");
return Task.CompletedTask;
}
}
My saga instance:
public class OrderState : SagaStateMachineInstance
{
public Guid CorrelationId { get; set; }
public string CurrentState { get; set; }
}
State machine:
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
public State Submitted { get; private set; }
public Event<OrderSubmitted> OrderSubmitted { get; set; }
public OrderStateMachine()
{
Event(() => OrderSubmitted);
InstanceState(x => x.CurrentState);
Initially(
When(OrderSubmitted)
.TransitionTo(Submitted));
DuringAny(
When(OrderSubmitted)
.TransitionTo(Submitted));
}
}
public class OrderStateMachineDefinition : SagaDefinition<OrderState>
{
public OrderStateMachineDefinition()
{
ConcurrentMessageLimit = 15;
}
}
DbContext:
public class OrderStateDbContext : SagaDbContext
{
public OrderStateDbContext(DbContextOptions options) : base(options)
{
}
protected override IEnumerable<ISagaClassMap> Configurations
{
get
{
yield return new OrderStateMap();
}
}
}
public class OrderStateMap : SagaClassMap<OrderState>
{
protected override void Configure(EntityTypeBuilder<OrderState> entity, ModelBuilder model)
{
entity.Property(x => x.CurrentState).HasMaxLength(64);
}
}
Program class:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<OrderStateDbContext>(builder =>
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=OrderState;Trusted_Connection=True;", m =>
{
m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
m.MigrationsHistoryTable($"__{nameof(OrderStateDbContext)}");
}));
services.AddMassTransit(config =>
{
config.AddSagaRepository<OrderState>()
.EntityFrameworkRepository(r =>
{
r.ExistingDbContext<OrderStateDbContext>();
r.LockStatementProvider = new SqlServerLockStatementProvider();
});
config.AddConsumer<OrderSubmittedConsumer>();
config.UsingRabbitMq((ctx, cfg) => {
cfg.Host("amqp://guest:guest#localhost:5672");
cfg.ReceiveEndpoint("order-queue", c => {
c.ConfigureConsumer<OrderSubmittedConsumer>(ctx);
// I'm assuming this is the place where something like c.StateMachineSaga() is missing, but I don't know how should this look like with EF Core
});
});
});
services.AddHostedService<Worker>();
});
}
Worker class:
public class Worker : IHostedService
{
private readonly IBusControl _bus;
public Worker(IBusControl bus)
{
_bus = bus;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await _bus.StartAsync(cancellationToken).ConfigureAwait(false);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _bus.StopAsync(cancellationToken);
}
}
In your code example, you aren't adding the saga, or configuring the saga on a receive endpoint. The consumer is a separate thing, and completely unrelated to the saga. You should be calling:
AddSagaStateMachine<OrderStateMachine, OrderState, OrderStateMachineDefinition>()
And then either using ConfigureSaga<OrderState> or switching to ConfigureEndpoints for the endpoint to be configured automatically.
When I run my app, I keep getting the following: ConnectionID required
So I can't seem to be able to connect my ASP.NET Core API server to my asp.net uwp client.
I have installed the Microsoft.AspNetCore.SignalR 1.1.0 nuget package on my server side and the Microsoft.AspNet.SignalR.Client 2.4.1 nuget package on my client side.
This is my Startup.cs class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("FlightAppContext"));
});
services.AddSession();
services.AddOpenApiDocument(c =>
{
c.DocumentName = "apidocs";
c.Title = "FlightApp API";
c.Version = "v1";
c.Description = "The FlightApp API documentation description.";
c.DocumentProcessors.Add(new SecurityDefinitionAppender("JWT Token", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.ApiKey,
Name = "Authorization",
In = SwaggerSecurityApiKeyLocation.Header,
Description = "Copy 'Bearer' + valid JWT token into field"
}));
c.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT Token"));
}); //for OpenAPI 3.0 else AddSwaggerDocument();
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc().AddXmlSerializerFormatters();
services.AddScoped<IEntertainmentRepository, EntertainmentRepository>();
services.AddScoped<IOrderlineRepository, OrderlineRepository>();
services.AddScoped<IPassengerRepository, PassengerRepository>();
services.AddScoped<IPersonnelRepository, PersonnelRepository>();
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IPassengerGroupRepository, PassengerGroupRepository>();
services.AddScoped<ApplicationDataInitializer>();
services.AddCors(options => options.AddPolicy("AllowAllOrigins", builder => builder.AllowAnyOrigin()));
}
//This method gets called by the runtime.Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDataInitializer dataInitializer)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//The default HSTS value is 30 days.You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
//app.UseHttpsRedirection();
app.UseSignalR(route => route.MapHub<ChatHub>("/api/Chat/GetAllChatMessages"));
app.UseMvc();
app.UseSwaggerUi3();
app.UseSwagger();
dataInitializer.InitializeData();
}
}
This is my ChatHub.cs class:
public class ChatHub : Hub
{
public Task SendMessage(int PassengergroupId, string userName, string message, DateTime sendTime)
{
return Clients.All.SendAsync("ReceiveMessage", userName, message, sendTime);
//Groups.AddToGroupAsync(Context.ConnectionId, Convert.ToString(PassengergroupId));
//return Clients.Groups(Convert.ToString(PassengergroupId)).SendAsync("ReceiveMessage", userName, message, sendTime);
}
}
this is my ChatController:
/// <summary>
/// Adds a message to a passengroup
/// </summary>
/// <param name="message">message to post</param>
[HttpPost("ChatMessages")]
public async Task<IActionResult> CreateMessage(MessageDTO message)
{
PassengerGroup passengerGroup = _passengerGroupRepository.GetById(message.PassengergroupId);
Passenger currentUser = _passengerRepository.GetById(message.PassengerId);
Message messageToCreate = new Message(currentUser, passengerGroup, message.Content);
await _dbContext.Messages.AddAsync(messageToCreate);
await _dbContext.SaveChangesAsync();
return Ok();
}
/// <summary>
/// Get all messages from passengergroup
/// </summary>
[HttpGet("GetAllChatMessagesFromPassengerGroup/{id}")]
public async Task<IEnumerable<MessageDTO>> GetAllChatMessagesFromPassengerGroup(int id)
{
var messages = await _dbContext.Messages.Include(m => m.Passenger)
.Include(m=>m.PassengerGroup).Where(e => e.PassengroupId == id).ToListAsync();
ICollection<MessageDTO> messagesDTO = new List<MessageDTO>();
foreach (var m in messages)
{
MessageDTO message = new MessageDTO(m);
messagesDTO.Add(message);
}
return messagesDTO;
}
/// <summary>
/// Get all messages
/// </summary>
[HttpGet("GetAllChatMessages")]
public async Task<IEnumerable<MessageDTO>> GetAllChatMessages()
{
var messages = await _dbContext.Messages.Include(m => m.Passenger)
.Include(m => m.PassengerGroup).ToListAsync();
ICollection<MessageDTO> messagesDTO = new List<MessageDTO>();
foreach (var m in messages)
{
MessageDTO message = new MessageDTO(m);
messagesDTO.Add(message);
}
return messagesDTO;
}
}
and this is my Chat.xaml.cs class:
public sealed partial class Chat : Page
{
public HubConnection hubConnection;
public IHubProxy hubProxy;
public ObservableCollection<Message> MessagesList { get; set; }
private HttpClient client = new HttpClient();
public Chat()
{
InitializeComponent();
}
public int CurrentPassengerId;
private Passenger current;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var currentPassengerId = e.Parameter.ToString();
this.CurrentPassengerId = Int32.Parse(currentPassengerId);
base.OnNavigatedTo(e);
GetCurrentUser(CurrentPassengerId);
LoadMessages(current.PassengerGroupId);
InitilizeHub(current.PassengerGroupId);
}
private async void GetCurrentUser(int id)
{
var json = await client.GetStringAsync(new Uri($"http://localhost:53385/api/Passenger/{id}"));
current = JsonConvert.DeserializeObject<Passenger>(json);
this.userTextBlock.Text = current.FirstName + current.LastName;
}
private async void InitilizeHub(int id)
{
hubConnection = new HubConnection($"http://localhost:53385/api/Chat/GetAllChatMessages");
hubProxy = hubConnection.CreateHubProxy("ChatHub");
hubProxy.On<string, string, DateTime>("receiveMessage", GetMessage);
await hubConnection.Start();
if (hubConnection.State != ConnectionState.Connected)
{ await hubConnection.Start(); }
}
private async void GetMessage(string userName, string message, DateTime sendTime)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
this.MessageListbox.Items.Add($"{sendTime.ToString("HH:mm")}\n{userName}: {message}")
);
}
private async void SendButton_Click(object sender, RoutedEventArgs e)
{
await hubProxy?.Invoke("sendMessage", current.PassengerGroupId, userTextBlock.Text, messageTextBox.Text, DateTime.Now);
var message = new Message(messageTextBox.Text, userTextBlock.Text, current.PassengerGroupId);
var messageJson = JsonConvert.SerializeObject(message);
var res = await client.PostAsync("http://localhost:53385/api/Chat/ChatMessages",
new StringContent(messageJson, System.Text.Encoding.UTF8, "application/json"));
messageTextBox.Text = string.Empty;
}
public async void LoadMessages(int id)
{
var json = await client.GetStringAsync(new Uri($"http://localhost:53385/api/Chat/GetAllChatMessagesFromPassengerGroup/{id}"));
var lst = JsonConvert.DeserializeObject<ObservableCollection<Message>>(json);
foreach (var message in lst)
{
MessagesList.Add(message);
this.MessageListbox.Items.Add($"{message.TimeSend.ToString("HH:mm")}\n{message.Name}: {message.Content}");
}
}
}
I've implemented ng-chat https://github.com/rpaschoal/ng-chat (SignalR).
I have 3 users: User1, User2 and User3
If I send a message from User1 to User2 it works well User2 receives the message, but if I create a group (with User1 I open User2's chat and then Add the User3) a new group is created with Users (User2 and User3).
So, when I send a message from this new chat, the users (User2 and User3) doesn't receive any message
Here is my SingalR Hub:
using AdvansysOficina.Api._Core.Infraestructura;
using AdvansysOficina.Api.Generales.Servicios.UsuarioNs;
using Microsoft.AspNetCore.SignalR;
using NgChatSignalR.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AdvansysOficina.Api.Desarrollo.Servicios.ConversacionPuntoNs.HubNs
{
public class ConversacionHub : Hub
{
private static List<ParticipantResponseViewModel> AllConnectedParticipants { get; set; } = new List<ParticipantResponseViewModel>();
private static List<ParticipantResponseViewModel> DisconnectedParticipants { get; set; } = new List<ParticipantResponseViewModel>();
private readonly object ParticipantsConnectionLock = new object();
private ISesion _sesion;
private IUsuarioServicio _usuarioServicio;
public ConversacionHub(ISesion sesion, IUsuarioServicio usuarioServicio)
{
_sesion = sesion;
_usuarioServicio = usuarioServicio;
}
public static IEnumerable<ParticipantResponseViewModel> ConnectedParticipants(string currentUserId)
{
return AllConnectedParticipants
.Where(x => x.Participant.Id != currentUserId);
}
public void Join(string userName, dynamic grupo)
{
lock (ParticipantsConnectionLock)
{
AllConnectedParticipants.Add(new ParticipantResponseViewModel()
{
Metadata = new ParticipantMetadataViewModel()
{
TotalUnreadMessages = 0
},
Participant = new ChatParticipantViewModel()
{
DisplayName = userName,
Id = Context.ConnectionId,
}
});
// This will be used as the user's unique ID to be used on ng-chat as the connected user.
// You should most likely use another ID on your application
//Clients.Caller.SendAsync("generatedUserId", Context.ConnectionId);
Clients.Caller.SendAsync("generatedUserId", Context.ConnectionId);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
}
public void SendMessage(MessageViewModel message)
{
var sender = AllConnectedParticipants.Find(x => x.Participant.Id == message.FromId);
if (sender != null)
{
Clients.Client(message.ToId).SendAsync("messageReceived", sender.Participant, message);
}
}
public override Task OnDisconnectedAsync(Exception exception)
{
lock (ParticipantsConnectionLock)
{
var connectionIndex = AllConnectedParticipants.FindIndex(x => x.Participant.Id == Context.ConnectionId);
if (connectionIndex >= 0)
{
var participant = AllConnectedParticipants.ElementAt(connectionIndex);
AllConnectedParticipants.Remove(participant);
DisconnectedParticipants.Add(participant);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
return base.OnDisconnectedAsync(exception);
}
}
public override Task OnConnectedAsync()
{
lock (ParticipantsConnectionLock)
{
var connectionIndex = DisconnectedParticipants.FindIndex(x => x.Participant.Id == Context.ConnectionId);
if (connectionIndex >= 0)
{
var participant = DisconnectedParticipants.ElementAt(connectionIndex);
DisconnectedParticipants.Remove(participant);
AllConnectedParticipants.Add(participant);
Clients.All.SendAsync("friendsListChanged", AllConnectedParticipants);
}
return base.OnConnectedAsync();
}
}
}
}
My signalR Adapter (Angular)
import { ChatAdapter, Message, ParticipantResponse, Group, IChatController } from 'ng-chat';
import { map, catchError } from 'rxjs/operators';
import { HttpClient } from '#angular/common/http';
import * as signalR from '#aspnet/signalr';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { AlertasHelper } from '../../../shared/helpers/alertas.helper';
import { PushNotificationHelper } from './notifications/push-notification';
export class SignalRAdapter extends ChatAdapter {
public static serverBaseUrl = 'http://192.168.16.51:5021/'; // if running locally
public userId: string;
private grrupo;
private hubConnection: signalR.HubConnection;
constructor(private username: string, private http: HttpClient, private notification: PushNotificationHelper
) {
super();
this.initializeConnection();
}
private initializeConnection(): void {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${SignalRAdapter.serverBaseUrl}chat`, { transport: signalR.HttpTransportType.LongPolling })
.build();
this.hubConnection
.start()
.then(() => {
this.joinRoom();
this.initializeListeners();
})
.catch(err => console.log(`Error while starting SignalR connection: ${err}`));
}
private initializeListeners(): void {
this.hubConnection.on('generatedUserId', (userId) => {
// With the userId set the chat will be rendered
this.userId = userId;
});
this.hubConnection.on('messageReceived', (participant, message) => {
// Handle the received message to ng-chat
console.log(message);
this.notification.notify('Nuevo mensaje de: ' + participant.displayName, message);
this.onMessageReceived(participant, message);
});
this.hubConnection.on('friendsListChanged', (participantsResponse: Array<ParticipantResponse>) => {
// Handle the received response to ng-chat
this.onFriendsListChanged(participantsResponse.filter(x => x.participant.id !== this.userId));
});
}
joinRoom(): void {
if (this.hubConnection && this.hubConnection.state === signalR.HubConnectionState.Connected) {
this.hubConnection.send('join', this.username, '');
}
}
listFriends(): Observable<ParticipantResponse[]> {
// List connected users to show in the friends list
// Sending the userId from the request body as this is just a demo
// return this.http
// .post(`${SignalRAdapter.serverBaseUrl}listFriends`, { currentUserId: this.userId })
// .pipe(
// map((res: any) => res),
// catchError((error: any) => Observable.throw(error.error || 'Server error'))
// );
return of([]);
}
getMessageHistory(destinataryId: any): Observable<Message[]> {
// This could be an API call to your web application that would go to the database
// and retrieve a N amount of history messages between the users.
return of([]);
}
sendMessage(message: Message): void {
if (this.hubConnection && this.hubConnection.state === signalR.HubConnectionState.Connected) {
console.log(message);
this.hubConnection.send('sendMessage', message);
}
}
groupCreated(group: Group): void {
console.log( group);
}
}
Use of component
<ng-chat #chat *ngIf="signalRAdapter && signalRAdapter.userId"
[adapter]="signalRAdapter"
[userId]="signalRAdapter.userId"
[groupAdapter]="signalRAdapter"
(onParticipantChatOpened)="chatOpened($event)"
[historyEnabled]="false">
</ng-chat>
I've downloaded the example of github's creator page, but he doesn't have an example with signalr using groups, I hope you can help me.
ng-chat treats groups as individual participants. You will have to join your room when this event gets invoked:
groupCreated(group: Group): void {
console.log( group);
// Invoke your SignalR hub and send the details of the newly created group
}
ng-chat will generate unique ids every time a group is created so you can track which group is which whenever one gets created from a running ng-chat instance. How you will handle the persistence of these groups is up to your application.
You might want to push a notification to involved users from your SignalR adapter that their friends list has changed (They'll be able to see the group at this stage). You could also decide not to do so and only push a notification if the user who has created the group send an initial message (Once again, up to your application requirements and needs).
You might also want to implement IChatGroupAdapter on your adapter to make the contract more explicit.
Hope this helps!
I have a Hangfire instance hosted in a windows service using Topshelf:
static void Main()
{
HostFactory.Run(x =>
{
x.Service<Application>(s =>
{
s.ConstructUsing(name => new Application());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("Hangfire Windows Service Sample");
x.SetDisplayName("Hangfire Windows Service Sample");
x.SetServiceName("hangfire-sample");
});
}
private class Application
{
private IDisposable host;
public void Start()
{
host = WebApp.Start<Startup>("http://localhost:12345");
Console.WriteLine();
Console.WriteLine("Hangfire Server started.");
Console.WriteLine("Dashboard is available at {0}/hangfire", configSettings.Jobs.EndPoint);
Console.WriteLine();
}
public void Stop()
{
host.Dispose();
}
}
My StartUp class is pretty basic:
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.UseSqlServerStorage(
"DefaultConnection",
new SqlServerStorageOptions {QueuePollInterval = TimeSpan.FromMinutes(1)});
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AuthorizationFilters = new[] { new AuthorizationFilter() }
});
app.UseHangfireServer();
}
I'm trying to use a custom authorization filter:
public class AuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
var context = new OwinContext(owinEnvironment);
return context.Authentication.User.Identity.IsAuthenticated;
}
}
I was hoping to use context.Authentication.User to authenticate but it always returns null.
Is there any way to make this work for a self-hosted hangfire service?