I trying to implement a typical languages menu where users can select the language they want to view the site in through a menu that appears throughout all pages in the site.
The menu will appear on multiple master pages (currently one for pages where users are logged in and one for pages where users are not).
My current implementation is having a single master page base class (let's call it MasterBase). MasterBase has an event
public event LanguageChangedEventHandler LanguageChanged;
where LanguagedChangedEventHandler is simply defined as
public delegate void LanguageChangedEventHandler(string NewCulture);
MasterBase also has an overridable method
protected virtual void OnLanguageChanged(string NewCulture)
which just basically fires the event.
Each master page that inherits MasterBase overrides OnLanguageChanged and does the usual stuff like set the Thread's CurrentUICulture and the language cookie then does a
Server.Transfer(this.Page.AppRelativeVirtualPath, true);
to get the page to reload with localized values for the new culture. On the master page for logged in users it also updates the user's language pref in the db.
Each language option is currently a LinkButton on a master page that inherits from MasterBase. When the link is clicked it calls the base OnLanguagedChanged method passing in the correct culture info. E.g.
protected void btnEnglish_Click(object sender, EventArgs e) {
this.OnLanguageChanged("en-US");
}
Each page that needs to handle a language change then has some code in the page load that looks like...
((MasterBase)this.Master).LanguageChanged += this.Page_OnLanguageChanged;
// Where Page_OnLanguageChanged has the signature of LanguageChangedEventHandler
// and has stuff like reloading options in a drop down using the new language.
Quite a convoluted 'framework' =)
Firstly it's hard for new developers to know they have to hook up a method to the MasterBase's LanguageChanged event to handle language changes. Yes, we do document it. But still it's not something straightforward and obvious.
Secondly, all language changes are post backs. This is problematic especially when you want to navigate back with the browser Back button.
I'm looking for a more elegant solution. One that doesn't have both the problems above and also handles my current requirements.
Greatly appreciate any suggestions. Thanks.
It seems to me that it would be better to implement this in a control that sets an application variable that all pages could use. That way you could just implement the code in one place and have it always available on each page that displays the control (could be in your master's so all pages that inherit get it automatically). I think in the control you would have a handler that sets the global language setting and then reloads the page. Each page would check the language setting during page_load or prerender and load the proper localized strings accordingly.
I would just use the PreInit event on base page to set the current ui culture. I am not clear on why you would need each page to know when language is changed.
Related
I am creating a website that will be multilingual in nature.
I am providing a functionality whereby the user can select desired language by selecting it from the drop down.
Now my problem starts here (when a language is selected from the drop down). I am currently implementing 2 languages English and Arabic.
Problem is when the user select Arabic from the drop down on the login page the page is refreshed and the browser loads all the content in Arabic.
But...
When i select English back again, the page refreshes but the language content does not change !!
i have check the code and the values (culture name value) are being applied correctly!!
Any clues as to what is wrong and where...
Here is my code...
protected override void InitializeCulture()
{
String selectedLanguage = string.Empty;
if (Request.Form["ddlLanguage"] != null)
{
selectedLanguage = Request.Form["ddlLanguage"];
CultureInfo ci = new CultureInfo(selectedLanguage);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
}
base.InitializeCulture();
}
Any help will be great !!
Thanks
The Culture settings must set on each request. It's not enough to set the Thread cultures once when the selection changes.
In order to set the culture according to the user selection on each request, there are several possible ways.
Set the culture in (for instance) the Page_Init event of the master page.
Create custom base page (maybe MyBasePage) for the content pages and override the InitializeCulture method of that class. Then make all the content pages derive from the class instead of directly from Page.
Add code for setting the culture in a suitable event in Global.asax.
Probably there are several other possible ways to handle this as well.
Regardless of which method you will use, the culture that the user has chosen must be available to the code that is going to set the culture on the thread. So when the user changes his/her selection, you must save that selection in a place where you can access it in the coming requests. This can also be solved in several possible ways. Here are some options:
If you are using a ProfileProvider, you could save the selection to the user's profile.
Alternatively you can store it in a cookie.
You could possibly store it in the Session although that would mean that the user must re-select it whenever the Session has been reset.
You could use some other custom way to store the selection (database, some file on disk etc).
For a more detailed exmple of how this could be done using Global.asax and a cookie, have a look over here.
Check this article - How to create ASP.NET MVC multilingual web application ?
We will see mainly two approaches-
Approach 1: Using Static Pages
Approach 2: Using Dynamic page with localized data at runtime
I am using this code to check if the request came from a page , if not then redirect somewhere.
string referer = Request.ServerVariables["HTTP_REFERER"];
if (string.IsNullOrEmpty(referer))
{
Response.Redirect("/UnauthorizedAccess.aspx");
}
It is working , I don't know whether it is perfect the solution.However I am checking this on load event of one of my page.How can I make it check on every request.Should I check this for all my pages.Also it is a good approach.Can anybody point me in the right direction.Any suggestion is welcome.
If you have logic that you would like to be run on the OnLoad of a bunch of your pages. You should probably create a BasePage that derives from Page and have the logic inside. Then all the pages you want that logic in can derive from BasePage instead of the regular Page.
Another approach can be using Master Pages
Note: After reading OPs additional comments. One thing to look out for when using a Master Page is that the Master Page's Page_Load event happens AFTER the Content Page's Page_Load event.
In other words the lifecycle is like this:
Master Page Init Event
Content Page Init Event
Content Page Load Event
Master Page Load Event
If your response.redirect moves the user to another page with the same master page (and same "validation" check) you might find yourself in an endless loop :)
If you have lot of pages, with these kind of common codes, than one possible solution is creating your own MyPage class as a child of the standard Page class. In your MyPage you can use something like:
Page_Load(object sender, EventArgs e)
{
string referer = Request.ServerVariables["HTTP_REFERER"];
if (string.IsNullOrEmpty(referer))
{
Response.Redirect("/UnauthorizedAccess.aspx");
}
base.Page_Load(sender, e);
}
Then any of your pages can inherit from this own MyPage class instead of the .NET's standard one.
In this way the common code reside in one place. In case of any change you have to modify that only there.
Or another possibility, you can consider using Master Pages.
I have couple of user controls which are statically referenced in aspx. We are setting some public properties on the user controls in Page_Preinit event.
The page also references a master page
This was working fine so far. Had to do a ui redesign and we implemented nested master pages.
Now, all of a sudden, the user controls are showing up as null. If I change the master page to parent one (instead of child -nested), it works fine.
Appreciate any pointers to this issue.
some sample code: here ucAddress is null
protected void Page_PreInit(object sender, EventArgs e) { ucAddress.City = "Dallas"; }
This blog post describes the problem very well and also offers a solution. I can confirm that the solution works and I'll repeat the relevant parts here:
The problem
We have a user control and initialize its control in the Init event so that it is ready when the ViewState is restored (between Init and Load).
Once you start using this encapsulation technique, it won’t be long until you want to pass in a parameter that affects the data you load. Before we do, we need to be aware that the Init event is fired in reverse order. That is, the child controls have their Init event fired before that event is fired at the parent. As such, the Page.Init event is too late for us to set any properties on the controls.
The natural solution is to try and use the Page.PreInit event, however when you do you’ll often find that your control references are all null. This happens when your page is implemented using a master page, and it relates to how master pages are implemented. The <asp:ContentPlaceHolder /> controls in a master page use the ITemplate interface to build their contents. This content (child controls) is not usually prepared until the Init event is called, which means the control references are not available. For us, this represents a problem.
The Solution
The fix is remarkably simple; all we need to do is touch the Master property on our Page and it will cause the controls to become available. If we are using nested master pages, we need to touch each master page in the chain.
The author of the blog post then offers a nice little snippet that you can execute in the PreInit handler of your Page that uses a MasterPage and contains a user control:
protected override void OnPreInit(EventArgs e)
{
// Walk up the master page chain and tickle the getter on each one
MasterPage master = this.Master;
while( master != null ) master = master.Master;
// Access now initialized user control
ucAddress.City = "Dallas";
}
Found the issue. had to initialize child master page before accessing user control.
I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible).
If I add user controls on the page at compile time and set visible to false for User Control B, does it still execute the B's code behind? I can then set the visibility to true after loading User Control A
Should I be using events/delegates for notifying the completion of loading User Control A?
Don't load everything in the page event in control b, just put a method on control b to be called. Then add an event to control a which the page consumes, when the event is raised, call the load method on control b.
Edit: SampleCode
Ok so for example, create
a ASPX page
2x user controls
Put both user controls into the aspx page.
<cc:control1 runat="server" id="control_one" />
<cc:control2 runat="server" id="control_two" />
Then in control 1, create a delegate and event.
public delegate void MyCustomEvent (EventArgs args);
public event MyCustomEvent MyEvent;
protected void Page_Load(object sender, EventArgs e)
{
MyEvent(e);
}
So I have the event raised on page load. So you would have your logic in there thats required, when your done, calls MyEvent event.
In the page you want to add a handler for that event so when it's called you can do something with it.
protected override void OnInit(EventArgs e)
{
control_one.MyEvent += new WebUserControl1.MyCustomEvent(control_one_MyEvent);
base.OnInit(e);
}
void control_one_MyEvent(EventArgs args)
{
control_two.MyCustomLoad();
}
So when the page is initialized I add the event handler. In the event handler I call a custom method on the second control to load stuff.
Then in the second control I have:
public void MyCustomLoad()
{
//Stuff only loaded when event is raised and calls this method.
}
So this allows control 1 to load something, say it's done, when the page knows it's done, it can tell control 2 to do something.
Edit: After discussing this with a friend I'll explain what I mean by controlling the order.
You cannot control the order of page-life-cycle events. i.e: You can't have Control A, run through all it's page-life-cycle events, then once it's done, have Control B run through all it's page-life-cycle events.
If you do-away with the page life cycle, you can do a degree, as my example above shows, create a way of controlling the order in which the controls are rendered. By raising an event(s) at certain points when Control A is finished, you can tell Control B to do something.
The intermediate between the two controls is the page which handles the events raised by Control A which calls a method on Control B. You (well you can hack around to do it) can't specifically make Control A tell Control B to do something because that creates a direct dependency between the two controls which is bad.
Yes, the code behind will still run
Events could be useful
But if your controls have a specific dependency on each other, maybe they should just be a single control?
This is a fatally-flawed design. You should design your UI so that it doesn't matter in what order the controls load. The order in which controls load is outside of your control.
To address "Phill's" issue with an Order/Orderlines control pair:
I assume that the Order control was developed because it's useful by itself. I assume that OrderLines was developed to be able to show the line items for a given order displayed by the Order control.
I contend that there should be a single, composite control which combines Order and OrderLines. this control will pass to the OrderLines control, a DataSource consisting of the line items it is to display. This makes OrderLines independent of any other control - it simply displays the data it is told to display. It has no idea where that data came from.
Note that this can extend to a typical grid / detail / detail lines scenario, where you pass the grid a set of orders; when selected, a particular grid row will pass the Order control the selected order; when its' time to display the line items, pass the line items collection of the current order to the OrderLines control, etc.
This leaves each control with nothing to do but the Single job it is Responsible for.
"I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible). "
I would suggest using WebFormsMVP: -
http://webformsmvp.com/
http://wiki.webformsmvp.com/index.php?title=Main_Page
As well as being a good implementation of Model-View-Presenter, one of the most useful features of this framework is its support for Messaging.
In a nutshell, you create Message Types, and your usercontrols (views) can post messages of whichever type you need to a message bus. Other controls can subscribe to messages of a particular type and the WebFormsMVP framework will ensure they are delivered.
This allows you to handle interaction between usercontrols by messaging publish & subscribe, without worrying about which order they load in.
Your only other option is to use dynamic control loading because, as others have pointed out, you can't rely on the order in which ASP.NET loads controls into a container.
On my page I have n-userControls (same control) I need to communicate between them(to be more specific I need to pass one in value) .
I don't want to involve the hosting page for that.
The controls acts as "pagers" and interact with the paged data on the hostin page via events that hosting page is subscribed to.
So when user click on one of the pager and changes it's state, the other control should know about it and change itself accordingly.
I can not use VieState because viewstate is per control and so is the controlstate.
Can I use Session for that? (session is shared and there is only one value that i need to store)
Or maybe there is something better I can use? (no QueryString)
Personally there isn't an "easy" way to do this without doing it through the controlling page, or an event.
From what you are saying what I would envision would be something like this. Assuming two controls A and B that are your pager controls.
The containing page subscribes to the "PageSelectionChanged" event on both controls, in response to that event it updates the data, which you already have, AND it enumerates through all pager controls setting the "Current Page" value.
You already have event plumbing in place for communication from control -> page, use what you already have built.
Why Not Session?
I was asked in the comments if this would be better than session, and the answer is yes, for a number of reasons.
Session information, unless explicitly cleaned up exists for the duration of a users session (typically 20 minutes)
Becase of number 1, you would need to add items to the page, for if(!ispostback) to "clear" the session variables so that the user didn't start on a different page.
Future application growth, session information has to be moved out of process to SQL Server or otherwise to work in a web farm environment, for this I try to avoid it as well.
Using session stores this information in memory on the webserver, although small (4 bytes if integer) it can add up and is un-necessary
Depending on the nature of your updates, you cannot ensure control order with session alone to ensure that 1 control forces an update to all controls.
There are other solutions, the solution similar to the one posted above that does a recursive look at the page, but you have to be careful with that to ensure that you do not get into a looping/endless recursion situation, in addition, if you have a lot of controls on the page, it can add a lot of overhead to constantly loop through everything.
The container page has a property in viewstate that stores the state. When a user clicks on one of the pager, it raises an event that is handled by the container page. This then loops through the n user controls and calls a public method on those controls.
You can build a quick modified version of the Observer Pattern. I would suggest building a manger control on the pages. But if you don't want to modify the page, here is a quick solution.
You can create a static method that will notify all of the same type of controls. By Calling their Update Method. Feel free to pass what ever data you need.
protected void control_event(object sender, EventArgs e)
{
UpdateAllControls(page);
}
public static void UpdateAllControls(Control parent /* can be Page */)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == this.GetType())
((MyType)).Update()
if (c.HasControls())
controls = GetAllControls(controls, t, c);
}
}