Trying to make use of Async await in ASP.Net Webforms.
Consider I have a base user control as below
public class BaseUserControl :UserControl
{
public int SomeBaseValue { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Page.RegisterAsyncTask(new PageAsyncTask(PageLoadAsync));
}
public async Task PageLoadAsync()
{
SomeBaseValue = await GetBaseValueFromDB();
}
}
An EmployeeUserControl that derives from BaseUserControl
public class EmployeeUserControl : BaseUserControl
{
public int SomeDerivedValue { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e); //Want this method to wait for the completion of base class's async method
SomeDerivedValue = base.SomeBaseValue * 2;
}
}
So the derived class has a property named SomeDerivedValue that depends on the base class's
SomeBaseValue, so I want, when the Derived Class base.OnLoad is called it should wait for the BaseUserControl's OnLoad method completion including the completion of Async Method.
As of now the execution doesnt wait for the completion of Async method, so the SomeBasevalue is null when it executes the line SomeDerivedValue = base.SomeBaseValue * 2;
Is there anyway to achieve this?
You might think adding a TaskCompletionSource to BaseUserControl would solve it, but it would (probably - haven't tried) deadlock the page.
Web Forms only supports async/await in PageAsyncTasks. Those respect the recommended lifetime for page processing.
Using Asynchronous Methods in ASP.NET 4.5
Related
Is there anyway to define and add method filters for hub functions (like ActionFilters in mvc)
I mean something like this :
public class MyHub : Hub
{
[Log]
public string RegisterUser(UserModel model){
...
}
}
where I can do some control inside the LogAttribute implementation.
You should be able to achieve similar functionality to action filters in ASP.NET MVC by using SignalR's Hub pipeline:
public class LoggingPipelineModule : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
Debug.WriteLine("Invoking '{0}.{1}({2})'.",
context.MethodDescriptor.Hub.Name,
context.MethodDescriptor.Name,
string.Join(", ", context.Args));
return base.OnBeforeIncoming(context);
}
protected override object OnAfterIncoming(object result, IHubIncomingInvokerContext context)
{
Debug.WriteLine("Finished Invoking '{0}.{1}'. Returned '{2}'.",
context.MethodDescriptor.Hub.Name,
context.MethodDescriptor.Name,
result);
return base.OnAfterIncoming(result, context);
}
}
If you only want to log for methods with a custom attribute attached, you can check for your custom attribute before logging:
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
if (context.MethodDescriptor.Attributes.OfType<MyAttribute>().Any())
{
// Log here.
}
return base.OnBeforeIncoming(context);
}
You can register your module before your call to MapSignalR:
public void Configuration(IAppBuilder app)
{
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
}
public abstract class MyControllerBase : Controller
{
protected override void OnActionExecuting(ActionExecutingContext context)
{
// do some magic
}
}
All of my controllers inherit from MyControllerBase. The problem is that now I can't unit test certain methods because the filter sets some authorisation/logic flags which influence code path.
Is there any way to manually trigger OnActionExecuting? How does the pipeline trigger these events?
EDIT: to show a little more the idea behind this design in response to comments. I basically have something like this:
public abstract class MyControllerBase : Controller
{
protected override void OnActionExecuting(ActionExecutingContext context)
{
UserProperties =
_userService
.GetUserProperties(filterContext.HttpContext.User.Identity.Name);
ViewBag.UserProperties = UserProperties;
}
public UserProperties { get; private set; }
public bool CheckSomethingAboutUser()
{
return UserProperties != null
&& UserProperties.IsAuthorisedToPerformThisAction;
}
// ... etc, other methods for querying UserProperties
}
So now anywhere in View or Controller I can get details of the current user, what is their email, what authorisation they have, which department they work for etc.
Example:
public class PurchasingController : MyControllerBase
{
public ActionResult RaisePurchaseOrder(Item item)
{
// can use UserProperties from base class to determine correct action...
if (UserProperties.CanRaiseOrders)
if (UserProperties.Department == item.AllocatedDepartment)
}
}
So this design works really nice, but as you can see testing the above action is difficult as I can't directly manipulate the UserProperties in the test set up.
I'm not sure you're suppose to override OnActionExecuting like that in MCV, normally I make an ActionFilterAttribute
public class SomeMagicAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
Then your class:
[SomeMagic]
public abstract class MyControllerBase : Controller
{
}
Then in your unit test you can just do
var magic = new SomeMagicAttribute();
var simulatedContext = new ActionExecutingContext();
magic.OnActionExecuting(simulatedContext);
There are plenty of examples for having it worked on an MVC application. How is it done on Web Forms?
Here are the steps to use Ninject with WebForms.
Step1 - Downloads
There are two downloads required - Ninject-2.0.0.0-release-net-3.5 and the WebForm extensions Ninject.Web_1.0.0.0_With.log4net (there is an NLog alternative).
The following files need to be referenced in the web application: Ninject.dll, Ninject.Web.dll, Ninject.Extensions.Logging.dll and Ninject.Extensions.Logging.Log4net.dll.
Step 2 - Global.asax
The Global class needs to derive from Ninject.Web.NinjectHttpApplication and implement CreateKernel(), which creates the container:
using Ninject; using Ninject.Web;
namespace Company.Web {
public class Global : NinjectHttpApplication
protected override IKernel CreateKernel()
{
IKernel kernel = new StandardKernel(new YourWebModule());
return kernel;
}
The StandardKernel constructor takes a Module.
Step 3 - Module
The Module, in this case YourWebModule, defines all the bindings the web application will need:
using Ninject;
using Ninject.Web;
namespace Company.Web
{
public class YourWebModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<ICustomerRepository>().To<CustomerRepository>();
}
In this example, wherever the ICustomerRepository interface is referenced the concrete CustomerRepository will be used.
Step 4 - Pages
Once that's done each page needs to inherit from Ninject.Web.PageBase:
using Ninject;
using Ninject.Web;
namespace Company.Web
{
public partial class Default : PageBase
{
[Inject]
public ICustomerRepository CustomerRepo { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
Customer customer = CustomerRepo.GetCustomerFor(int customerID);
}
The InjectAttribute -[Inject] - tells Ninject to inject ICustomerRepository into the CustomerRepo Property.
If you already have a base page you just need to get your base page to derive from the Ninject.Web.PageBase.
Step 5 - Master Pages
Inevitably, you'll have master pages, and to allow a MasterPage to access injected objects you'll need to derive your master page from Ninject.Web.MasterPageBase:
using Ninject;
using Ninject.Web;
namespace Company.Web
{
public partial class Site : MasterPageBase
{
#region Properties
[Inject]
public IInventoryRepository InventoryRepo { get; set; }
Step 6 - Static Web Service Methods
The next problem was not being able to inject into static methods. We had a few Ajax PageMethods, which are obviously static, so I had to move the methods into a standard web service. Again, the web service needs to derive from a Ninject class - Ninject.Web.WebServiceBase:
using Ninject;
using Ninject.Web;
namespace Company.Web.Services
{
[WebService(Namespace = "//tempuri.org/">http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class YourWebService : WebServiceBase
{
#region Properties
[Inject]
public ICountbackRepository CountbackRepo { get; set; }
#endregion
[WebMethod]
public Productivity GetProductivity(int userID)
{
CountbackService _countbackService =
new CountbackService(CountbackRepo, ListRepo, LoggerRepo);
In your JavaScript you'll need to reference the standard service - Company.Web.Services.YourWebService.GetProductivity(user, onSuccess), rather than PageMethods.GetProductivity(user, onSuccess).
The only other problem I found was injecting objects into User Controls. While it's possible to create your own base UserControl with Ninject capabilities, I found it quicker to add a Property to the user control for the required object and setting the Property in the container page. I think supporting UserControls out of the box is on the Ninject "to-do" list.
Adding Ninject is quite simple and it is an eloquent IoC solution. Many people like it because there is no Xml configuration. It has other useful "tricks" such as turning objects into Singletons with just the Ninject syntax - Bind<ILogger>().To<WebLogger>().InSingletonScope(). There is no need to change WebLogger into an actual Singleton implmentation, I like this.
It's gotten easier with the release of Ninject v3.0 (as of 4/12/2012). Injection is implemented via HttpModule so there is no need to have your pages inherit from a custom Page / MasterPage. Here are the steps (and code) for a quick spike.
Create a new ASP.NET WebForms project
Use NuGet to add the Ninject.Web lib (which will also bring down the Ninject.Web.Common and Ninject libs)
Register your custom bindings in App_Start / NinjectWebCommon.cs / RegisterServices method
Use attribute injection on your pages
NinjectWebCommon / RegisterServices
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IAmAModel>().To<Model1>();
}
Default
public partial class _Default : System.Web.UI.Page
{
[Inject]
public IAmAModel Model { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine(Model.ExecuteOperation());
}
}
Site.Master
public partial class SiteMaster : System.Web.UI.MasterPage
{
[Inject]
public IAmAModel Model { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine("From master: "
+ Model.ExecuteOperation());
}
}
Models
public interface IAmAModel
{
string ExecuteOperation();
}
public class Model1 : IAmAModel
{
public string ExecuteOperation()
{
return "I am a model 1";
}
}
public class Model2 : IAmAModel
{
public string ExecuteOperation()
{
return "I am a model 2";
}
}
Results from output window
I am a model 1
From master: I am a model 1
The answer here currently does not work due to a open bug. Here is a modified version of #Jason's steps using a customer httpmodule to inject into pages and controls without needing to inherit from ninject classes.
Create a new ASP.NET WebForms project
Use NuGet to add the Ninject.Web lib
Register your custom bindings in App_Start / NinjectWebCommon.cs / RegisterServices method
Add InjectPageModule and register in NinjectWebCommon
Use attribute injection on your pages
InjectPageModule.cs
public class InjectPageModule : DisposableObject, IHttpModule
{
public InjectPageModule(Func<IKernel> lazyKernel)
{
this.lazyKernel = lazyKernel;
}
public void Init(HttpApplication context)
{
this.lazyKernel().Inject(context);
context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
}
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var currentPage = HttpContext.Current.Handler as Page;
if (currentPage != null)
{
currentPage.InitComplete += OnPageInitComplete;
}
}
private void OnPageInitComplete(object sender, EventArgs e)
{
var currentPage = (Page)sender;
this.lazyKernel().Inject(currentPage);
this.lazyKernel().Inject(currentPage.Master);
foreach (Control c in GetControlTree(currentPage))
{
this.lazyKernel().Inject(c);
}
}
private IEnumerable<Control> GetControlTree(Control root)
{
foreach (Control child in root.Controls)
{
yield return child;
foreach (Control c in GetControlTree(child))
{
yield return c;
}
}
}
private readonly Func<IKernel> lazyKernel;
}
NinjectWebCommon / RegisterServices
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IHttpModule>().To<InjectPageModule>();
kernel.Bind<IAmAModel>().To<Model1>();
}
Default
public partial class _Default : System.Web.UI.Page
{
[Inject]
public IAmAModel Model { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine(Model.ExecuteOperation());
}
}
Site.Master
public partial class SiteMaster : System.Web.UI.MasterPage
{
[Inject]
public IAmAModel Model { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine("From master: "
+ Model.ExecuteOperation());
}
}
Models
public interface IAmAModel
{
string ExecuteOperation();
}
public class Model1 : IAmAModel
{
public string ExecuteOperation()
{
return "I am a model 1";
}
}
public class Model2 : IAmAModel
{
public string ExecuteOperation()
{
return "I am a model 2";
}
}
Results from output window
I am a model 1
From master: I am a model 1
I think here are the steps to implement Ninject.Web on ASP.NET Web Forms.
Implement NinjectHttpApplication at Global.asax. For the Kernel, pass it in by implementing NinjectModule.
On each web forms page load event at code behind, implement Ninject.Web.PageBase. Add instance class with [Inject] filter on top of it.
For more detailed example, below are some useful links I found:
1.http://joeandcode.net/post/Ninject-2-with-WebForms-35
2.http://davidhayden.com/blog/dave/archive/2008/06/20/NinjectDependencyInjectionASPNETWebPagesSample.aspx
Check the book "Pro ASP.NET MVC 2 Framework, 2nd Edition" by Steve Sanderson (Apress). The author uses Ninject to connect with a database. I think you can use the examples and adapt them to your needs.
public IGoalsService_CRUD _context { get; set; }
The _context object is being set to null somehow. Following are the rest of the settings
public partial class CreateGoal : Page
{
[Inject]
public IGoalsService_CRUD _context { get; set; }
}
For Global File
protected override IKernel CreateKernel()
{
IKernel kernel = new StandardKernel(new Bindings());
return kernel;
}
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<goalsetterEntities>().To<goalsetterEntities>();
Bind<IGoalsService_CRUD>().To<GoalsService_CRUD>();
}
}
I'm trying to add logging with aspect orientated programming using castle windsor in plain asp.net, i.e. not MVC
I've added a class that implements the IInterceptor interface and an attribute that inherits from Attribute.
public class LogAttribute : Attribute
{
public Level LogLevel { get; set; }
public LogAttribute(Level level)
{
LogLevel = level;
}
}
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
MethodInfo mi = invocation.Method;
LogAttribute[] atts = (LogAttribute[])mi.GetCustomAttributes(typeof(LogAttribute), true);
// if method not marked with InternalUseRestricted attribute, then pass on call
if (atts.Length == 0)
{
invocation.Proceed();
}
else
{
ISeiLogger log = LoggerFactory.GetLogger(mi.DeclaringType.ToString());
//assume only one logging attribute
//log on entry
log.LogEnter(atts[0].LogLevel);
//allow code to continue
invocation.Proceed();
//log on exit
log.LogExit(atts[0].LogLevel);
}
}
}
Now in the global.asax.cs I've added the following:
public partial class Global : System.Web.HttpApplication, IoCProvider
{
private void InitializeIoC()
{
container = new WindsorContainer();
container.Install(new Sei.Aspect.AspectInstaller());
}
public IWindsorContainer Container
{
get { return container; }
}
private static Sei.Logging.ISeiLogger log;
private IWindsorContainer container;
public override void Init()
{
base.Init();
InitializeIoC();
}
and I've created an installer class:
public class AspectInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
//container.Register(AllTypes.FromAssembly(Assembly.GetExecutingAssembly()).BasedOn<IInterceptor>().Configure(component => component.LifeStyle.PerWebRequest));
container.Register(Component.For<IInterceptor>().ImplementedBy<LoggingInterceptor>().LifeStyle.PerWebRequest);
container.Register(Component.For<IInterceptor>().ImplementedBy<InternalUseRestrictedInterceptor>().LifeStyle.PerWebRequest);
container.Register(Component.For<IInterceptor>().ImplementedBy<CachingInterceptor>().LifeStyle.PerWebRequest);
}
}
I now want to add the attribute to some arbitary page's code behind class and some arbitary virtual method, as in
[Log(Level.Info)]
protected string Login(string username, string password)
{
DoSomething();
}
This obviously doesn't work. Do I need to change the way I'm instantiating the page (its a page's code-behind class) to use a container? Or is it the way I'm registering the interceptors? I want to be able to use the interceptors on any class going forward and not have to tell the container about each and every class that I have in my application.
Short answer: it's not possible.
Long answer: due to the way ASP.NET Web Forms works, it doesn't let anyone interfere with the page instantiation. Some claim that using a custom PageHandlerFactory lets you do IoC, but this only lets you set properties after the page has been instantiated, which is simply not enough for proxying.
So runtime proxy libraries such as DynamicProxy or LinFu can't do anything about this. But you may be able to use compile-time aspect weavers, such as PostSharp.
Alternatively, make your code-behind as slim as possible, deferring actual logic to Windsor-managed components.
I have the Global.asax like the code below:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
// ....
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(IOCControllerFactory));
}
}
public class IOCControllerFactory : DefaultControllerFactory
{
private readonly IKernel kernel;
public IOCControllerFactory()
{
kernel = new StandardKernel(new NanocrmContainer());
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return base.GetControllerInstance(requestContext, controllerType);
var controller = kernel.TryGet(controllerType) as IController;
if (controller == null)
return base.GetControllerInstance(requestContext, controllerType);
var standartController = controller as Controller;
if (standartController is IIoCController)
((IIoCController)standartController).SetIoc(kernel);
return standartController;
}
class NanocrmContainer : Ninject.Modules.NinjectModule
{
public override void Load()
{
// ...
Bind<DomainModel.Entities.db>().ToSelf().InRequestScope().WithConstructorArgument("connection", "Data Source=lims;Initial Catalog=nanocrm;Persist Security Info=True;User ID=***;Password=***");
}
}
}
In this case if somewhere it is the class, defined like:
public class UserRepository : IUserRepository
{
private db dataContext;
private IUserGroupRepository userGroupRepository;
public UserRepository(db dataContext, IUserGroupRepository userGroupRepository)
{
this.dataContext = dataContext;
this.userGroupRepository = userGroupRepository;
}
}
then the dataContext instance is created (if no one was created in this request scope) by Ninject.
So the trouble now is - where to invoke dataContext method .Dispose()?
UPD:
so i followed the advice from KeeperOfTheSoul and solved the issue in such way:
public override void ReleaseController(IController controller)
{
base.ReleaseController(controller);
var db = kernel.Get<DomainModel.Entities.db>();
db.Dispose();
}
A good place to handle this is in IControllerFactory.ReleaseController, eg
public override void ReleaseController() {
base.ReleaseController();
//Do whatever you need to clean up the IoC container here
}
In NInject this could be handled by scoping using an activation block, at the start of the request when creating the controller you can store the activation block in the HttpContext's current items, during ReleaseController you can retrieve the previously created activation block and dispose it.
You could also consider using InScope and having the custom scope implement INotifyWhenDisposed. After that the usage is the same as with an activation block, except now you store the scope in the HttpContext's current items.
A pattern that is sometimes used to dispose db connections is to call Dispose from the finaliser.
public class db : IDisposable {
//called by the garbage collector
~db() {
//Call dispose to make sure the resources are cleaned up
Dispose(false);
}
//IDisposable implementation
public void Dispose() {
Dispose(true);
}
//subclasses of db can override Dispose(bool) and clean up their own fields
protected virtual void Dispose (bool disposing) {
if (disposing) {
//Supress finalization as all resources are released by this method
//Calling Dispose on IDisposable members should be done here
GC.SupressFinalize();
}
//Clean up unmanaged resources
//Do not call other objects as they might be already collected if called from the finalizer
}
}
You could hook it into Application_EndRequest.