Open Closed

Replace Signal R with Azure Signal R #9976


0
devinedon created

Angular .NET Core 9.3

Looks like I cant connect to Azure Signal R via the web host. Still trying to connect to localhost?

Is there anything you think is missing? I even tried removing MapHub via UseEndpoints and moved them to UseAzureSignalR (not that I should need to do this?)

Thanks!


24 Answer(s)
  • 0
    devinedon created

    Any idea why its using localhost rather than the actual Azure service? I thought it was automatic?

  • 0
    ismcagdas created
    Support Team

    Hi @devinedon,

    Did you correctly configure your appsettings.json (or appsettings.Production.json) file and your Angular app's appconfig.production.json ? It should contain the correct URL for your deployed app.

  • 0
    devinedon created

    Hey,

    What config are you talking about?

    services.AddSignalR().AddAzureSignalR(_appConfiguration.GetValue<string>("Azure:SignalR:ConnectionString"));
    

    My setup is now setup like this

      "Azure": {
        "SignalR": {
          "ConnectionString": "Endpoint=https://synap-staging-signalr-service.service.signalr.net;AccessKey=abcdef=;Version=1.0;"
        }
      },
    

    However its still using localhost?

    Even adding the exact Azure Signal R Service sample code (const connection) shows localhost too (via the console.log)

  • 0
    devinedon created

    https://support.aspnetzero.com/QA/Questions/9493/SignalR-404-on-Azure-Multiple-App-Servers#answer-adf4666b-0f04-9740-3a44-39f7119bbbde

    This is why I'm confused, its the same as this....

  • 0
    rickfrankel created

    Assuming you are using the Angular front end. What do you have in your package.json for signalr.

    I am using "@microsoft/signalr": "^5.0.1",

    Everything just works for me.

    Also @ismcagdas is talking about the config files in the angular folder not on the server. They shouldn't matter too much however if you can login correctly.

  • 0
    rickfrankel created

    Can you provide more of the host project startup.cs file.

  • 0
    devinedon created

    Thanks for the response @rickfrankel

    I updated but sadly didnt work

    Here is the startup file

    using Abp.AspNetCore;
    using Abp.AspNetCore.Mvc.Antiforgery;
    using Abp.AspNetCore.SignalR.Hubs;
    using Abp.AspNetZeroCore.Web.Authentication.JwtBearer;
    using Abp.Castle.Logging.Log4Net;
    using Abp.Extensions;
    using Abp.Hangfire;
    using Abp.PlugIns;
    using Abp.Timing;
    using Castle.Facilities.Logging;
    using GraphQL.Server;
    using GraphQL.Server.Ui.Playground;
    using Hangfire;
    using HealthChecks.UI.Client;
    using IdentityServer4.Configuration;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Diagnostics.HealthChecks;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Server.Kestrel.Https;
    using Microsoft.Azure.Documents.Client;
    using Microsoft.Azure.SignalR;
    using Microsoft.Azure.Storage;
    using Microsoft.Azure.Storage.Blob;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.OpenApi.Models;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Serialization;
    using Owl.reCAPTCHA;
    using Stripe;
    using SynapWare.Authorization;
    using SynapWare.BlobStorage;
    using SynapWare.Configuration;
    using SynapWare.Configure;
    using SynapWare.CosmosDB;
    using SynapWare.EntityFrameworkCore;
    using SynapWare.Identity;
    using SynapWare.Schemas;
    using SynapWare.Web.Chat.SignalR;
    using SynapWare.Web.Common;
    using SynapWare.Web.HealthCheck;
    using SynapWare.Web.IdentityServer;
    using SynapWare.Web.Swagger;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using HealthChecksUISettings = HealthChecks.UI.Configuration.Settings;
    using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory;
    
    namespace SynapWare.Web.Startup
    {
        public class Startup
        {
            private const string DefaultCorsPolicyName = "localhost";
    
            private readonly IConfigurationRoot _appConfiguration;
            private readonly IWebHostEnvironment _hostingEnvironment;
    
            public Startup(IWebHostEnvironment env)
            {
                _hostingEnvironment = env;
                _appConfiguration = env.GetAppConfiguration();
            }
    
            public IServiceProvider ConfigureServices(IServiceCollection services)
            {
                //set time zone
                Clock.Provider = ClockProviders.Utc;
    
                // Add CosmosDb. This verifies database and collections existence.
                ConfigureCosmosDB(services);
    
                //MVC
                services.AddControllersWithViews(options =>
                {
                    options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
                }).AddNewtonsoftJson();
    
                services
                .AddSignalR(options => { options.EnableDetailedErrors = true; })
                .AddAzureSignalR(options => options.Endpoints = new ServiceEndpoint[]
                {
                    new ServiceEndpoint(_appConfiguration["Azure:SignalR:PrimaryConnectionString"], EndpointType.Primary, "Name1"),
                    new ServiceEndpoint(_appConfiguration["Azure:SignalR:SecondaryConnectionString"], EndpointType.Secondary, "Name2"),
                });
    
                //Configure CORS for angular2 UI
                services.AddCors(options =>
                {
                    options.AddPolicy(DefaultCorsPolicyName, builder =>
                    {
                        //App:CorsOrigins in appsettings.json can contain more than one address with splitted by comma.
                        builder
                            .WithOrigins(
                                // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
                                _appConfiguration["App:CorsOrigins"]
                                    .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                    .Select(o => o.RemovePostFix("/"))
                                    .ToArray()
                            )
                            .SetIsOriginAllowedToAllowWildcardSubdomains()
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();
                    });
                });
    
                if (bool.Parse(_appConfiguration["KestrelServer:IsEnabled"]))
                {
                    ConfigureKestrel(services);
                }
    
                IdentityRegistrar.Register(services);
                AuthConfigurer.Configure(services, _appConfiguration);
    
                //Identity server
                if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
                {
                    IdentityServerRegistrar.Register(services, _appConfiguration, options =>
                         options.UserInteraction = new UserInteractionOptions()
                         {
                             LoginUrl = "/UI/Login",
                             LogoutUrl = "/UI/LogOut",
                             ErrorUrl = "/Error"
                         });
                }
    
                if (WebConsts.SwaggerUiEnabled)
                {
                    //Swagger - Enable this line and the related lines in Configure method to enable swagger UI
                    services.AddSwaggerGen(options =>
                    {
                        options.SwaggerDoc("v1", new OpenApiInfo() { Title = "SynapWare API", Version = "v1" });
                        options.DocInclusionPredicate((docName, description) => true);
                        options.ParameterFilter<SwaggerEnumParameterFilter>();
                        options.SchemaFilter<SwaggerEnumSchemaFilter>();
                        options.OperationFilter<SwaggerOperationIdFilter>();
                        options.OperationFilter<SwaggerOperationFilter>();
                        options.CustomDefaultSchemaIdSelector();
                    }).AddSwaggerGenNewtonsoftSupport();
                }
    
                //Recaptcha
                services.AddreCAPTCHAV3(x =>
                {
                    x.SiteKey = _appConfiguration["Recaptcha:SiteKey"];
                    x.SiteSecret = _appConfiguration["Recaptcha:SecretKey"];
                });
    
                if (WebConsts.HangfireDashboardEnabled)
                {
                    //Hangfire(Enable to use Hangfire instead of default job manager)
                    services.AddHangfire(config =>
                    {
                        config.UseSqlServerStorage(_appConfiguration.GetConnectionString("Default"));
                    });
                }
    
                if (WebConsts.GraphQL.Enabled)
                {
                    services.AddAndConfigureGraphQL();
                }
    
                if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
                {
                    services.AddAbpZeroHealthCheck();
    
                    var healthCheckUISection = _appConfiguration.GetSection("HealthChecks")?.GetSection("HealthChecksUI");
    
                    if (bool.Parse(healthCheckUISection["HealthChecksUIEnabled"]))
                    {
                        services.Configure<HealthChecksUISettings>(settings =>
                        {
                            healthCheckUISection.Bind(settings, c => c.BindNonPublicProperties = true);
                        });
                        services.AddHealthChecksUI();
                    }
                }
    
                services.AddScoped<IDeviceDataRepository, DeviceDataRepository>();
    
                services.AddScoped<IBlobRepository>(factory =>
                {
                    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_appConfiguration["ConnectionStrings:DeviceBlobStorage"]);
                    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
                    var container = blobClient.GetContainerReference(_appConfiguration["BlobStorage:DeviceContainerName"]);
                    return new AzureBlobRepository(container);
                });
    
                //Configure Abp and Dependency Injection
                return services.AddAbp<SynapWareWebHostModule>(options =>
                {
                    //Configure Log4Net logging
                    options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                        f => f.UseAbpLog4Net().WithConfig(_hostingEnvironment.IsDevelopment()
                                ? "log4net.config"
                                : "log4net.Production.config")
                    );
    
                    options.PlugInSources.AddFolder(Path.Combine(_hostingEnvironment.WebRootPath, "Plugins"), SearchOption.AllDirectories);
                });
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
            {
                //Initializes ABP framework.
                app.UseAbp(options =>
                {
                    options.UseAbpRequestLocalization = false; //used below: UseAbpRequestLocalization
                });
    
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseStatusCodePagesWithRedirects("~/Error?statusCode={0}");
                    app.UseExceptionHandler("/Error");
                }
    
                app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; await next(); } });
                app.UseStaticFiles();
                app.UseRouting();
    
                app.UseCors(DefaultCorsPolicyName); //Enable CORS!
    
                app.UseAuthentication();
                app.UseJwtTokenMiddleware();
    
                if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
                {
                    app.UseJwtTokenMiddleware("IdentityBearer");
                    app.UseIdentityServer();
                }
    
                app.UseAuthorization();
    
                using (var scope = app.ApplicationServices.CreateScope())
                {
                    if (scope.ServiceProvider.GetService<DatabaseCheckHelper>().Exist(_appConfiguration["ConnectionStrings:Default"]))
                    {
                        app.UseAbpRequestLocalization();
                    }
                }
    
                if (WebConsts.HangfireDashboardEnabled)
                {
                    //Hangfire dashboard &server(Enable to use Hangfire instead of default job manager)
                    app.UseHangfireDashboard(WebConsts.HangfireDashboardEndPoint, new DashboardOptions
                    {
                        Authorization = new[] { new AbpHangfireAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) }
                    });
                    app.UseHangfireServer();
                }
    
                if (bool.Parse(_appConfiguration["Payment:Stripe:IsActive"]))
                {
                    StripeConfiguration.ApiKey = _appConfiguration["Payment:Stripe:SecretKey"];
                }
    
                if (WebConsts.GraphQL.Enabled)
                {
                    app.UseGraphQL<MainSchema>();
                    if (WebConsts.GraphQL.PlaygroundEnabled)
                    {
                        app.UseGraphQLPlayground(
                            new GraphQLPlaygroundOptions()); //to explorer API navigate https://*DOMAIN*/ui/playground
                    }
                }
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapHub<AbpCommonHub>(AppConsts.SignalRAbpCommonHubPath);
                    endpoints.MapHub<ChatHub>(AppConsts.SignalRChatHubPath);
                    endpoints.MapHub<TestChatHub>("/chat");
    
                    endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
                    endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    
                    if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
                    {
                        endpoints.MapHealthChecks("/health", new HealthCheckOptions()
                        {
                            Predicate = _ => true,
                            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
                        });
                    }
                });
    
                //app.UseAzureSignalR(endpoints =>
                //{
                //    endpoints.MapHub<AbpCommonHub>(AppConsts.SignalRAbpCommonHubPath);
                //    endpoints.MapHub<ChatHub>(AppConsts.SignalRChatHubPath);
                //    endpoints.MapHub<ChatHub>("/chat");
                //});
    
                if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
                {
                    if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksUI:HealthChecksUIEnabled"]))
                    {
                        app.UseHealthChecksUI();
                    }
                }
    
                if (WebConsts.SwaggerUiEnabled)
                {
                    // Enable middleware to serve generated Swagger as a JSON endpoint
                    app.UseSwagger();
                    // Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
    
                    app.UseSwaggerUI(options =>
                    {
                        options.SwaggerEndpoint(_appConfiguration["App:SwaggerEndPoint"], "SynapWare API V1");
                        options.IndexStream = () => Assembly.GetExecutingAssembly()
                            .GetManifestResourceStream("SynapWare.Web.wwwroot.swagger.ui.index.html");
                        options.InjectBaseUrl(_appConfiguration["App:ServerRootAddress"]);
                    }); //URL: /swagger
                }
            }
    
            private void ConfigureKestrel(IServiceCollection services)
            {
                services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
                {
                    options.Listen(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 443),
                        listenOptions =>
                        {
                            var certPassword = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Password");
                            var certPath = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Path");
                            var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath, certPassword);
                            listenOptions.UseHttps(new HttpsConnectionAdapterOptions()
                            {
                                ServerCertificate = cert
                            });
                        });
                });
            }
    
            private void ConfigureCosmosDB(IServiceCollection services)
            {
                //https://github.com/Azure-Samples/PartitionedRepository
                //To connect to multiple collections see example above
                var serviceEndpoint = new Uri(_appConfiguration.GetValue<string>("CosmosDb:ServiceEndpoint"), UriKind.Absolute); 
                var authKey = _appConfiguration.GetValue<string>("CosmosDb:authKey");
                var databaseName = _appConfiguration.GetValue<string>("CosmosDb:DatabaseName");
                var collectionNames = new List<string>
                {
                    _appConfiguration.GetValue<string>("CosmosDb:DeviceDataCollectionName"),
                    _appConfiguration.GetValue<string>("CosmosDb:LatestDeviceDataCollectionName")
                };
    
                var documentClient = new DocumentClient(serviceEndpoint, authKey, new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore,
                    DefaultValueHandling = DefaultValueHandling.Ignore,
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                });
                documentClient.OpenAsync().Wait();
    
                var cosmosDbClientFactory = new CosmosDbClientFactory(databaseName, collectionNames, documentClient);
                cosmosDbClientFactory.EnsureDbSetupAsync().Wait();
                services.AddSingleton<ICosmosDbClientFactory>(cosmosDbClientFactory);
            }
        }
    }
    
    
  • 0
    ismcagdas created
    Support Team

    Hi @devinedon

    Could you go to SignalRHelper.ts and change remoteServiceBaseUrl to Azure SignalR url ?

  • 0
    devinedon created

    Hey,

    Similar outcome. Is the handshaking logic similar? Surely there is more to all this?

  • 0
    rickfrankel created

    Hey @devinedon,

    Undo those changes in the SignalR helper.

    What happens is the client code makes a call to https://youapisite/signalr/negotiate?enc_auth_token=bigtokenhere&negotiateVersion=1

    This is an API in the the host site that then returns a payload which contains the URL to use. In my case something like this.

    { accessToken: xxxx, availableTransports: [], negotiateVersion: 0, url: "https://myazure.signalr.net/client/etcetcetc }

    I assume if you use the network tab in the browser debug tools you are seeing this API return localhost for you.

    https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-internals

    This negotiate endpoint is exposed by registering the AzureSignalR correctly on the server.

    From the looks of your startup.cs it looks to be the same as mine (I'm on V10 so some small differences but have been using Azure SignalR since v7).

    I would be confirming that the API project that your front end code is calling is the same host backend that you have your configured startup.cs in. Eg: Somehow you don't have two API hosts running in the backend.

    Also confirm in the appsettings.json in your host project that you have "AllowAnonymousSignalRConnection": "true"

    Not sure that will make a difference but it does affect the security of the call to the /signalr endpoint.

    Hope that gets you in the right direction.

    It really does look like something is not registering on the server correctly and thus the negotiate call is not returning the right data.

  • 0
    devinedon created

    Hey @rickfrankel

    Thanks for the awesome info!

    So what you said got me digging into the service mode. What is yours? Default, serverless, classic?

    In Serverless: Azure SignalR Service is not connected yet, please try again later.

    In Default Connection count reaches limit.

    However, surely Serverless is the correct approach if I plan on utilizing an Azure Function to push messages (IoT device telemetry messages) as well, outside of the web host?

    ...Im now starting to think not, 2 seperate Azure Signal R resources. One default for abp and a serverless for azure function?

    EDIT:

    Its working! However, needs to be in classic mode. Azure functions need to be set in serverless mode. Trigger off say a cosmosdb insert, would love to utilize appnotifier to push deviceeventdata through the system.

  • 0
    devinedon created

    If you dont mind explaining your Azure Signal R setup - Settings and how you are pushing messages outside the web.host that would be greatly appreciated. I want to try utilize the core libs (abp framework in general) as much as possible without bringing in too much of my own code hence why pushing on with this.

  • 0
    ismcagdas created
    Support Team

    Hi @devinedon

    Its working! However, needs to be in classic mode

    Happy to hear that. I hope @rickfrankel will share his experience.

  • 1
    rickfrankel created

    Hi All,

    Sorry been busy actually release our version 10 upgrade to production over the weekend. All looks finally to be stable so hopefully if Monday goes through smoothly I'll come back in the next day or so with our SignalR details.

    Just for reference I am using default mode.

  • 0
    devinedon created

    Goodluck for that and thanks for responding guys :)

    Yeah Im still puzzled about how to best push messages into Azure Signal R from outside the web host whilst still using as much of the abp/anz framework/work as possible. I can do it manually but would love to stream line it as best as possible - such as using AppNotifier?

    BTW on default mode it now works with web.host. It might have been me flipping too often and breaking Azure :)

  • 0
    ismcagdas created
    Support Team

    Hi @devinedon

    Yes, AppNotifier should do it. Did you have any problems while using it ?

  • 0
    devinedon created

    Hey @ismcagdas

    I will demo the process using appNotifier.SendMessageAsync

    I have an Azure function which gets triggered by a cosmosDB changefeed (on insert). This is where I would like to push to the relevent user with information about the relevent IoT Message. The problem comes how to setup Signal R/Azure Signal R within an Azure function and even more so, in order to reuse existing logic and abp modules etc etc.

    As it stands registering Signal R like this wont work based on the Azure function design:

    builder.Services
    .AddSignalR(options => { options.EnableDetailedErrors = true; })
    .AddAzureSignalR(options => options.Endpoints = new ServiceEndpoint[]
    {
        new ServiceEndpoint(_appConfiguration["Azure:SignalR:PrimaryConnectionString"], EndpointType.Primary, "Name1"),
        new ServiceEndpoint(_appConfiguration["Azure:SignalR:SecondaryConnectionString"], EndpointType.Secondary, "Name2"),
    });
    

    Azure Function : Microsoft.Azure.WebJobs.Extensions.SignalRService is the package that I attempted with [SignalRConnectionInfo(HubName = "chathub")] SignalRConnectionInfo connectionInfo - https://github.com/Azure/azure-functions-signalrservice-extension

    namespace SynapWare.AzureFunction.IoT
    {
        public class CosmosdbChangeFeed
        {
            private readonly LatestDeviceDataRepository _latestdeviceDataRepository;
    
            public CosmosdbChangeFeed(ICosmosDbClientFactory latestdeviceDataRepository)
            {
                _latestdeviceDataRepository = new LatestDeviceDataRepository(latestdeviceDataRepository);
            }
    
            [FunctionName("CosmosdbChangeFeed")]
            public async Task Run([CosmosDBTrigger(
                databaseName: "synap-staging-db",
                collectionName: "devicedata",
                ConnectionStringSetting = "ConnectionString",
                LeaseCollectionName = "lease-latestdevicedata",
                CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> input, ILogger log,
                //SignalR output Binding
                //[SignalR(HubName = "chat")] IAsyncCollector<SignalRMessage> signalRMessages)
                [SignalRConnectionInfo(HubName = "chathub")] SignalRConnectionInfo connectionInfo)
            {
                if (input != null && input.Count > 0)
                {
                    //Build connection with token
                    HubConnection _connection = new HubConnectionBuilder()
                    .WithUrl(connectionInfo.Url, option =>
                    {
                        option.Headers.Add("Authorization", $"Bearer {connectionInfo.AccessToken}");
                    })
                    .Build();
    
                    //Start the hub connection
                    await _connection.StartAsync();
    
                    //My Hub has a BroadcastMessage method that receives 2 arguments
                    //await _connection.SendAsync("getChatMessage", "test", "test", default);
                    //ChatMessageDto tst = new ChatMessageDto()
                    //{
                    //    TenantId = null,
                    //    UserId = 1,
                    //    Message = "test",
                    //    CreationTime = DateTime.UtcNow,
                    //    TargetUserId = 5,
                    //    TargetTenantId = null
                    //};
                    //await _connection.SendAsync("getChatMessage", tst);
                    
                    await _connection.SendAsync("App.SimpleMessage", "test");
    
                    foreach (var document in input)
                    {
                        //TODO : Only use the latest event for each device 
    
                        var jsonSer = new JsonSerializerSettings
                        {
                            NullValueHandling = NullValueHandling.Ignore,
                            DefaultValueHandling = DefaultValueHandling.Ignore,
                            ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }
                        };
                        var deviceData = JsonConvert.DeserializeObject<DeviceData>(document.ToString(), jsonSer);
                        deviceData.Id = deviceData.Serial;
                        await _latestdeviceDataRepository.UpsertAsync(deviceData);
    
                        // broadcast through SignalR
                        //var signalR = new AzureSignalR("Endpoint=https://synap-staging-signalr-              service.service.signalr.net;AccessKey=abc=;Version=1.0;");
                        //await signalR.SendAsync("chathub", "updateSignInStats", "hello world");
    
                        //await signalRMessages.AddAsync(new SignalRMessage
                        //{
                        //    Target = "getChatMessage",//"newMessage",
                        //    Arguments = new[] { deviceData }
                        //});
    
                        using (var obj = Startup.iocManager.ResolveAsDisposable<AzFunctionAbpExecuter>())
                        {
                            //await obj.Object.AddNewDeviceEventAppNotifier(deviceData);
    
                            await obj.Object.SendTestMessageAppNotifier();
                        }
                    }
                }
            }
        }
    }
    

    The function above updates a cosmosdb collection and then I would love to push telemetry messages to the UI.

    The ABP module for the Azure Function is defined as

    namespace SynapWare.AzureFunction.IoT
    {
        [DependsOn
            (typeof(AbpZeroCoreEntityFrameworkCoreModule),
            typeof(SynapWareEntityFrameworkCoreModule),
            typeof(AbpAspNetZeroCoreModule),
            typeof(SynapWareCoreModule),
            typeof(SynapWareClientModule), 
            typeof(AbpAutoMapperModule),
            typeof(AbpZeroCoreModule),
            typeof(AbpAspNetCoreSignalRModule))]
        public class SynapWareAzFuncIoTModule : AbpModule
        {
            private readonly IConfigurationRoot _appConfiguration;
            private readonly IServiceCollection _serviceCollection;
    
            public SynapWareAzFuncIoTModule()
            {
                _appConfiguration = Startup._appConfiguration;
                _serviceCollection = Startup.serviceCollection;
            }
    
            public override void PreInitialize()
            {
                Configuration.Localization.IsEnabled = false;
                Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
                Configuration.Modules.AspNetZero().LicenseCode = _appConfiguration["AbpZeroLicenseCode"];
                //set time zone
                Clock.Provider = ClockProviders.Utc;
    
                Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString(
                SynapWareConsts.ConnectionStringName
                );
    
                Configuration.ReplaceService(typeof(IEventBus), () =>
                {
                    IocManager.IocContainer.Register(
                        Component.For<IEventBus>().Instance(NullEventBus.Instance)
                    );
                });
            }
    
            public override void Initialize()
            {
                IocManager.RegisterAssemblyByConvention(typeof(SynapWareAzFuncIoTModule).GetAssembly());
                Register(IocManager);
            }
    
            public static void Register(IIocManager iocManager)
            {
                var services = new ServiceCollection();
                IdentityRegistrar.Register(services);
                WindsorRegistrationHelper.CreateServiceProvider(iocManager.IocContainer, services);
            }
        }
    }
    

    And finally the AbpExecutor

    namespace SynapWare.AzureFunction.IoT
    {
        public class AzFunctionAbpExecuter : ITransientDependency
        {
            private readonly IRepository<Tenant> _tenantRepository;
            private readonly UserManager _userManager;
            private readonly IAppNotifier _appNotifier;
    
            public AzFunctionAbpExecuter(IRepository<Tenant> tenantRepository, IAppNotifier appNotifier, UserManager userManager)
            {
                _tenantRepository = tenantRepository;
                _appNotifier = appNotifier;
                _userManager = userManager;
            }
    
            public async Task AddNewDeviceEventAppNotifier(SynapWare.CosmosDB.DeviceData data)
            {
                await _appNotifier.NewDeviceEventAsync(data);
            }
    
            public async Task SendTestMessageAppNotifier()
            {
                var user = await _userManager.FindByEmailAsync("admin@aspnetzero.com");
    
                await _appNotifier.SendMessageAsync(
                    user.ToUserIdentifier(), "test", Abp.Notifications.NotificationSeverity.Warn);
            }
        }
    }
    

    The UI updates (with the relevenet db changes) when using await _appNotifier.SendMessageAsyn for example but nothing real time (as signal r not setup or pushed correctly)

  • 0
    ismcagdas created
    Support Team

    Hi @devinedon

    Is it possible to share your project with us via email ? I think we can find the problem faster when looking into your project. If that's OK, you can send it to info@aspnetzero.com.

    Thanks,

  • 0
    devinedon created

    Great stuff, thanks. I will get going and prepare a solution for you. You will need access to an Azure account regarding an Azure Signal R resource so either if you have a dev one to use yours or we can expose ours all already done in the solution.

    In fact the entire process will be easier if you just had full access. Let me get that for you thanks!

  • 0
    ismcagdas created
    Support Team

    Hi @devinedon

    Thanks a lot. It would be easier if we can use your account but we can also use ours if it is going to be a problem for you.

  • 0
    devinedon created

    Hey,

    Thanks so much for the kind offer. We have decided to rather push the telemetry messages to the web host using Azure Service Bus and then from there out to all the users via AppNotifier. We are very familiar with Service Bus it and it will streamline better with ABP imo. We also wont need Azure Signal R either.

    Going to close this question. Thanks all.

  • 0
    devinedon created

    Sorry, me again. From a consumer point of view, do you think this is a good idea? https://damienbod.com/2019/04/23/using-azure-service-bus-queues-with-asp-net-core-services/

    Using service bus queues (or event hub) to consume and then make use of AppNotifier (and mobile app push) from within web.host.

  • 1
    ismcagdas created
    Support Team

    Hi @devinedon

    Sorry for the delay, yes, this is a good approach I think.

  • 0
    devinedon created

    Perfect, done :)

    Using background tasks with hosted services in ASP.NET Core it works well. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio

    Beautiful repo explaining all this -> https://github.com/Daniel-Krzyczkowski/AzureDeveloperTemplates/tree/34394d365a72c0325a5e155d7bc0b4547f8ded9e

    if anyone needs.