WebAPI + OWIN + SignalR + Autofac
Asked Answered
D

1

7

I have been struggling on this issue for weeks now.

I have an app where i have configured owin backend with web api and autofac DI with background handfire jobs. I have alsmost looked at every question on Stackoveflow regarding this but nothing seems to work. My app regarding OWIN/Hangfire/WebAPI all seems to work okay. Until it comes to SignalR push messages.

If i call any notification hub endpoint from js client push messages go okay and i can receive push messages on any other connected client. But when i wan to send message from my api controller or hangfire job it never reaches to any client.

Startup.cs

public void Configuration(IAppBuilder app)
    {
        //var signalRHelper = new SignalRHelper(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>());
        var constants = new Constants();
        constants.Set(ConstantTypes.AllyHrNoReplyEmailAddress, Util.Constants.AllyHrNoReplyEmailAddress);
        constants.Set(ConstantTypes.SendGridKey, Util.Constants.SendGridKey);
        constants.Set(ConstantTypes.EncryptionKey, Util.Constants.EncryptionKey);
        constants.Set(ConstantTypes.ApiUrl, Util.Constants.ApiUrl);
        constants.Set(ConstantTypes.RootFolder, Util.Constants.RootFolder);
        constants.Set(ConstantTypes.FrontEndUrl, Util.Constants.FrontEndUrl);

        GlobalConfiguration.Configuration
            .UseSqlServerStorage("AllyHrDb");
        var config = System.Web.Http.GlobalConfiguration.Configuration;

        var builder = new ContainerBuilder();
        var jobBuilder = new ContainerBuilder();
        var signalRBuilder = new ContainerBuilder();
        var hubConfig = new HubConfiguration();

        builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();

        builder.Register(x => constants);
        builder.RegisterModule(new ServiceModule());


        jobBuilder.Register(x => constants);
        jobBuilder.RegisterModule(new HangfireServiceModule());


        signalRBuilder.RegisterModule(new SignalRServiceModule());
        signalRBuilder.Register(x => constants);
        signalRBuilder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
        signalRBuilder.RegisterType<ConnectionManager>().As<IConnectionManager>().ExternallyOwned().SingleInstance();
        signalRBuilder.RegisterType<NotificationHub>().ExternallyOwned().SingleInstance();
        signalRBuilder.RegisterType<SignalRHelper>().PropertiesAutowired().ExternallyOwned().SingleInstance();
        signalRBuilder.Register(context => context.Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<NotificationHub, INotificationHub>()).ExternallyOwned().SingleInstance();

        var hubContainer = signalRBuilder.Build();

        builder.RegisterInstance(hubContainer.Resolve<IConnectionManager>());
        builder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
        builder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
        builder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());

        jobBuilder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
        jobBuilder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
        jobBuilder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());

        var container = builder.Build();
        var jobContainer = jobBuilder.Build();


        var idProvider = new SignalRCustomUserIdProvider();
        hubConfig.Resolver = new AutofacDependencyResolver(hubContainer);
        hubConfig.Resolver.Register(typeof(IUserIdProvider), () => idProvider);
        app.Map("/signalr", map =>
        {
            map.UseCors(CorsOptions.AllowAll);
            map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
            {
                Provider = new QueryStringOAuthBearerProvider()
            });
            map.RunSignalR(hubConfig);
        });
        GlobalConfiguration.Configuration.UseAutofacActivator(jobContainer);

        app.UseAutofacMiddleware(container);
        app.UseAutofacWebApi(config);
        app.UseHangfireServer();
        app.UseHangfireDashboard();
        ConfigureAuth(app);
        app.UseWebApi(config);
    }

I had to use different container because i have db set to InstancePerRequest scope.

All my services are being resolved in notification hub class, no problems there. The only issues is when i try and send message from hangfire service or even from api controller using hub context it never reaches to any client.

NotificationHub.cs

public interface INotificationHub
{
    /// <summary>
    /// 
    /// </summary>
    void pushNotification(string message);
    /// <summary>
    /// 
    /// </summary>
    /// <param name="model"></param>
    void getNotification(object model);
    void getMessage(object model);
}
/// <summary>
/// Notification Hub
/// </summary>
[HubName("NotificationHub")]
[Authorize]
public class NotificationHub : Hub<INotificationHub>
{
    /// <summary>
    /// 
    /// </summary>
    public static IHubContext<INotificationHub> GlobalContext { get; private set; }
    private readonly IChatMessagingService _chatMessagingService;
    private readonly IUserService _userService;
    private Guid LoggedInUserId
    {
        get
        {
            var claims = ((ClaimsIdentity)Context.User.Identity).Claims.ToArray();
            var userIdClaim = claims.FirstOrDefault(x => x.Type.Equals("UserId"));
            if (userIdClaim == null) return Guid.Empty;
            return Guid.Parse(userIdClaim.Value);
        }
    }

    /// <summary>
    /// Consructor
    /// </summary>
    /// <param name="lifetimeScope"></param>
    /// <param name="context"></param>
    public NotificationHub(ILifetimeScope lifetimeScope, IHubContext<INotificationHub> context)
    {
        GlobalContext = context;
        try
        {
            var childScope = lifetimeScope.BeginLifetimeScope();
            _chatMessagingService = childScope.Resolve<IChatMessagingService>();
            _userService = childScope.Resolve<IUserService>();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    /// <summary>
    /// Notifications
    /// </summary>
    public void Notifications()
    {
        Clients.All.pushNotification("AllyHr" + LoggedInUserId);
    }

    /// <summary>
    /// Send Message
    /// </summary>
    /// <param name="model"></param>
    public void SendMessage(SendChatMessageBindingModel model)
    {
        var chatMessage = _chatMessagingService.SendMessageToGroup(LoggedInUserId, model.GroupId, model.Message);
        var recipientIds = _chatMessagingService.GetChatMembersByGroupId(LoggedInUserId, model.GroupId);
        var stringUserIds = new List<string>();
        var chatGroup = _chatMessagingService.GetChatGroupById(model.GroupId);
        foreach (var recipientId in recipientIds)
        {
            stringUserIds.Add(recipientId.ToString());
        }
        Clients.Users(stringUserIds).getNotification(new
        {
            message = "A new Message is Recieved in Chat Group: " + chatGroup.Name,
            groupId = chatGroup.Id
        });

        var chatMessageVm = chatMessage.Map<ChatMessage, ChatMessageViewModel>();
        chatMessageVm.Sender = _userService.Get(chatMessageVm.SenderId).Map<User, UserViewModel>();
        stringUserIds.Add(LoggedInUserId.ToString());
        Clients.Users(stringUserIds).getMessage(chatMessageVm);
    }

}

signalRhelper.cs use to call from api or from Hangfire services

public class SignalRHelper
{
    public IConnectionManager ConnectionManager { get; set; }
    public IHubContext<INotificationHub> HubContext { get; set; }

    /// <summary>
    /// Send Notifications to Users
    /// </summary>
    /// <param name="message"></param>
    /// <param name="userIds"></param>
    public void GetNotification(object message, IList<string> userIds)
    {
        HubContext.Clients.Users(userIds).getNotification(message);
    }

    /// <summary>
    /// Get LoggedInUser Id for SignalR
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    public static Guid GetLoggedInUserId(IPrincipal user)
    {
        var claim = GetLoggedinUserClaim(user);
        if (claim == null) return Guid.Empty;
        return Guid.Parse(claim.Value);
    }
    private static Claim GetLoggedinUserClaim(IPrincipal user)
    {
        var claim = ((ClaimsIdentity)user.Identity).Claims.ToArray();
        return claim.FirstOrDefault(x => x.Type.Equals("UserId"));
    }
}
Driving answered 4/9, 2018 at 8:38 Comment(8)
Does the hangfire job fires?Unheardof
Yes, it fires. Does what it needs to do. Calls signalR helper instance, which in turn calls hub context but nothing happens and we even have proper instance of hub in thereDriving
So to be clear: every thing works, except when you are signaling from a hangfire job?Unheardof
Yes, everything works except signaling from hangfire job or even from web api controller.Driving
I have even tried with Clients.All so everyone would receive, but nothing happens within hangfire service and api controllers.Driving
Okay, clear. So all calls from SignalRHelper aren't received by the clients. Although it seems not yo be the case, my guess would be that you are receiving a new instance of HubContext in the SignalRHelper class.Unheardof
@Unheardof - Yes, that was my first assumption but i put debugger points on contructor and Autofac created only single instances for these as debugger points were hit only once in constructors :(Driving
There's a ton of code here, can it be trimmed down at all?Trinitrocresol
A
0

Could this be related to Autofac creating a new lifetimescope for your call, but you were expecting to continue using the existing scope? Maybe check your autofac registrations for singleinstance / instanceperlifetimescope Just saying, but have you registered any static classes? They can keep your scope alive for far too long.

I see you're using multiple containerbuilders - that's not something we do over here, we have one 'massive' containerbuilder for each app. I'm curious why you're doing that? To satisfy my curiosity, could you try using a single containerbuilder and registering everything on that single builder? (Although it looks like this is a pattern for SignalR and autofac)

The documentation says: " a common error in OWIN integration is the use of GlobalHost."

It looks like you're doing exactly that.

Attachment answered 7/9, 2018 at 8:2 Comment(5)
I had to use a different container builder because main container builder uses DBContext as intance per request, when i use the same container for signalr DI and Hangfire DI throws exception immediately because this scope is not available in there.Driving
I now have make changes to startup file and still the same issue. See this link. filemanager.dynamitsolutions.com/index.php/s/5X71bHUBiwr4u6qDriving
I'll be happy to edit my answer once we get to the finer points. Let's try and help him first, yes?Attachment
Above link you'll find a very simplifies Startup.cs and another class from where as a demo i want to push notifications to clients.Driving
I removed global host stuff from code and now using single container. Still same issue. If i use Global host then everything works fine except that i am unable to resolve any of my services in hub class.Driving

© 2022 - 2024 — McMap. All rights reserved.