Faking MVC Server.Transfer: Response.End() does not end my thread - asp.net

I have two issues here, the second one is irrelevant if the first one got answered, but still technically interesting in my opinion... I will try to be as clear as possible:
1st question: my goal is to fake a Server.Transfer in MVC, is there any descent way to do that, I found quite a few articles about it, but most where about redirecting / rerouting, which is not possible in my case (not that I can think of at least).
Here is the context, we have two versions of our website, a "desktop" one and a mobile one. Our marketing guy wants both versions of the home page to be served on the same url (because the SEO expert said so).
This sounds trivial and simple, and it kind of is in most cases, except... Our desktop site is a .NET 4.0 ASPX site, and our mobile site is MVC, both run in the same site (same project, same apppool, same app).
Because the desktop version represents about 95% of our traffic, this should be the default, and we want to "transfer" (hence same url) from the ASPX code behind to the MVC view only if user is on a mobile device or really wants to see the mobile version. As far as I saw so far, there is no easy way to do that (Server.Transfer only executes a new handler - hence page - if there is a physical file for it). Hence question has any one done that in a proper way so far?
And which brings me to:
2nd question: I did build my own transfer to MVC mechanism, but then figured out that a Response.End() does not actually ends the running thread anymore, does anyone have a clue why?
Obviously, I don't expect any answer out of the blue, so here is what I am doing:
in the page(s) which needs transfering to mobile, I do something like:
protected override void OnPreInit(EventArgs e) {
base.OnPreInit(e);
MobileUri = "/auto/intro/index"; // the MVC url to transfer to
//Identifies correct flow based on certain conditions 1-Desktop 2-Mobile
BrowserCheck.RedirectToMobileIfRequired(MobileUri);
}
and my actual TransferToMobile method called by RedirectToMobileIfRequired (I skipped the detection part as it is quite irrelevant) looks like:
/// <summary>
/// Does a transfer to the mobile (MVC) action. While keeping the same url.
/// </summary>
private static void TransferToMobile(string uri) {
var cUrl = HttpContext.Current.Request.Url;
// build an absolute url from relative uri passed as parameter
string url = String.Format("{0}://{1}/{2}", cUrl.Scheme, cUrl.Authority, uri.TrimStart('/'));
// fake a context for the mvc redirect (in order to read the routeData).
var fakeContext = new HttpContextWrapper(new HttpContext(new HttpRequest("", url, ""), HttpContext.Current.Response));
var routeData = RouteTable.Routes.GetRouteData(fakeContext);
// get the proper controller
IController ctrl = ControllerBuilder.Current.GetControllerFactory().CreateController(fakeContext.Request.RequestContext, (string)routeData.Values["controller"]);
// We still need to set routeData in the request context, as execute does not seem to use the passed route data.
HttpContext.Current.Request.RequestContext.RouteData.DataTokens["Area"] = routeData.DataTokens["Area"];
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"] = routeData.Values["controller"];
HttpContext.Current.Request.RequestContext.RouteData.Values["action"] = routeData.Values["action"];
// Execute the MVC controller action
ctrl.Execute(new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData));
if (ctrl is IDisposable) {
((IDisposable)ctrl).Dispose(); // does not help
}
// end the request.
HttpContext.Current.Response.End();
// fakeContext.Response.End(); // does not add anything
// HttpContext.Current.Response.Close(); // does not help
// fakeContext.Response.Close(); // does not help
// Thread.CurrentThread.Abort(); // causes infinite loading in FF
}
At this point, I would expect the Response.End() call to end the thread as well (and it does if I skip the whole faking the controller execution bit) but it doesn't.
I therefore suspect that either my faked context (was the only way I found to be able to passed my current context with a new url) or the controller prevents the thread to be killed.
fakeContext.Response is same as CurrentContext.Response, and the few attempts at ending the fake context's response or killing the thread didn't really help me.
Whatever code is running after the Response.End() will NOT actually be rendered to the client (which is a small victory), as the Response stream (and the connection, no "infinite loading" in the client) is being closed. But code is still running and that is no good (also obviously generates loads of errors when trying to write the ASPX page, write headers, etc.).
So any new lead would be more than welcome!
To sum it up:
- does anyone have a less hacky way to achieve sharing a ASPX page and a MVC view on the same url?
- if not, does anyone have a clue how I can ensure that my Response is really being ended?
Many thanks in advance!

Well,
for whoever is interested, I at least have answer to question 1 :).
When I first worked on that feature, I looked at the following (and very close) question:
How to simulate Server.Transfer in ASP.NET MVC?
And tried both the Transfer Method created by Stan (using httpHandler.ProcessRequest) and Server.TransferRequest methods. Both had desadvantages for me:
the first one does not work in IIS, (because I need to call that in a page, and that seems too late already).
the second one makes it terribly annoying for developers who all need to run their site in IIS (no biggy, but still...).
Seeing that my solution obviously wasn't optimal, I had to come back to the IIS solution, which seems to be the neatest for production environment.
This solution worked for a page and triggered an infinite loop on another one...
That's when I got pointed to what I had lazily discarded as not being the cause: our url redirect module. It uses Request.RawUrl to match a rule, and oh surprise, Server.TransferRequest keeps the original Request.RawUrl, while app.Request.Url.AbsolutePath will contain the transfered-to url. So basically our url rewrite module was always redirecting to the original requested which was trying to transfer to the new one, etc.
Changed that in the url rewriting module, and will hope that everything still works like a charm (obviously a lot of testing will follow such a change)...
In order to fix the developers issue, I chose to combine both solutions, which might make it a bit more of a risk for different behaviors between development and production, but that's what we have test servers for...
so here is my transfer method looks like in the end:
Once again this is meant to transfer from an ASPX page to a MVC action, from MVC to MVC you probably don't need anything that complex, as you can use a TransferResult or just return a different view, call another action, etc.
private static void Transfer(string url) {
if (HttpRuntime.UsingIntegratedPipeline) {
// IIS 7 integrated pipeline, does not work in VS dev server.
HttpContext.Current.Server.TransferRequest(url, true);
}
// for VS dev server, does not work in IIS
var cUrl = HttpContext.Current.Request.Url;
// Create URI builder
var uriBuilder = new UriBuilder(cUrl.Scheme, cUrl.Host, cUrl.Port, HttpContext.Current.Request.ApplicationPath);
// Add destination URI
uriBuilder.Path += url;
// Because UriBuilder escapes URI decode before passing as an argument
string path = HttpContext.Current.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
// Rewrite path
HttpContext.Current.RewritePath(path, true);
IHttpHandler httpHandler = new MvcHttpHandler();
// Process request
httpHandler.ProcessRequest(HttpContext.Current);
}

I haven't done much research, but here's what seems to be happening upon Response.End():
public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
if (this._context.ApplicationInstance != null)
{
this._context.ApplicationInstance.CompleteRequest();
}
}
}
That could at least provide the "Why" (_context.IsInCancellablePeriod). You could try to trace that using your favourite CLR decompiler.

Related

What I misunderstood about the Mediator Pattern?

I'm new here and a beginner architect. I'm helping the company I work to design our new product and since I start it, I read a lot about but is never enough.
I decided to use Mediator Pattern (with Mediatr) to call my application layer.
It's cool and I got how to work with it sometimes, but sometimes I get confused.
For example, when we publish a document on our new product, we uses a RequestHandler to do everything and check all rules it needs, it's fine and works like a charm, but, when I want just a quick data, it looks likes too much for just a simple thing.
As an example, every time the user do any kind of action on my web application, I have to check if he is still logged. We have single login per user, so, if the same user connect anywhere else, the older session expires. We do it by saving on database.
On every action of my app, I go to base and check if the session key is the same, as bellow.
var sessionKey = bibliotecaCookie.Value;
var mediator = controller.GetMediator();
var isUserSessionKeyValidRequest = new IsUserSessionKeyValidRequest()
{
sessionKey = sessionKey
};
var isValidSession = mediator.Send(isUserSessionKeyValidRequest).Result;
if (!isValidSession)
throw new UnauthorizedAccessException();
So, I have a RequestHandler (a Handler and a Request which returns a bool) just to check if the user session is ok.
When the handler catches this request, it goes to database and execute a simple rule which is "Is the passed session the same as the stored session key?".
Is it right? Is it the right approach? Did I understand it right?
Thanks in advance guys

Technology to send "real-time" console output to the client's browser

I have a .NET console application that I want to start on the server of an ASP.NET MVC application. It produces output continuously for a certain time and I want to intercept this output and show it to the client in his browser window.
From another console application, I can do it like this:
public static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = "RandomOutputCreator.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
Console.ReadKey();
}
The problem is that in the MVC application, I can't push the data I read to the client, but rather rely on requests to the controller to get my data.
Something like WebSockets could maybe help me here, but I'm new to this kind of thing and wonder if there might be a "built-in" way to accomplish this.
Then there's the Web API thing. Could this be of use perhaps, since it seems to go well with MVC?
(Since I do not know what a fitting technology might be, please excuse the lack of tags and feel free to fill some in that you think fit).
This topic typically reminds me of a tutorial I followed in order to allow real-time communication from my browser to an ASP.NET application.
In summary : What you're looking for are indeed WebSocket, and there is no standard built-in functions to handle that. But, in order to help you doing some stuff, you still have the library signalR!
Here's the link to the referenced tutorial : http://www.asp.net/signalr/overview/getting-started/real-time-web-applications-with-signalr
You can try "print" console output in a separate frame (see iframe HTML tag).
You should set one of your actions as a source (URL) of the frame. You'll need to configure the IIS to run this action without execution time limit.
Next, your action should run an external program, intercept its output, and write it to HTTP output (see ContentResult).
I have a small project that does exactly that: https://github.com/vtortola/WebSocketListener/wiki/WebSocketListener-Terminal-Server
Give it a look, it may give you some ideas.

Recaptcha - Invalid Operation Exception: Http request context does not exist

Well, recaptcha is used twice on site (both in process of registration). First time it works perfect, but second i got exception above (When GetCaptchaVerificatoinHelper method is called).
Also: first time recaptcha is located in form (page) which is loaded synchronously, second time recaptcha is located in PartialView, which is updated via ajax. How to fix this? Also, it would be nice if anybody gave me a reasons this happening. Thanks in advance.
This is problem method:
public Task<ActionResult> SendSmsAgain(CaptchaStubViewModel viewmodel)
{
viewmodel.NotificationMessage = null;
return Task<ActionResult>.Factory.StartNew(() =>
{
if (!HttpContext.Request.IsAjaxRequest())
return null;
var val = WebConfigurationManager.AppSettings["recaptchaPrivateKey"];
RecaptchaVerificationHelper helper = this.GetRecaptchaVerificationHelper(val);
viewmodel.CaptchaValue = helper.Response;
if (string.IsNullOrEmpty(helper.Response))
{ ModelState.AddModelError("CaptchaValue", "You should insert Captcha value to get an SMS");}...}
Update
Has rewritten logic without ajax verification, but this doesn't still work.
Btw: Second captcha is used after redirection. Can anybody help me? I still get the same error.
According to recaptcha.codeplex, recaptcha hasn't supported verification through ajax yet.

How to prevent multiple browser windows from sharing the same session in asp.net

I have ASP.net application that is basically a data entry screen for a physical inspection process. The users want to be able to have multiple browser windows open and enter data from multiple inspections concurrently. At first I was using cookie based sessions, and obviously this blew up.
I switched to using cookie-less sessions, which stores the session in the URL and in testing this seemed to resolve the problem. Each browser window/tab had a different session ID, and data entered in one did not clobber data entered in the other.
However my users are more efficient at breaking things than I expected and it seems that they're still managing to get the same session between browsers sometimes. I think that they're copying/pasting the address from one tab to the other in order to open the application, but I haven't been able to verify this yet (they're at another location so I can't easily ask them).
Other than telling them don't copy and paste, or convince them to only enter one at a time, how can I prevent this situation from occurring?
Think of using ViewState instead of Session, since ViewState renders state information to the client (HTML page). I'm not sure if you'll ever be able to gain detailed control over the browser's session behaviour, because the session ID is maintained by the browser the way the manufacturer did it. So ViewState is more predicable, not only but also for future browser versions.
this is a very good question that i have also thought long and hard about.
Store your main web page in an iframe. Have javascript to check if your web page is still in the parent iframe. If not, then they have opened multiple browser windows.
You need to make sure your entire app is iframe friendly.
I am unsure why you wish to restrict a session to handle only one inspection process, and then force multiple sessions in order for users to work simultaneously on multiple inspections. That feels like a rather awkward style of isolation.
The web application (and pages) ought to be able to handle multiple inspection processes within a single user session.
Whatever data that is being held in Session variables should not be plainly exposed for singular handling. They ought to be stored in collections that readily identify which set of data belongs to which inspection process. Every page submission back to the web server ought to carry an identifier which inspection process it pertains to, so that the correct session set can be matched and pulled for use.
pseudo code concept
var inspectionID = this.inspectionLabel.Text;
var inspectionSets = (Hashtable)Session["inspections"];
var inspection = (Inspection)inspectionSets[inspectionID];
Must the users be logged in with different accounts to access different physical inspections? It seems to me that as long as the PhysicalInspectionID is part of the URL, then there should be no problem in editing multiple physical inspections at the same time.
E.g.,
http://inspections.mydomain.com/edit/23
Of course, if they copy the URL, they will get a duplicate of the other window, but this will teach them not to do that. Instead, they open another window and browse to the proper inspection (or add a new one) via the UI.
This is what I use in ASP.NET MVC to forbid authenticated users to open multiple tabs:
<script language="javascript" type="text/javascript">
#if(Request.IsAuthenticated)
{
<text>
if (window.name != 'singleWindow') {
window.location.href = "Content/ErrorPages/SingleTab.htm";
}
</text>
}
else
{
<text>
window.name = "singleWindow";
</text>
}
</script>
Basically, this sets the window name first time when the user visits the login page. After logging in, for each subsequent page load the window name is tested.
Two problems:
does not wok if JavaScript disabled
if by mistake the user closes the original tab and then pastes some other link to my website in the address bar, the user will always receive the error page. To give the user a chance to recover, I have included "Log out" link in the SingleTab.htm page, so the user can destroy his session cookie and start a new session.
A solution to that problem can be implemented by the following:
public static class CommonHelper
{
public static bool SiteGuard
{
get
{
if(HttpContext.Current.Session["SiteGuard"] == null)
return true;
return (bool)HttpContext.Current.Session["SiteGuard"];
}
set
{
HttpContext.Current.Session["SiteGuard"] = value;
}
}
}
public partial class TestPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack)
{
bool go = false;
for(int i = 0; i < 50; i++) // wait for the service to work (5 secs max)
{
if(CommonHelper.SiteGuard)
{
go = true;
break;
}
Thread.Sleep(100);
}
if(!go)
Response.Redirect("Login.aspx");
SiteGuard = false; // from now on, nobody can visit your site
}
// Now as long as Page.IsPostBack is true you are in a good shape
}
}
Add an asmx web service (or any other type of services you think is suitable) to your root project and add the following method to it:
[WebMethod(EnableSession = true)]
public void FreeSiteGuard()
{
HttpContext.Current.Session["SiteGuard"] = null;
}
In the master page, or on every page add the following javascript:
<script type="text/javascript">
window.onbeforeunload = function (e) {
e = e || window.event;
if (e) {
// Invoke web service
YourProject.YourWebServiceName.FreeSiteGuard();
}
};
</script>
Note that your site response time gets affected by the speed of the web service.
Create a new sessionId when the request has no referer.
That solves the copy-paste url problem.
Store the sessionId in the url like you did.

ASP.NET (MVC) Outputcache and concurrent requests

Let's say that, theoratically, I have a page / controller action in my website that does some very heavy stuff. It takes about 10 seconds to complete it's operation.
Now, I use .NET's outputcache mechanism to cache it for 15 minutes (for examle, I use [OutputCache(Duration = 900)]) What happens if, after 15 minutes, the cache is expired and 100 users request the page again within those 10 seconds that it takes to do the heavy processing?
The heavy stuff is done only the first time, and there is some locking mechanism so that the other 99 users will get the cache result
The heavy stuff is done 100 times (and the server is crippled as it can take up to 100 * 10 seconds)
Easy question maybe, but I'm not 100% sure. I hope it is number one, though :-)
Thanks!
Well, it depends upon how you have IIS configured. If you have less than 100 worker threads (let's say, 50), then the "heavy stuff" is done 50 times, crippling your server, and then the remaining 50 requests will be served from cache.
But no, there is no "locking mechanism" on a cached action result; that would be counterproductive, for the most part.
Edit: I believe this to be true, but Nick's tests say otherwise, and I don't have time to test now. Try it yourself! The rest of the answer is not dependent on the above, though, and I think it's more important.
Generally speaking, however, no web request, cached or otherwise, should take 10 seconds to return. If I were in your shoes, I would look at somehow pre-computing the hard part of the request. You can still cache the action result if you want to cache the HTML, but it sounds like your problem is somewhat bigger than that.
You might also want to consider asynchronous controllers. Finally, note that although IIS and ASP.NET MVC will not lock on this heavy computation, you could. If you use asynchronous controllers combined with a lock on the computation, then you would get effectively the behavior you're asking for. I can't really say if that's the best solution without knowing more about what your doing.
It seems to lock here, doing a simple test:
<%# OutputCache Duration="10" VaryByParam="*" %>
protected void Page_Load(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(new Random().Next(1000, 30000));
}
The first page hits the a breakpoint there, even though it's left sleeping...no other request hits a breakpoint in the Page_Load method...it waits for the first one to complete and returns that result to everyone who's requested that page.
Note: this was simpler to test in a webforms scenario, but given this is a shared aspect of the frameworks, you can do the same test in MVC with the same result.
Here's an alternative way to test:
<asp:Literal ID="litCount" runat="server" />
public static int Count = 0;
protected void Page_Load(object sender, EventArgs e)
{
litCount.Text = Count++.ToString();
System.Threading.Thread.Sleep(10000);
}
All pages queued up while the first request goes to sleep will have the same count output.
Old question, but I ran in to this problem, and did some investigation.
Example code:
public static int Count;
[OutputCache(Duration = 20, VaryByParam = "*")]
public ActionResult Test()
{
var i = Int32.MaxValue;
System.Threading.Thread.Sleep(4000);
return Content(Count++);
}
Run it in one browser, and it seems to lock and wait.
Run it in different browsers (I tested in IE and firefox) and the requests are not put on hold.
So the "correct" behaviour has more to do with which browser you are using than the function in IIS.
Edit: To clarify - No lock. The server gets hit by all requests that manage to get in before the first result is cached, possibly resulting in a hard hit on the server for heavy requests. (Or if you call an external system, that system could be brought down if your server serves many requests...)
I made a small test that might help. I believe what I've discovered is that the uncached requests do not block, and each request that comes in while the cache is expired and before the task is completed ALSO trigger that task.
For example, the code below takes about 6-9 seconds on my system using Cassini. If you send two requests, approximately 2 seconds apart (i.e. two browser tabs), both will receive unique results. The last request to finish is also the response that gets cached for subsequent requests.
// CachedController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace HttpCacheTest.Controllers
{
public class CachedController : Controller
{
//
// GET: /Cached/
[OutputCache(Duration=20, VaryByParam="*")]
public ActionResult Index()
{
var start = DateTime.Now;
var i = Int32.MaxValue;
while (i > 0)
{
i--;
}
var end = DateTime.Now;
return Content( end.Subtract(start).ToString() );
}
}
}
You should check this information here:
"You have a single client making multiple concurrent requests to the server. The default behavior is that these requests will be serialized;"
So, if the concurrent request from the single client is serialized, the subsequent request will use the cache. That explain some behavior seem in some answer above (#mats-nilsson and #nick-craver)
The context that you showed us is multiple users, that will hit you Server in the same time, and you server will get busy until have completed at least one request and created the output cache, and use it for the next request. So if you want to serialize multiple users requesting the same resource, we need to understand how the serialized request works for single user. Is that what you want?

Resources