Application Insights in IHostedService console application - .net-core

I am trying to enable Application Insights in a console application using IHostedService (for the moment, it's a simple console application which we run as WebJob, in future in containers).
As far as my knowledge goes, in the following code, so far we do not have any extension to register globally Application Insights as an implementation of ILogger:
public static class Program
{
public static Task Main(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile("appsettings.json", optional: false);
config.AddEnvironmentVariables();
})
.ConfigureLogging((context, logging) =>
{
logging.AddConfiguration(context.Configuration.GetSection("Logging"));
if (context.HostingEnvironment.IsDevelopment())
{
logging.AddConsole();
}
else
{
//TODO: register ApplicationInsights
}
});
return hostBuilder.RunConsoleAsync();
}
}
So far, I found out that potentially, I should be able to set everything up using custom implementation of the logger, i.e. public class ApplicationInsightsLogger : ILogger, and then... register it in the container so that DI resolves it.
Is this the right direction?

I made an extension that I could use from either an IHost or an IWebHost:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
public static class LoggingBuilderExtensions
{
public static ILoggingBuilder AddLogging(this ILoggingBuilder loggingBuilder)
{
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingBuilder.AddAzureWebAppDiagnostics();
loggingBuilder.AddApplicationInsights();
return loggingBuilder;
}
}
Since I'm not sending in the context (HostBuilderContext or WebHostBuilderContext), I can use it in either app type like this:
new HostBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
or
WebHost.CreateDefaultBuilder().ConfigureLogging(loggingBuilder => loggingBuilder.AddLogging())
If you needed a specific property from the context (like environment type), you could extract that and send it in as a parameter to the extension.
Here's a reference: https://github.com/Microsoft/ApplicationInsights-dotnet-logging/blob/develop/src/ILogger/Readme.md

Related

.Net core 2.2 API versioning and proper routing

I am creating an API. I use swagger but due to a huge number of controllers and actions, I want to split API endpoint by domain. To get this I thought about versioning of the API. I thought about using the Status of ApiVersion. The code of my controllers is below.
[ApiVersion("1.0-First")] //This is ApiVersion MajorVersion = 1, Status = "First"
[Route("api/v{version:apiVersion}/[controller]")]
public class FirstController
[ApiVersion("1.0-Second")]
[Route("api/v{version:apiVersion}/other")]
public class SecondController
My swagger looks fine and the definitions of parts of API are good. (I know that path should be without capital letters - this is for test purposes only)
But swagger can't reach any endpoint. Because the valid endpoint is at /api/v1.0-First/First not /api/v1/First.
My startUp class looks like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddApiExplorer();
services.AddApiVersioning(c =>
{
c.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("V"),
new UrlSegmentApiVersionReader());
c.ReportApiVersions = false;
c.DefaultApiVersion = new ApiVersion(1, 0);
});
services.AddVersionedApiExplorer(options =>
{
options.SubstituteApiVersionInUrl = true;
options.SubstitutionFormat = "V";
options.DefaultApiVersion = new ApiVersion(1, 0);
});
services.RegisterSwaggerConfiguration();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseMvc();
app.AddSwagger(app.ApplicationServices.GetService<IApiVersionDescriptionProvider>(), Configuration);
}
There is some static class I wrote to add the dependencies based on IApiVersionDescriptionProvider
public static class SwaggerExtension
{
public static void RegisterSwaggerConfiguration(this IServiceCollection services)
{
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen();
}
public static void AddSwagger(this IApplicationBuilder app, IApiVersionDescriptionProvider provider, IConfiguration configuration)
{
var prefix = "swagger";
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.RoutePrefix = string.Empty;
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"{prefix}/{description.GroupName}/swagger.json", description.GroupName);
}
});
}
}
And another class for SwaggerDoc generation.
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider provider;
private readonly IConfiguration configuration;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IConfiguration configuration)
{
this.provider = provider;
this.configuration = configuration;
}
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = description.GroupName,
Version = description.ApiVersion.ToString(),
};
if (description.IsDeprecated)
{
info.Description += " This API version has been deprecated.";
}
return info;
}
}
I want to get the routing work as api/v1/First or api/v1.0/First (this should not matter).
Maybe writting some custom middleware to handle this case would be good idea?
By now I am out of ideas and in general I couldn't find any articles about using status of ApiVersion.
EDIT:
Changed Title.
We had a similar problem some time ago. We needed to split an Api by a customer privilege/domain. The research took some time as well :), please note that we are using NSwag.
So as you already mentioned (custom middleware) we've created a custom OperationProcessor and used base type checking. Take a look at an example:
services.AddOpenApiDocument(document =>
{
document.Title = "API A";
document.OperationProcessors.Insert(0, new IncludeAApiControllersInSwagger());
});
services.AddOpenApiDocument(document =>
{
document.Title = "API B";
document.OperationProcessors.Insert(0, new IncludeBApiControllersInSwagger());
});
and then
private class IncludeAApiControllersInSwagger : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
return IsControllerInType(context, typeof(AApiController));
}
}
private class IncludeBApiControllersInSwagger : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
return IsControllerInType(context, typeof(BApiController));
}
}
The last step would be to build a proper inheritance over your controllers.
An API version is always an API version; the values are explicit - by design. There is no universe where 1.0-First can map to an API, but not include the status.
The status is most useful for pre-releases. For example, you might have /first?api-version=1.0-preview.1. When you have a volatile, preview version of an API, this prevents you from having to bump up to 1.1 and so on. 1.0 is greater than 1.0-preview.1.
From your description, it sounds like you want to group or categorize your APIs by an additional level. The Swagger UI only supports a single level of grouping, but ASP.NET API Versioning 7.0+ now has support to make custom group names with API versions easy to configure using the FormatGroupName option.
If your API has a custom group name like this:
[ApiVersion(1.0)]
[ApiExplorerSettings(GroupName = "First")]
[Route("api/v{version:apiVersion}/[controller]")]
public class FirstController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
}
You can now configure the combination of both like this:
services.AddApiVersioning()
.AddApiExplorer(
options =>
{
options.SubstituteApiVersionInUrl = true;
options.FormatGroupName = (group, version) => $"{version}-{group}";
});
This only works if you set a custom group name and define a callback. The rules are:
Default configuration; formatted ApiVersion
Group name set, but not callback; use group name
Group name and callback set; result for callback with group and formatted ApiVersion
Only callback set; ignored and uses default configuration as there's no group name
The ApiVersion is formatted according to GroupNameFormat. By default, this will simply be ApiVersion.ToString(). You can still use it if you want to. For example, if GroupNameFormat = "'v'VVV";, then the formatted name via the callback will result in v1-First.
Despite all of this configuration and grouping, the route to your API will still be: api/v1/first. I believe that will get you both of your goals.

DryIoc and IServiceProvider on Prism for Xamarin.Forms (DryIoc.Microsoft.DependencyInjection)

I've got a Prism application with DryIoc as container.
I'd like IHttpClientFactory to provide HttpClients to my typed clients, which are like this:
public class ExampleService : IExampleService
{
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetExamplesAsync()
{
// Code deleted for brevity.
}
}
In App.xaml.cs I register my typed client so they can be injected in viewmodels with the following:
public partial class App
// ...
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.Register<IExampleService, ExampleService>();
}
And that's before trying to use IHttpClientFactory.
Now, to add it, we should AddHttpClient() on IServiceCollection. That's where I thought DryIoc.Microsoft.DependencyInjection was needed, so, still in App.xaml.cs, I wrote the following:
public partial class App
// ...
protected override IContainerExtension CreateContainerExtension()
{
var services = new ServiceCollection();
services.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
var container = new Container(CreateContainerRules())
.WithDependencyInjectionAdapter(services);
return new DryIocContainerExtension(container);
}
The problem is that in my ExampleService I'm getting client with the following specs:
{
"DefaultRequestHeaders":[
],
"BaseAddress":null,
"Timeout":"00:01:40",
"MaxResponseContentBufferSize":2147483647
}
whilst I expected BaseAddress to be https://api.example.com/, so the REST API call fails.
What is the correct pattern to use IServiceProvider when using Prism for Xamarin.Forms with DryIoc? Unfortunately there's no documentation or open source code available on the following matter, and I am kind of lost.
Thanks you, and have a great day.
UPDATE #1
As per kind Dan S. guidance, DryIoc.Microsoft.DependencyInjection was uninstalled so the project came back at its state before trying to use IServiceCollection dependencies (in my case, IHttpClientFactory), then I installed Prism.Forms.Extended and later Prism.DryIoc.Extensions.
After that CreateContainerExtension() in App.xaml.cs became:
protected override IContainerExtension CreateContainerExtension()
{
var containerExtension = PrismContainerExtension.Current;
containerExtension.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
return containerExtension;
}
and containerRegistry.Register<IExampleService, ExampleService>(); was removed from RegisterTypes().
Now ExampleService finally gets its HttpClient injected and everything is working.
UPDATE #2
The only packages related to Prism I am using are Prism.DryIoc.Forms and Prism.DryIoc.Extensions.
I completely removed the override of CreateContainerExtension() in App.xaml.cs and refactored RegisterTypes() to
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
}
This way I get thrown a NotImplementedException.
However, by overriding CreateContainerExtension() with the following:
protected override IContainerExtension CreateContainerExtension() => PrismContainerExtension.Current;
Everything is finally back to working!
If you want to use IServiceCollection extensions such as AddHttpClient I would suggest that you use the Prism Container Extensions. In your case it would be Prism.DryIoc.Extensions. The Container Extensions provide a lot of additional support including support for registering services with via the Service Collection extensions.
You can either install Prism.Forms.Extended and it will all just work, or you can update your App as follows:
protected override IContainerExtension CreateContainerExtension() =>
PrismContainerExtension.Current;
Adding as this is the only post I've found in weeks of searching that explains how to do this.
I'm using Unity rather than Dryloc but the solution is the same.
Install ONLY these additional packages:
Prism.Forms.Extended
Prism.Unity.Extensions
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//Omitted Code
containerRegistry.RegisterServices(serviceCollection =>
{
serviceCollection.AddHttpClient<IApiService, ApiService>(client =>
{
client.BaseAddress = new Uri("Your Address Here");
});
});
}
public ApiService(HttpClient client)
{
//Do Stuff
}

DI in Azure Functions

I have some class libraries that I use in my ASP.NET Web API app that handle all my backend stuff e.g. CRUD operations to multiple databases like Azure SQL Database, Cosmos DB, etc.
I don't want to re-invent the wheel and able to use them in a new Azure Functions that I'm creating in Visual Studio 2017. All my repository methods use an interface. So, how will I implement dependency injection in my new Azure function?
I'm not seeing any support for DI but I'm a bit confused. It appears Azure Functions are based on the same SDK as WebJobs and I think last year Microsoft had started supporting DI in WebJobs - I know for sure because I implemented it using Ninject.
Is there way around this so that I can use my existing libraries in my new Azure Functions project?
I see these two techniques in addition to the service locator (anti)pattern. I asked the Azure Functions team for their comments as well.
https://blog.wille-zone.de/post/azure-functions-dependency-injection/
https://blog.wille-zone.de/post/azure-functions-proper-dependency-injection/
There is an open feature request on the GitHub pages for Azure Functions concerning this matter.
However, the way I'm approaching this is using some kind of 'wrapper' entry point, resolve this using the service locator and and start the function from there.
This looks a bit like this (simplified)
var builder = new ContainerBuilder();
//register my types
var container = builder.Build();
using(var scope = container.BeginLifetimeScope())
{
var functionLogic = scope.Resolve<IMyFunctionLogic>();
functionLogic.Execute();
}
This is a bit hacky of course, but it's the best there is until there is at the moment (to my knowledge).
I've seen the willie-zone blog mentioned a lot when it comes to this topic, but you don't need to go that route to use DI with Azure functions.
If you are using Version2 you can make your Azure functions non-static. Then you can add a public constructor for injecting your dependencies. The next step is to add an IWebJobsStartup class. In your startup class you will be able to register your services like you would for any other .Net Core project.
I have a public repo that is using this approach here: https://github.com/jedi91/MovieSearch/tree/master/MovieSearch
Here is a direct link to the startup class: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Startup.cs
And here is the function: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Functions/Search.cs
Hope this approach helps. If you are wanting to keep your Azure Functions static then the willie-zone approach should work, but I really like this approach and it doesn't require any third party libraries.
One thing to note is the Directory.Build.target file. This file will copy your extensions over in the host file so that DI will work once the function is deployed to Azure. Running the function locally does not require this file.
Azure Functions Depdendency Injection was announced at MSBuild 2019. Here's an example on how to do it:
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton((s) => {
return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
});
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}
}
}
GitHub Example
Documentation
As stated above, it was just announced at Build 2019. It can now be setup almost exactly like you would in an ASP .Net Core app.
Microsoft Documentation
Short Blog I Wrote
Actually there is a much nicer and simpler way provided out of the box by Microsoft. It is a bit hard to find though. You simply create a start up class and add all required services here, and then you can use constructor injection like in regular web apps and web apis.
This is all you need to do.
First I create my start up class, I call mine Startup.cs to be consistent with Razor web apps, although this is for Azure Functions, but still it's the Microsoft way.
using System;
using com.paypal;
using dk.commentor.bl.command;
using dk.commentor.logger;
using dk.commentor.sl;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using org.openerp;
[assembly:Microsoft.Azure.WebJobs.Hosting.WebJobsStartup(typeof(dk.commentor.starterproject.api.Startup))]
namespace dk.commentor.starterproject.api
{
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddSingleton<ILogger, CommentorLogger>();
builder.Services.AddSingleton<IPaymentService, PayPalService>();
builder.Services.AddSingleton<IOrderService, OpenERPService>();
builder.Services.AddSingleton<ProcessOrderCommand>();
Console.WriteLine("Host started!");
}
}
}
Next I change the method call in the function from static to non-static, and I add a constructor to the class (which is now also non-static). In this constructor I simply add the services I require as constructor parameters.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using dk.commentor.bl.command;
namespace dk.commentor.starterproject.api
{
public class ProcessOrder
{
private ProcessOrderCommand processOrderCommand;
public ProcessOrder(ProcessOrderCommand processOrderCommand) {
this.processOrderCommand = processOrderCommand;
}
[FunctionName("ProcessOrder")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger ProcessOrder called!");
log.LogInformation(System.Environment.StackTrace);
string jsonRequestData = await new StreamReader(req.Body).ReadToEndAsync();
dynamic requestData = JsonConvert.DeserializeObject(jsonRequestData);
if(requestData?.orderId != null)
return (ActionResult)new OkObjectResult($"Processing order with id {requestData.orderId}");
else
return new BadRequestObjectResult("Please pass an orderId in the request body");
}
}
}
Hopes this helps.
I would like to add my 2 cents to it. I used the technique that it's used by Host injecting ILogger. If you look at the Startup project I created GenericBindingProvider that implements IBindingProvider. Then for each type I want to be injected I register it as follow:
builder.Services.AddTransient<IWelcomeService, WelcomeService>();
builder.Services.AddSingleton<IBindingProvider, GenericBindingProvider<IWelcomeService>>();
The downside is that you need to register the type you want to be injected into the function twice.
Sample code:
Azure Functions V2 Dependency Injection sample
I have been using SimpleInjector perfectly fine in Azure Functions. Just create a class (let's call it IoCConfig) that has the registrations and make a static instance of that class in function class so that each instance will use the existing instance.
public interface IIoCConfig
{
T GetInstance<T>() where T : class;
}
public class IoCConfig : IIoCConfig
{
internal Container Container;
public IoCConfig(ExecutionContext executionContext, ILogger logger)
{
var configurationRoot = new ConfigurationBuilder()
.SetBasePath(executionContext.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Container = new Container();
Configure(configurationRoot, logger);
}
public IoCConfig(IConfigurationRoot configurationRoot, ILogger logger)
{
Container = new Container();
Configure(configurationRoot, logger);
}
private void Configure(IConfigurationRoot configurationRoot, ILogger logger)
{
Container.RegisterInstance(typeof(IConfigurationRoot), configurationRoot);
Container.Register<ISomeType, SomeType>();
}
public T GetInstance<T>() where T : class
{
return Container.GetInstance<T>();
}
}
Then in root:
public static class SomeFunction
{
public static IIoCConfig IoCConfig;
[FunctionName("SomeFunction")]
public static async Task Run(
[ServiceBusTrigger("some-topic", "%SUBSCRIPTION_NAME%", Connection = "AZURE_SERVICEBUS_CONNECTIONSTRING")]
SomeEvent msg,
ILogger log,
ExecutionContext executionContext)
{
Ensure.That(msg).IsNotNull();
if (IoCConfig == null)
{
IoCConfig = new IoCConfig(executionContext, log);
}
var someType = IoCConfig.GetInstance<ISomeType>();
await someType.Handle(msg);
}
}
AzureFunctions.Autofac is very easy to use.
Just add a config file:
public class DIConfig
{
public DIConfig(string functionName)
{
DependencyInjection.Initialize(builder =>
{
builder.RegisterType<Sample>().As<ISample>();
...
}, functionName);
}
}
Add the DependencyInjectionConfig attribute then inject:
[DependencyInjectionConfig(typeof(DIConfig))]
public class MyFunction
{
[FunctionName("MyFunction")]
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequestMessage request,
TraceWriter log,
[Inject]ISample sample)
{
https://github.com/introtocomputerscience/azure-function-autofac-dependency-injection
I think this is a better solution:
https://github.com/junalmeida/autofac-azurefunctions
https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions
Install the NuGet in your project and then make a Startup.cs and put this in it:
[assembly: FunctionsStartup(typeof(Startup))]
public class Startup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder
.UseAppSettings() // this is optional, this will bind IConfiguration in the container.
.UseAutofacServiceProviderFactory(ConfigureContainer);
}
private void ConfigureContainer(ContainerBuilder builder)
{
// do DI registration against Autofac like normal! (builder is just the normal ContainerBuilder from Autofac)
}
...
Then in your function code you can do normal constructor injection via DI:
public class Function1 : Disposable
{
public Function1(IService1 service1, ILogger logger)
{
// logger and service1 injected via autofac like normal
// ...
}
[FunctionName(nameof(Function1))]
public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem)
{
//...
Support for Dependency injection begins with Azure Functions 2.x which means Dependency Injection in Azure function can now leverage .NET Core Dependency Injection features.
Before you can use dependency injection, you must install the following NuGet packages:
Microsoft.Azure.Functions.Extensions
Microsoft.NET.Sdk.Functions
Having Dependency Injection eases things like DBContext, Http client usage (Httpclienfactory), Iloggerfactory, cache support etc.
Firstly, update the Startup class as shown below
namespace DemoApp
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<IHelloWorld, HelloWorld>();
// Registering Serilog provider
var logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
builder.Services.AddLogging(lb => lb.AddSerilog(logger));
//Reading configuration section can be added here etc.
}
}
}
Secondly, Removal of Static keyword in Function class and method level
public class DemoFunction
{
private readonly IHelloWorld _helloWorld;
public DemoFunction(IHelloWorld helloWorld)
{
_helloWorld = helloWorld;
}
[FunctionName("HttpDemoFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
}
If we look into above e.g. IHelloWorld is injected using .NET Core DI
**Note:**In-spite of having latest version of Azure function v3 for Dependency Injection to enable few steps are manual as shown above
Sample code on github can be found here

How to get SignalR Hub Context in a ASP.NET Core?

I'm trying to get the context for a hub using the following:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
The problem is that GlobalHost is not defined. I see it is part of the SignalR.Core dll. At the moment, I have the following in my project .json file, under dependencies:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*"
If I add the latest available version of Core:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*",
"Microsoft.AspNet.SignalR.Core" : "2.1.2"
I get a whole bunch of errors because server and core are conflicting. If I change them to both use version "3.0.0-*", all the conflicts go away, but GlobalHost cannot be found. If I remove Server, and just user Core version 2.1.2 then GlobalHost works, but all the other things needing Server, obviously do not.
Any ideas?
IConnectionManager does not exist any more in SignalR for ASP.Net Core.
I've been using HubContext for getting access to a hub.
public class HomeController : Controller
{
private readonly IHubContext<LiveHub> _hubContext;
public HomeController(IHubContext<LiveHub> hubContext)
{
_hubContext = hubContext;
}
public void SendToAll(string message)
{
_hubContext.Clients.All.InvokeAsync("Send", message);
}
}
I'm using .net core 2.0.0 and SignalR 1.0.0-alpha1-final
Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager is a DI injected service through which you can get the hub context...For example:
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.Mvc;
public class TestController : Controller
{
private IHubContext testHub;
public TestController(IConnectionManager connectionManager)
{
testHub = connectionManager.GetHubContext<TestHub>();
}
.....
To use the hub in a backgroud service, in addition to controllers, you must use the IHostedService interface and get the hub by DI.
public class MyBackgroundService : IHostedService, IDisposable
{
public static IHubContext<NotifierHub> HubContext;
public MyBackgroundService(IHubContext<NotifierHub> hubContext)
{
HubContext = hubContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
//TODO: your start logic, some timers, singletons, etc
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//TODO: your stop logic
return Task.CompletedTask;
}
public void Dispose()
{
}
}
Then you can call your hub from anywhere in your code from HubContext static field:
MyBackgroundService.HubContext.Clients.All.SendAsync("UpdateData", myData).Wait();
Learn more about IHostedService:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
You can create and start a timer in MyBackgroundService and call the hub in ElapsedEvent.
I needed to be able to access the Hub Context from outside the app request thread - because I was subscribing to NServicebus messages, and needed to be able to trigger a client function when I received a message.
Here's how I got it sorted:
public static IServiceProvider __serviceProvider;
then during startup configuration
app.UseServices(services =>
{
__serviceProvider = new ServiceCollection()
.BuildServiceProvider(CallContextServiceLocator.Locator.ServiceProvider);
});
Then anywhere else in the vNext asp.net application (any other thread)
var manager = Startup.__serviceProvider.GetRequiredService<IConnectionManager>();
var hub = manager.GetHubContext<ChatHub>();
Hope this helps!
I added some code to my Startup.cs to grab reference to the ConnectionManager which you can then use to do a GetHubContext at anytime from anywhere in your code. Similar to Nimo's answer but a little different, maybe simpler.
services.AddSignalR(options =>
{
options.Hubs.EnableDetailedErrors = true;
});
var provider = services.BuildServiceProvider();
//Hold on to the reference to the connectionManager
var connManager = provider.GetService(typeof(IConnectionManager)) as IConnectionManager;
//Use it somewhere else
var hub = connManager.GetHubContext<SignalHub>();
I'm looking at SignalR source code and it seems that IHubContext is registered as a singleton.
Which means you get the same instance whenever you access it.
Which means you can simply save it in a static var and use it from whatever.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubContext<MyHub> hubContext)
{
_staticVar = hubContext;
}
But be warned - it's an anti-pattern.

How to correctly bind types by convention?

The projects is planned to target multiplatform, so I pull the maximum of code in class libraries so that it can be easily reused.
The architecture is based on the Model-View-Presenter principle.
The project structure is the following:
Solution
-> Domain
-> Domain.Tests
-> MVP
-> MVP.Tests
-> Web
-> Web.Tests
-> Windows
-> Windows.Tests
MVP
This project contains the presenters and the views of the project. For example:
public interface IApplicationView : IView, IHasUiHandler<IApplicationUiHandler> {
}
public class ApplicationPresenter : Presenter<IApplicationView>
, IApplicationUiHAndler {
public ApplicationPresenter(IApplicationView view) : base(view) {
View.Handler = this;
}
}
Windows
This project contains the WPF GUI of the application, and the so-called Composition Root. For example:
public class ApplicationWindow : Window, IApplicationView {
}
public class App : Application {
protected override void OnStartUp(StartupEventArgs e) {
IKernel kernel = new StandardKernel();
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses().EndingWith("Window")
.BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
);
}
}
Web
This project contains the ASP.NET GUI pages for the application, and the so-called Composition Root.
public class ApplicationPage : Page, IApplicationView {
}
public class MvcApplication : HttpApplication {
protected override void Application_Start() {
IKernel kernel = new StandardKernel();
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses().EndingWith("Page")
.BindAllInterfaces().EndingWith("View") // Here's what I'd like to do.
}
}
Well, I guess you get the idea...
I'm quite new to Dependency Injection, and even newer in convention binding.
I'd like to know how to configure the bindings using the conventions, with Ninject.
Any idea how to bind those views with Windows (WPF) and Pages (Web)?
EDIT
After trying what #BatteryBackupUnit suggested, I guess my problem is all about my search of assemblies.
using (var kernel = new StandardKernel())
kernel.Bind(scanner => scanner
.From(AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => (a.FullName.Contains("MyProject.MVP")
|| a.FullName.Contains("Windows"))
&& !a.FullName.Contains("Tests")))
.SelectAllClasses()
.EndingWith("Window")
.BindSelection((type, baseType) =>
type.GetInterfaces().Where(iface => iface.Name.EndsWith("View"))));
As stated previously, the View interfaces aren't located in the same assembly as the Window classes. The above code, base on #BatteryBackupUnit's answer, works great.
How about this:
using FluentAssertions;
using Ninject;
using Ninject.Extensions.Conventions;
using System.Linq;
using Xunit;
public interface ISomeView { }
public interface ISomeOtherView { }
public interface INotEndingWithViewWord { }
public class SomePage : ISomeView, ISomeOtherView, INotEndingWithViewWord
{
}
public class Demo
{
[Fact]
public void Test()
{
using (var kernel = new StandardKernel())
{
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.EndingWith("Page")
.BindSelection((type, baseType) =>
type.GetInterfaces()
.Where(iface => iface.Name.EndsWith("View"))));
kernel.Get<ISomeView>().Should().BeOfType<SomePage>();
kernel.Get<ISomeOtherView>().Should().BeOfType<SomePage>();
kernel.Invoking(x => x.Get<INotEndingWithViewWord>())
.ShouldThrow<ActivationException>();
}
}
}
Note: I've been using nuget packages
Ninject
Ninject.Extensions.Conventions
xunit.net
FluentAssertions
of those xunit.net and FluentAssertions are only there to run the test and not be used in production.
Or you could also use .BindWith<T : IBindingGenerator> or .BindUsingRegex(...).

Resources