Can I modify the Request.Headers collection? - asp.net

I have an ASP.NET site that uses a third-party reporting component. This component is misbehaving by throwing a NullReferenceException whenever the client browser is not specifying a User-Agent in the request headers.
It's basically an odd scenario that I'm just trying to come up with a workaround for. I do not know who/what client is not specifying a User-Agent, which seems like bad form IMO, but we have to deal with the exceptions it is generating. I have logged a support ticket with the third-party regarding the bug in their reporting component, but I have my doubts about how fruitful that route is going to be. So my thought was just to detect when the User-Agent is blank and default it to something just to appease the reporting component. However, I can't seem to change anything in the Request.Headers collection. I get the following exception:
Operation is not supported on this platform.
I'm starting to believe I'm not going to be able to do this. I understand why ASP.NET wouldn't allow this, but I haven't come up with any other workaround.
Update: At penfold's suggestion, I tried to add the User-Agent to the Request.Headers collection using an HttpModule. This got it added to the Headers collection, but did nothing to update the Request.UserAgent property, which is what is causing the reporting component to fail. I've been looking through .NET Reflector to determine how that property is set so that I can update it, but I haven't come up with anything yet (there isn't just a private field that drives the property that I can find).

Recently I also facing similar problem same as you. I overcome the problem
of Request.UserAgent by using a mock HttpWorkerRequest.
(Assuming you already solve the agent string in Request.Headers with custom HttpModule)
Here is the sample code:
Friend Class MockedRequestWorker
Inherits HttpWorkerRequest
Private ReadOnly _BaseHttpWorkerRequest As HttpWorkerRequest
Private ReadOnly _UserAgent As String
Friend Sub New(ByVal base As HttpWorkerRequest,
ByVal UserAgent As String)
_BaseHttpWorkerRequest = base
_UserAgent = UserAgent
End Sub
Public Overrides Sub EndOfRequest()
_BaseHttpWorkerRequest.EndOfRequest()
End Sub
Public Overrides Sub FlushResponse(ByVal finalFlush As Boolean)
_BaseHttpWorkerRequest.FlushResponse(finalFlush)
End Sub
'Note: remember to override all other virtual functions by direct invoke functions
'from _BaseHttpWorkerRequest, except the following function
Public Overrides Function GetKnownRequestHeader(ByVal index As Integer) As String
'if user is requesting the user agent value, we return the
'override user agent string
If index = HttpWorkerRequest.HeaderUserAgent Then
Return _UserAgent
End If
Return _BaseHttpWorkerRequest.GetKnownRequestHeader(index)
End Function
End Class
then, in your custom HttpApplication.BeginRequest handler, do this
Private Sub BeginRequestHandler(ByVal sender As Object, ByVal e As EventArgs)
Dim request As HttpRequest = HttpRequest.Current.Request
Dim HttpRequest_wrField As FieldInfo = GetType(HttpRequest).GetField("_wr", BindingFlags.Instance Or BindingFlags.NonPublic)
Dim ua As String = "your agent string here"
Dim _wr As HttpWorkerRequest = HttpRequest_wrField.GetValue(request)
Dim mock As New MockedRequestWorker(_wr, ua)
'Replace the internal field with our mocked instance
HttpRequest_wrField.SetValue(request, mock)
End Sub
Note: this method still does not replace the user agent value in ServerVariables, but it should able to solve what you need(and my problem too)
Hope this help :)

protected void Application_BeginRequest(Object sender, EventArgs e) {
const string ua = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
Request.Headers["User-Agent"] = ua;
var httpWorkerRequestField = Request.GetType().GetField("_wr", BindingFlags.Instance | BindingFlags.NonPublic);
if (httpWorkerRequestField != null) {
var httpWorkerRequest = httpWorkerRequestField.GetValue(Request);
var knownRequestHeadersField = httpWorkerRequest.GetType().GetField("_knownRequestHeaders", BindingFlags.Instance | BindingFlags.NonPublic);
if (knownRequestHeadersField != null) {
string[] knownRequestHeaders = (string[])knownRequestHeadersField.GetValue(httpWorkerRequest);
knownRequestHeaders[39] = ua;
}
}
}

I think the best way of handling this is to use a http module that will check the header and inject the user agent if necessary.
As you have found out you cannot use the set method on the Headers object. Instead you will have to inject the user agent string into the header via protected properties that can be accessed through reflection as outlined in the answer to this question.
UPDATE
Unfortunately Request.UserAgent doesn't use the information held in Request.Headers, instead it calls the method GetKnownRequestHeader in HttpWorkerRequest. This is an abstract class and from looking at the decompiled library code the actual implementation varies depending on the hosting environment. Therefore I cannot see a way to replace the user agent string in a reliable manner via reflection. You could roll your own WorkerRequest class but for the amount of effort I don't think the payoff would be worth it.
Sorry to be negative but I think its just not possible to set the user agent within the web application in a simple manner. Your best option at the moment would be to perform a pre-check for a user agent, and if the request doesn't have one return a browser not supported error message.
You could also investigate injecting something earlier on, i.e. in IIS or at your proxy server if you use one.
Also I would recommend that this issue is reported to SAP. I know they are actively working on the Viewer at the moment and who knows they might fix it and maybe even add support for Opera!

Related

Instantiating a class within WCF

I'm writing a WCF WebMethod to upload files to, of which I taken snippets from around the web. The WCF interface looks like this:
<ServiceContract()>
Public Interface ITransferService
<OperationContract()>
Sub UploadFile(ByVal request As RemoteFileInfo)
End Interface
<MessageContract()>
Public Class RemoteFileInfo
Implements IDisposable
<MessageHeader(MustUnderstand:=True)>
Public FileName As String
<MessageHeader(MustUnderstand:=True)>
Public Length As Long
<MessageBodyMember(Order:=1)>
Public FileByteStream As System.IO.Stream
Public Sub Dispose() Implements IDisposable.Dispose
If FileByteStream IsNot Nothing Then
FileByteStream.Close()
FileByteStream = Nothing
End If
End Sub
End Class
Within ASP.NET, when the web method is consumed, for some reason it only works when the interface is used as part of the instantiation of RemoteFileInfo:
Protected Sub btn_Click(sender As Object, e As EventArgs) Handles btn.Click
If fu.HasFile Then
Dim fi As New System.IO.FileInfo(fu.PostedFile.FileName)
' this is the line in question --------------
Dim cu As ServiceReference1.ITransferService = New ServiceReference1.TransferServiceClient()
' -------------------------------------------
Dim uri As New ServiceReference1.RemoteFileInfo()
Using stream As New System.IO.FileStream(fu.PostedFile.FileName, IO.FileMode.Open, IO.FileAccess.Read)
uri.FileName = fu.FileName
uri.Length = fi.Length
uri.FileByteStream = stream
cu.UploadFile(uri)
End Using
End If
End Sub
Can anyone advise why it is not possible to create an instance of TransferService using the following approach:
Dim cu As New ServiceReference1.TransferServiceClient()
If I try the above, it breaks this line:
cu.UploadFile(uri)
...and UploadFile must be called with three parameters (FileName, Length, FileByteStream) even there is no method that uses this signature.
Why is the Interface required when creating an instance of this class please?
When you create the proxy for your service with the "Add Service Reference" dialog, by default the proxy creation code will "unwrap" message contracts, like the one you have. If you want the message contract to appear as you defined on the server side on your proxy, you need to select the "Advanced" tab, and check the "Always generate message contracts" option. With that you'll get the message contract in your client as well.
The issue is that when a MessageContract is encountered as a parameter, the WCF client generation assumes by default that you want to implement a messaging-style interface, and provides the discrete properties from the message contract as part of the client-side interface.
The Using Messaging Contracts article in MSDN contains a very detailed description of what can be done with a messaging contract and I suspect that Microsoft chose this default behavior because of some of the "games" that you can play with the messages.
However, if you examine the code generated for your UploadFile on the client side, there are some interesting tidbits that help to explain what is going on.
The first is the comments for the UploadFile method in the interface:
'CODEGEN: Generating message contract since the operation UploadFile is neither RPC nor document wrapped.
...
Function UploadFile(ByVal request As ServiceReference1.RemoteFileInfo) As ServiceReference1.UploadFileResponse
This implies that the contract would have been generated differently if the message contract had a different implementation.
The second is that you will see that there is nothing special about the code that is used to actually make the service call:
Public Sub UploadFile(ByVal FileName As String, ByVal Length As Long, ByVal FileByteStream As System.IO.Stream)
Dim inValue As ServiceReference1.RemoteFileInfo = New ServiceReference1.RemoteFileInfo()
inValue.FileName = FileName
inValue.Length = Length
inValue.FileByteStream = FileByteStream
Dim retVal As ServiceReference1.UploadFileResponse = CType(Me,ServiceReference1.ITransferService).UploadFile(inValue)
End Sub
So in this case, your code is doing exactly what the generated code does. However, if the MessageContract were more complex, I suspect that this would no longer be the case.
So, for your question:
Can anyone advise why it is not possible to create an instance of
TransferService using the following approach...
There is no reason not to take this approach as long as you verify that the implementation of the method call is functionality equivalent to your code.
There are a couple of options for changing the default generation of the method in the client:
1) Remove the MessageContract attribute from the RemoteFileInfo class.
2) Although it seems to be counter-intuitive, you can check the Always generate message contracts checkbox in the Configure Service Reference Dialog Box.

Response.Redirect to Class that inherits from UI.Page?

everyone, thank for your time.
Well this my problem (well it's not a probleam at all), it is possible to have a class that inherits from ui.page and then instance an object of that class and do a redirect to it ?
Something like this:
Public sub myMethod()
Dim myPage as new myClassThatInheritsFromUIPage()
Response.redirect(myPage) 'Here is one of my "no-idea" line
end sub
I do this in my webForm (and that what I want to do in a class that inherits from ui.page):
Response.BufferOutput = True
Response.ClearContent()
Response.ClearHeaders()
ostream=crReportDocument.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat)
Response.AppendHeader("Cache-Control", "force-download")
Response.AppendHeader("Content-disposition","attachment;filename=ReportAsPDF.pdf")
Response.ContentType = "application/pdf"
Response.BinaryWrite(ostream.ToArray())
Response.Flush()
HttpContext.Current.ApplicationInstance.CompleteRequest()
What I want to do is perfectly possible with a normal WebForm, but my webForm doesn't render nothing at all (at least as (x)html so, that's because I would like to know if what I'm asking is possible to achieve.
Thank you everyone.
Well at the end I just add "HttpContext.Current." to all the lines that include a "response" attribute, so now I have just a class that NOT inherits from "UI.Page" and just call the method that clear the buffer (a custom method), add the headers (to force the download,set the mime type and filename) and flush the response by itself and get the effect/use I want it.
In order to access to the Session vars just add "HttpContext.Current." and it works, I don't know how secure or if is a good way,but appears to work well.
So the code now looks something like this:
Imports CrystalDecisions.CrystalReports.Engine
Imports CrystalDecisions.Shared
Imports CrystalDecisions.ReportSource
Namespace foo
Public Class requestReport
'just to instance the object'
Public Sub New()
End Sub
Public Sub downloadReport()
'do all the stuff to connect and load the report'
HttpContext.Current.Response.BufferOutput = True
HttpContext.Current.Response.ClearContent()
HttpContext.Current.Response.ClearHeaders()
ostream=crReportDocument.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat)
HttpContext.Current.Response.AppendHeader("Cache-Control", "force-download")
HttpContext.Current.Response.AppendHeader("Content-disposition","attachment;filename=ReportAsPDF.pdf")
HttpContext.Current.Response.ContentType = "application/pdf"
HttpContext.Current.Response.BinaryWrite(ostream.ToArray())
HttpContext.Current.Response.Flush()
HttpContext.Current.ApplicationInstance.CompleteRequest()
End Sub
End Class
End Namespace
And from a command for example do this:
dim myReport as new foo.requestReport()
myReport.downloadReport()
Of course now you can add more attributes or method if you need it.
So now I don't even don't use Response.redirect() or inherits from "UI.Page", just a class that "change" the "current response" and "flush" the content created on fly with the crystal report, I think I was kind of totally lost but your answers really help me, especially what Tejs says, what is almost the same or the same what I just did. Thank you.
UPDATE:
By the way, I just realize that the ReportDocument class has this method: ExportToHttpResponse that let us flush the Crystal Report to the browser as PDF/XSL etc forcing (or not) the download of the file.
No, as there is no current overload that accepts a UI.Page instance. However, you could call the Render method on your new page and write directly to the response stream. AKA
Clear the Response Buffer.
Render your page instance to the response buffer.
Invoke Response.End() to flush the request and send it to the client.
If your new page doesnt actually do anything, you can additional just send no content back with the response.
You can use Server.Transfer and pass in the instance of the page object that you want to tranfer to.
Here is the documentation: HttpServerUtility.Transfer
Try just doing this instead:
HttpContext.Current.Response.Redirect("...");
HttpRequest Request = HttpContext.Current.Request;
HttpResponse Response = HttpContext.Current.Response;
And after that you can redirect any page.

ASP.NET Is it possible to handle all the null session checking in masterpage

I have the following steps in my webpage
1) User Logs in and I set the following session variables
Session("userName") = reader_login("useremail").ToString()
Session("userId") = reader_login("user_ID").ToString()
Session("firstName") = reader_login("firstName").ToString()
2) Now on my logged in VB.NET templates I reference a MasterPage called LoggedIn.Master. In Which I added the following method to check for the above null session variables. And if they null to redirect back to login page.
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
'#Check that User is Logged in, if not redirect to login page
If (Session("userId") Is Nothing) Or (Session("userName") Is Nothing) Or (Session("firstName") Is Nothing) Then
Response.Redirect(ConfigurationManager.AppSettings("site_base_url").ToString & "login/", False)
End If
3) Now my question is if I want to use any above 3 Session variables in different .net templates or usercontrols referencing the above master page do i need to AGAIN add the check
If (Session("userId") Is Nothing) Or (Session("userName") Is Nothing) Or (Session("firstName") Is Nothing) Then
Response.Redirect(ConfigurationManager.AppSettings("site_base_url").ToString & "login/", False)
End If
In the respective pages or will the check in master page do. Because at the moment i.e. if in a usercontrol I attempt to do i.e.
customerName.Text = Session("userName").ToString()
or
Response.Write(Session("userName").ToString())
I am getting the error
Object reference not set to an instance of an object.
customerName.Text = Session("userName").ToString()
You can write a wrapper around the Session to handle null values and just call the wrapper when you access the items:
Public Class SessionWrapper
Public Shared ReadOnly Property Item()
'Access session here and check for nothing
End Property
End Class
And use it like this
SessionWrapper.Item("itemName")
In answer to your question - as long as the masterpage checks the session and redirects before all your controls and page code make a reference to Session, you should be fine.
You were using OnInit() which seems reasonable, but see this article for a good understanding of the timing of events.
Incidentally, I strongly discourage the use of ad-hoc calls to Session in your page and control code. Instead, I recommend you create a static SessionManager class that does the Session referencing for you. That way, you get to benefit from strong typing, and won't be able to accidentally make hard-to-debug 'session key' typos in your code like Session["FiirstName"]. Also, you can incorporate your null-session check right into the call for the session value:
EXAMPLE (in C#, sorry!)
public static class SessionManager
{
private static void EnsureUserId()
{
if (Session["userId"] == null)
{
Response.Redirect("YourLogin.aspx", false);
}
}
public static string FirstName
{
get
{
EnsureUserId();
if (Session["firstName"] == null)
Session["firstName"] = "";
return (string)Session["firstName"];
}
set
{
Session["firstName"] = value;
}
}
}
You can create an http module that asks about the session objects and if they are null, it will redirect to the login page and by developing this http module, in each page request the module will do the check and then you can use it normally without checking.
A better way to handle this would be to add a base class for all controls that require this session variables to be present. You can then add properties to wrap access to the session and other cool stuff and the check will work even if the controls are used with a different master page.

ASP.NET multi language website?

How can I transform a website to be able to handle multi language (example : english, french, spanish)?
I do not like the resource file because I feel limited and it's pretty long to build the list. Do you have any suggestion?
Update
For the moment the best way we found is to use an XML file and with some Xpath et get values.
Implicit localization (on the Visual Studio - Tools menu - Generate Local Resources) is about as easy as it can be. Write your pages in your default language, pick the menu option, and your resource files are created and can be sent to someone to translate.
The resx file is just xml, so if the translation company wants you can transform it into (and out of) spreadsheets easily.
Using a databases instead of resx as your backing store is not difficult. Rick Strahl has a good explanation and example code for a database-driven localization provider here - there's a nice built in localization editor too with interface to Google translations and Babelfish.
We store resources for multilingual sites in a database. We've created a couple of tools to make it easy to create and access these. There's a custom ExpressionBuilder that allows us to use this syntax:
<asp:linkbutton runat='server' text='<%$ LanguageStrings:ClickMe%>' />
And a custom label that contains the default text, and adds a row to the database if there's not already one.
<r:languagelabel runat="server" name="AboutUs">About Us</r:languagelabel>
The table containing the strings has one column per language. This makes it very easy to create the site in English (or whatever the default language is), then hand off the table (which populates itself) to a translator. It's also very easy to see what languages you need to have stuff translated for. With resources, every time you need to add a new string, you have to stop what you're doing, and then go to the resource file for each language and add the resource.
Here's the code for the language label:
''' <summary>
''' Retrieves a language-specific string.
''' </summary>
Public Class LanguageLabel
Inherits Label
Private _Name As String
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private Sub Populate()
If Len(Me.Name) > 0 Then
Dim LanguageString As String = GetLanguageString(Me.Name, Me.Text)
If Len(LanguageString) > 0 Then Me.Text = LanguageString
End If
End Sub
Private Sub LanguageLabel_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
Populate()
End Sub
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
' By default a label wraps the text in a <span>, which we don't want in some situations
writer.Write(Me.Text)
End Sub
End Class
and the utility function:
Public Function GetLanguageString(ByVal Name As String, Optional ByVal DefaultText As String = "") As String
Dim DefaultLanguage As Language = Languages.GetById(1)
Name = StripPunctuation(Name).Trim.Replace(" ", "") ' Remove punctuation, spaces from name
Dim SelectSql As String = String.Format("Select {0},{1} from LanguageStrings where Name=#Name", Languages.CurrentLanguage.Code, DefaultLanguage.Code)
Dim LanguageStringTable As DataTable = ExecuteDataset(cs, CommandType.Text, SelectSql, New SqlParameter("#Name", Name)).Tables(0)
If LanguageStringTable IsNot Nothing AndAlso LanguageStringTable.Rows.Count > 0 Then
Dim LanguageText As String = LanguageStringTable.Rows(0)(Languages.CurrentLanguage.Code).ToString
Dim DefaultLanguageText As String = LanguageStringTable.Rows(0)(DefaultLanguage.Code).ToString
If Len(LanguageText) > 0 Then
' We have a string in this language
Return LanguageText
Else
' Nothing in this language - return default language value
Return DefaultLanguageText
End If
Else
' No record with this name - create a dummy one
If DefaultText = "" Then DefaultText = Name
Dim InsertSql As String = String.Format("Insert into LanguageStrings (Name, {0}) values (#Name, #Text)", DefaultLanguage.Code)
ExecuteNonQuery(cs, CommandType.Text, InsertSql, New SqlParameter("#Name", Name), New SqlParameter("#Text", DefaultText))
Return Name
End If
End Function
Resource files are the way to go. We ship our product in 12 languages. We pull all strings out into resource files and ship them to a translation company. It's a pain at times, but that is the defacto way to do it.
It also gets fun when 4-letter English words get translated into 17-letter phrases and you have to tweak your UI.
How late in the design process are you? If not too late, and if the budget allows, consider porting to a multi-lingual CMS like Ektron CMS300.net (which has built-in translation tools). If not, then you've got a huge task ahead of you.
Another solution I am using is to create the language folders which contain the aspx pages containing all the required text in that particular language.
The only problem here is how can you inject as little code as possible into those replicating pages. I am using a controller pattern here to do this, and then a object data source to get the data and bind it to the controls in all pages.
In this way I have achieved the goal of getting rid of the resource files and I can keep the code behind in one place without replicating it (unless necessary).
Edit: I would recommend a good CMS framework as well.
One of the web apps I develop has this NLS requirement too.
I found that there are at least 3 locations where you have localized texts:
user interface
database tables ("catalogs" or whatever you want to call them)
backend code (services etc)
My solution has one table for the pages, tables, etc ("Container"), one table for each item in that container (e.g. labels, buttons by ID, record identifiers), and one table for the translated items (plus language identifier).
A translation application helps me keep the translations up-to-date, and exports all translations in XML.
The product ships with translations, but customers can adjust the translations, changes taking effect immediately.
Sample code i have done using resource file
add global.asax
void Application_BeginRequest(Object sender, EventArgs e)
{
// Code that runs on application startup
HttpCookie cookie = HttpContext.Current.Request.Cookies["CultureInfo"];
if (cookie != null && cookie.Value != null)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cookie.Value);
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cookie.Value);
}
else
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en");
}
}
http://satindersinght.blogspot.in/2012/06/create-website-for-multilanguage.html
http://satindersinght.wordpress.com/2012/06/14/create-website-for-multilanguage-support/

How can I use the URL to set the current culture in ASP.NET 2.0 Web App?

I am looking in to ways to enable a site to basically have something like:
http://mysite.com/en-US/index.aspx`
Where the "en-US" can vary by culture..
This culture in the URL will then basically set the CurrentUICulture for the application..
Basically, we currently have a page where the user explicitly clicks it, but some are favouriting past that, and it is causing some issues..
I know this sort of thing is easily done in ASP.NET MVC, but how about those of us still working in 2.0? Can you guys in all your wisdom offer any suggestions/pointers/ANYTHING that may get me started? This is new to me :)
I'm sure there must be some way to pick up the request and set/bounce as appropriate.. HttpModule maybe?
Update
Just had a thought.. May be best to create VirtDirs in IIS and then pull the appropriate part from the Requested URL and set the culture in InitializeCulture?
Is storing the choice in a cookie out of the question?
Nice of you to give the users a choice but why not just default to the users client/web browser settings?
If they bookmark a page and have lost the cookie you could fall back to the default and if that is a culture you do not support then fallback further to en-US.
If you want to keep your solution you could use a rewrite engine. I've used http://www.managedfusion.com/products/url-rewriter/ in the past. For a list of engines see http://en.wikipedia.org/wiki/Rewrite_engine#IIS
You can use the routing feature developed for MVC easily with webforms. This SO question addresses doing that:
ASP.NET Routing with Web Forms
If you can't use the 3.5 framework, there are a number of URL rewriting modules out there. I have no experience with any to be able to make a recommendation.
I'm doing it in some sites with ASP.net Routing.
Here is the code:
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs on application startup
RegisterRoutes(RouteTable.Routes)
End Sub
Public Sub RegisterRoutes(ByVal routes As RouteCollection)
Dim reportRoute As Route
Dim DefaultLang As String = "es"
reportRoute = New Route("{lang}/{page}", New LangRouteHandler)
'* if you want, you can contrain the values
'reportRoute.Constraints = New RouteValueDictionary(New With {.lang = "[a-z]{2}"})
reportRoute.Defaults = New RouteValueDictionary(New With {.lang = DefaultLang, .page = "home"})
routes.Add(reportRoute)
End Sub
Then LangRouteHandler.vb class:
Public Class LangRouteHandler
Implements IRouteHandler
Public Function GetHttpHandler(ByVal requestContext As System.Web.Routing.RequestContext) As System.Web.IHttpHandler _
Implements System.Web.Routing.IRouteHandler.GetHttpHandler
'Fill the context with the route data, just in case some page needs it
For Each value In requestContext.RouteData.Values
HttpContext.Current.Items(value.Key) = value.Value
Next
Dim VirtualPath As String
VirtualPath = "~/" + requestContext.RouteData.Values("page") + ".aspx"
Dim redirectPage As IHttpHandler
redirectPage = BuildManager.CreateInstanceFromVirtualPath(VirtualPath, GetType(Page))
Return redirectPage
End Function
End Class
Finally I use the default.aspx in the root to redirect to the default lang used in the browser list.
Maybe this can be done with the route.Defaults, but don't work inside Visual Studio (maybe it works in the server)
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
Dim DefaultLang As String = "es"
Dim SupportedLangs As String() = {"en", "es"}
Dim BrowserLang As String = Mid(Request.UserLanguages(0).ToString(), 1, 2).ToLower
If SupportedLangs.Contains(BrowserLang) Then DefaultLang = BrowserLang
Response.Redirect(DefaultLang + "/")
End Sub
Some sources:
* Mike Ormond's blog
* Chris Cavanagh’s Blog
* MSDN
Just try to add that parameter
http://yoursite/yourPage.aspx?lang=en-US
If you were utilizing resources files it will work automatically.
Good Luck

Resources