I've setup a series of tasks using the Application events in a global.asax.cs file like so:
// Note: For instructions on enabling IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=301868
public class MvcApplication : System.Web.HttpApplication
{
private static IContainer ContainerGlobal;
private static ILogger Logger;
public ILifetimeScope Container
{
get { return (ILifetimeScope)HttpContext.Current.Items["_Container"]; }
set { HttpContext.Current.Items["_Container"] = value; }
}
protected void Application_Start()
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//configure the Autofac IoC container
var container = AutofacBuilder.Configure(Assembly.GetExecutingAssembly(),
new MvcModule(), new TaskModule());
// startup the logging
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase)
.Replace("file:\\", string.Empty);
var file = new FileInfo(path + #"\log4net.ui.config");
Logger = new Logger(MethodBase.GetCurrentMethod().DeclaringType, file);
var runAtInits = container.Resolve<IEnumerable<IRunAtInit>>();
if (runAtInits != null)
{
Logger.LogFormat(LogType.Debug, "Found {0} IRunAtInit instances",
runAtInits.Count());
foreach (var task in runAtInits)
{
task.Execute();
}
}
var runAtStartups = container.Resolve<IEnumerable<IRunAtStartup>>();
if (runAtStartups != null)
{
Logger.LogFormat(LogType.Debug, "Found {0} IRunAtStartup instances",
runAtStartups.Count());
foreach (var task in runAtStartups)
{
task.Execute();
}
}
ContainerGlobal = container;
}
public void Application_BeginRequest()
{
try
{
Container = ContainerGlobal.BeginLifetimeScope();
var runOnEachRequests =
Container.Resolve<IEnumerable<IRunOnEachRequest>>();
if (runOnEachRequests == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunOnEachRequest instances",
runOnEachRequests.Count());
foreach (var task in runOnEachRequests)
{
task.Execute();
}
}
catch(Exception ex)
{
Logger.Log(LogType.Error, ex);
}
}
public void Application_Error()
{
try
{
var runOnErrors = Container.Resolve<IEnumerable<IRunOnError>>();
if (runOnErrors == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunOnError instances",
runOnErrors.Count());
foreach (var task in runOnErrors)
{
task.Execute();
}
}
catch (Exception ex)
{
Logger.Log(LogType.Error, ex);
}
}
public void Application_EndRequest()
{
try
{
var runAfterEachRequests =
Container.Resolve<IEnumerable<IRunAfterEachRequest>>();
if (runAfterEachRequests == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunAfterEachRequest instances",
runAfterEachRequests.Count());
foreach (var task in runAfterEachRequests)
{
task.Execute();
}
}
catch (Exception ex)
{
Logger.Log(LogType.Error, ex);
}
finally
{
if (Container != null)
{
Container.Dispose();
Container = null;
}
}
}
}
This is discussed by Matt Honeycut here:
https://github.com/MattHoneycutt/Fail-Tracker
As we make extensive use of Autofac throughout our application, we've implemented it using Autofac rather than Structuremap.
The problem is, we get exceptions raised for the following events: Application_EndRequest and sometimes for Application_BeginRequest. The exception raised in both cases is:
"No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself"
We've ensured that we use the ILifeScope for our Container object, rather than the root Container object. However, this doesn't fix the errors. Does anyone else have any suggestions as to what else we need to do?
For information, the call to AutofacBuilder.Configure above registers all necessary library types and modules and returns the root Container from autofac by calling the Build method.
thanks
PS We're using:
Visual Studio 2013,
MVC 5.1
Autofac 3.3
The error message pretty much says it all, but to be more specific, one or more of the following is occurring:
One or more of your begin/end request handlers is being registered as InstancePerHttpRequest
One or more of the dependencies required by your begin/end request handlers is being registered as InstancePerHttpRequest
One or more of the request handlers (or the dependencies for the request handlers) is trying to use DependencyResolver.Current during app start/end.
The Autofac dependency resolver requires a web request context to work. You can read more about that in the answer here: Autofac - The request lifetime scope cannot be created because the HttpContext is not available - due to async code?
App start/end don't have a web request running. They're outside the request pipeline.
Go through your registrations (which appear to be in AutofacBuilder.Configure) and check to see which ones are InstancePerHttpRequest. Something in there that is required during your handlers' execution is getting incorrectly registered that way, so when the web request scope isn't found - boom. And, again, it may not be just one thing - it could be that all your handlers are registered correctly, but one of the dependencies for your handlers are registered InstancePerHttpRequest.
If you find the issues and you don't want to switch to registering them as SingleInstance, consider switching the registration to InstancePerLifetimeScope. Chances are, unless your app is creating a bunch of lifetime scopes for units of work or something, that InstancePerLifetimeScope will behave just like InstancePerHttpRequest but will properly resolve without a web request.
That said, I would recommend wrapping the task execution in a lifetime scope so memory gets cleaned up:
using(var scope = container.BeginLifetimeScope())
{
// Resolve from a scope.
var runAtStartups = scope.Resolve<IEnumerable<IRunAtStartup>>();
// Do the run, etc.
}
Finally, something to be aware of: I see you're manually generating some sort of request lifetime scope yourself in the BeginRequest event. I'm not sure if it's important, but the scope you're creating there will not be the request lifetime scope that Autofac actually uses. It will only be used for your components, and it won't work with InstancePerHttpRequest registered stuff.
Related
I'm writing a .NET 6 application for which users can create plugins. In some situations however, when a plugin throws an unhandled exception, my own application crashes as well. That should not happen, no matter what. The plugin may stop working, it may unload, it may whatever, just leave the parent app alive.
Loading happens like this:
public static ServiceInfo? LoadService(string relativePath)
{
var loadContext = new ServiceLoadContext(relativePath);
_alcs.Add(loadContext);
try
{
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(relativePath)));
var shouldLoadDll = false;
foreach (var type in assembly.GetTypes())
{
if (typeof(IMonitorableService).IsAssignableFrom(type))
{
var directoryName = new FileInfo(relativePath).Directory!.FullName;
if (Activator.CreateInstance(type, new object[] { directoryName }) is IMonitorableService result)
{
shouldLoadDll = true;
return new ServiceInfo
{
Category = Directory.GetParent(relativePath)!.Name,
Name = Path.GetFileNameWithoutExtension(relativePath),
AssemblyPath = relativePath,
Service = result!
};
}
}
}
if (!shouldLoadDll)
{
loadContext.Unload();
}
}
catch (Exception)
{
// This is handled, but this won't catch the exception in the plugin
}
return null;
}
I have my share of try/catch phrases, and since these IMonitorableServices are BackgroundServices, they're started like
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var service in _options.Services)
{
try
{
await service.Service.StartAsync(cancellationToken);
}
catch (Exception ex)
{
// This is handled, but it won't catch the exception in the plugin
}
}
}
Now I doubt that it's really relevant to provide the specific error, but just in case: it's a
'System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute',
following an operation on event subscriptions. I know how to solve that in the plugin, but I could never trust my future plugin writers to always handle their exceptions (or prevent them from happening). I need some way to catch absolutely everything in my own application. I've been breaking my head over this and I can find many considerations on plugins loaded in AppDomains, but they're from the .NET Framework era...
Who has an idea how to solve this? I could hardly imagine this is something that has been overlooked in .NET Core/6 development.
Update: I find that other type of exceptions actually are caught within the StartAsync method. So it might have something to do with the exception being raised from an event in the plugin (don't want to put you on the wrong track though). I must add, the event is registered from within the StartAsync method, but it seems to bypass the regular catch.
I noticed that when using ASP.NET Core's IHttpClientFactory, the typed client registration method AddHttpClient<TClient,TImplementation> does two things:
It registers DI for <TClient,TImplementation> as transient, as if calling services.AddTransient<TClient,TImplementation> in startup.cs
It will inject a HttpClient instance of this registered type for each object initiated.
My concern is, if this is configured as transient, will it be able to handle a large number of concurrent TImplementation objects making http calls, because there will be a new HttpClient as well as a new TClient created for every call? These clients will all access the same URL, will sockets be re-used properly?
As King King has already pointed out the HttpMessageHandler which matters.
To better understanding how does it work I suggest to examine the DefaultHttpClientFactory's source code.
Let's take a look at the CreateClient method:
public HttpClient CreateClient(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
HttpMessageHandler handler = CreateHandler(name);
var client = new HttpClient(handler, disposeHandler: false);
HttpClientFactoryOptions options = _optionsMonitor.Get(name);
for (int i = 0; i < options.HttpClientActions.Count; i++)
{
options.HttpClientActions[i](client);
}
return client;
}
As you can see it calls CreateHandler:
public HttpMessageHandler CreateHandler(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
ActiveHandlerTrackingEntry entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
StartHandlerEntryTimer(entry);
return entry.Handler;
}
Here we have a pool of handlers via _activeHandlers. And a factory method _entryFactory, which is called when a given entry does not exist. Let's take a look at their definitions:
_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>
{
return new Lazy<ActiveHandlerTrackingEntry>(() =>
{
return CreateHandlerEntry(name);
}, LazyThreadSafetyMode.ExecutionAndPublication);
};
_expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();
So, as you can see it uses a Lazy structure to minimize the cost of the initializations.
The related CreateHandlerEntry's source code can be found here if you are interested.
I also suggest to read Stephen Gordon's excellent article about this topic.
I currently have a asp.net mvc application with ABP implementation. I currently want to execute a service method inside the Session_Start(), may I ask how abouts would I do that.
The service can be executed anywhere I have access to the IOC resolve but I'm in the global file and I'm not entirely sure how to do that from there.
protected void Session_Start()
{
// starting a session and already authenticated means we have an old cookie
var existingUser = System.Web.HttpContext.Current.User;
if (existingUser != null && existingUser.Identity.Name != "")
{
// execute app service here.
// if I'm exposed to IOCresolver I would do the following below
var srv = _iocResolver.Resolve<SettingsAppService>();
srv.UpdateItems();
}
}
May I ask how do I access IOC resolver on global.asax.cs file, if even possible. My goal is to execute the service when the user has re-established his session.
From the documentation on Dependency Injection:
The IIocResolver (and IIocManager) also have the CreateScope extension method (defined in the Abp.Dependency namespace) to safely release all resolved dependencies.
At the end of using block, all resolved dependencies are automatically removed.
If you are in a static context or can not inject IIocManager, as a last resort, you can use a singleton object IocManager.Instance everywhere.
So, use a scope with IocManager.Instance:
using (var scope = IocManager.Instance.CreateScope()) { ... }
→ IocManager.Instance.UsingScope(scope => { ... })
protected void Session_Start()
{
// Starting a session and already authenticated means we have an old cookie
var existingUser = System.Web.HttpContext.Current.User;
if (existingUser != null && existingUser.Identity.Name != "")
{
IocManager.Instance.UsingScope(scope => // Here
{
// Execute app service here.
var srv = scope.Resolve<SettingsAppService>();
srv.UpdateItems();
});
}
}
You can create a static link to your IoC resolver and use it in Global.asax. You even can add it to Global.asax.cs. Set this property after container registration and use it from anywhere.
public static YourIocResolver IocResolver { get; set; }
Switch between production and test Webservice.
I have 2 version for the same WebService definition. Each version has its own database url etc.
MyLib.FooWebServicePROD.FooWebService _serviceProd;
MyLib.FooWebServiceTEST.FooWebService _serviceTest;
For now to siwtch form one to the other I used the Rename option in Visual Studio.
I would like to wrap all my instance and definition in a layer of abstration so the programe will not be edited everytime.
So I made mine singleton public sealed class FooBarWrap but with a huge amount a duplication like:
public bool Close()
{
if (_serviceProd != null)
{
_serviceProd.logout(guid);
log4N.Info("Closing PROD");
}
if (_serviceTest != null)
{
_serviceTest.logout(guid);
log4N.Info("Closing TEST");
}
return true;
}
public bool Login()
{
try
{
log4N.Info("Connection to FooBar webservice...");
if (isProd)
{
_serviceProd = new MyLib.FooWebServicePROD.FooWebService();
_serviceProd.Timeout = System.Threading.Timeout.Infinite;
_serviceProd.Logon(guid);
}
else {
_serviceTest = new MyLib.FooWebServiceTEST.FooWebService();
_serviceTest.Timeout = System.Threading.Timeout.Infinite;
_serviceTest.Logon(guid);
}
log4N.Info("done");
return true;
}
catch (Exception ex)
{
log4N.Info("failed !");
log4N.Error("Echec connexion au webservice FooBar", ex);
return false;
}
}
Is there a simplier way to achieve this? Without the client having a reference to one or the other web service, and without the heavy code duplication?
if (FooBarWrap.Instance.Login()){
//DoSomething
var ClientResult = FooBarWrap.Instance.SomeRequest()
}
Is there a simplier way to achieve this? Without the client having a reference to one or the other web service, and without the heavy code duplication?
It is.
You could simply use conditional dependency injection where depending on the environment you are or any other condition like host name, port number or url path, you would get different implementation of the service interface.
A simple conditional dependency injection that depending on condition provides one or the other implementation of the same interface.
kernel.Bind<ISomeService>().To<SomeService1>();
kernel.Bind<ISomeService>().To<SomeService2>().When(x => HttpContext.Current[host|port|url path] == "some value");
Ninject calls that kind of injection contextual binding
https://github.com/ninject/ninject/wiki/Contextual-Binding
I'd like to use Redis for caching, but I still want my service to be functional if a Redis instance isn't found at runtime.
Are there any examples of this in practice?
You could do something like the following in your AppHost Configure method
public override void Configure(Container container)
{
...
try
{
var redisManager = new PooledRedisClientManager("localhost:6379");
// do some sort of test to see if we can talk to the redis server
var client = redisManager.GetCacheClient();
var fakeKey = "_________test_______";
client.Add(fakeKey, 1);
client.Remove(fakeKey);
// if it worked register the cacheClient
container.Register(c => redisManager.GetCacheClient()).ReusedWithin(ReuseScope.None);
}
catch (Exception ex)
{
// fall back to in memory cache
// Log some sort of warning here.
container.Register<ICacheClient>(c => new MemoryCacheClient());
}
...
}