I have a web site running Burrow, and I'd like to use it for Quartz jobs as well.
The thing is that I want them to not share any state. The Quartz jobs is running in each own threads while the Mvc framework closes the Workspace at the end of every request.
Ideally, mvc should have it's own session, and each job should have it's own session.
What are my possibilities here?
PS: I'm very new to Quartz, Burrow and MVC btw, so I'm probably missing some very essential knowledge :|
I tried a simple naive way that at least seem to work for now. Is there something fundamentally wrong I'm doing here? Will these variables be garbage collected when a thread exits?
public static class SessionManager
{
[ThreadStatic]
private static IDictionary<ISessionFactory, ISession> _sessions;
public static ISession GetSession(Type type)
{
var burrow = new BurrowFramework();
if (burrow.WorkSpaceIsReady)
{
return burrow.GetSession(type);
}
else
{
if (_sessions == null)
{
_sessions = new Dictionary<ISessionFactory, ISession>();
}
var factory = burrow.GetSessionFactory(type);
if (!_sessions.ContainsKey(factory))
{
_sessions[factory] = null;
}
var session = _sessions[factory];
if (session == null || !session.IsOpen)
{
session = _sessions[factory] = factory.OpenSession();
}
return session;
}
}
}
Related
I have a task that runs in a different thread and requires the session. I've done:
public GenerateDocList(LLStatistics.DocLists.DocList docs)
{
this.docs = docs;
context = HttpContext.Current;
}
and
public void StartTask()
{
//this code runs in a separate thread
HttpContext.Current = context;
/* rest of the code */
}
Now the thread has knowledge of the session and it works for a while but at some point in my loop HttpContext.Current.Session becomes null. Any ideas what can I do about this?
public static LLDAC.DAL.DBCTX LLDB
{
get
{
LLDAC.DAL.DBCTX currentUserDBContext = HttpContext.Current.Session["LLDBContext"] as LLDAC.DAL.DBCTX;
if (currentUserDBContext == null)
{
currentUserDBContext = new LLDAC.DAL.DBCTX();
HttpContext.Current.Session.Add("LLDBContext", currentUserDBContext);//this works only for a few loop iterations
}
return currentUserDBContext;
}
}
In general, this is a very fragile pattern for a multi-threaded operation. Long-running tasks (which I assume this is) are best suited to instance methods in a class rather than static methods such that the class can maintain any dependent objects. Also, since the session state is not thread safe and can span multiple requests you are getting into some very risky business by cashing your DB context in the session at all.
If you are convinced this is best done with static methods and stored in the session, you may be able to do something like this:
public static HttpSessionState MySession { get; set; }
public GenerateDocList(LLStatistics.DocLists.DocList docs)
{
this.docs = docs;
MySession = HttpContext.Current.Session;
}
Then:
public static LLDAC.DAL.DBCTX LLDB
{
get
{
LLDAC.DAL.DBCTX currentUserDBContext = MySession["LLDBContext"] as LLDAC.DAL.DBCTX;
if (currentUserDBContext == null)
{
currentUserDBContext = new LLDAC.DAL.DBCTX();
if (MySession == null)
{
thow new InvalidOperaionException("MySession is null");
}
MySession.Add("LLDBContext", currentUserDBContext);
}
return currentUserDBContext;
}
}
Note that you could still run into issues with the session since other threads could still modify the session.
A better solution would probably look something like this:
public class DocListGenerator : IDisposable
{
public LLDAC.DAL.DBCTX LLDB { get; private set; }
public DocListGenerator()
{
LLDB = new LLDAC.DAL.DBCTX();
}
public void GenerateList()
{
// Put loop here.
}
public void Dispose()
{
if (LLDB != null)
{
LLDB.Dispose();
}
}
}
Then your calling code looks like this:
public void StartTask()
{
using (DocListGenerator generator = new DocListGenerator()
{
generator.GenerateList();
}
}
If you really want to cache something, you could cache your instance like this:
HttpContext.Current.Sesssion.Add("ListGenerator", generator);
However, I still don't think that is a particularly good idea since your context could still be disposed or otherwise altered by a different thread.
Using anything related to the HttpContext.Current on anything besides the main Request thread is generally going to get you into trouble in ASP.net.
The HttpContext is actually backed on a thread belonging to a Thread Pool and the thread may very well get reused on another request.
This is actually a common issue with using the new Async/Await keywords in ASP.net as well.
In order to help you, it would help to know why you're attempting this in the first place?
Is this a single server or a web farm with multiple load balanced servers?
Are you hosting it yourself, or is it the site hosted by a provider?
What is the SessionState implementation (SQL Server, State Server, In-Process, or something custom like MemCached, Redis, etc...)
What version of ASP .net?
Why are you starting a new thread instead of just doing the processing on the request thread?
If you really can't (or shouldn't) use session. Then you could use something like a correlation ID.
Guid correlationID = Guid.NewGuid();
HttpContext.Current.Session["DocListID"] = correlationID;
DocList.GoOffAndGenerateSomeStuffOnANewThread(correlationID);
... when process is done, store the results somewhere using the specified ID
// Serialize the result to SQL server, the file system, cache...
DocList.StoreResultsSomewhereUnderID();
... later on
DocList.CheckForResultsUnderID(HttpContext.Current.Session["DocListID"]);
I twist myself around a workable solution to use several databases in RavenDB for an ASP.Net MVC app using Castle Windsor for the wiring.
This is the current installer
public class RavenInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>().Instance(CreateDocumentStore()).LifeStyle.Singleton,
Component.For<IDocumentSession>().UsingFactoryMethod(GetDocumentSesssion).LifeStyle.PerWebRequest
);
}
static IDocumentStore CreateDocumentStore()
{
var store = new DocumentStore { ConnectionStringName = "RavenDb_CS9" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Users).Assembly, store);
return store;
}
static IDocumentSession GetDocumentSesssion(IKernel kernel)
{
var store = kernel.Resolve<IDocumentStore>();
return store.OpenSession();
}
}
The above works perfect but only for one Database.
I can't find the proper thinking how to handle another database. The whole chain starts with a domain service asking for an IDocumentSession. Then the flow is as specified in the above installer. But where/how do I ask for a "SessionToDb1" or a "SessionToDb2"?
The important is of course what connection string to use (where the DB property is specified) but also what indexes to create in respective DB / DocumentStore.
Did anyone accomplish this using Windsor? Am I thinking/attacking it wrong here?
Thanks!
Because you have:
Component.For<IDocumentSession>()
.UsingFactoryMethod(GetDocumentSesssion)
.LifeStyle.PerWebRequest
Your GetDocumentSession method is going to be called any time you inject an IDocumentSession. This is good.
When working with multiple databases, you need to pass the database name as a parameter to OpenSession. So, you need some way to resolve which database you would like to connect to based on the current web request.
You need to modify the GetDocumentSession method to implement whatever custom logic you are going to use. For example, you may want to look at a cookie, asp.net session item, current thread principal, or some other criteria. The decision is custom to your application, all that matters is somehow you open the session with the correct database name.
I've run into this problem before with nhibernate.
I found the best solution is to create a SessionManager class which wraps the Creation of the document store and the Session..
So I.E.
public interface ISessionManager
{
void BuildDocumentStore();
IDocumentSession OpenSession();
}
public interface ISiteSessionManager : ISessionManager
{
}
public class SiteSessionManager : ISiteSessionManager
{
IDocumentStore _documentStore;
public SiteSessionManager()
{
BuildDocumentStore();
}
public void BuildDocumentStore()
{
_documentStore = new DocumentStore
{
Url = "http://localhost:88",
DefaultDatabase = "test"
};
_documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(SiteSessionManager).Assembly, _documentStore);
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
// And then!.
Container.Register(Component.For<ISiteSessionManager>().Instance(new SiteSessionManager()).LifestyleSingleton());
// And then!.
public class FindUsers
{
readonly ISiteSessionManager _siteSessionManager;
public FindUsers(ISiteSessionManager siteSessionManager)
{
_siteSessionManager = siteSessionManager;
}
public IList<User> GetUsers()
{
using (var session = _siteSessionManager.OpenSession())
{
// do your query
return null;
}
}
}
Rinse and repeat for multiple databases.!
I'm trying to create an Orchard CMS module that enables a RESTful web service using OpenRasta for a given route (/openrasta/* for example).
I need to get to the Orchard ContentManager to get the content for the service to return, so my OpenRasta handler (ContentHandler) uses a ContentService, which implements IContentService, which inherits IDependency. Normally this would work because Orchard will inject a ContentManager into the constructor:
public class ContentService : IContentService {
public IContentManager content;
public ContentService(IContentManager content) {
this.content = content;
}
public IEnumerable<string> GetContentTypeDefinitionNames() {
return content.GetContentTypeDefinitions().Select(d => d.Name);
}
}
But when I run it I get an error because OpenRasta doesn't know anything about the Orchard dependencies and it's trying to create ContentService, not Orchard, which is fair enough:
OpenRasta.DI.DependencyResolutionException: Could not resolve type
ContentService because its dependencies couldn't be fullfilled
Constructor: Orchard.ContentManagement.IContentManager
Is there a way to achieve this, can I go to an Orchard class somewhere and say "give me an instance of the ContentManager"?
Update: See my comments on #rfcdejong's answer for updates on my progress.
Are u using a ServiceRoute, added in a class implementing IRouteProvider
Look at the ServiceRoute summary, it says "Enables the creation of service routes over HTTP in support of REST scenarios."
public class Routes : IRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
private static ServiceRoute _rastaService = new ServiceRoute(
"openrasta",
new MyServiceHostFactory<IOpenRastaService>(),
typeof(IOpenRastaService));
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[]
{
new RouteDescriptor
{
Priority = -1,
Route = _rastaService
}
};
}
}
And want to resolve ContentService? U might have to resolve the interface.
i guess u want the following to work:
var contentService = LifetimeScope.ResolveNew<IContentService>();
I have used HostContainer.Resolve directly and had issues as well. I will describe the solution i'm using at the moment in my own ServiceHostFactory
Do u have a own ServiceHostFactory deriven from OrchardServiceHostFactory?
In that case u can implement the following code to help u resolve instances
private ILifetimeScope _lifetimeScope = null;
private ILifetimeScope LifetimeScope
{
get
{
if (_lifetimeScope == null)
{
IHttpContextAccessor accessor = HostContainer.Resolve<IHttpContextAccessor>();
IRunningShellTable runningShellTable = HostContainer.Resolve<IRunningShellTable>();
ShellSettings shellSettings = runningShellTable.Match(accessor.Current());
IOrchardHost orchardHost = HostContainer.Resolve<IOrchardHost>();
ShellContext shellContext = orchardHost.GetShellContext(shellSettings);
_lifetimeScope = shellContext.LifetimeScope;
}
return _lifetimeScope;
}
}
I also created LifetimeScopeExtensions that has the following code
public static class LifetimeScopeExtensions
{
public static T ResolveNew<T>(this ILifetimeScope scope)
{
IWorkContextAccessor workContextAccessor = scope.Resolve<IWorkContextAccessor>();
WorkContext workContext = workContextAccessor.GetContext();
if (workContext == null)
{
using (IWorkContextScope workContextScope = workContextAccessor.CreateWorkContextScope())
{
ILifetimeScope lifetimeScope = workContextScope.Resolve<ILifetimeScope>();
return lifetimeScope.Resolve<T>();
}
}
else
{
ILifetimeScope lifetimeScope = workContext.Resolve<ILifetimeScope>();
return lifetimeScope.Resolve<T>();
}
}
public static object ResolveNew(this ILifetimeScope scope, Type type)
{
IWorkContextAccessor workContextAccessor = scope.Resolve<IWorkContextAccessor>();
WorkContext workContext = workContextAccessor.GetContext();
if (workContext == null)
{
using (IWorkContextScope workContextScope = workContextAccessor.CreateWorkContextScope())
{
ILifetimeScope lifetimeScope = workContextScope.Resolve<ILifetimeScope>();
return lifetimeScope.Resolve(type);
}
}
else
{
ILifetimeScope lifetimeScope = workContext.Resolve<ILifetimeScope>();
return lifetimeScope.Resolve(type);
}
}
}
var settingsService = LifetimeScope.ResolveNew<ITokenServiceSettingsService>();
So the issue is that your CMS uses its own IoC container. By default OpenRasta does that too.
This means that services that are present in Orchard won't be visible to OpenRasta.
For all other IoC containers, the answer is damn right simple: You use the IoC adaptation layer that lets OpenRasta live in whatever ioc container you want. We support unity, structuremap, castle and ninject. That said, autofac is not supported as no one ever built it.
The cleanest way for you to solve this problem (and any other you may encounter in the future for those issues) would be to build your own autofac ioc adaptation layer for openrasta. If you need help doing that, you can join the openeverything mailing list where the devs would be happy to help you.
first I want to say that I hope this doesn't look like I am lazy but I have some trouble understanding a piece of code from the following project.
http://kigg.codeplex.com/
I was going through the source code and I noticed something that would be usefull for my own little project I am making. In their BaseController they have the following code:
private static readonly Type CurrentUserKey = typeof(IUser);
public IUser CurrentUser
{
get
{
if (!string.IsNullOrEmpty(CurrentUserName))
{
IUser user = HttpContext.Items[CurrentUserKey] as IUser;
if (user == null)
{
user = AccountRepository.FindByClaim(CurrentUserName);
if (user != null)
{
HttpContext.Items[CurrentUserKey] = user;
}
}
return user;
}
return null;
}
}
This isn't an exact copy of the code I adjusted it a little to my needs. This part of the code I still understand. They store their IUser in HttpContext.Items. I guess they do it so that they don't have to call the database eachtime they need the User object.
The part that I don't understand is how they maintain this object in between requests. If I understand correctly the HttpContext.Items is a per request cache storage.
So after some more digging I found the following code.
internal static IDictionary<UnityPerWebRequestLifetimeManager, object> GetInstances(HttpContextBase httpContext)
{
IDictionary<UnityPerWebRequestLifetimeManager, object> instances;
if (httpContext.Items.Contains(Key))
{
instances = (IDictionary<UnityPerWebRequestLifetimeManager, object>) httpContext.Items[Key];
}
else
{
lock (httpContext.Items)
{
if (httpContext.Items.Contains(Key))
{
instances = (IDictionary<UnityPerWebRequestLifetimeManager, object>) httpContext.Items[Key];
}
else
{
instances = new Dictionary<UnityPerWebRequestLifetimeManager, object>();
httpContext.Items.Add(Key, instances);
}
}
}
return instances;
}
This is the part where some magic happens that I don't understand. I think they use Unity to do some dependency injection on each request? In my project I am using Ninject and I am wondering how I can get the same result.
I guess InRequestScope in Ninject is the same as UnityPerWebRequestLifetimeManager? I am also wondering which class/method they are binding to which interface? Since the HttpContext.Items get destroyed each request how do they prevent losing their user object?
Anyway it's kinda a long question so I am grateful for any push in the right direction.
In Ninject, you pick your tech-specific extension (Ninject.Web or Ninject.Web.Mvc) and use InRequestScope to manage stuff in 'the .Items context'. They get Disposed at the end of the request and fresh ones will be Resolved as needed on subsequent requests.
It definitely wont be as much code or as complex as some of the stuff you're citing IMO :D
Has anyone ever eperienced session's being shared application wide?
My MVC application has the vanilla setup but for some reason, my sessions are being shared.
I didn't really think about it when I could switch between FF and IE and maintain a logged in state but now, I've noticed that I can switch machines too.
My web.config doesn't have anything in to setup the session state, so I assumed it was cookie based but it seem it's not.
Has anyone ever experienced this before and if so, how did you resolve it?
FYI: I'm running it on Server 2003 IIS6.
Thanks all!!
Gav
Are you specifically using storing things in Session or are you seeing this in TempData calls (which temporarily uses session as well)?
Well would you believe it... Stupid static variables...
I thought by using a static private variable that it would help me by not doing as much work when getting the data, but as it seems, it was the root of evil. (doctor evil pinky)
Thanks everyone!
** NOTE THIS IS HOW NOT!! TO DO IT **
public class UserHelper
{
private static UserSession _session;
public static UserSession Session
{
get
{
// If we already have the session, don't get it
// from the session state
if (_session == null)
{
// Attempt to get the session from the
// session state
_session = GetUserSessionFromSession(HttpContext.Current.Session);
if (_session == null)
{
// Create a new session object
_session = new UserSession();
}
}
return _session;
}
set
{
// Set the local value
_session = value;
// Add the object to the session state
HttpContext.Current.Session["SMEUser"] = _session;
}
}
public static void Logout()
{
Logout(HttpContext.Current.Session);
}
public static void Logout(HttpSessionState session)
{
_session = null;
session.Clear();
}
public static UserSession GetUserSessionFromSession(HttpSessionState session)
{
// Get the session from the session state
UserSession us = session["SMEUser"] as UserSession;
return us;
}
}