Response.Redirect issue with Asp.net async - c#

I'm new to asp.net 4.5 async and am running into the following with calling response.redirect within an async method. The issue is that the response just "hangs" Has anyone else experienced similar issues with attempting an redirect with async? This code will work in a brand new project, but, does not work with a new page in our existing code. I made sure to gut out everything I could out of our web.config and removed our master page. Hitting a brick wall...any ideas? Thanks!
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(PageLoadAsync));
}
private async Task PageLoadAsync()
{
var data = await GetData();
if (data == HttpStatusCode.OK)
Response.Redirect("http://www.google.com");
}
private async Task<HttpStatusCode> GetData()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://www.google.com");
return response.StatusCode;
}
}

This code will work in a brand new project, but, does not work with a new page in our existing code.
I assume your existing site has already been upgraded to .NET 4.5.
The first thing to check is that httpRuntime.targetFramework is set to 4.5. This is not set by default when you upgrade.
Edit from comments:
Another thing to check (just in case) is that Page.Async is set to true.
In this case, the solution was to call Response.Redirect("http://www.google.com", false), which explicitly passes false for the endResponse parameter. The default value of true is only for backwards-compatibility reasons as described here.

The hack I used is:
I used a static dictionary as var d= new Dictionary<string, bool>(); in the class where my API calling method is written.
I put the code line client.timeout = new System.TimeSpan(0,0,60); for API sending the request.
When API is timed out, it throws the TaskTimeoutException, in the TaskTimeoutExceptioncatch block write code as d.Add("timeout", true);
Now, I created the custom action filter and applied the following code:
public class MyCustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(MyApiClass.d.ContainsKey("timeout") && d["timeout"])
{
throw new Exception();
}
}
}
I applied the [MyCustomActionFilter ] on the action.
When action is executed and enter the custom filter it throws Exception by checking the dictionary entry.
If timeout would have occurred then dictionary entry will be true, so, on the basis of that, we check the entry and throws the exception. Now, we have Application_Error() in Global.asax.cs that catches the exception.
In the Application_Error() we have written the code for redirect to the required page.
NOTE: In step 4 you can create your custom exception to provide more precise detail for logging.

Related

MassTransit - Cannot override the Consume method from RoutingSlipResponseProxy

I'm trying to set up a RoutingSlipResponseProxy that will prevent a response from being sent if there is no RequestId. I am trying to do this by overriding the Consume method in my RoutingSlipResponseProxy, like so:
public class MigrateResponseProxy : RoutingSlipResponseProxy<IMigrationRequested, IMigrationComplete>
{
public new async Task Consume(ConsumeContext<RoutingSlipCompleted> context)
{
var isRequest = context.Message.Variables.ContainsKey("RequestId");
if (!isRequest)
return;
var request = context.Message.GetVariable<IMigrationRequested>("Request");
var requestId = context.Message.GetVariable<Guid>("RequestId");
Uri responseAddress = null;
if (context.Message.Variables.ContainsKey("ResponseAddress"))
responseAddress = context.Message.GetVariable<Uri>("ResponseAddress");
if (responseAddress == null)
throw new ArgumentException($"The response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
var endpoint = await context.GetResponseEndpoint<IMigrationComplete>(responseAddress, requestId).ConfigureAwait(false);
var response = await CreateResponseMessage(context, request);
await endpoint.Send(response).ConfigureAwait(false);
}
... remaining code ...
}
This is basically the same code as the original method, except with a check for the RequestId at the beginning. However, when debugging through the code, it seems as though this overridden method is never getting called, and instead the parent method is getting called. Is there something I might be missing here? Any help would be appreciated.
The methods aren't virtual, so you won't be able to override them. In this case, you'd be better of copying the proxy code into your own project instead of using the implementation that's included with MassTransit.
You're welcome to submit a PR to make those methods virtual.

Method Prerequisites

The Situation
I'm working on a OAuth2 Api Wrapper. Some api routes are for logged people and some for anonymous and logged.
Here is an example of one method in my wrapper :
public async Task<UploadListResponse> List(bool pagination = false, int page = 1, int limit = 10)
{
var request = UploadRequests.List(pagination, page, limit);
var cancellationTokenSource = new CancellationTokenSource();
var restResponse = await Context.Client.ExecuteTaskAsync(request, cancellationTokenSource.Token);
return restResponse.Handle<UploadListResponse>();
}
I build a request with all parameter set up then execute the request and then handle the answer in case I have an api error and then output an object containing all the data that request gave me.
The problem
With OAuth2, when you log to the API you'll receive an access token and a refresh token. If your access token is expired you have to contact the api with your refresh token to get a fresh new access token.
As I said earlier some of my method needs you to be logged but if your access token is expired I want to try to refresh token before throwing an exception like with this method :
public async Task<bool> NeedRelog()
{
try
{
var validAuth = await ValidAuth();
}
catch
{
try
{
var refresh = await Refresh(Context.Client.Config.RefreshToken);
}
catch
{
return true;
}
}
return false;
}
ValidAuth check with the API if you are logged and if I have an exception then I'll try to refreshToken.
I want to tag method that need logged to call NeedRelog() and those who aren't tag to not call it.
I may just do it in every method but it wouldn't be clean.
What I've done so far
I've found a great tool : PostSharp that seems to fit my needs.
I've started to do a checkLog aspect like this :
[Serializable]
public class CheckLog : OnMethodBoundaryAspect, IOnStateMachineBoundaryAspect
{
public CheckLog()
{
ApplyToStateMachine = false;
}
public override void OnEntry(MethodExecutionArgs args)
{
var instance = (ApiService)args.Instance;
var res = instance.Parent.OAuth.NeedRelog().Result;
if (!res)
{
args.Exception = new Exception("Need to relog");
args.FlowBehavior = FlowBehavior.Return;
}
}
}
Where I'm stuck
The Main problem is with the call to my NeedRelog() Method. Due to the fact this is an async method I'm struggling to make my aspect await for it.
If my OnEntry method is async then It won't block the call if you are not logged.
If my OnEntry method is not async and I wait for needLog it freeze and nothing happen.
I really want to know to use this kind of "conditional method call" with postsharp, it looks awesome but the fact is after looking for hours in the documentation I didn't find a way to do what I want.
I'm starting to ask myself if it is even possible to achieve what I'm aiming to do.
Did you try using a way to make the call synchronous maybe with something like this stackoverflow.com/a/25097498/3131696 ? – M22an 5 hours ago
As I can't mark a comment as answering a question I quote your comment to make this question answered as it is said here : link
Thanks you for this M22an.

Sending ELMAH error email manually

Note that this is not related to getting emails set up in the traditional sense of elmah, I have already successfully done so.
I am attempting to utilize a github project (https://github.com/dylanbeattie/ElmahASP) that was created to send "realish" errors to elmah from classic asp pages.
I have this implemented so that in the project on .net pages, errors are logged/emails sent. On classic asp pages, errors are logged, but no email is sent (here lies my issue)
The errors are logged in classic asp via a custom handler (provided by the github project):
public void ProcessRequest(HttpContext context) {
try {
var jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var aspError = JsonConvert.DeserializeObject<RemoteError>(jsonData);
var elmahError = aspError.ToElmahError();
//TODO: might be worth putting some code in here that will determine the username based on the cookie collection in the deserialized error object.
ErrorLog.GetDefault(HttpContext.Current).Log(elmahError);
context.Response.Write("Error logged OK!");
} catch (Exception ex) {
ErrorSignal.FromCurrentContext().Raise(ex);
}
context.Response.Write("THis is a response!");
}
It appears that doing a ErrorLog.Log does in fact log the error, but bypasses the event that would need to be thrown that would cause an error email to be sent out. The elmah class that handles sending out emails has several non public methods for sending out mail which I wanted to utilize, however the following is not working due to me not understanding... something.
I have attempted to write an extension of the mail module, have successfully registered it (as I've confirmed aspx pages are still sending out emails, but am still having problems with classic asp error mails). Here is my class:
public class ErrorMailModule : Elmah.ErrorMailModule
{
public void SendErrorMail(Elmah.Error error)
{
base.ReportError(error);
}
}
I then change the handler from above to:
public void ProcessRequest(HttpContext context) {
try {
var jsonData = new StreamReader(context.Request.InputStream).ReadToEnd();
var aspError = JsonConvert.DeserializeObject<RemoteError>(jsonData);
var elmahError = aspError.ToElmahError();
//TODO: might be worth putting some code in here that will determine the username based on the cookie collection in the deserialized error object.
ErrorLog.GetDefault(HttpContext.Current).Log(elmahError);
ErrorMailModule mail = new ErrorMailModule;
mail.SendErrorMail(elmahError);
context.Response.Write("Error logged OK!");
} catch (Exception ex) {
ErrorSignal.FromCurrentContext().Raise(ex);
}
context.Response.Write("THis is a response!");
}
This however doesn't work, I'm quite sure it's due to me newing up a error mail module, rather than by utilizing the one that I'm (assuming) is registered at init - but I'm unsure how to do that.
I feel like having a custom event registered and monitored for (then raised in the handler) would be one way to go... but I am unsure on how to proceed as I've never worked with events.
How would I go about implementing this? Would events be the way to go or is there another path I could take?
Not sure if you still need this, but you could fire it manually inside OnException event handler.
protected override void OnException(ExceptionContext filterContext)
{
var jsonData = new StreamReader(filterContext.Request.InputStream).ReadToEnd();
var aspError = JsonConvert.DeserializeObject<RemoteError>(jsonData);
var elmahError = aspError.ToElmahError();
ErrorLog.GetDefault(HttpContext.Current).Log(elmahError);
ErrorMailModule mail = new ErrorMailModule;
mail.SendErrorMail(elmahError);
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception); // to store error
base.OnException(filterContext);
}

How to make form page invalid?

I am creating pages in asp.net. I created WebForm called Form.aspx. Now I want all request *.htm request to load this pages where I will do what I need (maybe not the best approach but it works as needed for me). So I created something like this:
routes.MapPageRoute(null, "{file}.htm", "~/Pages/Form.aspx");
routes.MapPageRoute(null, "{folder}/{file}.htm", "~/Pages/Form.aspx");
Now everything like http://example.com/whatever.htm or http://example.com/whatever/whatever.htm is redirected to my Form.aspx. But this Form.aspx doesn't have any meaning on it’s own. So following page is nonsense http://example.com/Pages/Form.aspx. How can I make it invalid? So it would show to me something like "Error HTTP 404.0 – Not Found". I want the same behaviour as if I would wrote "http://example.com/doesntexist.aspx". I don't want to do any redirection (only if no other option exists). I so far tried only something like this (which doesn't work):
routes.MapPageRoute(null, "Pages/Form.aspx", "~/doesntexist.aspx");
It doesn't do anything... Any help appreciated.
In Global.asax add this code:
...
protected void Application_BeginRequest(object sender, EventArgs e)
{
string requestPath = Request.RawUrl.Trim().ToLower();
HttpApplication app = sender as HttpApplication;
if (!IsLocalRequest(app.Context.Request))
{
if (requestPath.IndexOf("form.aspx") > 0)
{
throw new HttpException(404, "Error HTTP 404.0 – Not Found.");
}
}
}
// This method determines whether request came from the same IP address as the server.
public static bool IsLocalRequest(HttpRequest request)
{
return request.ServerVariables["LOCAL_ADDR"] == request.ServerVariables["REMOTE_ADDR"];
}
...

UrlRewriter+HttpModule+Session problem

I need to write a custom "UrlRewriter" using a HttpModule, in the moment of "rewriting" I need access to the Session and has followed the advice from another SO thread:
Can I access session state from an HTTPModule?
Everything works, except the RewritePath/Redirect part. I don't get any exceptions, but the browser takes forever to load. Is this really the best way to build a urlrewriter like this?
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
namespace MyCompany.Campaigns
{
public class CampaignRewriteModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PostAcquireRequestState += new EventHandler(Application_PostAcquireRequestState);
application.PostMapRequestHandler += new EventHandler(Application_PostMapRequestHandler);
}
void Application_PostMapRequestHandler(object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
if (app.Context.Handler is IReadOnlySessionState || app.Context.Handler is IRequiresSessionState)
{
return;
}
app.Context.Handler = new MyHttpHandler(app.Context.Handler);
}
void Application_PostAcquireRequestState(object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
MyHttpHandler resourceHttpHandler = HttpContext.Current.Handler as MyHttpHandler;
if (resourceHttpHandler != null)
{
HttpContext.Current.Handler = resourceHttpHandler.OriginalHandler;
}
Debug.Assert(app.Session != null);
string path = HttpUtils.Path();
if (!CampaignCodeMethods.IsValidCampaignCode(path)) return;
string domain = HttpUtils.Domain();
CampaignCode code = CampaignManager.RegisterCode(path, domain.Equals(Config.Instance.Domain.ToLower()) ? null : domain);
if (code != null)
{
//app.Context.RewritePath(code.CampaignCodePath.Path, false);
app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
}
}
public void Dispose() { }
public class MyHttpHandler : IHttpHandler, IRequiresSessionState
{
internal readonly IHttpHandler OriginalHandler;
public MyHttpHandler(IHttpHandler originalHandler)
{
OriginalHandler = originalHandler;
}
public void ProcessRequest(HttpContext context)
{
throw new InvalidOperationException("MyHttpHandler cannot process requests.");
}
public bool IsReusable
{
get { return false; }
}
}
}
}
I think I know what it is. Your module is executed on ALL requests and assigns a handler that throws an error unless there is a valid campaign code (where a rewrite/redirect occurs).
But because this is not just for your "handler campaign code" url it is causing an error to be thrown, which is causing you to be redirected to your error page, which is being caught by the module, which is assigning the handler, which is throwing an error, which is redirecting... I think you get where I'm going ;)
Otherwise I'd try a few things:
Setup Fiddler and check for an infinite redirect loop
Put a breakpoint on app.Context.Response.Redirect - make sure your not in an infinite loop
Put a breakpoint on MyHttpHandler.ProcessRequest - make sure it's not being called and the exception swallowed
I wrote a simple URL rewriter module that did something similar. The url rewriting is done in BeginRequest by comparing the requested url to a list of known urls. If we find a mach we use HttpContext.RewritePath to change the requested url.
This appears to work well with no serious side effects.
I notice that you use Response.Redirect instead of Context.RewritePath. Using Redirect will cause the users browser to request a new page with the new url. Is this really what you want? The user will then see the new url in his browser. If this really is what you want you could use an alternative approach where you use a custom 404 page not found error handler to redirect the user to the appropriate page.
If you set up IIS to redirect all 404 errors to a new page, say Custom404.aspx, that you have set up. In this page you can check the requested url to see if the url should be rewritten. If it should you can simply set the Response.Status to "301 Moved Permanently" and write a header with the name "Location" and the new url as the value. If the url should not be rewritten you can just output the standard 404 page not found error.
This last approach works well, but as with your Response.Redirect approach the user will see the new url in his browser. Using Context.RewritePath allows you to serve a different page than the one requested.
Is your URL rewriter handling requests that aren't for an actual page? If they are, then I don't think you can access Session... the last URL rewriter that I had written was there to handle 404 errors, and I remember digging around and finding (somewhere, can't remember where) that you don't get access to Session if that request is not for an actual .aspx page.
I'm thinking the problem may be inside this block:
if (code != null)
{
//app.Context.RewritePath(code.CampaignCodePath.Path, false);
app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
}
Try putting a breakpoint in the if statement and see if continually gets hit.
I think there should be a call to 'return' after you reset it to the original handler, else you will continually rewrite the path.
Thinking about it, that's probably why the page is loading forever! :)

Categories