I'm using Castle Windsor 3.0 for dependency injection in a demo ASP.NET app. One of my controllers takes an ICustomerService instance, which in turn takes an ISession instance, all via constructor. The ISession is registered with Windsor using a factory method and PerWebRequest life style.
_container.Register(Component.For<ISessionFactory>().Instance(DbHelper.BuildSessionFactory()).LifestyleSingleton());
_container.Register(Component.For<ISession>().LifestylePerWebRequest().UsingFactoryMethod(x => x.Resolve<ISessionFactory>().OpenSession()));
In the global.asax file, I have an Application_EndRequest handler that attempts to commit the transaction:
protected void Application_EndRequest(object sender, EventArgs e)
{
if (!IsStaticResourceRequest())
{
var app = (HttpApplication)sender;
var factory = _container.Resolve<ISessionFactory>();
var session = ManagedWebSessionContext.Unbind(Context, factory);
if (session != null &&
session.Transaction != null &&
session.Transaction.IsActive)
{
session.Transaction.Commit();
session.Transaction.Dispose();
session.Dispose();
}
}
}
The problem is that the PerWebRequest lifestyle of Windsor has its own Application_EndRequest event handler which disposes of the service prior to my Application_EndRequest handler (in global.asax) executing, so the code in my Application_EndRequest handler never gets a chance to commit the transaction. Is there a workaround for this?
I moved away from this pattern and rather have the commit happen in your controller action. Why wait until you have left the page and at the end of the request before committing. Makes passing UI messages a lot more difficult. Currently I take in the ISession as a param in the controller to ensure it gets injected:
public void SomeController(ISession session)
{
_session = session;
}
then in your Action:
using(var trans = new _session.BeginTransaction()){
try{
..update etc
trans.commit();
}
catch(Exception ex){
trans.rollback();
// return message, log etc
}
}
Windsor will cleanup the session for you at the end of the request.
The solution ended up being committing my transaction in an event earlier in the ASP.NET lifecycle. I picked ReleaseRequestState, but any of the methods leading up to the EndRequest event should be adequate, as long as the handler has finished processing the request.
Related
Our ASP.NET app sends multiple Ajax requests in parallel on the same session. We also read/write to HttpSessionState during some of those requests. What I WANT is for all the concurrent requests to execute in parallel for performance reasons. What I get is they are serialized by ASP.NET. I experimented with configuring enableSessionState="ReadOnly", but this breaks our Forms Authentication.
Is there a way to get both session state AND concurrency in one session? Do I need to use a custom SessionState or Provider? Any samples of this out there?
PS I'm not worried about thread safety when accessing the SessionState - I can do that programmatically.
From MSDN (link):
However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished.
So at least for those AJAX calls that require write access to the Session you are out of luck with the default providers.
Not sure if you could get around this using a custom provider.
You can achieve parallell execution for those AJAX calls that do not need access to the session by blocking the ASP.NET_SessionId cookie in an HttpModule. See my answer to this question.
Edit: In order to make this answer more self reliant, I add a slightly modified version of the HttpModule and a bit of discussion (further down). Here's the module code that you can use to prevent Session state from serializing your Ajax calls:
using System;
using System.Web;
namespace TestModule
{
public class TestPreventCookie : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.PostAcquireRequestState +=
(new EventHandler(this.Application_PostAcquireRequestState));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//prevent session cookie from reaching the service
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
context.Request.Cookies.Remove("ASP.NET_SessionId");
}
}
private void Application_PostAcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
var s = context.Session;
if (s != null)
s.Abandon();
}
}
private bool BlockCookie(HttpContext context)
{
// put code here that determines if the session cookie should be blocked
// this could be based on the path of the current request for instance
// only block the cookie when you *know* that access to the Session is not needed
}
}
}
The idea behind this module is that using some criteria based on the project requirements, we remove the ASP.NET_SessionId cookie from the current context (note, we don't expire it on the client).
This means that further on in the request pipeline, the server will create a new session. In order to prevent this newly created session from destroying the existing ASP.NET_SessionId cookie on the client, we abandon it immediately after it has been created.
The end result is that each request that is "intercepted" by the module will execute as if it had no session.
I'm trying to setup a simple HttpModule to handle authentication between my single sign on server. I've included code for the module below. The module is hitting my SSO and properly authenticating; however, on pages with forms the postback events are not occurring properly (e.g. isPostBack value is always false even though a POST occurred, button click events don't get hit, etc.).
public sealed class MyAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
public void Dispose()
{
}
public static void OnAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.Initialize();
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
// Validate the ticket coming back from the authentication server
if (!string.IsNullOrEmpty(request["ticket"]))
{
// I can include code for this if you want, but it appears to be
// working correct as whenever I get a ticket from my SSO it is processed
// correctly. I only get a ticket after coming from the SSO server and
// then it is removed from the URL so this only gets hit once.
MyAuthentication.ProcessTicketValidation();
}
if (!request.IsAuthenticated)
{
// redirect to the login server
response.Redirect("https://sso.example.com/login.aspx" + "?" + "service=" +
HttpUtility.UrlEncode(context.Request.Url.AbsoluteUri), false);
}
}
}
EDIT
I would also like to note that if I change the line:
if (!string.IsNullOrEmpty(request["ticket"]))
to:
if (!string.IsNullOrEmpty(request.QueryString["ticket"]))
the problem goes away.
Is it possible that your postbacks have a duplicate form data variable, "ticket"? That would seem to explain the behavior to me.
Aside from that, this line is suspicous:
FormsAuthentication.Initialize();
The FormsAuthentication class uses the "Provider" pattern, which means it's a singleton. You should not re-initialize. From the msdn documentation:
The Initialize method is called when the FormsAuthenticationModule
creates an instance of the FormsAuthentication class. This method is
not intended to be called from your code.
I am running the 2.0 RTM of NServiceBus and am getting a NullReferenceException when my MessageModule binds the CurrentSessionContext to my NHibernate sessionfactory.
From within my Application_Start, I call the following method:
public static void WithWeb(IUnityContainer container)
{
log4net.Config.XmlConfigurator.Configure();
var childContainer = container.CreateChildContainer();
childContainer.RegisterInstance<ISessionFactory>(NHibernateSession.SessionFactory);
var bus = NServiceBus.Configure.WithWeb()
.UnityBuilder(childContainer)
.Log4Net()
.XmlSerializer()
.MsmqTransport()
.IsTransactional(true)
.PurgeOnStartup(false)
.UnicastBus()
.ImpersonateSender(false)
.LoadMessageHandlers()
.CreateBus();
var activeBus = bus.Start();
container.RegisterInstance(typeof(IBus), activeBus);
}
When the bus is started, my message module starts with the following:
public void HandleBeginMessage()
{
try
{
CurrentSessionContext.Bind(_sessionFactory.OpenSession());
}
catch (Exception e)
{
_log.Error("Error occurred in HandleBeginMessage of NHibernateMessageModule", e);
throw;
}
}
In looking at my log, we are logging the following error when the bind method is called:
System.NullReferenceException: Object reference not set to an instance of an object.
at NHibernate.Context.WebSessionContext.GetMap()
at NHibernate.Context.MapBasedSessionContext.set_Session(ISession value)
at NHibernate.Context.CurrentSessionContext.Bind(ISession session)
Apparently, there is some issue in getting access to the HttpContext. Should this call to configure NServiceBus occur later in the lifecycle than Application_Start? Or is there another workaround that others have used to get handlers working within an Asp.NET Web application?
Thanks,
Steve
I wouldn't use WebSessionContext in this case, precisely because NServiceBus can operate independently of HttpContexts. If you want to use a single session context implementation for both web and NServiceBus message handling, I'd implement NHibernate.Context.ICurrentSessionContext with an hybrid storage, i.e. if HttpContext.Current != null, use the HttpContext as session storage. Otherwise use a thread local storage. This is similar to what Castle ActiveRecord does with its HybridWebThreadScopeInfo.
We are trying to remove the global.asax from our many web applications in favor of HttpModules that are in a common code base. This works really well for many application events such as BeginRequest and PostAuthentication, but there is no Application Start event exposed in the HttpModule.
I can think of a couple of smelly ways to overcome this deficit. For example, I can probably do this:
protected virtual void BeginRequest(object sender, EventArgs e)
{
Log.Debug("Entered BeginRequest...");
var app = HttpContext.Current.Application;
var hasBeenSet app["HasBeenExecuted"] == null ? false : true;
if(!hasBeenSet)
{
app.Lock();
// ... do app level code
app.Add("HasBeenExecuted", true);
app.Unlock();
}
// do regular begin request stuff ...
}
But this just doesn't smell well to me.
What is the best way to invoke some application begin logic without having a global.asax?
Just keep a static bool in the HttpModule:
private static bool _hasApplicationStarted = false;
private static object _locker = new object();
private void EnsureStarted()
{
if (_hasApplicationStarted) return;
lock (_locker)
{
if (_hasApplicationStarted) return;
// perform application startup here
_hasApplicationStarted = true;
}
}
Then have any method that needs the application to have started just call EnsureStarted.
HttpModules and HttpHandlers will execute on every single request, while the Global.asax App Start event is when the application starts, thus only once.
You could make a general global.asax which will load all assemblies with a specific interface, and then drop in the dll's you want executed for that specific application. Or even register them in your web.config, and have your general global.asax read the keys, and then load and execute the code you want.
I think this is better than putting app once code in a module and checking on a state variable.
I need my linq to sql datacontext to be available across my business/data layer for all my repository objects to access. However since this is a web app, I want to create and destroy it per request. I'm wondering if having a singleton class that can lazily create and attach the datacontext to current HttpContext would work. My question is: would the datacontext get disposed automatically when the request ends? Below is the code for what I'm thinking. Would this accomplish my purpose: have a thread-safe datacontext instance that is lazily available and is automatically disposed when the request ends?
public class SingletonDC
{
public static NorthwindDataContext Default
{
get
{
NorthwindDataContext defaultInstance = (NorthwindDataContext)System.Web.HttpContext.Current.Items["datacontext"];
if (defaultInstance == null)
{
defaultInstance = new NorthwindDataContext();
System.Web.HttpContext.Current.Items.Add("datacontext", defaultInstance);
}
return defaultInstance;
}
}
}
What you imagine makes sense - using the HTTP Request context to store stuff - but No, disposable objects stored in the current HttpContext will not auto-magically be disposed when the request ends. You will have to hande that yourself, somehow.
There is an "End Request" event that you can hook into easily, for example using code that you drop into Global.asax.cs. In your Application_EndRequest() method, you can call Dispose() manually on each object in the list that requires it.
One way to do it is to iterate through each item in the context, test for IDisposable, and then call Dispose if appropriate.
protected void Application_EndRequest(Object sender, EventArgs e)
{
foreach (var key in HttpContext.Current.Items.Keys)
{
var disposable = HttpContext.Current.Items[key] as IDisposable;
if (disposable != null)
{
disposable.Dispose();
HttpContext.Current.Items[key] = null;
}
}
}
I think that oughtta do it. ASPNET doesn't do this for you automatically. Of course you need protection from exceptions and so on, before using this code in a real app.
Keith Craig of Vertigo wrote a relevant post on the topic a while ago, describing what you want to do as a pattern, in other words a way of doing things that ought to be repeated. He provides a class to help out with that, to lazy load the DB context and drop it into the current context. There are some pitfalls with the approach - you can read about them in the comment discussion on that post. Also there are a bunch of related articles cited in the comments.
Cheeso's code will generate an InvalidOperationException "Collection was modified; enumeration operation may not execute" because it is trying to modify the HttpContext items that it's iterating over.
You can use a copy of the list to prevent this.
protected void Application_EndRequest(Object sender, EventArgs e)
{
var keys = new ArrayList(HttpContext.Current.Items.Keys);
foreach (var key in keys)
{
var disposable = HttpContext.Current.Items[key] as IDisposable;
if (disposable != null)
{
disposable.Dispose();
HttpContext.Current.Items[key] = null;
}
}
}