Need help identifying a subtle bug.. in IIS? The Session? Elsewhere..? - c#

I have a very subtle bug that I'm having trouble identifying.
Background:
We have 2 sites running off the same application on the same web server.
SiteA -- accessed by www.SiteA.com
SiteB -- accessed by www.SiteB.com
When the request first comes in, the siteId is identified based on the Host and stored in the Session as follows:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string host = Request.Url.Host;
int siteId = new SiteManager().GetSiteByUrl(host).SiteId;
if (SessionUser.Instance().SiteId != siteId)
{
SessionUser.Instance().SiteId = siteId;
}
}
This ID used later in determining what data to retreive and to determine what style to present:
// this happens during an initialization phase
_siteConfiguration = _siteManager.GetSiteById(SessionUser.Instance().SiteId);
// then later:
private void SetPageTheme()
{
string theme = null;
switch (_siteConfiguration.SiteId)
{
case ConfigurationHelper.SITE.A:
theme = "SiteATheme";
break;
case ConfigurationHelper.SITE.B:
theme = "SiteBTheme";
break;
}
Page.Theme = theme;
}
The problem:
the problem I'm facing is if you load both sites at almost exactly the same time, i.e. within milliseconds, sometimes SiteA will load with SiteB's theme and vice versa. This doesn't happen very often but it has come to the attention of the client so it's now a problem.. Something is happening somewhere within those few milliseconds in the difference between SiteA loading and SiteB loading, and I don't know how to identify what that is.
The question:
Does anyone have any ideas what could be going wrong here? Something is getting confused somewhere. Is it IIS mixing up the requests? Is it the Session mixing up the site it's supposed to return the SiteId for?
If any more info is needed, I'll supply it.
Update:
For reference, this is the definition of SessionUser (basically, create a static instance of an object to get the SiteId value from the Session):
public class SessionUser
{
private static SessionUser _instance;
public int SiteId { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public static SessionUser Instance()
{
if (null == _instance)
{
_instance = new SessionUser();
if (null != HttpContext.Current.Session)
{
if (null == HttpContext.Current.Session["User"])
{
if (HttpContext.Current.Request.QueryString["sid"] != null)
{
int nSiteId = int.Parse(HttpContext.Current.Request.QueryString["sid"]);
_instance.SiteId = nSiteId;
HttpContext.Current.Session["User"] = _instance;
}
}
else
{
_instance = (SessionUser) HttpContext.Current.Session["User"];
}
}
}
return _instance;
}
}

Without knowing what the 'SessionUser' class looks like, I can only speculate.
I will assume that SessionUser.Instance() returns a 'static' instance (or member rather).
Now, these will be shared across the entire application. So it makes sense that this cannot be shared between 2 sites.
I suggest you rather use HttpContext to store the setting at BeginRequest.
The code will then look like the following:
class SessionUser
{
public static SessionUser Instance()
{
var ctx = HttpContext.Current;
var su = ctx["SessionUser"] as SessionUser;
if (su == null)
{
ctx["SessionUser"] = su = new SessionUser();
}
return su;
}
}

I guess you could put the code that stores the current Site ID inside a lock block, but this may hamper performance. It makes more sense to use something not shared by both sites, as leppie says.
For the lock example:
if (SessionUser.Instance().SiteId != siteId)
{
lock(somePrivateStaticObject)
{
if (SessionUser.Instance().SiteId != siteId)
{
SessionUser.Instance().SiteId = siteId;
}
}
}

Related

Multiple user seeing the same items in shopping cart

I have created a shopping cart using session to store the item being added to cart.Upon deployment i found out that the shopping cart is not unique to the user that is other user are getting items added to the cart by other user.
I tried to use the login username in the session name to make the session unique to that user but it is not working.
public class ListOfDataset
{
static ListOfDataset()
{
string username = ClaimsPrincipal.Current.Identity.Name;
// If the cart is not in the session, create one and put it there
// Otherwise, get it from the session
if (HttpContext.Current.Session[string.Format("ASPNETShoppingCart-{0}", username)] == null)
{
Instance = new ListOfDataset();
Instance.Items = new List<DataSet>();
HttpContext.Current.Session[string.Format("ASPNETShoppingCart-{0}", username)] = Instance;
}
else
{
Instance = (ListOfDataset)HttpContext.Current.Session[string.Format("ASPNETShoppingCart-{0}", username)];
}
}
}
Updated code,error in else statement-object reference not set and function ChekIfdatasetexist always return false:
public class ListOfDataset
{
public static ListOfDataset Instance
{
get
{
ListOfDataset cart = null;
if (HttpContext.Current.Session["ASPNETShoppingCart"] == null)
{
cart = new ListOfDataset();
cart.Items = new List<DataSet>();
HttpContext.Current.Session["ASPNETShoppingCart"] = cart.Items;
}
else
{
cart.Items =(List<DataSet> )HttpContext.Current.Session["ASPNETShoppingCart"];
}
return cart;
}
}
public List<DataSet> Items { get; private set; }
public void AddItem(DataSet itemdataset)
{
Items.Add(itemdataset);
HttpContext.Current.Session["ASPNETShoppingCart"] = Items;
}
public bool CheckIfDataSetExist(string servicename)
{
DataSet DataSetexist = null;
if (Items != null)
{
DataSetexist = Items.Where(i => i.DataSetName == servicename).FirstOrDefault();
}
if (DataSetexist != null) return true;
return false;
}
}
There are two issues here.
As someone said in comments you have made the Instance member static, which means it will remain the same application wide and shared among users.
Session is not the best place to store per user data cause it will blow up your server memory and wont scale. Better use a database to store the data.

NHibernate session factory count limit?

I am creating multiple session factories through code (not config file)
The problem I'm having is that after the 20th session factory creation, I start getting an exception (MappingException), and I don't know why.
Regardless of the order, it goes bad after the 20th. Sessions are successfully created as long as they are in the first 20 of them.
MappingException message: Unique suffix 100_ length must be less than maximum 4 characters
any help is appreciated.
public static void AddPortfolioToConnectionstrings(string portfolio, string
connectionString)
{
var configuration = new Configuration()
.Configure()
.SessionFactoryName(portfolio)
.SetProperty("connection.connection_string", connectionString);
...
_portfolios.Add(portfolio, configuration.BuildSessionFactory());
}
As usual the error messages of NHibernate aren't helping anyone.
In my case I had this error:
MappingException message: Unique suffix 100_ length must be less than maximum 4 characters
After intensive research, the cause of this error was that the connection string had the wrong server address, and then wrong credentials supplied.
About your question: having 20 destination databases to connect to is unusual, but it can be done. With the implementation I show you below, I use 12 session factories, but make sure you run your code in a 64-bit process space, otherwise it will quickly eat up the memory available to a 32-bit process.
The only thing you need to look out for is that you need to have a custom Session factory builder that you bind as a singleton. A lightweight version of my implementation looks like this:
public interface ISessionFactoryBuilder
{
IDictionary<string, ISessionFactory> SessionFactories { get; }
}
public IDictionary<string, ISessionFactory> SessionFactories { get; private set; }
private readonly IConfigurationManager _configurationManager;
public SessionFactoryBuilder(IConfigurationManager configurationManager)
{
this._configurationManager = configurationManager;
this.SessionFactories = this.BuildSessionFactories();
}
private IDictionary<string, ISessionFactory> BuildSessionFactories()
{
var sessionFactories = new Dictionary<string, ISessionFactory>(StringComparer.InvariantCultureIgnoreCase);
var connectionStrings = this._configurationManager.GetConnectionStrings();
if (connectionStrings.Count == 0)
throw new ConfigurationErrorsException("No connection descriptions can be found!");
foreach (ConnectionStringSettings item in connectionStrings)
if (item.Name != "LocalSqlServer" && item.Name != "OraAspNetConString")
sessionFactories.Add(item.Name, this.InitializeSessionFactory(item.ConnectionString, item.ProviderName));
return sessionFactories;
}
private class Connectiontypes
{
public string Db_type { get; set; }
public FluentConfiguration Configuration { get; set; }
}
private ISessionFactory InitializeSessionFactory(string connectionString = "", string providerName = "")
{
Trace.WriteLine($"{connectionString}");
List<SessionFactoryBuilder.Connectiontypes> conntypes = new List<SessionFactoryBuilder.Connectiontypes> {
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.SqlClient",
Configuration = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(connectionString).ShowSql()
.Dialect<XMsSql2005Dialect>()) },
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.OracleDataClient",
Configuration = Fluently.Configure().Database(OracleDataClientConfiguration.Oracle10
.ConnectionString(connectionString).Provider<NHibernate.Connection.DriverConnectionProvider>()
.Driver<NHibernate.Driver.OracleManagedDataClientDriver>()
.Dialect<XOracle10gDialect>().ShowSql())
},
new SessionFactoryBuilder.Connectiontypes
{
Db_type = "System.Data.MySQLDataClient", Configuration = Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString(connectionString).ShowSql())
}
};
FluentConfiguration fluentConfiguration = conntypes.Find(x => x.Db_type == providerName).Configuration;
fluentConfiguration.ExposeConfiguration(x =>
{
x.SetProperty("command_timeout", "120");
});
#if DEBUG
fluentConfiguration.ExposeConfiguration(x =>
{
x.SetInterceptor(new SqlStatementInterceptor());
});
#endif
var mappings = fluentConfiguration.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<UsersMap>();
});
var config = mappings.BuildConfiguration();
foreach (PersistentClass persistentClass in config.ClassMappings)
{
persistentClass.DynamicUpdate = true;
}
var sessionFactory = mappings
#if DEBUG
.Diagnostics(d => d.Enable(true))
.Diagnostics(d => d.OutputToConsole())
#endif
.BuildSessionFactory();
return sessionFactory;
}
public void Dispose()
{
if (this.SessionFactories.Count > 0)
{
foreach (var item in this.SessionFactories)
{
item.Value.Close();
item.Value.Dispose();
}
this.SessionFactories = null;
}
}
}
Then I bind this with NInject as:
Bind<ISessionFactoryBuilder>().To<SessionFactoryBuilder>().InSingletonScope().WithConstructorArgument("configurationManager", context => context.Kernel.Get<IConfigurationManager>());
Of course you have to execute this anywhere near your program startup, or as we call it the Composition Root.
The only thing missing here is the obvious implementation of the IConfigurationManager, which is just my custom wrapper around ConfigurationManager, that gets passed in to the constructor of SessionFactoryBuilder.
This way you can have your Session factory builder built at application startup and never again (obviously until the appdomain gets restarted or so), therefore you won't have memory leaks or OutOfMemory exceptions for having that many SessionFactories hanging around and getting created.

Prism NavigationService get previous view name

Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName

How to initialize and make use of a static class?

I have a static class which is used to access a static concurrentdictionary:
public static class LinkProvider
{
private static ConcurrentDictionary<String, APNLink.Link> deviceLinks;
static LinkProvider()
{
int numProcs = Environment.ProcessorCount;
int concurrencyLevel = numProcs * 2;
deviceLinks = new ConcurrentDictionary<string, APNLink.Link>(concurrencyLevel, 64);
}
public APNLink.Link getDeviceLink(string deviceId, string userId)
{
var result = deviceLinks.First(x => x.Key == deviceId).Value;
if (result == null)
{
var link = new APNLink.Link(username, accountCode, new APNLink.DeviceType());
deviceLinks.TryAdd(deviceId, link);
return link;
}
else
{
return result;
}
}
public bool RemoveLink(string deviceId)
{
//not implmented
return false;
}
}
how can I make use of this class in my controller in an asp.net
Ie I want to go:
LinkProvider provider;
APNLink.Link tmpLink = provider.getDeviceLink(id, User.Identity.Name);
//use my link
Back ground. The dictionary is used to save a link object between states / requests in a asp.net web api program. So when the service needs to use the link, it asks the linkprovider to find a link for it and if there isn't one it must create one. So I need the dictionary object to the same instance in all my http requests.
So I need the dictionary object to the same instance in all my http
requests
Then use a static class, and make every method static too, so you could call it using the following syntax:
APNLink.Link tmpLink = LinkProvider.getDeviceLink(id, User.Identity.Name);
That being said, you should be aware that in-memory static variables in an ASP.Net application are not always safe to use, because your application isn't stateless and in case the application pool is recycled, your dictionary will be re-instantiated.

WCF task scheduler, or timer like in OGame, Travian etc

I'm looking for resources, or anyone who have writting scheduler in WCF.
What I want to achive is basically what can you see in OGame, or Travian or doznes of other text browser based game.
Player click and send task to server to make building for him, or unit or something else.
From what I figured out I need to run some kind if scheduler on server that will gather all tasks, and will track them, until some perdiod of time will pass (2 mins, 10 mins, 3 days etc.), and after that period end service should call an action, like send data to database.
Well. I've been trying to make something very simply, that I can start from and ended with this:
public class BuildingScheduler
{
public int TaskID { get; set; }
public string UserName { get; set; }
public DateTime TaskEnd { get; set; }
public string BuildingName { get; set; }
public bool TaskDone { get; set; }
public DateTime RemainingTime { get; set; }
TestDBModelContainer _ctx;
TestData _testData;
public IDuplexClient Client { get; set; }
public BuildingScheduler()
{
TaskDone = false;
}
public void MakeBuilding()
{
while (DateTime.Now <= TaskEnd)
{
//Client.DisplayMessage(DateTime.Now.ToString());
RemainingTime = DateTime.Now;
}
_testData = new TestData { DataName = BuildingName, Created = DateTime.Now };
_ctx = new TestDBModelContainer();
_ctx.TestDataSet.AddObject(_testData);
_ctx.SaveChanges();
TaskDone = true;
//Client.DisplayMessage("Building completed!");
}
}
static List<UserChannel> _userChannels = new List<UserChannel>();
static List<BuildingScheduler> _buildingSchedules = new List<BuildingScheduler>();
List<BuildingScheduler> _buildingSchedulesToRemove = new List<BuildingScheduler>();
[OperationContract]
public void DoWork()
{
IDuplexClient client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();
UserChannel userChannel = new UserChannel { Client = client, ClientName = "TestClient" };
string clientName = (from p in _userChannels
where p.ClientName == "TestClient"
select p.ClientName).FirstOrDefault();
lock (((ICollection)_userChannels).SyncRoot)
{
if (clientName == null)
{
_userChannels.Add(userChannel);
}
}
CheckBuilding();
}
private void CheckBuilding()
{
BuildingScheduler bs = (from p in _buildingSchedules
where p.UserName == "TestClient"
select p).FirstOrDefault();
IDuplexClient client = (from p in _userChannels
where p.ClientName == "TestClient"
select p.Client).FirstOrDefault();
if (bs != null)
{
client.DisplayMessage(bs.RemainingTime);
}
}
private void StartBuilding()
{
foreach (BuildingScheduler bs in _buildingSchedules)
{
if (bs.TaskDone == false)
{
bs.MakeBuilding();
}
else if (bs.TaskDone == true)
{
_buildingSchedulesToRemove.Add(bs);
}
}
for(int i = 0; i <= _buildingSchedulesToRemove.Count; i++)
{
BuildingScheduler bs = _buildingSchedulesToRemove.Where(p => p.TaskDone == true).Select(x => x).FirstOrDefault();
_buildingSchedules.Remove(bs);
_buildingSchedulesToRemove.Remove(bs);
}
CheckBuilding();
}
[OperationContract]
public void MakeBuilding(string name)
{
BuildingScheduler _buildng = new BuildingScheduler();
//_buildng.Client = client;
_buildng.TaskID = 1;
_buildng.UserName = "TestClient";
_buildng.TaskEnd = DateTime.Now.AddSeconds(50);
_buildng.BuildingName = name;
_buildingSchedules.Add(_buildng);
StartBuilding();
}
I have hardcoded most values, for testing.
InstanceContextMode is set for PerCall.
Anyway. This code is working. At least to some point. If we ignore zylion exceptions from Entity Framework, I can add tasks from multiple clients and they are added to db in order from newset to oldest (or shortest to longest ?).
The point is, user CANT track his tasks. When I change page I don't see how much time remaining till task done. This code essentialy do not provide time tracking because i got rid of it as it wasn't working anyway.
I guess I should store my task in some presitant data storage and everytime user check page newset state should be draw from that storage and send to him.
I'm not and expert but i think best option here is to store all data in Memory until task is done.
Any relational database will be probably to slow, if I will have to constantly update records with newset state of task.
I know I should just synchronize client side timer with server and do not stream constatly time from server. But Big question is here how to get newset state of task pogress when user come back to page after 3 second or 3 hours ?
Any advices how to make it work as expected.
And btw. I'm using pollingduplex and Silverlight.
It sounds like you are using WCF to do something for which it wasn't designed (but I understand the need). I would suggest you look at Workflow Foundation (WF) as a possible solution to your problem. Here is a good explanation:
http://msdn.microsoft.com/library/dd851337.aspx
Here is also a good intro video:
http://channel9.msdn.com/Blogs/mwink/Introduction-to-Workflow-Services-building-WCF-Services-with-WF
Workflow can consume WCF services and it is designed to work over time. It holds data in state until something changes (regardless of time) without consuming excess resources. Also, it allows for persistance and parallel processes.

Categories