ASP.NET HttpContext Cache removes right after insertion - c#

I have an ASP.NET 4 web-service.
It has an ImportModule action in a ModuleController controller.
That's how it works:
The user uploads a module as a CSV-file.
This file is being read using HttpPostedFileBase.InputStream and custom CSV-reading class.
This file is being transformed to a C# object according to some rules and validations. If a file is valid, then it transforms to C# object, stores in a Cache with unique GUID name and a user is redirected to CompleteImportModule action.
User checks if data is correct and he confirms uploading.
Long story short, there is a code which tells you more:
ImportModule action.
public ActionResult ImportModule(HttpPostedFileBase file)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Index");
}
ModuleQuestion[] questions;
ModuleInfo moduleInfo;
string uploadId = Guid.NewGuid().ToString();
// It is my custom CSV-reader and it works. Values are assigned
FormDataCsvReader csvReader = new FormDataCsvReader(file.InputStream);
if (!csvReader.Process(out questions, out moduleInfo))
{
// File is invalid
return RedirectToAction("Index");
}
ViewBag.UploadId = uploadId;
ViewBag.ModuleInfo = moduleInfo;
ViewBag.Questions = questions;
HttpContext.Cache.Add("UploadModule_" + uploadId,
new Tuple<ModuleInfo, ModuleQuestion[]>(moduleInfo, questions),
null,
Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(30),
CacheItemPriority.NotRemovable,
(k, v, r) =>
{
LoggingFactory.GetLogger().Debug("Removed from cache: {0}. Reason: {1}", k, r);
});
return View();
}
In View ImportModule:
// Output data from ViewBag.ModuleInfo and ViewBag.Questions
<form method="POST" action="#Url.Action("CompleteImportModule")">
<input type="hidden" name="uploadId" value="#ViewBag.UploadId"/>
<input type="submit" value="Send">
</form>
CompleteImportModule action:
[HttpPost]
public ActionResult CompleteImportModule(string uploadId)
{
var item = HttpContext.Cache["UploadModule_" + uploadId];
if (item == null) RedirectToAction("Index");
// upload module
HttpContext.Cache.Remove("UploadModule_" + uploadId);
return RedirectToAction("Index");
}
However, I met some problems. I cannot upload the module because the value is removed from a Cache right after being inserted. It is stored only for a second:
DEBUG 2015-06-22 15:00:18,696 thread 85: Added to cache:
UploadModule_c843077d-21d0-4e9f-9e5e-3df82da4bac8
DEBUG 2015-06-22 15:00:19,935 thread 48: Removed from cache:
UploadModule_c843077d-21d0-4e9f-9e5e-3df82da4bac8. Reason: Removed
The reason is "Removed" meaning that it is not expired and IIS hasn't removed it due to optimization but it looks like I removed is myself.
I am pretty sure that I am even not accessing this cache record before CompleteImportModule.
I have tried putting new StackTrace().ToString() in a CacheItemRemovedCallback. That's it, if it can help somehow:
at CPMAdministrator.Controllers.ModulesReferenceController.<ImportModule>b__9(String key, Object value, CacheItemRemovedReason reason)
at System.Web.Caching.CacheEntry.CallCacheItemRemovedCallback(CacheItemRemovedCallback callback, CacheItemRemovedReason reason)
at System.Web.Caching.CacheEntry.Close(CacheItemRemovedReason reason)
at System.Web.Caching.CacheSingle.UpdateCache(CacheKey cacheKey, CacheEntry newEntry, Boolean replace, CacheItemRemovedReason removedReason, Object& valueOld)
at System.Web.Caching.CacheSingle.Dispose(Boolean disposing)
at System.Web.Caching.CacheMultiple.Dispose(Boolean disposing)
at System.Web.HttpRuntime.Dispose()
at System.Web.HttpRuntime.ReleaseResourcesAndUnloadAppDomain(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
Why is it happening? Is it sort of IIS pool recycling? How can I ensure that the file is not being removed? Or how can I effictively store this data another way?

I have spent several hours finding an answer and found it right after posting a question! Let me share my experience with you.
According to my StackTrace, I understood that the cache has been cleared because the application has been ended and disposed:
at System.Web.HttpRuntime.Dispose()
at System.Web.HttpRuntime.ReleaseResourcesAndUnloadAppDomain(Object state)
All I needed is to find a reason of it.
I have opened my Global.asax file and added a Application_End method.
public class MvcApplication : HttpApplication
{
protected void Application_End()
{
}
}
It has been triggered right after view rendering success and right before cache clearing. Success! Now I needed to know the reason of application ending.
This post helped me:
There's System.Web.Hosting.HostingEnvironment.ShutdownReason
property that indicates why the application is being terminated. Its
value can be retrieved from inside Application_End().
I added a breakpoint in a beginning of Application_End and added System.Web.Hosting.HostingEnvironment.ShutdownReason to watch.
That's what it stored: BinDirChangeOrDirectoryRename.
After that, I have understood that the reason is that my log4net is writing logs right in BinDirectory. I just never knew that IIS is finishing web applications if bin directory has been changed.
I have moved my logs to a parent (application itself) folder and now it works.
It looks like I need to read more about ASP.NET.
I hope that it will help someone. Thanks to everybody who tried to help.

The reason given for removal is Removed, from the documentation that means:
The item is removed from the cache by a Remove method call or by an Insert method call that specified the same key.
So you have either explicitly called Remove somewhere else or are overwriting it with an Insert.

Related

C# Circular reference. System.Text.Json.JsonException: A possible object cycle was detected .NET 5

I have created an API that is returning an object A, containing object B and object B is containing object A, see example below:
public class Person() {
string Name {get;set;}
List<Schema> Schemas {get;set;}
}
public class Schema(){
List<Person> Persons {get;set;}
}
In C# this will not be a problem, and an exception will not be catched (I've added try-catch-statements everywhere.
Problem: When I tried out the function using swagger the exception thrown to the client is 500.
The client calling the API is telling me that the it's a cors policy validation. This I know for sure that this is not the problem since it works well in other scenarios.
To view the actual problem I had to go to windows "Event Logger".
From event logger i found this:
Exception:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.
at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
To solve the problem with circular reference I added the code below in the configuration (startup.cs, API):
services.AddMvc()
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve;
});
Documentation can be found at:
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0

MVC Postal Emailer Conflicting With Mvc.Unity4

I'm experiencing a weird issue when attempting to use Postal within an MVC application that also uses Mvc.Unity4.
I believe the issue is thrown due to lacking access to the HttpContext.
I attempt to send the email from inside one of my Controllers using Postal:
dynamic e = new Email("AccountActivation");
e.To = "name#email.com"
e.Send();
Attempting to send an email results in the following exception within Unity.Mvc4.UnityDependencyResolver:
[NullReferenceException: Object reference not set to an instance of an object.]
Unity.Mvc4.UnityDependencyResolver.get_ChildContainer() +57
Unity.Mvc4.UnityDependencyResolver.GetService(Type serviceType) +241
System.Web.Mvc.DefaultViewPageActivator.Create(ControllerContext controllerContext, Type type) +87
System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +216
Postal.EmailViewRenderer.RenderView(IView view, ViewDataDictionary viewData, ControllerContext controllerContext, ImageEmbedder imageEmbedder) +182
Postal.EmailViewRenderer.Render(Email email, String viewName) +204
Postal.EmailService.CreateMailMessage(Email email) +72
Postal.EmailService.Send(Email email) +65
I'm not too familiar with Mvc.Unity4, as this was added by a different developer.
Grasping at straws, I did try to register the correct Postal types within the Application_Start. The initialization of the Unity container is occurring in Bootstrapper.cs:
container.RegisterType<UsersController>(new InjectionConstructor());
container.RegisterInstance<IEmailService>(new EmailService());
Within my controller, I have:
private IEmailService _emailService;
public UsersController()
{
_emailService = new Postal.EmailService();
}
public UsersController(Postal.EmailService emailService)
{
_emailService = emailService;
}
[HttpPost]
public async Task<ActionResult> SendEmail(EmailViewModel viewModel)
{
dynamic e = new Email("AccountActivation");
e.ViewData.Add("To", "name#email.com");
e.ViewData.Add("From", "no-reply#email.com");
_emailService.Send(e);
More code...
}
I think your suspicion is right. HttpContext is not available during application initialization. Therefore Postal will not work (because it relies on ControllerContext and ViewContext) in the Application_Start event.
Since you are using DI, this also extends to the constructor of every class that is configured using Unity - you cannot use Postal in the constructor, but can in methods that are called after Application_Start is complete.
You need to move the call to Postal outside of Application_Start or else use the native .NET mail libraries in this one case (because they rely on System.Net and have no dependencies on HttpContext).
Sorry to answer my own question, but I was finally able to get this working.
Once I removed the async from the method, everything began working as expected.
So by changing...
public async Task<ActionResult> SendEmail(EmailViewModel viewModel)
To this...
public ActionResult SendEmail(EmailViewModel viewModel)
I was then able to send the emails without triggering the exception inside Unity.Mvc4.UnityDependencyResolver.get_ChildContainer().
I'm not sure why I wasn't able to call Postal's .Send() from within an async method (side note, I tried to call .SendAsync() with the same Unity issue resulting). If anyone can shed some light on why this would work within an async method, it would be much appreciated.
Thanks.

ASP.Net Async Page with Action.BeginInvoke

I'm trying to write an async page in asp .net which runs a function in a different thread.
The problem in the following code, is that when I debug it, the function EndAsyncOperation is never called. As a result, the page isn't fully loaded and loads for ever.
I use Action to run the code in a different thread from the thread pool. Is there maybe another way of running the code in a different thread that works?
Where am I going wrong?
And another question. I read that in ASP .Net the pages are ran with a threadpool. So howcome when I debug my site and try to load a few pages together they are loaded one after another syncronously?
public partial class AsyncPage : System.Web.UI.Page
{
void Page_Load(object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation)
);
}
IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
Action action = () =>
{
Start();
};
IAsyncResult asyncResult = action.BeginInvoke(new AsyncCallback(action.EndInvoke), null);
return asyncResult;
}
void EndAsyncOperation(IAsyncResult ar)
{
// This function isn't reached
}
public void Start()
{
// Do something
}
}
I believe that you need to pass the AsyncCallback object provided to you in the BeginAsyncOperation method parameters to BeginInvoke, not create a new one.
Your pages are loading synchronously because of your session configuration.
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. 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. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the # Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.
Source: ASP.NET Session State Overview, my highlight.

Linq- Random Item with same key has already been added Error

I am getting error "An item with the same key has already been added." I get this error randomly when many user try to access the same page on site in production at the same time.
In the code docid is passed and relevant help is displayed to user. As each user is viewing the same page so same ID is passed for all users. There is no insert operation in this call
Stack trace and souce code of mentioned line is given as follows
public string DocDescription(int docid)
{
DocumentRepository _Documentrepository = new DocumentRepository();
return _Documentrepository.GetDocDescription(docid);
}
}
public string GetDocDescription(int DocID)
{
if (DocID != 0)
return db.sysDocuments.SingleOrDefault(p => p.DocumentID == DocID).Description==null?"No Description Available":db.sysDocuments.SingleOrDefault(p => p.DocumentID == DocID).Description;
else
return "No Description Available";
}
Stack Trace excerpt:
System.Web.HttpException (0x80004005): Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper'. ---> System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.Web.HttpException (0x80004005): Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'. ---> System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Data.Linq.DataContext.GetTable(MetaTable metaTable)
at System.Data.Linq.DataContext.GetTable[TEntity]()
at UserManagement.Models.DocumentRepository.GetDocDescription(Int32 DocID) in D:\Myproj\UserManagement\UserManagement\Models\DocumnetRepository.cs:line 109
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at Castle.Proxies.ControllerActionInvokerProxy.InvokeActionMethod_callback(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at Castle.Proxies.Invocations.ControllerActionInvoker_InvokeActionMethod.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Net.Interceptor.InvokeActionMethodInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
From your code and comments, I get that you store db context in a static variable. This is generally a bad idea when working with entities.
DbContext is not thread safe. So, multiple users working with your site will cause such errors in context.
The general suggestion for working with context is to prefer short lived context. So just create a new instance, when you need it, and then forget it. For the web sites, it is quite common to use inversion of control container like Unity, Castle.Winsdor etc, and configure it to return one instance per web request for your DbContext. This will give you enough performance, so all entities needed for the current request are cached, and will not cause threading issues at the same time.
See comment too, static members are application scoped variables thus they're sharing the same dictionary which can throw errors like this if multiple requests add the same key.

Exceptions in System.Web.Routing.RouteCollection.GetRouteData

I've been getting two exceptions at random times in my asp.net mvc code running on iis7:
Exception type: InvalidOperationException
Exception message: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List'1.Enumerator.MoveNextRare()
at System.Collections.Generic.List'1.Enumerator.MoveNext()
at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext)
at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
at System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
and
Exception type: NullReferenceException
Exception message: Object reference not set to an instance of an object.
at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext)
at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
at System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
It's not consistently reproducible, but I assume it's something changing (or corrupting) RouteTable.Routes. The only place I access RouteTable.Routes in my project is in Global.asax.cs and I know that the code there is only being called once, so it's not the problem. Any idea on how to track it down?
In my case, it ended up being a HttpModule: Ext.Direct.Mvc (Ext.Direct for ASP.NET MVC). This module had a bug (Fixed in version 0.8.0) which registered routes again every time Init() was called for the IHttpModule. (which might be called multiple times). If the timing was right, it would corrupt the RouteTable.Routes collection, and cause one of the two exceptions above.
That error is consistent with a collection not being thread-safe in .Net.
As per the MSDN article on RouteCollection.GetWriteLock(), RouteTable.Routes is not thread-safe. This issue is because some piece of code tried to modify the RouteTable.Routes in a manner which was not thread-safe.
The best practice is to modify the RouteTable.Routes in the Application_Start. If you have to modify it in a way which can be accessed by multiple threads at the same time, make sure you make it in a thread-safe way. Check the example from MSDN, copied below for ease of reference:
using (RouteTable.Routes.GetWriteLock())
{
Route newRoute = new Route("{action}/{id}", new ReportRouteHandler());
RouteTable.Routes.Add(newRoute);
}
Same applies for when reading from the RouteTable - RouteCollection.GetReadLock().
Probably, if this was fixed by removing the HttpModule, it is because this HttpModule does not implement such locking mechanisms.
Other answers explained WHAT happens but provided little details on how to actually track it down. It may not be your own code causing all this trouble, so Ctrl-F is not enough..
The idea
What makes solving it difficult, is the fact that you usually get errors on a different requests than the ones actually modifying RouteCollection. A generally safe call to UrlHelper.GenerateUrl() or the like would fail at seemingly random times. To track it down, I chose to move exceptions from victims to the culprit by forbidding changing routes after the initial set up.
Step 1: Implement custom RouteCollection
IF a flag is set then yell hard when someone tries to change routes. It's a crude implementation but you get the idea.
public class RestrictedRouteCollection : RouteCollection
{
public Boolean EnableRestrictions { get; set; }
protected override void InsertItem(Int32 index, RouteBase item)
{
if (EnableRestrictions) { throw new Exception("Unexpected route added."); }
base.InsertItem(index, item);
}
protected override void SetItem(Int32 index, RouteBase item)
{
if (EnableRestrictions) { throw new Exception("Unexpected change of RouteCollection item."); }
base.SetItem(index, item);
}
protected override void RemoveItem(Int32 index)
{
if (EnableRestrictions) { throw new Exception("Unexpected removal from RouteCollection, index: " + index); }
base.RemoveItem(index);
}
protected override void ClearItems()
{
if (EnableRestrictions) { throw new Exception("Unexpected clearing of routecollection."); }
base.ClearItems();
}
}
Step 2: Replace the default collection
In Application_start in Global.asax replace the default RouteCollection with your instance before the routes are set up. As there does not seem to be an API to replace it then we force it with reflection on the private fields:
var routeTable = new RestrictedRouteCollection();
var field = typeof(RouteTable)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic);
if (field == null) { throw new Exception("Expected field _instance was not found."); }
field.SetValue(null, routeTable);
Debug.Assert(RouteTable.Routes == routeTable);
Step3: Restrict changes once intended routes are set up
RouteConfig.RegisterRoutes(routeTable);
routeTable.EnableRestrictions = true;
All done! Now watch the bad request light up in exception logs.
A possible culprit: ImageResizer
In my case it was a 3-rd party component named ImageResizer (v4.0.4) whose internal MvcRoutingShimPlugin took the liberty of adding/removing a route without locking. This specific bug was already reported and fixed (though at this time not yet officially released).

Categories