- In Visual Studio, open the Package Manager Console and ensure the "Default project" selector is your web-application project.
- Run
Install-Package Jehoel.Unity.AspNetWebForms
- This will add the package reference to your project. Now you need to configure the Unity container.
-
Note that previous versions of the NuGet
Unity.WebForms
package added a starter file to the parent project atApp_Start\UnityWebFormsStart.cs
.- This has now been removed from
Jehoel.Unity.WebForms
because NuGet packages are not meant to include mutable content files intended to be edited by the consuming project's author, instead only immutable content files are meant to be included. - So you need to manually add code that configure the web-application's root Unity Container.
- This has now been removed from
-
Personally, I put this file in the same directory as
Global.asax
and name itGlobal-Unity.asax.cs
just so all of my application's startup code is together.- Though if you want to be consistent with other OWIN conventions, feel free to put it in
App_Start\UnityWebFormsStart.cs
.- Note that the
App_Start
directory is not special in ASP.NET 4 (unlike theApp_Code
,App_Data
,App_Theme
,App_Browser
andApp_WebReference
folders).
- Note that the
- Though if you want to be consistent with other OWIN conventions, feel free to put it in
using System;
using System.Web;
using Unity;
using Unity.WebForms;
[assembly: WebActivatorEx.PostApplicationStartMethod( typeof($rootnamespace$.UnityWebFormsStart), nameof($rootnamespace$.UnityWebFormsStart.PostStart) )]
namespace $rootnamespace$
{
/// <summary>Unity.WebForms startup and configuration class for your ASP.NET WebForms project.</summary>
internal static class UnityWebFormsStart
{
private static WebFormsUnityContainerOwner _containerOwner;
/// <summary>Initializes the unity container when the application starts up.</summary>
/// <remarks>Do not edit this method. Perform any modifications in the <see cref="RegisterDependencies" /> method.</remarks>
internal static void PostStart()
{
IUnityContainer rootContainer = new UnityContainer();
RegisterDependencies( rootContainer );
_containerOwner = new WebFormsUnityContainerOwner( rootContainer );
}
/// <summary>Registers dependencies in the supplied container.</summary>
/// <param name="container">Instance of the container to populate.</param>
private static void RegisterDependencies( IUnityContainer container )
{
// TODO: Add any dependencies needed here
container
// Registers a service such that Unity.WebForms will only ever create a single instance for the life of the container (i.e. the instance is shared by all HttpApplication and HttpContext instances)
.RegisterSingleton<ISingletonService,SingletonImplementation>()
// Registers a service such that Unity.WebForms will create (and dispose, if necessary) a new instance for each HTTP request where that service is requested.
// If requested outside of a HTTP request's context then the service will have the same lifetime as the root container.
// When multiple dependents depend on one of these dependencies then they will share the same instance.
// Examples include Entity Framework DbContexts and OAuth2/OIDC-based HttpClient factories that use request cookies to store tokens.
.RegisterRequest<YourDbContext>()
// Registers a service such that Unity.WebForms will create a new instance for each call to Resolve (i.e. a transient dependency).
// Examples include services that depend on request-lifetime-limited ILogger instances
// WARNING: Do not register services that implement `IDisposable` with `RegisterType` unless your code will be responsible for wrapping the returned object in a `using(service){}` block or otherwise manually calling `.Dispose()` as Unity will not dispose of transient objects.
.RegisterType<ITransientService,TransientServiceImplementation>();
}
}
}
This README assumes you're already familiar with Unity and/or DI service registration in general.
This project adds a new set of extension method to IUnityContainer
to simplify service registration for services that should have their lifetimes constrained to their associated HTTP request. RegisterRequest
. There are many overloads for registration using generic types or System.Type
, named and unnamed registrations, and so on.
(Actually, I have never been asked these questions, but I imagine people attempting to use this library might ask these questions)
What if you need to access the HttpContext
(HttpContextBase
) associated with the current request in a request-scoped service?
This project adds a built-in service: Unity.WebForms.IHttpContextAccessor
. This service is registered only in the per-request child IUnityContainer
to prevent inadvertent resolution of IHttpContentAccessor
To avoid making a static reference to WebConfigurationManager
and to make your components testable, use Unity.WebForms.Services.IWebConfiguration
. Add it using container.AddWebConfiguration()
.
IWebConfiguration
provides access to:
<appSettings>
<connectionStrings>
- And any other section that uses
DictionarySectionHandler
,NameValueSectionHandler
,SingleTagSectionHandler
, and others.
DbContext
should be registered using RegisterRequestFactory
. Supply a factory method (or IServiceFactory
implementation) that uses the DbContext(String connectionString)
constructor with the connection-string pulled from your configuration system.
If you're using web.config
for configuration then you can define an IServiceFactory<DbContext>
which itself takes a dependency on IWebConfiguration
to get the connection-string from your web.config file:
public class MyDbContextFactory : IServiceFactory<MyDbContext>
{
private readonly String connectionString;
public MyDbContextFactory( IWebConfiguration webConfig )
{
if( webConfig == null ) throw new ArgumentNullException(nameof(webConfig));
this.connectionString = webConfig.RequireConnectionString( "myDbConnectionString" ); // `RequireConnectionString` is an extension method.
}
public MyDbContext CreateInstance()
{
return new MyDbContext( this.connectionString );
}
}
Then your registration code should look like this:
container
.AddWebConfiguration()
.RegisterRequestFactory<MyDbContext,MyDbContextFactory>();
If you need to choose your connection-string at runtime based on an <appSetting>
value, then use IWebConfigurationExtensions.RequireIndirectConnectionString
instead of RequireConnectionString
.
Another advantage of this approach is that if you need to use a DbContext
inside a non-page-lifetime component of your web-application you can take add MyDbContextFactory
to your constructor parameters and get a short-lived DbContext
that way without needing to create a new child IUnityContainer
though you would be responsible for disposing it.
All included services are exposed as interfaces so you can replace them with your own implementation for testing purposes or for different production scenarios.
- Provides thread-safe access to
HttpContext
(as aHttpContextBase
). - Always automatically registered in the per-request child
IUnityContainer
.
- Provides access to
WebConfigurationManager
. - Requires manual registration. Call
container.AddWebConfiguration()
. - Comes with extension methods to easily get connection-strings directly by name or indirectly via a named
<appSettings>
entry.
When performing any troubleshooting involving ASP.NET's built-in support for constructor dependency injection in *.aspx
, *.ascx
, and *.master
files, it is important to verify that the project is fully targeting .NET Framework 4.7.2:
-
Verify that your
web.config
file specifically targets .NET Framework 4.7.2, as below: -
Verify that your
*.csproj
is targeting .NET Framework 4.7.2 or later:-
Project Properties > Application > Target framework > .NET Framework 4.7.2
- If you don't see ".NET Framework 4.7.2" listed, ensure you have the NET Framework 4.7.2 SDK installed.
- Also verify you're using a version of Visual Studio that supports .NET Framework 4.7.2, such as Visual Studio 2017 or Visual Studio 2019.
-
Or edit your
v4.7.2*.csproj
file in a text editor:
-
-
Also, make sure you're not using an older version of any CodeDom, Roslyn or CompilerServices packages or assembly references.
-
This includes these NuGet packages:
Microsoft.Net.Compilers
(currently at version3.2.0
at time of writing)Microsoft.CodeDom.Providers.DotNetCompilerPlatform
(currently at version2.0.1
at time of writing)
-
These packages don't seem to be required to use WebObjectActivator-based constructors.
-
(Where Default_aspx
is the web-page, master-page or user-control being used)
When running your webapplication you may get a yellow-screen-of-death with a stack-trace similar to this:
[MissingMethodException: Constructor on type 'ASP.login_aspx' not found.]
System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1431
System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +184
Unity.WebForms.UnityContainerServiceProvider.DefaultCreateInstance(Type type) +32
Unity.WebForms.UnityContainerServiceProvider.GetService(Type serviceType) +375
__ASP.FastObjectFactory_app_web_xkkmukhw.Create_ASP_Defaultaspx() +118
First, perform the .NET Framework target version verification checks described immediately underneath the "Troubleshooting" header above.
This can happen when the intermediate files (when your *.aspx
, *.ascx
, and *.master
files are transpiled to *.cs
) were created without support for WebObjectActivator
. Delete all intermediate and output files from your project to cause Visual Studio and ASP.NET to recreate them with WebObjectActivator support. You should only have to do this once.
- Nuke your
Temporary ASP.NET Files
folder - this contains the*.cs
transpiled source versions of your*.aspx
,*.ascx
, and*.master
files).- On your development machine, it will be located at
C:\Users\%you%\AppData\Local\Temp\Temporary ASP.NET Files
- In production servers, it is located at:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files
- On your development machine, it will be located at
- Ensure that you have fully closed IIS Express (or the full IIS if applicable)
- Verify that you have performed a proper Clean and Build of your project (and it doesn't hurt to restart Visual Studio too).
- Ensure the
bin
andobj
directories of your ASP.NET are empty before doing a Build. - Verify that your ASP.NET project is specifically targeting .NET Framework 4.7.2 in the
*.csproj
file, as per the instructions above.