Castle Windsor 3 persists PerWebRequest objects across multiple web requests - asp.net

I have an MVC 4 project and am trying to inject a PerWebRequest object into my controller. However it appears the object is not being recreated across multiple requests
private static IWindsorContainer InitializeWindsor()
{
var container = new WindsorContainer().Install(FromAssembly.This());
// Add Factory facility
container.AddFacility<TypedFactoryFacility>();
// Register all controllers from this assembly
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
container.Register(
AllTypes.FromAssembly(assembly).BasedOn<Controller>().Configure(c => c.LifestyleTransient())
);
}
// Register HTTP Handlers
container.Register(Component.For<HttpRequestBase>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => new HttpRequestWrapper(HttpContext.Current.Request)));
container.Register(Component.For<HttpContextBase>().LifeStyle.PerWebRequest.UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));
// Register components
container.Register(Component.For<PerWebRequestObject>().LifeStyle.PerWebRequest);
}
Here's my PerWebRequestObject:
public class PerWebRequestObject
{
public DateTime DateCreated { get; set; }
public PerWebRequestObject()
{
DateCreated = DateTime.UtcNow;
Debug.WriteLine("Created: " + DateCreated.ToLongTimeString());
}
}
Here's my TestController:
public class TestController : BaseController
{
public PerWebRequestObject pwrObject { get; set; }
public ActionResult Test()
{
Debug.WriteLine(pwrObject.DateCreated.ToLongTimeString());
return new ContentResult();
}
}
Why is this not working? Note that I'm using the WindsorControllerFactory and releasing the Controller like so:
public override void ReleaseController(IController controller)
{
var disposableController = controller as IDisposable;
if (disposableController != null)
disposableController.Dispose();
_windsorContainer.Release(controller);
}

Two things:
how do you know the object is not lifecycled properly (not re-created across web-requests)?
One possible explanation is that your dependency looks like this:
controller --> a singleton --> your per-web-request-component
this is a lifestyle mismatch, and that would explain the behaviour you're seing
do not dispose the controller. That's container's job.

Upgrading to Castle Windsor v3.1 fixed the issue for us without needing to change any code.
I assume there must've been an issue with the combination of Windsor v3.0 + MVC 4.

Related

Autofac InstancePerRequest returns new isntance

I have an ASP.NET Web API, .Net 4.6.1 project where I need to capture some info in the middleware and then retrieve it in a code that will be called from controller. In .Net core it is very easy with registering my custom context class as Scoped and resolving it in different stages of message processing. In .Net Framework, what looked similar to it was Autofac's InstancePerRequest so I tried but it does not work as I expected. Apparently every time I do BeginScope() it returns a new instance even if I am within same reuest? I am implementing the IAutofacContinuationActionFilter interface where I resolve my service, registered with InstancePerRequest, then later in controller I try to resolve it again and get new instance. What am I missing here?
Oh and in the controller below, both instances of IHomeService: injected via constructor and resolved manually are creating new instances.
UPDATE:
The code above is oversimplification of the real situation. The call where I need the information passed from filter is in a separate class and call happens through a series of autogenerated code. The constructor injection is not an option for me so I was hoping to have a solution similar to .Net Core DI.
my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var container = MyContainerBuilder.Build(config);
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
my container builder class:
public class MyContainerBuilder
{
public static IContainer Build(HttpConfiguration config)
{
var builder = new ContainerBuilder();
builder.RegisterWebApiFilterProvider(config);
builder
.Register(c => new MyCustomFilter())
.AsWebApiActionFilterForAllControllers()
.InstancePerRequest();
// var assembly = typeof(IHomeService).Assembly;
// builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces().InstancePerRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder
.RegisterType<HomeService>()
.As<IHomeService>()
.InstancePerRequest();
return builder.Build();
}
}
filter:
public class MyCustomFilter : IAutofacContinuationActionFilter
{
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> next)
{
using (var scope = GlobalConfiguration.Configuration.DependencyResolver.BeginScope().GetRequestLifetimeScope())
{
var hs = scope.Resolve<IHomeService>();
++hs.Counter;
var hs1 = scope.Resolve<IHomeService>();
++hs1.Counter;
var r = next().Result;
return await Task.FromResult(r);
}
}
}
controller:
[Route("home")]
public class HomeController : ApiController
{
public IHomeService HomeService { get; set; }
public HomeController(IHomeService homeService)
{
HomeService = homeService;
}
[HttpGet]
[Route("")]
public string Index()
{
var dr = GlobalConfiguration.Configuration.DependencyResolver as AutofacWebApiDependencyResolver;
using (var scope = dr.GetRequestLifetimeScope())
// This does not work either, returns new instance:
// using (var scope = GlobalConfiguration.Configuration.DependencyResolver.BeginScope().GetRequestLifetimeScope())
{
var hs = scope.Resolve<IHomeService>();
++hs.Counter;
}
return "Home";
}
}
service class I try to resolve:
public interface IHomeService
{
int Counter { get; set; }
}
public class HomeService : IHomeService
{
public HomeService()
{
Console.WriteLine("Yet another instance of HomeService!!!");
}
public int Counter { get; set; }
}
Thanks in advance
You can't create your own request scope, you need to get it from the request message. In the filter, that's like:
var scope = actionContext.Request.GetDependencyScope();
An example filter showing this is in the Autofac docs
However, since you're using Autofac interfaces, they're injected by Autofac for each request - if your filter needs a per-request service, it's better to make it a constructor parameter on the filter. You only need to do service location if you're not using Autofac filter interfaces.
If you seriously need service location in the filter, you can still use the constructor to make things easier - add an ILifetimeScope parameter to the filter constructor and you'll get the request scope as a parameter.
For the controller, same thing: inject what you need in the constructor rather than using service location. If you need the request scope because you can't escape service location, either inject an ILifetimeScope into the controller constructor or get the request lifetime off the HttpRequestMessage.

.NET Core 3.1 GetService return null

I am new to .NET Core and I got stuck on this.
In my startup.cs, I have
services.AddScoped<IEmailService, EmailService>();
and I have created a IHostedService:
services.AddHostedService<CooldownExpiredService>();
inside my CooldownExpiredService, I have the following code
using (var emailScope = _serviceScopeFactory.CreateScope())
{
var _emailService = emailScope.ServiceProvider.GetService<EmailService>();
}
but the object I get is null. How can I get the EmailService inside from HostedService?
i dont knwo why i should have use the interface.
emailScope.ServiceProvider.GetService<IEmailService>()
You can add Extension Method for all Services
first : add class =>
public static class ServiceTool
{
private static IServiceProvider ServiceProvider { get; set; }
public static IServiceCollection Create(IServiceCollection servicesProvider)
{
ServiceProvider = servicesProvider.BuildServiceProvider();
return servicesProvider;
}
public static T Resolve<T>()
{
return ServiceProvider.GetService<T>();
}
}
And go to the Startup and add it
ServiceTool.Create(services);
For use in controller write on constructor controller
example:
_logRepository = ServiceTool.Resolve<ILogRepository>();

An error occurred when trying to create a controller of type 'XXXXController'. Make sure that the controller has a parameterless public constructor

I have created a asp.net web api project and implemented the below HTTP GET method in AccountController and the related service method & repository method in AccountService & AccountRepository respectively.
// WEB API
public class AccountController : ApiController
{
private readonly IAccountService _accountService;
public AccountController(IAccountService accountService)
{
_accountService = accountService;
}
[HttpGet, ActionName("UserProfile")]
public JsonResult<decimal> GetUserSalary(int userID)
{
var account = _accountService.GetUserSalary(userID);
if (account != null)
{
return Json(account.Salary);
}
return Json(0);
}
}
Service / Business Layer
public interface IAccountService
{
decimal GetUserSalary(int userId);
}
public class AccountService : IAccountService
{
readonly IAccountRepository _accountRepository = new AccountRepository();
public decimal GetUserSalary(int userId)
{
return _accountRepository.GetUserSalary(userId);
}
}
Repository / Data Access Layer
public interface IAccountRepository
{
decimal GetUserSalary(int userId);
}
public class AccountRepository : IAccountRepository
{
public decimal GetUserSalary(int userId)
{
using (var db = new AccountEntities())
{
var account = (from b in db.UserAccounts where b.UserID == userId select b).FirstOrDefault();
if (account != null)
{
return account.Salary;
}
}
return 0;
}
}
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IAccountService, AccountService>();
container.RegisterType<IAccountRepository, AccountRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
But when I invoke the API method GetUserSalary() I get an error saying
An error occurred when trying to create a controller of type 'AccountController'. Make sure that the controller has a parameterless public constructor.
Check that you did not forget to register Unity IoC container itself:
if you use ASP.NET Framework it could be - Global.asax or Startap.cs (Owin) via UnityConfig.RegisterComponents() method.
if you use ASP.NET Core then in the Startup.cs file (I was unable to find official guides for its configuting)
Your current constructor has parameters (or args if you prefer).
see:
public AccountController(IAccountService accountService)
{
_accountService = accountService;
}
All you need to do is add a "Parameter-less Constructor" into the controller as well.
public AccountController()
{
}
Parameter-less constructors are usually above the ones that have params, though as far as I am aware this is only due to standards not any actual effect(s) it may cause.
There is also an already existing issue/question similar to this I will link below that may provide further details.
Make sure that the controller has a parameterless public constructor error

Create/Get DefaultHtmlGenerator from MVC Controller

I am trying to create(Or get an instance of it somehow) for Microsoft.AspNet.Mvc.Rendering.DefaultHtmlGenerator inside my MVC6 controller method
I wanted to generate the html for validation for my Model my self inside my controller of asp.net mvc. My issue is where to get the constructor data for DefaultHtmlGenerator like antiforgery, metadataProvider..etc
[HttpGet]
public IActionResult GetMarkup()
{
// IHtmlGenerator ge = this.CurrentGenerator();
IHtmlGenerator ge = new DefaultHtmlGenerator(params);
var tag= ge.GetClientValidationRules(params)
}
here is the a link about the HtmlGenerator class
DefaultHtmlGenerator
Since MVC 6 is based on dependency injection, all you have to do is require IHtmlGenerator in your constructor, and the DI container will automatically fill in all of the dependencies of DefaultHtmlGenerator (provided that is what is setup in your DI configuration).
public class HomeController : Controller
{
private readonly IHtmlGenerator htmlGenerator;
public HomeController(IHtmlGenerator htmlGenerator)
{
if (htmlGenerator == null)
throw new ArgumentNullException("htmlGenerator");
this.htmlGenerator = htmlGenerator;
}
public IActionResult GetMarkup()
{
// Use the HtmlGenerator as required.
var tag = this.htmlGenerator.GetClientValidationRules(params);
return View();
}
}
That said, it appears that the GetClientValidationRules method is only designed to work within a view, since it accepts ViewContext as a parameter. But this does answer the question that you asked.

ASP.NET 5 - Using a Configuration Setting

I am playing with ASP.NET 5. I am trying to understand the new configuration model. I have read several articles. However, I am still unsuccessful in loading a configuration setting. My config.json file looks like this:
{
"App" : {
"Info" : {
"Version":"1.0.0",
"ReleaseDate":"03-15-2015"
}
}
}
My Startup.cs file looks like this:
public class Startup
{
public IConfiguration Configuration { get; private set; }
public Startup()
{
Configuration = new Configuration()
.AddJsonFile("config.json");
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseErrorPage();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index" });
});
app.UseMvc();
app.UseWelcomePage();
}
}
In one of my controllers, I have the following
MyController.cs
using System;
using Microsoft.AspNet.Mvc;
namespace MyOrg.MyApp
{
public class MyController : Controller
{
[HttpGet()]
public ActionResult Index()
{
var version = Configuration.Get("App:Info:Version");
return new HttpStatusCodeResult(200);
}
}
}
When I start the app, I get an error that says:
error CS0103: The name 'Configuration' does not exist in the current context
at Microsoft.Framework.Runtime.Roslyn.RoslynProjectReference.Load(IAssemblyLo
adContext loadContext)
at Microsoft.Framework.Runtime.Loader.ProjectAssemblyLoader.Load(String name,
IAssemblyLoadContext loadContext)
at Microsoft.Framework.Runtime.Loader.ProjectAssemblyLoader.Load(String name)
at kre.host.LoaderContainer.Load(String name)
at kre.hosting.RuntimeBootstrapper.<>c__DisplayClass6_0.<ExecuteAsync>b__4(As
semblyName assemblyName)
at kre.hosting.RuntimeBootstrapper.<>c__DisplayClass6_0.<ExecuteAsync>b__7(Ob
ject sender, ResolveEventArgs a)
at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
What am I doing wrong? I feel like I've followed the examples I've seen. Yet, I can figure out what I'm doing wrong.
Clearly you want to access Configuration property in your Startup class. And the error method says it doesn't know what Configuration is. So you need a using statement or a fully qualified name. Also, you should avoid naming things the same thing as stuff found in the framework. Your Startup class has a Configuration property, but it also tries to use the Configuration class from Microsoft.Framework.ConfigurationModel. How confusing is that?
Your Configure() method in Startup needs a using statement or fully qualified name so it knows what the Configuration class is.
using Microsoft.Framework.ConfigurationModel; //at the top of your class
Configuration = new Configuration(); //later in the code, we can access without fully qualifying name
or
Configuration = new Microsoft.Framework.ConfigurationModel.Configuration();
In your controller, you may have a similar issue. Replace MyOrg.MyApp.Startup in the example below with whatever the namespace is for your Startup class.
using MyOrg.MyApp.Startup //at the top of your class
Startup.Configuration.Get("App:Info:Version"); //later in the code, we can access without fully qualifying name
or
MyOrg.MyApp.Startup.Startup.Configuration.Get("App:Info:Version");
Better way of doing things
That should be enough to get you started. However, accessing the Startup class to retrieve your configuration isn't ideal, because now your controller's action methods depend on having the Startup class there. That's not very unit testable. Ideally your controllers should be isolated from each other. You should define some sort of interface to hold the configuration info you want, then have the controller depend on that interface. When you're in your site, you'll respond with a class specific to the site's configuration. When unit testing, you can have tight control over the test values by using a different class.
interface ISiteConfig
{
string Version {get; set;}
DateTime ReleaseDate {get; set;}
}
public class SiteConfig : ISiteConfig
{
public string Version {get; set;}
public DateTime ReleaseDate {get; set;}
public SiteConfig()
{
var c = new Configuration()
.AddJsonFile("config.json");
Version = c.Get("App:Info:Version");
ReleaseDate = c.Get("App:Info:ReleaseDate"); //may need to parse here
}
}
public class TestConfig : ISiteConfig
{
public string Version {get; set;}
public DateTime ReleaseDate {get; set;}
public TestConfig(string version, DateTime releaseDate)
{
Version = version;
ReleaseDate = releaseDate;
}
}
Then you'd use Dependency Injection to inject instances of your configuration into the Controller.
public class MyController : Controller
{
private readonly ISiteConfig Config;
public MyController(ISiteConfig config)
{
Config = config;
}
[HttpGet()]
public HttpStatusCodeResult Index()
{
var version = Config.Version;
return new HttpStatusCodeResult(200);
}
}
public class Startup
{
public void Configure(IBuilder app)
{
...
app.UseServices(services =>
{
...
// Set up the dependencies
services.AddTransient<ISiteConfig, SiteConfig>();
...
});
...
}
}
Now you can more easily unit test your action methods, because your unit tests can use the TestConfig class while the site can use the SiteConfig class. And also if you want to change how your configuration is done, you don't have to replace strings in a bunch of different places. You'll have one class where you do so, the rest is strongly typed and easy to change without blowing up your application.
Your unit test might look like this:
//Arrange
var testConfig = new TestConfig("1.0", DateTime.Now );
var controller = new MyController(testConfig );
//Act
var response = controller.Index();
//Assert
Assert.AreEqual(200, response.StatusCode);
As of Beta 5 the accepted answer is no longer correct. There is no longer a Get method on IConfiguration. Also the way of constructing the configuration object is also changed.
The following code works on Beta 7:
// showing using statements here since this is new from Beta 5
using Microsoft.Dnx.Runtime; // renamed was Microsoft.Framework.Runtime
using Microsoft.Framework.Configuration; // renamed was Microsoft.Framework.ConfigurationModel
// other using statements here
// Startup constructor. Note: now takes IApplicationEnvironment
// this is required in order to get base path
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// Setup configuration sources.
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile("dbconfig.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// property to hold configuration object created in constructor
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
// this will bind to an IOptions<AppSettings> instance
// where AppSettings is a class you define that has a set of
// properties that match your configuration section loaded from the
// json file
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// here I am loading a connection string from a json file and passing into an
// new EF 6.x DB Context class
services.AddInstance<TalentAgencyContainer>(new TalentAgencyContainer(Configuration["ConnectionStrings:TalentAgencyContainer"]));
// Add MVC services to the services container.
services.AddMvc();
}

Resources