I've been tasked with taking over an existing ASP.NET MVC 2.0 web application that was developed by a third party developer who is no longer around to provide any assistance. There has been a requirement to add some functionality to the project, which required a project upgrade to .NET 4.5, which has been performed.
The sites underlying MSSQL 2008 R2 database access has been implemented using NHibernate version 2.0.1.4000, along with Castle and FluentNHibernate.
This is the first project I've been involved in that has used NHibernate, and I've hit a problem that has me stumped. The problem did not exist until the upgrade to .NET 4.5.
All database operations are working normally, except for one. Saving a particular object (
Opportunity type) to the database (this object directly maps to an Opportunity database table) fails. Prior to saving (in this case a SQL UPDATE statement), the object has new values set. But the record in the database always has the old values after saving.
Hooking up log4net to view the debug code, shows that the record is indeed updated, but using the old values in the UPDATE statement.
Surprisingly, the Opportunity object is intially saved using the same Save method (albeit via a different action method), and that is saving to the database just fine.
So my question is, what would cause this to happen? Being that I'm not an NHibernate expert, is it the case that the NHibernate version is simply incompatible with .NET 4.5? Or can anyone provide a pointer as to what the problem might be? I'm happy to show any code, but as there is so much I would need to know what. Below is a starter:
The Global.asax has the following references to NHibernate:
private static void MvcApplication_BeginRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.BeginTransaction();
}
private static void MvcApplication_EndRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.CommitTransaction();
}
The NHibernateSessionManager class is defined as (Opportunity derives from DomainBase):
public sealed class NHibernateSessionManager
{
private ISessionFactory sessionFactory;
private Configuration config;
#region Thread-safe, lazy Singleton
public static NHibernateSessionManager Instance
{
get
{
return Nested.nHibernateSessionManager;
}
}
private NHibernateSessionManager()
{
InitSessionFactory();
}
private class Nested
{
internal static readonly NHibernateSessionManager nHibernateSessionManager = new NHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var autoMappings = AutoPersistenceModel.MapEntitiesFromAssemblyOf<DomainBase>()
.Where(type =>
typeof(DomainBase).IsAssignableFrom(type) &&
type.IsClass &&
!type.IsAbstract)
.WithSetup(s =>
{
s.IsBaseType = type =>
type == typeof (DomainBase);
})
.UseOverridesFromAssemblyOf<OpportunityMappingOverride>()
.ConventionDiscovery.Add(DefaultLazy.AlwaysTrue())
.ConventionDiscovery.Add<CascadeAllHasOneConvention>()
.ConventionDiscovery.Add<CascadeAllHasManyConvention>()
.ConventionDiscovery.Add<CascadeAllReferenceConvention>();
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.FromConnectionStringWithKey("Default"))
.UseReflectionOptimizer()
.Cache(c => c.UseQueryCache().UseMininmalPuts().ProviderClass<SysCacheProvider>())
.ShowSql())
.Mappings(m => m.AutoMappings.Add(autoMappings))
.ExposeConfiguration(SetConfiguration)
.BuildSessionFactory();
}
private void SetConfiguration(Configuration cfg)
{
config = cfg;
}
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = threadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a Session has already been opened");
}
GetSession(interceptor);
}
public void GenerateSchema()
{
new SchemaExport(config).Execute(false, true, false, false);
}
public ISession GetSession()
{
return GetSession(null);
}
private ISession GetSession(IInterceptor interceptor)
{
ISession session = threadSession;
if (session == null)
{
if (interceptor != null)
{
session = sessionFactory.OpenSession(interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
threadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = threadSession;
threadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = threadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
threadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = threadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
threadTransaction = null;
}
}
catch (HibernateException)
{
RollbackTransaction();
throw;
}
}
public void RollbackTransaction()
{
ITransaction transaction = threadTransaction;
try
{
threadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
finally
{
CloseSession();
}
}
private static ITransaction threadTransaction
{
get
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
set
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
private static ISession threadSession
{
get
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
set
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
I'm hoping I won't get shot down for this question being too general. I've spent a day trying to work out what is happening, including extensive searches online.
It turned out the problem was that the NHibernateSessionManager class was storing its ITransaction and ISession objects in System.Runtime.Remoting.Messaging.CallContext.
Swapping it out to store the objects in the HttpContext.Current.Items collection resolved the issue.
I found this post which implies .NET 4.5 handles CallContext slightly differently compared with previous versions, which obviously caused my issue.
Because the NHibernateSessionManager class was in a class library that was also used by a couple of rarely used console applications, I left a fallback to the CallContext object as per below (not pretty, and there might have been an better alternative, but worked for me [subject to testing], as I've spent far to long figuring this one out using remote debugging):
private static ITransaction threadTransaction
{
get
{
try
{
return (ITransaction)System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"];
}
catch
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"] = value;
}
catch
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
}
private static ISession threadSession
{
get
{
try
{
return (ISession)System.Web.HttpContext.Current.Items["THREAD_SESSION"];
}
catch
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_SESSION"] = value;
}
catch
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
Related
I am working on a project where I want to keep users logged in using access tokens/refresh tokens. I store these values in a cookie and whenever a user visits the site, I want to automatically log him in regardless of the page that he uses to access the site. To do this, I created a BaseController, that all other controllers inherit from. The BaseController looks like this:
public abstract class BaseController : Controller
{
public BaseController()
{
LoginModel.SetUserFromAuthenticationCookie();
}
}
This constructor gets executed every time before an action is executed and is therefore exactly what I want. The problem is that SetUserFromAuthenticationCookie() is an async method, because it has to do calls to other async methods. It looks like this:
public async static Task SetUserFromAuthenticationCookie()
{
// Check if the authentication cookie is set and the User is null
if (AuthenticationRepository != null && User == null)
{
Api api = new Api();
// If a new authentication cookie was successfully created
if (await AuthenticationRepository.CreateNewAuthenticationCookieAsync())
{
var response = await api.Request(HttpMethod.Get, "api/user/mycredentials");
if(response.IsSuccessStatusCode)
{
User = api.serializer.Deserialize<UserViewModel>(await response.Content.ReadAsStringAsync());
}
}
}
}
The problem is that the execution order is not as I anticipated and because of that the user does not get logged in. I tried to work with .Result for the async methods, but that resulted in a deadlock. Besides that I read many threads on SO concerning the issue and eventually also found one that managed to get the login to work: How would I run an async Task<T> method synchronously?. It is somewhat hacky though and works with this helper:
public static class AsyncHelpers
{
/// <summary>
/// Execute's an async Task<T> method which has a void return value synchronously
/// </summary>
/// <param name="task">Task<T> method to execute</param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Execute's an async Task<T> method which has a T return type synchronously
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">Task<T> method to execute</param>
/// <returns></returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue<Tuple<SendOrPostCallback, object>> items =
new Queue<Tuple<SendOrPostCallback, object>>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple<SendOrPostCallback, object> task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null) // the method threw an exeption
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
If I then change the content of the BaseController constructor to:
AsyncHelpers.RunSync(() => LoginModel.SetUserFromAuthenticationCookie());
the functionality works as anticipated.
I would like to know though if you have any suggestions on how to do this in a nicer manner. Perhaps I should move the call to the SetUserFromAuthenticationCookie() to another location, but at this time I do not know where that would be.
I found this solution on another stack. Synchronously waiting for an async operation, and why does Wait() freeze the program here
Your constructor would need to look like this.
public BaseController()
{
var task = Task.Run(async () => { await LoginModel.SetUserFromAuthenticationCookie(); });
task.Wait();
}
I use Entity Framework to deal with my database, and as I googled a lot, I ran into some solution around updating my entities, here is extension method for updating entity the code :
public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
//(CDLTLL)Apply property changes to all referenced entities in context
//Custom extensor method
context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb);
}
else
{
throw new ObjectNotFoundException();
}
}
}
public static void ApplyReferencePropertyChanges(this ObjectContext context,
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
// this related end is a reference not a collection
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
And here is the code for saving or adding new object :
public void save (Category obj)
{
OurWebSiteEntities en = new OurWebSiteEntities();
if (obj.CategoryID == -1)
{
en.AddToCategories(obj);
}
else
en.AttachUpdated(obj);
en.SaveChanges();
}
And finally this the code I use to update my category entity in asp.net mvc :
if (cat.Category.CategoryID == -1)
{
// it is a new category
// and needs some property initialization
}
else
catItem = _categoryRepo.Items.FirstOrDefault(x => x.CategoryID == cat.Category.CategoryID);
TryUpdateModel(catItem, "Category");
_categoryRepo.Save(catItem);
But unfortunately it does not work for updating any category, and I am only able to create new one.
In your repository, you could create an update function and set entity state to modified.
public abstract class Repository<T>
{
public void Update(T entity) {
Context.Entry(entity).State = EntityState.Modified;
}
}
I am trying to use Fluent NHibernate with ASP.NET MVC 3 and I cannot seem to find a tutorial that explains how to get it all configured with ASP.NET MVC. I mainly am wondering where to put the ISession building function and how to call it when I need it. I see so many different implementations but none of them specify where they put this code. So if anyone can explain how to get it all configured to work with MVC 3 or where a very detailed tutorial is, that would be greatly appreciated.
You can have a look at S#arp Architecture.
It's a pretty solid architectural framework to work with ASP.NET MVC & NHibernate. They have a decent documentation and there's some sample projects to look at.
http://www.sharparchitecture.net/
If you are not using dependency injection you can try something like this
public class MvcApplication : System.Web.HttpApplication
{
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
private static ISessionFactory _session_factory;
private static object _session_factory_lock = new object();
protected static ISessionFactory CreateSessionFactory()
{
if (_session_factory != null) return _session_factory;
if (ConfigurationManager.ConnectionStrings["DbConnection"] != null)
{
var conn = ConfigurationManager.ConnectionStrings["DbConnection"];
SqlServerSessionFactoryBuilder fb = new SqlServerSessionFactoryBuilder(conn.ConnectionString);
_session_factory = fb.GetSessionFactory();
return _session_factory;
}
throw new Exception("Cannot build session factory, connection string is not defined.");
}
public MvcApplication()
{
_session_factory = CreateSessionFactory();
BeginRequest += delegate
{
try
{
CurrentSession = _session_factory.OpenSession();
}
catch (FluentConfigurationException ex)
{
logger.FatalException(string.Format("Error configuring the database {0}", ex.Message), ex);
}
};
EndRequest += delegate
{
if (CurrentSession != null)
{
if (CurrentSession.Transaction != null && CurrentSession.Transaction.IsActive)
{
logger.Error("Rolling back uncommited transaction");
CurrentSession.Transaction.Rollback();
}
else
{
CurrentSession.Flush();
}
CurrentSession.Close();
}
};
Error += delegate
{
var error = this.Server.GetLastError();
logger.ErrorException(string.Format("Unhandled error : {0}", error.Message), error);
};
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}
What is the best approach to managing NHibernate transaction using Autofac within web application?
My approach to session is
builder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
.ContainerScoped();
For ITransaction, I have found an example on Google Code, but it relies on HttpContext.Current.Error when deciding whether to rollback.
Is there a better solution? And what scope NHibernate transaction should have?
I posted this a while ago:
http://groups.google.com/group/autofac/browse_thread/thread/f10badba5fe0d546/e64f2e757df94e61?lnk=gst&q=transaction#e64f2e757df94e61
Modified, so that the interceptor has logging capability and [Transaction] attribute can also be used on a class.
[global::System.AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : Attribute
{
}
public class ServicesInterceptor : Castle.Core.Interceptor.IInterceptor
{
private readonly ISession db;
private ITransaction transaction = null;
public ServicesInterceptor(ISession db)
{
this.db = db;
}
public void Intercept(IInvocation invocation)
{
ILog log = LogManager.GetLogger(string.Format("{0}.{1}", invocation.Method.DeclaringType.FullName, invocation.Method.Name));
bool isTransactional = IsTransactional(invocation.Method);
bool iAmTheFirst = false;
if (transaction == null && isTransactional)
{
transaction = db.BeginTransaction();
iAmTheFirst = true;
}
try
{
invocation.Proceed();
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Commit();
transaction = null;
}
}
catch (Exception ex)
{
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Rollback();
db.Clear();
transaction = null;
}
log.Error(ex);
throw ex;
}
}
private bool IsTransactional(MethodInfo mi)
{
var atrClass = mi.DeclaringType.GetCustomAttributes(false);
foreach (var a in atrClass)
if (a is TransactionAttribute)
return true;
var atrMethod = mi.GetCustomAttributes(false);
foreach (var a in atrMethod)
if (a is TransactionAttribute)
return true;
return false;
}
}
When I use autofac I use the same container scoped method but instead of passing the same session to my Repository/DAO objects I pass an UnitOfWork that is container scoped. The Unit of work has this in the constructor.
private readonly ISession _session;
private ITransaction _transaction;
public UnitOfWork(ISession session)
{
_session = session;
_transaction = session.BeginTransaction();
}
And the dispose is:
public void Dispose()
{
try
{
if (_transaction != null &&
!_transaction.WasCommitted &&
!_transaction.WasRolledBack)
_transaction.Commit();
_transaction = null;
}
catch (Exception)
{
Rollback();
throw;
}
}
I am (ab)using the deterministic disposal stuff in autofac in order to manage this, and well I sort of like it.
The other thing is that I am basically only targeting an ASPNet environment and made a conscious decision that a transaction is tied to a web request. So a transaction per web request pattern.
Because of that I can do this error handling code in an IHttpModule:
void context_Error(object sender, System.EventArgs e)
{
_containerProvider.RequestContainer.Resolve<IUnitOfWork>().Rollback();
}
I haven't taken a look at NHibernate.Burrow too closely but I'm sure there is something there that does most of this.
I usually manage the transaction myself..
public ActionResult Edit(Question q){
try {
using (var t = repo.BeginTransaction()){
repo.Save(q);
t.Commit();
return View();
}
catch (Exception e){
...
}
}
We can access session data in controllers and views like this:
Session["SessionKey1"]
How do you access Session values from a class other than a controller or view?
I'd use dependency injection and pass the instance of the HttpContext (or just the session) to the class that needs access to the Session. The other alternative is to reference HttpContext.Current, but that will make it harder to test since it's a static object.
public ActionResult MyAction()
{
var foo = new Foo( this.HttpContext );
...
}
public class Foo
{
private HttpContextBase Context { get; set; }
public Foo( HttpContextBase context )
{
this.Context = context;
}
public void Bar()
{
var value = this.Context.Session["barKey"];
...
}
}
You just need to call it through the HttpContext like so:
HttpContext.Current.Session["MyValue"] = "Something";
Here is my version of a solution for this problem. Notice that I also use a dependency injection as well, the only major difference is that the "session" object is accessed through a Singleton
private iSession _Session;
private iSession InternalSession
{
get
{
if (_Session == null)
{
_Session = new SessionDecorator(this.Session);
}
return _Session;
}
}
Here is the SessionDecorator class, which uses a Decorator pattern to wrap the session around an interface :
public class SessionDecorator : iSession
{
private HttpSessionStateBase _Session;
private const string SESSIONKEY1= "SESSIONKEY1";
private const string SESSIONKEY2= "SESSIONKEY2";
public SessionDecorator(HttpSessionStateBase session)
{
_Session = session;
}
int iSession.AValue
{
get
{
return _Session[SESSIONKEY1] == null ? 1 : Convert.ToInt32(_Session[SESSIONKEY1]);
}
set
{
_Session[SESSIONKEY1] = value;
}
}
int iSession.AnotherValue
{
get
{
return _Session[SESSIONKEY2] == null ? 0 : Convert.ToInt32(_Session[SESSIONKEY2]);
}
set
{
_Session[SESSIONKEY2] = value;
}
}
}`
Hope this helps :)
Haven't done it myself, but this sample from Chad Meyer's blog might help (from this post: http://www.chadmyers.com/Blog/archive/2007/11/30/asp.net-webforms-and-mvc-in-the-same-project.aspx)
[ControllerAction]
public void Edit(int id)
{
IHttpSessionState session = HttpContext.Session;
if (session["LoggedIn"] == null || ((bool)session["LoggedIn"] != true))
RenderView("NotLoggedIn");
Product p = SomeFancyDataAccess.GetProductByID(id);
RenderView("Edit", p);
}
I would also wrap all session variables into a single class file. That way you can use intelliSense to select them. This cuts down on the number of paces in code where you need to specify the "strings" for the session.