asp.net MapPageRoute loading page twice - c#

Not sure whats going on, but I have a rewrite with two parameters. For some reason the page is loading twice when it's called. I know that it's the rewrite because it works fine when it's just one parameter. Thanks for any help.
This is in my Global.asax
routeCollection.MapPageRoute("RouteForAlbum", "album/{autoID}/{albumName}", "~/SitePages/AlbumView.aspx");
This is on my page load
if (!Page.IsPostBack)
{
string id = Page.RouteData.Values["autoID"].ToString();
string albuname = Page.RouteData.Values["albumName"].ToString();
}

Wow, found the answer after more searching. If you have javascript reference with ../ this causes issues with URL rewritting.
asp.net Multiple Page_Load events for a user control when using URL Routing

UDPATE:
This can also happen when using CSS3PIE together with ASP.net Routing and the two don't play nicely together.
Any CSS3PIE css styles with a URL in the value can cause the target page to execute the code behind multilple times. For me specifically, it was these two lines:
behavior: url(PIE.htc);
-pie-background: url(bg-image.png) no-repeat, linear-gradient(#FFFFFF, #53A9FF);
Changing the above two lines to start with a leading slash "/" fixed it along with specifying the whole path to the files.
behavior: url(/scripts/PIE-1.0.0/PIE.htc);
-pie-background: url(/scripts/PIE-1.0.0/bg-image.png) no-repeat, linear-gradient(#FFFFFF, #53A9FF);

Related

Shell routing issue on back button when passing page ID

I am receiving the following error on Android only (fine on iOS)
Shell: Failed to Navigate Back: System.ArgumentException: Ambiguous routes matched for: //D_FAULT_FlyoutItem12/IMPL_homepage/homepage/indexpage/indexpage
I declare the page like so in AppShell
Routing.RegisterRoute("indexpage", typeof(Pages.Index));
Because the content of the page is dynamic, I navigate to it like so
await Shell.Current.GoToAsync($"indexpage?indexid={clicked.ID}");
This works fine when going to an index page from a list
However, when I get to the index page and then go to another page of a different type, also via the route registered in AppShell, but again by passing in an ID, and then try and go back, I get the error
Again, the error only occurs on Android - iOS works fine
or anyone that will face this issue, and have this error message too;
System.ArgumentException: 'Ambiguous routes matched for: ...'
This occures when you register your routes in XAML (in the appshell file) and you also try registering your routes in code behind in C#. Only register your routes once using either XAML or C# not both.
to fix your error: most likely you have a page register 2 times... 1st time register in AppShell.xaml.cs file and 2nd time register in AppShell.xaml.
just remove double register and it should work. good luck

Output caching, VaryByParam/VaryByCustom

OK, there seem to be no end to the number of articles that sing the praises of output caching and how much it will speed up your website. I have now read hundreds of articles and Q&As on the topic, but I still can't seem to make it work. (I think output caching might be stealing my soul)
My requirements are pretty simple, I have 3 pages that I would like to cache based on parameters: Home, Results, and Details. I also have a small area of the page that needs to be varied by user. I also need to cache to a central repository and I have chosen redis to hold my data. I should also mention that this is still an old web forms app.
My original approach was to attempt to supply my own custom string using the "VaryByCustom" option. The Microsoft page seems to make this look simple:
https://msdn.microsoft.com/en-us/library/5ecf4420.aspx
This requires placing an overridden method "GetVaryByCustomString" in the global.asax file. The problem is that most of the examples show using variables from HttpContext that are always available (Browser, Browser Version etc.) and even though Microsoft and others seem to suggest this as the preferred "cache by custom string" method I can't find any working examples that allow the page to define the string. I have seen a few examples of people using session and claiming that it works (session is null for me) and context.user (some people say that user is null) but I don't see any practical way to deliver a string except by using Response.Cache.SetVaryByCustom("my_custom_string"). After a day of strugling with implementation in the main project I decided to build an isolated project for testing/proof of concept. In this project I was able to get my custom string working as long as I passed it as the string to SetVaryByCustom. The problem is that this doesn't really match any examples I've seen. The examples show "SomeKey" with "SomeValue" being returned by GetVaryByCustomString. What worked for me was essentially "SomeValue" with "SomeValue". Here was my code:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg != null)
{
return arg;
}
return base.GetVaryByCustomString(context, arg);
}
This worked the first time on the page, after setting the value of a dropdown which created a postback, but never after that. So the "OnselectedItemChanged" fired once and created a new set of cache entries based on the selection, but it never fired again. I tried modifying just about every cache parameter I could to make it work but no matter what series of settings I attempted (set cache declaratively, set caching in code, tried various combinations of VaryByParams, adding location, etc) I was never successful.
After attempting for 2 days to get this to work I decided it was time for a different approach. I have gone back to a more traditional/accepted approach.
I created 3 cache profiles in my web.config:
<caching>
<cache disableExpiration="false" />
<outputCache defaultProvider="RedisOutputCacheProvider" enableOutputCache="true" enableFragmentCache="true" sendCacheControlHeader="true">
<providers>
<add name="RedisOutputCacheProvider"
type="LAC.OutputCacheProvider, LAC"
connectionString="192.168.XX.XX:6379,connectTimeout=10000"
dbNumber="5"
keyPrefix="LAC:OutputCache:" />
</providers>
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Home" varyByParam="*" varyByCustom="user" duration="86400"/>
<add name="Results" varyByParam="*" varyByCustom="user" duration="7200" />
<add name="Details" varyByParam="*" varyByCustom="user" duration="7200"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
So each page has a custom vary by user string that should help me cache a new page for an authenticated user vs an anonymous one.
Then I added a GetVaryByCustomString that looks like:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
switch (arg)
{
case "user":
var identity = context.User.Identity.Name != "" ? context.User.Identity.Name : "Anonymous";
return string.Format("IsAuthenticated={0},Identity={1}", context.Request.IsAuthenticated, identity);
}
return string.Empty;
}
I am as close as I have ever been to having this working but it's still not 100%. The Home page works fine, the details page works fine, but the results page never caches. The results page has a filter containing 12 different dropdown lists. So it needs to store version of the page based on selections. I added a Timestamp to the top of all of my pages so I could see if the page was caching. The time on the results page changes every time I hit control-F5, which is not true on the other 2 pages. No cache entries are ever created and GetVaryByCustomString is never called. I have triple checked the code for some sort of "Turn cacheability Off" setting but as far as I can tell there is no code anywhere in the page that disables the cache. My global.asax file inherits from the correct class, my custom ouputcache provider seems to be adding the correct entries for the 2 pages that work. Of course now with VaryByParam="*" the key can be as long as the content thanks to viewstate (which has to be left on) BLECH!
So essentially I have caching set on three pages that are similar and one doesn't work. I have no idea where to look next. I am hoping someone like #kevinmontrose who really has a handle on this sort of stuff will take pity on me and give me a shove in a direction that will lead me to a solution.
UPDATE:
I have opened a case with Microsoft.
have you been setting VaryByParam? i forget the details at the moment but i recall getting stuck before finding out that it is mandatory. (in every case i think.(?)) i think duration is also required. (i'm referring to directives in the .aspx page.)
rereading your question i saw the issue: "I can't find any working examples that allow the page to define the string." i was rereading some articles and i'm pretty sure that's the wrong approach.
i haven't tested all of this but for ex, if you take the standard example:
<%# OutputCache Duration="10" VaryByParam="None" VaryByCustom="minorversion" %>
what seems to be happening is that you have just set the string to look for, and that's it.
when a request comes in, you check the HTTP request header - the Vary header, which is what you set in the page - and if it exists you do what you want.
<%# Application language="C#" %>
<script runat="server">
public override string GetVaryByCustomString(HttpContext context,
string arg)
{
// arg is your custom 'Vary' header.
if(arg == "minorversion")
{
return "Version=" +
context.Request.Browser.MinorVersion.ToString();
}
return base.GetVaryByCustomString(context, arg);
}
</script>
i just did a quick test of the basic example and added a label that is updated on page load with the current time. i refreshed the live page several times and the time did not change until 10 seconds had passed. so it definitely works. i think the key is that you set the value and you're done, but you can work with it if you want to.
... on second thought, i think you create the header in the page, and return the value from the global.asax. where?? i think it's sent to the caching system which decides if a new or cached page should be sent. in the example above the browser's minor version is checked and set and since there is a cached version available (after first load) it sends that, until 10 secs are up.
the docs for VaryByCustom is in a section for 'caching different versions of a page' so that's what this is doing. if someone with a different browser shows up, the browser version is checked - then the caching system checks to see if a version of the page for that browser version is available. if not, create a new page; if so, send the cached version.

.Net RouteTable for images not triggering

I have setup a routing table in the global.asax file for images that have been moved to the database. When I use the url such that EmpImages/[numeric id] as a basic format, it works fine if I use the url that does that same ~/EmpImages/42, but we have hundreds of hardcoded links that are ~/EmpImages/42.png. When I try to use the EmpImages/[numeric id].png, the handler is never called.
I have looked at several samples that show the .ext, but they are using page routes instead of handlers. With the code below, can you tell me what I'm missing?
This part works:
RouteTable.Routes.Add(new Route("EmpImages/{id}/{size}", new EmployeeImageRouteHandler()));
RouteTable.Routes.Add(new Route("EmpImages/{id}", new EmployeeImageRouteHandler()));
When using the URL:
~/EmpImages/42
~/EmpImages/42/256
But when I try:
RouteTable.Routes.Add(new Route("EmpImages/{id}/{size}.png", new EmployeeImageRouteHandler()));
RouteTable.Routes.Add(new Route("EmpImages/{id}.png", new EmployeeImageRouteHandler()));
When using the URL:
~/EmpImages/42.png
~/EmpImages/42/256.png
It fails. The handler is never called.
What simple thing am I missing?
According to the documentation for Route, it seems that the way you are specifying the route is unsupported.
Quoting the docs:
The URL pattern consists of segments that come after the application name in an HTTP request... Each segment is delimited by the / character. When a segment is enclosed in braces ( { and } ), the segment is referred to a URL parameter. ASP.NET routing retrieves the value from the request and assigns it to the URL parameter... If the segment is not enclosed in braces, the value is treated as a literal value.
Apparently, it does not support segments with mixed URL parameters and literals.
You need to have your EmployeeImageRouteHandler internally deal with the ".png" extension of the size parameter with string processing.
EDIT: In addition to this point, it seems that there is a known problem in handling URLs with dots see this StackOverflow question . The solution proposed there is to include
<httpRuntime relaxedUrlToFileSystemMapping="true" />
in your web.config, but this works only for ASP 4.0 and IIS 7.0 and above. The details of this problem, as mentioned in the indicated question, are discussed on Haacked.
As far as I know, dmi_ above is right about route parses limitations. But you can draw a simple workaround from there:
Register your routes as folows:
RouteTable.Routes.Add(new Route("EmpImages/{id}/{size}/i.png", new EmployeeImageRouteHandler()));
RouteTable.Routes.Add(new Route("EmpImages/{id}/i.png", new EmployeeImageRouteHandler()));
And request them:
~/EmpImages/42/i.png
~/EmpImages/42/256/i.png
The issue is that the path you're attempting to catch with routing is a static resource. Routing is ignored if a file exists in the location of the route. This is by design and to both prevent conflicts between static url references and prevent the routing engine from being engaged for every single request.
Try putting a break point in your handler and then specifying an id that you know does not exists to see if this is the case.

Msdn style address URL localization possible implementation

I am trying to come up with idea how to implement msdn like localization based on address string
http://msdn.microsoft.com/en-us/library/system.string.aspx
http://msdn.microsoft.com/ru-ru/library/system.string.aspx
I tried to find various information about ASP.NET localization, but failed to find a description of strategy that can be used to get above result.
I am not very experienced with ASP.NET and would like to get some advice on how to implement something that will result in similar paths on my website(using best possible way with least code duplication for example).
I understand that I could duplicate both files in 2 folders. or 10 files in 10 folders if i got 10 cultures. But that sounds like not the best strategy. Is there some rewrite & parameter pass going on behind or ?
Update: I ended up implementing it the following way:
for my localizable pages I register routes to all current cultures ( in a generic method),
for example for help.aspx I register ru-ru/help/ and en-us/help/ routes, inside help.aspx (and other localizable pages, oop way) I analyze address string and retrieve desired language. After that I setup html contents matching the related culture provided in url.
Microsoft appear to be either rewriting the URLs, or taking advantage of custom routing in ASP.NET. They are using using the URL to determine which culture to select. They could have used a query string but the URLs would not look as good.
I'd suggest that you start by looking into ASP.NET MVC as it uses routing out of the box. Then modify the routes to match those used by MS above, before using them to apply a culture.
Where you will be hosting your application could have an impact on whether you can do similar things. If you are managing the whole server and not using shared hosting it will be easier to implement these things, especially URL rewriting. If you have to use ASP.NET 2.0 because you are in some aging corporate environment you will probably have an even more difficult time.
Valentin, try to use url routing:
1 - Add routing info to global.asax
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("cultureCrossroad", "{culture}/Library/{article}", "~/library/ArticleHost.aspx");
}
2 - Create host page at ~/Library/ArticleHost.aspx
public partial class ArticleHost : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// ! Here we have access to url's parts and can redirect user for desired page via Server.Transfer method.
var response = string.Format("culture: {0}<br/> article: {1}", Page.RouteData.Values["culture"], Page.RouteData.Values["article"]);
Response.Write(response);
}
}

ASP.NET 4.0 webforms routing

I have an existing site that I'd like to convert to use routing, and after reading Scott Guthrie's post here, I built a working sample that works for most circumstances. However, since not all of the pages on the existing site match a particular pattern, I'll need to check against a database to determine which route (destination .aspx page) to use.
For example, most pages are like this:
http://www.mysite.com/people/person.html
This is fine - I can easily route these to the view_person.aspx page because of the 'people' directory.
But some pages are like this:
http://www.mysite.com/category_page.html
http://www.mysite.com/product_page.html
This necessitates checking the database to see whether to route to the view_category.aspx page or the view_product.aspx page. And this is where I'm stuck. Do I create an IRouteHandler that checks the database and returns the route? Or is there a better way? The only code I've found that kind of fits is the answer to this question.
Thanks in advance.
If you don't mind doing so, the cleanest solution is to:
http://www.mysite.com/pages/category_page.html
In ASP.NET MVC, this situation would be handled a little differently, by specifying a default controller and action method on the root route.
Your route handler doesn't check the database. It sends all the requests to a handler .aspx script. It's that script that checks the database.
My register route looks like...
private static void RegisterRoutes()
{
Route currRoute = new Route("{resource}.axd/{*pathInfo}",
new StopRoutingHandler());
RouteTable.Routes.Add( "IgnoreHandlers", currRoute);
currRoute = new Route("{urlname}",
new EPCRouteHandler("~/Default.aspx"));
currRoute.Defaults = new RouteValueDictionary {{"urlname", "index.html"}};
RouteTable.Routes.Add( "Default", currRoute);
}
The custom handler, which shouldn't be needed in ASP.Net 4.0, simply passes the urlname parameter to the responding script as a URL variable.
Now how often the responding script checks the database depends on how often the data in the database is changed. You can easily cache paths and invalidate the cache when the data is suppose to have changed for instance.
For anyone stuck in the same situation, I ended up adapting the code from this answer to check against a database and return the proper ASPX page.

Categories