24 Answer(s)
-
0
Any idea why its using localhost rather than the actual Azure service? I thought it was automatic?
-
0
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
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
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
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
Can you provide more of the host project startup.cs file.
-
0
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
Hi @devinedon
Could you go to
SignalRHelper.ts
and changeremoteServiceBaseUrl
to Azure SignalR url ? -
0
-
0
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
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
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
Hi @devinedon
Its working! However, needs to be in classic mode
Happy to hear that. I hope @rickfrankel will share his experience.
-
1
-
0
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
Hi @devinedon
Yes, AppNotifier should do it. Did you have any problems while using it ?
-
0
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("[email protected]"); 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
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 [email protected].
Thanks,
-
0
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
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
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
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
Hi @devinedon
Sorry for the delay, yes, this is a good approach I think.
-
0
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.