I'm working with dynamic fields in ASP.NET due to a very specifc and rigid end-user requirement that would take 2 hours just to explain. Suffice it to say, I can't make the requirement go away.
Anyway, I have a working solution in place; no problems with controls loading, rendering or maintaining their ViewState. This is what my OnLoad looks like:
public void override OnLoad(EventArgs e){
//don't need to check IsPostback, we have to load the controls on every POST
FormDefinition initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
}
In order to implement some biz logic around which dynamic fields are required, disabled or optional, I need to get the posted values (i.e. the ViewState) of my dynamic controls before I can actually add them to the page control hierarchy.
It's sort of a chicken/egg problem I suppose. ASP.NET won't automagically associate ViewState with the proper dynamic control until I've added them all to the page. On the other hand, I can't add these controls to the page until my service layer has applied biz rules that hinge on their current values. I tried to get around this rather unpleasant problem by writing this bit of pseudo-code :
public void override OnLoad(EventArgs e){
FormDefinition initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
if (IsPostBack){
PushControlValuesIntoForm(initialFormDefinition);
var updatedFormDefinition = ServiceLayer.ApplyBizRules(initialFormDefinition);
ReBuildControls(updatedFormDefinition); //remove controls and re-add them
}
}
Unfortunately, when you clear a control and re-add it, the ViewState is lost, even if the control type and ControlID are exactly the same, so this solution is a bust. Any reasonable ideas on how to accomplish what I'm after are welcome!
One way could be to load your controls and then decide if you need form definition to be be updated and if yes then re-initiate page life cycle again. See the below sample code:
public void override OnLoad(EventArgs e){
var updatedFormDef = Context.Items["UpdatedDef"] as FormDefinition;
if (null != updatedFormDef)
{
// Updated form def, rebuild controls
BuildControls(updatedFormDef);
}
else
{
// load initial form def
var initialFormDefinition = ServiceLayer.GetFormDefinition(id);
BuildControls(initialFormDefinition);
// check whether we need to update form def
if (IsPostBack){
PushControlValuesIntoForm(initialFormDefinition);
var updatedFormDefinition = ServiceLayer.ApplyBizRules(initialFormDefinition);
if (null != updatedFormDefinition)
{
// we have to update UI, transfer to self
Context.Items["UpdatedDef"] = updatedFormDefinition;
try
{
Server.Transfer(this.Request.RawUrl, true);
}
catch(ThreadAbortException)
{
// Do nothing
}
}
}
}
Related
I have tried searching around for this, but what I found was mainly for disabling a single input type.
What I want to do is disable every input type on a single page. Everything. Textboxes, checkboxes the whole lot.
I couldnt figure out how to modify the loops I found, which is why I am asking here, beacause it's likely one of you has a piece of code laying around that can do it.
Thank you in advance.
try below if you like to do it in javascript/jquery
$(document).ready(function(){
$('input').attr('disabled','disabled');
});
OR try below if you want in asp.net
ach control has child controls, so you'd need to use recursion to reach them all:
protected void DisableControls(Control parent, bool State) {
foreach(Control c in parent.Controls) {
if (c is DropDownList) {
((DropDownList)(c)).Enabled = State;
}
DisableControls(c, State);
}
}
Then call it like so:
protected void Event_Name(...) {
DisableControls(Page,false); // use whatever top-most control has all the dropdowns or just the page control
} // divs, tables etc. can be called through adding runat="server" property
Summary
I'm having style issues when flipping master pages via a button event in asp.net 4.0. The new master switches, but the css from the old master remains. I don't understand how this could happen as the styles are defined within the head of the old master, and i can clearly see via the markup the new master is being displayed with whats supposed to be a totally different set of styles. Also, viewing source shows all the new css declarations in the head. How can i get this to "refresh" or "reload"?
Some details
I'm implementing a mobile version of my asp.net site. If a mobile device is detected i set a cookie and switch the master page in the preinit to a mobile friendly one. This works fine:
protected virtual void Page_PreInit(Object sender, EventArgs e)
{
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}
I have a "full site" button at the bottom that allows you to flip back and forth between the mobile and desktop view. When clicking it, i change the value in the cookie. Then when the page redirects to itself, the value is checked, and it gives the respective masterpage. This also "works", i can tell the right masterpage is rendering via markup. Except the styles from the mobile version remain even when the desktop master is being displayed. I did the redirect thinking it would prevent this.
// desktop/mobile site toggle button click event
protected void viewMobileButton_Click(Object sender, EventArgs e)
{
HttpCookie isMobileCookie = Cookies.snatchCookie("isMobile");
if (bool.Parse(isMobileCookie.Value))
Cookies.bakeCookie("isMobile", "false");
else
Cookies.bakeCookie("isMobile", "true");
Response.Redirect(Request.RawUrl);
}
This is the first time I've done anything like this, and not sure if i'm even going about it the right way, or how to debug from here. Thanks in advance for any help.
Edit
Ok, so i figured out it's related to the JQuery Mobile Scripts. JQuery Mobile has this way of tying pages together. I don't fully understand it, i think they use it for page transitions, and it's preventing my new CSS from registering. When i turn it off, my masterpage flips fine with css included. I'm looking into a way to turn off JQuery Mobile before my redirect. Note sure how though yet.
The problem ended up being related to JQuery Mobile AJAX for page-transitions. JQuery Mobile does not load the head of the document on additional page requests after the first.
So when i'd switch the mobile master to the desktop master, the head of the document wouldn't load to bring in my styles. There are a few way's this can be fixed:
This way just turns off AJAX altogether, and fixes the problem, but then you can't benefit from it:
<form data-ajax="false">
This is a way to do it problematically, but remind you, it will not work via an event after initialization of JQuery Mobile, so again you can't benefit from it:
$.mobile.ajaxEnabled = false;
The above two solutions i support could work if you redirected through a page first if you have to use an onclick event and an event handler.
A better solution is to add rel="external" to the link to tell JQM it's and outgoing link.
<a href="myself.com?mobile=true" rel="external" >
But because i couldn't run some code i wanted to in order to change the cookie, i had to pass a query string parameter, check it on the preinit, then set the cookie which my page also looks at on the preinit and flips the master.
Here's my full solution below in case someone is out there doing the exact same thing. Note because my website is using aliasing, i had to read Request.RawUrl and parse it myself since the Request.QueryString object did not contain the values i passed.
// reusable function that parses a string in standard query string format(foo=bar&dave=awesome) into a Dictionary collection of key/value pairs
// return the reference to the object, you have to assign it to a local un-instantiated name
// will accept a full url, or just a query string
protected Dictionary<string, string> parseQueryString(string url)
{
Dictionary<string, string> d = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(url))
{
// if the string is still a full url vs just the query string
if (url.Contains("?"))
{
string[] urlArray = url.Split('?');
url = urlArray[1]; // snip the non query string business away
}
string[] paramArray = url.Split('&');
foreach (string param in paramArray)
{
if (param.Contains("="))
{
int index = param.IndexOf('=');
d.Add(param.Substring(0, index), param.Substring(++index));
}
}
}
return d;
}
Then i just use my dictionary object to evaluate and rebuild my url with the opposite mobile value, dynamically setting the href on the toggle link. Some code is obviosuly left out, but for perspective, base._iPage.QueryStringParams hold my dictionary object that was returned, and base._iPage.IsMobile is just a bool property i also have via the page interface i use, that all my pages, and user controls, ect, can talk to.
// get the left side fo the url, without querystrings
StringBuilder url = new StringBuilder(Request.RawUrl.Split('?')[0]);
// build link to self, preserving query strings, except flipping mobile value
if (base._iPage.QueryStringParams.Count != 0)
{
if (base._iPage.QueryStringParams.ContainsKey("mobile"))
{
// set to opposite of current
base._iPage.QueryStringParams["mobile"] = (!base._iPage.IsMobile).ToString();
}
int count = 0;
url.Append('?');
// loop through query string params, and add them back on
foreach (KeyValuePair<string, string> item in base._iPage.QueryStringParams)
{
count++;
url.Append(item.Key + "=" + item.Value + (count == base._iPage.QueryStringParams.Count ? "" : "&" ));
}
}
// assign rebuild url to href of toggle link
viewMobileButton.HRef = url.ToString();
}
Then on my pageinit this is where i actually check, first the quesry string, then the cookie, if neither of those are present, i run my mobile detection method, and set a cookie, and my interface bool property for easy access to conditionals that depends on it.
QueryStringParams = base.parseQueryString(Request.RawUrl);
if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("true") : false)
{
Cookies.bakeCookie("isMobile", "true"); // create a cookie
IsMobile = true;
}
else if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("false") : false)
{
Cookies.bakeCookie("isMobile", "false"); // create a cookie
IsMobile = false;
}
else
{
IsMobile = base.mobileDetection();
}
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}
I'm hoping there is a pattern for verifying when a property is set for a custom control in asp.net.
Considering that there is a page life cycle, we keep having issues where the control can get into an invalid state. The best thing we can do is raise an exception with an instructional message for things like, setting values selected before adding data.
Please note, ideally the component wouldn't rely on things like ordering of when a property is set. Unfortunately I can only move the company to better practices one step at a time. There are too many components to re-write from scratch and is an unrealistic expectation.
That said, here's an example.
We have two properties. SelectedValues which will set the values that match a comma separated list and InsertAll which will insert "All" at the top of a list.
Potential issue: The developer sets the SelectedValues in the Page's PreInit event, but the InsertAll property, if true, will add the "All" value and select it during the control's Init event. The trick is, SelectedValues will directly set the values when set, not later during the life cycle. Which means, when they see the page, they think there is a bug in the component because they didn't set All to be selected, but it is.
Page:
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
listBoxSelection.SelectedValues = "value1,value2";
listBoxSelection.InsertAll = true;
}
Control:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (this.InsertAll)
{
ListBoxItem allItem = new ListBoxItem()
{
Text = "All",
Value = "0"
Selected = true
};
this.Items.Insert(0, allItem);
}
}
How can I ensure that the developer knows they goofed when using this control? This is a simple example, but I see it all the time and in many forms.
I have a dropdown list that pulls data from template table. I have an Add button to insert new template. Add button will brings up jQuery popup to insert new values. There will be a save button to save the new data. On_Save_Click I enter the new data and close the popup.
Here is the proplem:
When I refresh the page, the page entering the values again. So, I get duplicate entries!
Question:
How can I avoid this issue? I check out Satckoverflow and Google, both they suggest to redirect to another page. I don't want to redirect the user to another page. How can I use the same form to avoid this issue? Please help.
You can use viewstate or session to indicate if data already inserted (button pressed).
Something like this:
private void OnbuttonAdd_click()
{
if(ViewState["DataInserted"] != "1")
{
...
// Add new entry...
...
if(data inserted successfully)
{
ViewState["DataInserted"] = "1";
}
}
}
Edit:
public bool DataInserted
{
get
{
if (HttpContext.Current.Session["DataInserted"] == null)
{
HttpContext.Current.Session["DataInserted"] = false;
}
bool? dataInserted = HttpContext.Current.Session["DataInserted"] as bool?;
return dataInserted.Value;
}
set
{
HttpContext.Current.Session["DataInserted"] = value;
}
}
...
private void OnbuttonAdd_click()
{
if(!DataInserted)
{
...
// Add new entry...
...
if(data inserted successfully)
{
DataInserted = true;
}
}
}
The simplest way is to use a post/redirect/get pattern.
Basically, the refresh action for page build with post requires to repost the data. Using this pattern, you will reload the whole page.
With ASP.Net, you have a simple alternative, use an UpdatePanel. This will refresh only part of the page using AJAX. As the page itself is still the result of a GET request, you can refresh the page. And as you use ASP.Net, it's quite easy to integrate.
Finally, you can use a home made AJAX refresh. A combination of jQuery, KnockOut and rest services (for example), can help you to avoid refreshing the full page in benefits of an ajax call.
There is some experience:
Disable Submit button on click (in client side by JavaScript).
change Session['issaved'] = true on save operation at server side and change it on new action to false.
use view state for pass parameters like RecordId (instead of QueryString) to clear on refresh page. i always pass parameter's with Session to new page, then at page load set
ViewState['aaa']=Session['aaa'] and clear Sessions.
...I hope be useful...
Do this it is very easy and effective
Intead of giving IsPostBack in the page load(),please provide inside the button click (To send or insert data)
Call the same page again after reseting all input values
protected void Btn_Reg_Click1(object sender, EventArgs e)
{
try
{
if (IsPostBack)
{
Registration_Save();
Send_Mail();
txtEmail.Text = "";
txtname.Text = "";
Response.Redirect("~/index.aspx");
}
}
catch (Exception) { }
}
You won't see any server messages after refreshing the page..
I'm building a custom server control derived from CompositeControl.
The control contains a number of child controls (Labels, DropDownList, ListSearchExtender, etc). All of them reside inside an UpdatePanel.
The control also publishes events. For this I added two Properties: EnableCallBacks and CallBacksAsPostBacks. Those two properties should configure the postback behaviour of the update panel.
Any ideas what a correct implementation should look like?
I'm getting some problems with the way I implemented it:
the PostBackTrigger does not always get rendered into the output html.
Having both Triggers.Add(trigger) and Controls.Add(_updatePanel) inside the CreateChildControls methods leads to the PostBackTrigger always being rendered, even if I remove it later on (e.g. within RenderControl() or PreRender()). If I do not add the trigger here but later on, then it does never get rendered. At this stage I do not have the correct values of all my properties yet (e.g. EnableCallBacks and CallBacksAsPostBacks).
It is not possible to place the statement of Controls.Add(_updatePanel) inside the RenderControl-method due to it beeing too late for AJAX (latest ist PreRender() otherwise I get an exception).
Ideally I would instantiate all controls in CreateChildControls() and then set their values later on in e.g. PreRender or RenderControl
Having both statements in the PreRender method results in, that the trigger gets rendered corretly depending on my settings in the containing page, but I don't get the DropDownList populated with its data from the ViewState (on call/postbacks).
protected override void CreateChildControls()
{
base.CreateChildControls();
_updatePanel = new UpdatePanel();
_updatePanel.ID = "FprDropDownList_UpPnl";
_updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
_label = new FprLabel();
_label.ID = "FprDropDownList_Lbl";
_updatePanel.ContentTemplateContainer.Controls.Add(_label);
_dropDownList = new DropDownList();
_dropDownList.ID = "FprDropDownList_Ddl";
_dropDownList.CssClass = "fprDropDownList";
_dropDownList.AutoPostBack = true;
_updatePanel.ContentTemplateContainer.Controls.Add(_dropDownList);
_label.AssociatedControlID = _dropDownList.ClientID;
_listSearchExtender = new ListSearchExtender();
_listSearchExtender.ID = "FprDropDownList_Lse";
_listSearchExtender.TargetControlID = _dropDownList.ClientID;
_listSearchExtender.PromptPosition = ListSearchPromtPosition;
_listSearchExtender.PromptCssClass = "fprListSearchExtender";
_updatePanel.ContentTemplateContainer.Controls.Add(_listSearchExtender);
_ddlPostBackTrigger = new PostBackTrigger();
_ddlPostBackTrigger.ControlID = _dropDownList.ClientID;
//_updatePanel.Triggers.Add(_ddlPostBackTrigger);
Controls.Add(_updatePanel);
}
protected override void OnPreRender(EventArgs pE)
{
if (EnableCallBacks)
{
_dropDownList.SelectedIndexChanged += DropDownList_SelectedIndexChanged;
}
if (EnableCallBacks && CallBacksAsPostBacks)
{
_updatePanel.Triggers.Add(_ddlPostBackTrigger);
}
//Controls.Add(_updatePanel);
base.OnPreRender(pE);
}
public override void RenderControl(HtmlTextWriter pWriter)
{
// Do some things... like set Enable-state of child controls
base.RenderControl(pWriter);
}
You should add your dynamic controls in PreInit for the events to fire properly.
Use this event for the following:
Check the IsPostBack property to
determine whether this is the first
time the page is being processed. The
IsCallback and IsCrossPagePostBack
properties have also been set at this
time.
Create or re-create dynamic
controls.
Set a master page
dynamically.
Set the Theme
property dynamically.
Read or set
profile property values.