HttpContext.Current.Session is null while calling async webservices - c#

I'm working on a Asp.Net 3.5 Web Application which requires Async Web service calls.
Here is some code. I have written the code for Async calling using delegates but somehow my HttpContext.Current.Session is null. To make sure, I even tried passing HttpContext.
static HttpContext httpContext;
public void AsyncGetUser()
{
httpContext = HttpContext.Current;//not getting Session as null here
GetUserDelegate delegate = new GetUserDelegate(InvokeWrapperMethod);
IAsyncResult async = delegate.BeginInvoke(httpContext,new AsyncCallback(CallbackMethod), null);
}
static void CallbackMethod(IAsyncResult ar)
{
AsyncResult result = (AsyncResult)ar;
GetUserDelegate caller = (GetUserDelegate)result.AsyncDelegate;
caller.EndInvoke(ar);
}
private static void InvokeWrapperMethod(HttpContext hc)
{
HttpContext.Current = hc; //getting Session as null here
UserInfo userInfo = Service.GetUserInfo();
SetInSession(USER_INFO, userInfo);
}
I have tried <modules runAllManagedModulesForAllRequests="true"> and
<system.webServer>
<modules>
<remove name="Session"/>
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
</modules>
as this SO question suggested but didn't work. I would appreciate if you guys could give some pointers. Thanks.

This can occur in ASP.NET apps using the async/await (TAP) pattern. To solve this, you need to add this to your appSettings section in web.config.
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
This may require .NET 4.5, I note you are not using this version but I add this answer here for others that have upgraded projects and are hitting this issue with their TAP code.

your web service should use , IRequiresSessionState marker interface to enable session.

I ended up with the decision to refactor the whole project.

It might be that you're using delegates to get the 'handy' BeginInvoke method. This is inefficient since behind the scenes, its using a load of Reflection. If the background work is always significant then this overhead gets amortised, but its its sometimes just a few reads, then it'll take longer than doing it on the original thread.
You could install RX for 3.5 and use the 'mini TPL' to get at Tasks. This in itself could solve the problem (the issue might be the way in which that delegate.BeginInvoke is written), else try passing the context in as the state instance.
I know you've refactored out of the problem (probably a good thing) but I add this answer for completeness having looked more at what you were originally doing.

Related

Restart IIS AppDomain on type initializer/static constructor exception

I've got an ASP.NET that relies on some code that uses static constructors. The code in these type initializers sometimes fails. Let's say, for sake of argument, that the code is:
public static readonly string Thing = SomeSpecialCallThatRarelyFails();
Perhaps that's vile, but it cannot be changed. And this kinda code is in every controller, so ASP.NET can't create the controller and just sits there broken until someone comes along to restart it.
I understand this is the way it should be, because the problem may very well be non-transient and auto-restarting would create a loop. Or perhaps only one controller fails, so the app is still sort of alive. So I get the default behaviour to just keep returning the error. But in this particular case, let's pretend the best thing is to notice this failure and restart.
How can I automate the detection of this scenario and trigger a restart or recycle of the IIS app pool/AppDomain?
I've noticed that if I cause an exception on Application_Start, then the app will auto-restart. So one way is for me to iterate over all my types and try accessing them. If they have .cctor failures, then I'll crash Application_Start and ASP.NET will restart. But that's pretty hacky, plus it won't help if the actual request code references another type that I don't know about which throws on .cctor.
Is there a better way? Should I write a Web API filter and look for TypeInitializerException or something?
Just a thought. Is the 'rare failure' deterministic? Could it be solved by adding retry logic?
public static readonly string Thing = RetrySpecialCall();
private static string RetrySpecialCall()
{
while (true)
{
try
{
return SomeSpecialCallThatRarelyFails();
}
catch (Exception) {}
}
}
So here's a way to handle it in Web API 1:
In Application_Start, iterate over your controller types, calling System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor to force all your known type constructors to run. If it Application_Start fails, ASP.NET seems to restart.
Add an exception filter that looks for TypeInitializationExceptions. Then call HttpRuntime.UnloadAppDomain().
The two parts are needed as a controller failing to construct will not hit the exception filters.
With Web API 2 it seems like you could do it in one go by implementing System.Web.Http.ExceptionHandling.IExceptionLogger and registering it as a global service. Same logic: check for TypeInitializationException and UnloadAppDomain if so.

HttpContext.Current null inside async task

I have a method that uses a repository (userRepo):
public override Task<IdentityResult> CreateLocalUserAsync(IUser user, string password, CancellationToken cancellationToken)
{
var task = new Task<IdentityResult>(() => {
TUserEntity newUser = new TUserEntity
{
Id = user.Id,
UserName = user.UserName,
Password = password
};
userRepo.Save(newUser).Flush();
return new IdentityResult(true);
}, cancellationToken);
task.Start();
return task;
}
The userRepoobject has a dependency that uses HttpContext.Current. Both of these are resolved using ninject InRequestScope.
The above method is called inside the default AccountController in Mvc 5:
var result = await IdentityManager.Users.CreateLocalUserAsync(user, model.Password);
I have tried adding this setting to web.config:
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
Also, I am definitely using .NET 4.5. This is also in my web.config:
<httpRuntime targetFramework="4.5" />
It is not possible to get the information from the HttpContext before I start the task because a dependency of the userRepo in the task is using the information and both objects are resolved using Ninject.
How can I ensure that HttpContext.Current will not be null?
The "task friendly sync context" here applies to the continuation from the await: whatever you do with result, it will have the http-context. It does not, however, relate to task.Start. That relates to the TaskScheduler, not the sync-context.
Basically, by performing this on a worker, you are (in the process, as a consequence) divorcing that worker from the http-context. You must either:
get the information you need from the http-context and pass that into the worker, or
don't use a worker
Personally, I doubt you're gaining much by pushing this onto a worker. If you really want to go async, the ideal would be for your repo to internally support *Async methods. That requires more than using threads: it usually means architectural changes, for example, using async SQL methods. Something written from the ground up to use async and sync-context-aware continuations (aka await) would automatically preserve things like the http-context.
The important difference here is that an async/await implementation is linear but not continuous, i.e.
<===(work)==>
<===(callback; more work)===>
<===(another callback)===>
where-as your existing code potentially performs things in parallel, i.e.
<==========(original work)=================>
<===========(task on worker thread)=============>
The fact that the async/await approach is basically linear makes it far more suitable for access to things like http-context, since it knows that (done right) there will only be one thread at a time accessing it - even if it isn't the same thread end-to-end.

HttpContext.Current.Items after an Async operation

Consider the following ASP.NET Web API Delegating Handler:
public class MyHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var guid = Guid.NewGuid();
HttpContext.Current.Items["foo"] = guid;
// An Async operation
var result = await base.SendAsync(request, cancellationToken);
//All code from this point is not gauranteed to run on the same thread that started the handler
var restoredGuid = (Guid)HttpContext.Current.Items["foo"];
//Is this gauranteed to be true
var areTheSame = guid == restoredGuid;
return result;
}
}
The above example is in a delegating handler, the same problem I am trying to fix applies in Controllers, Business Objects, etc.
I am ultimately trying to provide some simple in-memory shared state between various objects per HTTP Request
As I understand it during Async operations the ASP.NET thread originally running the operation is returned to the thread pool and a different thread may be used to finish the request after the Async operation has completed.
Does this affect the HttpContext.Current.Items collection?
Is an item that was in the Items collection guaranteed to be there when the Request resumes?
I'm aware that using HttpContext.Current is often frowned upon by
the wider community these days for reasons I completely agree
with... I'm just helping someone out of a jam.
Storing this data in the Request.Items collection is not suitable to solve this problem as my colleague requires a static due to some poor design decisions.
Many Thanks
As I understand it during Async operations the ASP.NET thread originally running the operation is returned to the thread pool and a different thread may be used to finish the request after the Async operation has completed.
That is correct. But let's talk about async on ASP.NET for just a minute.
async requires .NET 4.5. Furthermore, ASP.NET 4.5 introduces a "quirks mode" on the server side, and you have to turn the SynchronizationContext quirk off. You can do this by either setting httpRuntime.targetFramework to 4.5 or using an appSettings with aspnet:UseTaskFriendlySynchronizationContext value of true.
If your web.config does not have one of those entries, then the behavior of async is undefined. See this post for more details. I recommend using the targetFramework setting and fixing any problems that come up.
Does this affect the HttpContext.Current.Items collection? Is an item that was in the Items collection guaranteed to be there when the Request resumes?
The AspNetSynchronizationContext preserves the current request context across await points. This includes HttpContext.Current (which includes Items, User, etc).
Another possibility is CallContext.Logical[Get|Set]Data, which also flows across await points. This is useful if you don't want a code dependency on HttpContext, but has slightly more overhead.
I gave a talk at ThatConference a couple weeks ago on async on the server side; you may find the slides helpful, particularly the ones dealing with Context and Thread-Local State.
Cutting a long story short, it normally should. Unless you are using ConfigureAwait(false) which can have a side effect with continuation not flowing the context.
Alternatively try adding this setting in your app.
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
UPDATE
NOTE!!
Initially I put false. But it must be true so that context flows.

JQuery/WCF without ASP.NET AJAX:

I am proper struggling getting that "magic" moment when WCF is configured nicely and jQuery is structuring its requests/understanding responses nicely.
I have a service:
<%# ServiceHost Language="C#" Debug="true" Service="xxx.yyy.WCF.Data.ClientBroker" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>
This was recommended by the man Rick Strahl to avoid having to define the behaviours within Web.config.
My interface for the WCF service sits in another assembly:
namespace xxx.yyy.WCF.Data
{
[ServiceContract(Namespace = "yyyWCF")]
public interface IClientBroker
{
[OperationContract]
[WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
IClient GetClientJson(int clientId);
}
}
The concrete service class is:
namespace xxx.yyy.WCF.Data
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
class ClientBroker : IClientBroker
{
public IClient GetClientJson(int clientId)
{
IClient client=new Client();
// gets and returns an IClient
return client;
}
}
}
My IClient is an Entity Framework class so is decorated with DataContract/DataMember attributes appropriately.
I am trying to call my WCF service using the methods outlined on Rick Strahl's blog at http://www.west-wind.com/weblog/posts/324917.aspx (the "full fat" version). The debugger jumps into the WCF service fine (so my jQuery/JSON is being understood) and gets the IClient and returns it. However, when I return the response, I get various useless errors. The errors I am getting back don't mean much.
I am using POST.
Am I right to be using an Interface instead of a concrete object? As it does get into the WCF service, it does seem to be the encoding of the result that is failing.
Does anyone have any ideas?
At first glance there are three problems with your code:
1: you should use the ServiceKnownTypeAttribute to specify known types when exposing only base types in your operation contracts:
[ServiceContract(Namespace = "yyyWCF")]
public interface IClientBroker
{
[OperationContract]
[ServiceKnownType(typeof(Client))]
[WebInvoke(
Method="GET",
BodyStyle=WebMessageBodyStyle.WrappedRequest,
ResponseFormat=WebMessageFormat.Json)]
IClient GetClientJson(int clientId);
}
2: You should use WebMessageBodyStyle.WrappedRequest instead of WebMessageBodyStyle.Wrapped because the latter is not compatible with WebScriptServiceHostFactory.
3: IMHO using Method="GET" would be more RESTful for a method called GetClientJson than Method="POST"
Another advice I could give you when working with WCF services is to use SvcTraceViewer.exe bundled with Visual Studio. It is a great tool for debugging purposes. All you need is to add the following section to your app/web.config:
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="sdt"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData= "WcfDetailTrace.e2e" />
</listeners>
</source>
</sources>
</system.diagnostics>
Then invoke the web method and WcfDetailTrace.e2e file will be generated in your web site root directory. Next open this file with SvcTraceViewer.exe and you will see lots of useful information. For example it could say:
Cannot serialize parameter of type
'MyNamespace.Client' (for operation
'GetClientJson', contract
'IClientBroker') because it is not the
exact type 'MyNamespace.IClient' in
the method signature and is not in the
known types collection. In order to
serialize the parameter, add the type
to the known types collection for the
operation using
ServiceKnownTypeAttribute.
Of course you should not forget commenting this section before going into production or you might end up with some pretty big files.
I am 99% sure you cant return an interface. I dont think Interfaces are serializable.
check out this thread
Related to the question, a while ago I posted an article on my blog showing all the steps needed to get a WCF service working together with jQuery code on the client side:
http://yoavniran.wordpress.com/2009/08/02/creating-a-webservice-proxy-with-jquery/

HttpContext.Current.Session is null when routing requests

Without routing, HttpContext.Current.Session is there so I know that the StateServer is working. When I route my requests, HttpContext.Current.Session is null in the routed page. I am using .NET 3.5 sp1 on IIS 7.0, without the MVC previews. It appears that AcquireRequestState is never fired when using the routes and so the session variable isn't instantiated/filled.
When I try to access the Session variables, I get this error:
base {System.Runtime.InteropServices.ExternalException} = {"Session state can only be used when enableSessionState is set to true, either in a configuration file or in the Page directive. Please also make sure that System.Web.SessionStateModule or a custom session state module is included in the <configuration>.
While debugging, I also get the error that the HttpContext.Current.Session is not accessible in that context.
--
My web.config looks like this:
<configuration>
...
<system.web>
<pages enableSessionState="true">
<controls>
...
</controls>
</pages>
...
</system.web>
<sessionState cookieless="AutoDetect" mode="StateServer" timeout="22" />
...
</configuration>
Here's the IRouteHandler implementation:
public class WebPageRouteHandler : IRouteHandler, IRequiresSessionState
{
public string m_VirtualPath { get; private set; }
public bool m_CheckPhysicalUrlAccess { get; set; }
public WebPageRouteHandler(string virtualPath) : this(virtualPath, false)
{
}
public WebPageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess)
{
m_VirtualPath = virtualPath;
m_CheckPhysicalUrlAccess = checkPhysicalUrlAccess;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
if (m_CheckPhysicalUrlAccess
&& !UrlAuthorizationModule.CheckUrlAccessForPrincipal(
m_VirtualPath,
requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod))
{
throw new SecurityException();
}
string var = String.Empty;
foreach (var value in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items[value.Key] = value.Value;
}
Page page = BuildManager.CreateInstanceFromVirtualPath(
m_VirtualPath,
typeof(Page)) as Page;// IHttpHandler;
if (page != null)
{
return page;
}
return page;
}
}
I've also tried to put EnableSessionState="True" on the top of the aspx pages but still, nothing.
Any insights? Should I write another HttpRequestHandler that implements IRequiresSessionState?
Thanks.
Got it. Quite stupid, actually. It worked after I removed & added the SessionStateModule like so:
<configuration>
...
<system.webServer>
...
<modules>
<remove name="Session" />
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
...
</modules>
</system.webServer>
</configuration>
Simply adding it won't work since "Session" should have already been defined in the machine.config.
Now, I wonder if that is the usual thing to do. It surely doesn't seem so since it seems so crude...
Just add attribute runAllManagedModulesForAllRequests="true" to system.webServer\modules in web.config.
This attribute is enabled by default in MVC and Dynamic Data projects.
runAllManagedModulesForAllRequests=true is actually a real bad solution. This increased the load time of my application by 200%. The better solution is to manually remove and add the session object and to avoid the run all managed modules attribute all together.
None of these solutions worked for me. I added the following method into global.asax.cs then Session was not null:
protected void Application_PostAuthorizeRequest()
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
What #Bogdan Maxim said. Or change to use InProc if you're not using an external sesssion state server.
<sessionState mode="InProc" timeout="20" cookieless="AutoDetect" />
Look here for more info on the SessionState directive.
Nice job! I've been having the exact same problem. Adding and removing the Session module worked perfectly for me too. It didn't however bring back by HttpContext.Current.User so I tried your little trick with the FormsAuth module and sure enough, that did it.
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule"/>
It seems that you have forgotten to add your state server address in the config file.
<sessionstate mode="StateServer" timeout="20" server="127.0.0.1" port="42424" />
The config section seems sound as it works if when pages are accessed normally. I've tried the other configurations suggested but the problem is still there.
I doubt the problem is in the Session provider since it works without the routing.
I think this part of code make changes to the context.
Page page = BuildManager.CreateInstanceFromVirtualPath(
m_VirtualPath,
typeof(Page)) as Page;// IHttpHandler;
Also this part of code is useless:
if (page != null)
{
return page;
}
return page;
It will always return the page wither it's null or not.
I was missing a reference to System.web.mvc dll in the session adapter, and adding the same fixed the issue.
Hopefully it will help someone else going through same scenario.
a better solution is
runAllManagedModulesForAllRequest is a clever thing to do respect removing and resinserting session module.
alk.

Categories