Preventing users from skipping pages in an ASP.NET form - asp.net

I have a data entry form that I've broken apart into 5 pages and I want to make sure that the users go through the pages in the proper order each time. The navigation is set up to take them through it properly, including a breadcrumb to go back and change previous pages. I want to make sure, however, that they don't get to a later page without going through the earlier pages first (i.e., go through pages A and B then skip to D by entering the URL directly, or start out on D using a bookmark).
I've made some attempts to prevent this, with my latest one being this, which checks for required session variables specific to each page:
public static string CheckForPageSkipping()
{
var redirectTo = "";
if (HttpContext.Current.Session["TestDate"] == null) redirectTo = "~/Auth/SignOff.aspx";
if (HttpContext.Current.Session["Repairs"] == null) redirectTo = "~/Auth/RepairNotes.aspx";
if (HttpContext.Current.Session["MeterNum"] == null) redirectTo = "~/Auth/TestResults.aspx";
if (HttpContext.Current.Session["PremiseOwner"] == null) redirectTo = "~/Auth/PremiseInfo.aspx";
return redirectTo;
}
I called this during each Page_Load event and redirected to the page it returned if it was not the page they were on. This almost worked, until I realized that it prevents you from being able to navigate backwards (it always sends you back to the latest page you haven't completed). I'm sure I can cobble something together to make this work, but I wanted to see if there is a better way to do it. Something either built in to ASP.NET, or something more elegant that someone had built in the past.
My search attempts, both in Google and on StackOverflow, yielded no useful results. Maybe I was using the wrong terms, but it was mostly stuff like I've written here (the suggestions made at the top now have not been helpful either).

It seems like you have control of the pages so why not get rid of them and put the code into user controls, register these controls in one page in panels or placeholders and control the visibility of these panels in the code so that you can properly enforce the order in which these are visited?

Have you looked at the <asp:wizard> built-in control? I think it will fit your needs and you can configure it for many different scenarios.
Here is a link describing its use: http://msdn.microsoft.com/en-us/magazine/cc163894.aspx
Also see the current official documentation: MSDN Library

There is a good pattern explained here for using multi-part forms using panels.
Multi-Part Form using Panels

If you don't want to use the Wizard control and you don't want to use Panels, either Server.Transfer or Cross Page Posting should work. With either method, checking the PreviousPage should cover your page skipping concerns.
Server.Transfer has the URL oddity you mentioned, but is otherwise pretty straightforward.
Cross-Page Posting is quite appropriately tailored to your needs as well.
Post each form to the next form page.
Receive and process the data there (with strong typing if you set it
up correctly).
Only load the new page if everything checks out (PreviousPage is
correct, data is valid).
I admit, I don't have direct experience using either of these methods myself, but I can see how they could be implemented to handle your situation.

Related

Dynamically disable/enable outputcache for a page during runtime

I am using a CMS system, by the name of Composite C1. It renders all of it's content through single page (Page.aspx), which has a custom output cache profile attached.
This is all good, but I have run into a problem.
I want to have caching, but there are certain URLs that I would like to disable outputcaching for.
I know there is the varybycustom attribute that I can add to the cache profile, but I don't think this will give me exactly what I want. I want to be able to disable the cache completely when hitting specific URLs (or perhaps some other condition).
This seems to be very tricky as every page/url renders through the single Page.aspx file with it's outputcache profile defined.
Does anyone have any advice on how I might be able to solve this problem?
In \Global.asax you'll find an override of GetVaryByCustomString that calls into Composite C1 to evaluate if the response should be cached or not. You could intervene that, and only call into Composite if the request is not for one of the urls that you don't want cached:
if (context.Request.Url.AbsolutePath != "/dont-cache-this")
{
return ApplicationLevelEventHandlers.GetVaryByCustomString(context, custom) ?? base.GetVaryByCustomString(context, custom);
}
return null;
Note that upgrading Composite C1 later on might replace \Global.asax and wipe your changes.
Also see http://msdn.microsoft.com/en-us/library/5ecf4420.aspx
You can insert the following code to functions which are on the pages that shouldn't be cached. Or add a small function that would do just that:
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);

Why do I have to do a page refresh to get my Telerik TabStrip to update in ASP.Net MVC 4?

I am trying to show/hide Tabs depending on a user access level that I pass to my View that contains a Telerik tabStrip as shown below:
#{ Html.Telerik().TabStrip()
.Name("Main_Tabstrip")
.Items(tabstrip =>
{
tabstrip.Add()
.Visible((int)ViewData["UserLevel"] < 2)
.Text("Topic A")
.LoadContentFrom("_TopicATab", "TopicA");
tabstrip.Add()
.Visible((int)ViewData["UserLevel"] < 2)
.Text("Topic B")
.LoadContentFrom("_TopicBTab", "TopicB");
tabstrip.Add()
.Visible((int)ViewData["UserLevel"] < 2)
.Text("Topic C")
.LoadContentFrom("_TopicCTab", "TopicC");
})
However, when I call the Action that generates the View after a change in user status, although the View appears to update (I can step through it and see the UserLevel change) the Tab visibility remains as it was on the first rendering of the view.
If I subsequently refresh the Page either in the browser or via a JavaScript location.reload() call then the Tab Visibility works fine.
Additional information:
The Action referred to above calls View() to Render the full page that contains the above View.
Although I was able to work around the problem on this occasion by doing a page reload in JavaScript, I would really like to know why this was necessary and would appreciate any suggestions or solutions.
(I am posting this as an answer, since it is too long for a comment.)
Sorry, I should have been more specific. I meant from where in the page are you calling the action?
Your action returns some html (generated from the view) that is returned to the browser and one of two things happen depending on how the action was called:
(1) The whole page is replaced (and the browser might change the displayed address depending on the request verb)
(2) A part of the page, for example the content of a div, is replaced.
To accomplish (1) you will probably use a call to Html.ActionLink or an old fashioned anchor tag.
I would however advise you to use (2) instead, since it can give better UX, but it is harder to do. You would make an Ajax call, either via jQuery's ajax method, or an Ajax.ActionLink call.
So basically my counter-question was about which of these you are using. My suspicion is that you are requesting the action, but not writing the response anywhere. Can you perhaps show code for the action and the rest of the view, or reduce it to a minimal example to paste here?
More to the point of your question though, I have looked around a little and you are right that showing/hiding the tabs with javascript is not supported out-of-the-box. I did however find these two posts which might still help you
http://www.aspnetwiki.com/page:extending-the-telerik-mvc-client-api
http://www.aspnetwiki.com/telerik-mvc:dynamically-add-a-tab-to-the-tabstrip

ASP.NET - how to change requested page names transparently?

Very new to this subject and under the gun to come up with a solution. The problem is how I could load one of several versions of the same ASPX page, for any given page. E.g. unknown to the unsuspecting user who requests catalog.aspx, I would actually serve one of catalog_1.aspx, catalog_2.aspx, or catalog_3.aspx, etc.
Strange request indeed. It's due to an inherited decade-old product having inlined styles all over the ASPXs. Instead of re-writing the hundreds of ASPXs to be flexible, I'm trying to regexp-replace them to get versions suitable for various screen sizes. I'd then choose the best one after measuring window size at user login (and perhaps store the size in a cookie).
I thought this would involve some lower level object like an http handler. Close?
LJ
Update: I ended up doing this through url rewriting which works much better. The easiest place to do this in asp.net is apparently global.asax, and under Application_BeginRequest event. Call context.RewritePath(newpath, False) to send the request to a different page than requested.
In the way I did it, the destination page can change from request to request, and that apparently upsets postbacks, if the recipient of the postback isn't the exact version of the page that generated the viewstate. I tried to turn off viewstate validation but didn't help. So had to prevent flipping between versions once a user's logged in. Hope this helps someone.
Server.Transfer is probably the quickest way of doing just that.
string TransferTo = string.Empty;
if( Something )
TransferTo = "catalog_1.aspx";
else if( SomethingElse )
TransferTo = "catalog_2.aspx";
else
TransferTo = "catalog_3.aspx";
Server.Transfer( TransferTo, false );
Documentation
Note
If the subsequent pages have postback controls on them, they will reveal the true URL of the page at that point. If that matters, then this method will not work.
I don't like this method, but maybe you could use a full-window IFRAME to hold the appropriate page - catalog.aspx would be nothing but a big frame, and you could set the source of that frame in your codebehind.

Output caching a page except a user control in it

I have a page which contains a user control. The structure of the page is as shown below:
Incase your not able to see the above image, please check it at http://i54.tinypic.com/2r4id5f.jpg Now, apart from the contents of the UserControl, I'd like to cache the entire page. I tried using the OutputCache attribute in the .aspx page, however it caches the contents of the UserControl as well.
Kindly let me know how will I be able to cache the contents of the page except that of the user control.
Thanks in advance.
I think you can use the asp.net Substitution control to achieve this. Here is a link to ScottGu walking through an example.
The basic idea is that you cache you whole page as per usual, but mark parts for substitution that can be replaced for each request.
I think you are looking for VaryByControl. Also check out this post on fragment caching
Look at using substitutions.
This should help
However, the snag is, since substitution is done outside of the Page lifecycle, you can't render a user control for your substitution. You have to write a method that returns a string for the substitution. But this may work for you.
Have you tried adding the #OutputCache to both the usercontrol and the page but the usercontrol set the varyByParam="qsvalue;postvalue" where qsvalue is a generated query string you make random for every call of the page and postvalue is the same for postback.
The user control will still get cached, but in theory it should never get a chache hit as the qsvalue/postvalue is always different from that cached. It may not scale well - best set duration to the minimum as well, to prevent large numbers of them building up in the cache.

A big dilemma - ASP.NET and jQuery

I have a wizard style interface where I need to collect data from users. I've been asked by my managers that the information is to be collected in a step by step type process.
I've decided to have a page.aspx with each step of the process as a separate user control. step1.ascx step2.ascx etc...
The way it works now, is that when the initial GET request comes in, I render the entire page (which sits inside of a master page) and step1.ascx. When then next POST request comes in for step 2 (using query string step=2), I render only step2.ascx to the browser by overriding the Render(HtmlTextWriter) method and use jQuery html() method to replace the contents of a div.
The problem with this whole approach, besides being hacky (in my opinion) is that it's impossible to update viewstate as this is usually handled server side.
My workaround is to store the contents of step1.ascx into temporary session storage so if the user decides to click the Back button to go back one step, I can spit out the values that were stored for it previously.
I feel I'm putting on my techy hat on here in wanting to try the latest Javascript craze as jQuery with .NET has taken a lot of hack like approaches and reverse engineering to get right. Would it be easier to simply use an updatepanel and be done with it or is there a site with a comprehensive resource of using jQuery to do everything in ASP.NET?
Thanks for taking the time to read this.
Another approach, that might be easier to work with, is to load the entire form with the initial GET request, and then hide all sections except the first one. You then use jQuery to hide and show different parts of the form, and when the final section is shown the entire form is posted in one POST to the server. That way you can handle the input on the server just as if the data entry was done in one step by the user, and still get the step-by-step expreience on the client side.
You could just place all your user controls one after another and turn on the visibility of the current step's control and turn on other controls when appropriate . No need to mess with overriding Render(). This way the user controls' viewstate will be managed by the server. and you can focus on any step validation logic.
Using an UpdatePanel to contain the steps would give the ajax experience and still be able to provide validation on each step. If you are OK with validating multiple steps at once, Tomas Lycken's suggestion (hide/show with JQuery), would give a fast step by step experience.
Did you look into using the ASP.NET Wizard control? It's a bit of a challenge to customize the UI, but otherwise it's worked well for me in similar scenarios.

Resources