We have a website using Webforms. We want to use a custom PageStatePersister which saves the viewstate into a redis cache (see code below). On page level we override the PageStatePersister property to use our PageStatePersister. All works fine so far, but when we have a control within another control, we encounter a few issues.
public class RedisPageStatePersister : PageStatePersister
{
const string ViewStateFieldName = "__VIEWSTATEKEY";
const string ViewStateKeyPrefix = "ViewState_";
public RedisPageStatePersister(Page page) : base(page)
{
}
public override void Load()
{
var key = Page.UniqueID;
// The cache key for this viewstate is stored in a hidden field, so grab it
string viewStateKey = Page.Request.Form[ViewStateFieldName] as string;
// Grab the viewstate data using the key to look it up
if (viewStateKey != null)
{
var database = RedisConnectorHelper.Connection.GetDatabase(1);
var viewstate = database.StringGet(viewStateKey);
if (!viewstate.IsNull)
{
Pair p = (Pair)StateFormatter.Deserialize(database.StringGet(viewStateKey));
ViewState = p.First;
ControlState = p.Second;
}
}
}
public override void Save()
{
string viewStateKey = Page.Request.Form[ViewStateFieldName] as string;
if (viewStateKey == null)
{
viewStateKey = ViewStateKeyPrefix + Guid.NewGuid().ToString();
}
// Put viewstate data on writer
var viewStateSerialized = StateFormatter.Serialize(new Pair(base.ViewState, base.ControlState));
// Store the viewstate's key in a hidden field, so on postback we can grab it from the cache
Page.ClientScript.RegisterHiddenField(ViewStateFieldName, viewStateKey);
// Get the Redis database
var database = RedisConnectorHelper.Connection.GetDatabase(1);
// Save viewstate to the database
database.StringSet(viewStateKey, viewStateSerialized, TimeSpan.FromMinutes(15));
}
}
For example:
When using ListViews. We have a ListView with DataKeys within another ListView. On postback, the datakeys are not restored for the nested ListView
<asp:ListView runat="server" ID="lvOuter" DataKeyNames="These,Keys,Work">
<ItemTemplate>
<asp:ListView runat="server" ID="lvInner" DataKeyNames="These,Keys,Dont,Work">
<ItemTemplate></ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
We use dynamic creates TabPanel with GridView within. The GridView within loose their datakeys values on postback
The problem only seems to encounter for DataKeys, the use of HiddenField for example is no problem.
I also tried to use the SessionPageStatePersister, since this is a built-in persister, but here too the problem occurs
Below a code snippet where dummy data is bound to a listview and its inner listview. The error occurs when clicking on the button and trying to retrieve the datakey values of the inner listview. The property "DataKeys" is an empty list, so a IndexOutOfRangeException is thrown.
public partial class InnerListViewTest : Page
{
public class Outer
{
public List<Inner> Inner { get; set; }
public string Key1 { get; set; }
public string Key2 { get; set; }
}
public class Inner
{
public string InnerKey1 { get; set; }
public string InnerKey2 { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var data = new List<Outer>();
for (int i = 0; i < 5; i++)
{
data.Add(new Outer()
{
Inner = new List<Inner>()
{
new Inner() {
InnerKey1 = "InnerData1_" + i,
InnerKey2 = "InnerData2_" + i
},
new Inner() {
InnerKey1 = "InnerData3_" + i,
InnerKey2 = "InnerData4_" + i
},
new Inner() {
InnerKey1 = "InnerData5_" + i,
InnerKey2 = "InnerData6_" + i
}
},
Key1 = "Data1_" + i,
Key2 = "Data2_" + i
});
}
lvOuter.DataSource = data;
lvOuter.DataBind();
}
}
protected void lvOuter_ItemDataBound(object sender, ListViewItemEventArgs e)
{
var outerData = e.Item.DataItem as Outer;
var lvInner = e.Item.FindControl("lvInner") as ListView;
lvInner.DataSource = outerData.Inner;
lvInner.DataBind();
}
protected void Unnamed_Click(object sender, EventArgs e)
{
foreach (ListViewItem outerItem in lvOuter.Items)
{
var lvInner = outerItem.FindControl("lvInner") as ListView;
foreach (ListViewItem innerItem in lvInner.Items)
{
var innerKey1 = lvInner.DataKeys[innerItem.DataItemIndex]["InnerKey1"];
var innerKey2 = lvInner.DataKeys[innerItem.DataItemIndex]["InnerKey2"];
}
}
}
}
The problem is probably with the pagepersister, but i cant seem to find out why!
I already found my own answer! The PageStatePersister had to be enabled using a PageAdapter instead of overriding the property of the Page!
Related
I have a DataGridView whose DataSource is a DataTable with five columns. If I attempt to access a column's ReadOnly property, like so:
datagridview.Columns[1].ReadOnly = true;
It throws a NullReferenceExcpetion.
I understand this is due to how the framework manages its auto generated columns, as noted by the answer to this question.
My question is: How do I make a column(s) readonly when the data source is auto generated?
Can't really say why it's not working, but a simple test with this code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = GenerateData();
dataGridView1.Columns[0].ReadOnly = true;
}
private List<DataSourceTest> GenerateData()
{
return new List<DataSourceTest>()
{
new DataSourceTest(1, "A"),
new DataSourceTest(2, "B"),
new DataSourceTest(3, "C"),
new DataSourceTest(4, "D"),
new DataSourceTest(5, "E"),
new DataSourceTest(6, "F"),
};
}
}
public class DataSourceTest
{
public DataSourceTest(int id, string name) { ID = id; Name = name; }
public int ID { get; set; }
public string Name { get; set; }
}
and making the gridview EditMode set to EditOnEnter so we can easily check if it's readonly or not, shows that it does the job well.
But if you still have issues, the best bet is to use an event, and the closest event for your question is the DataBindingComplete that will fire after the binding is done, so on that time, you will have full access to all your columns as they already bind to the gridview object.
double click on the event in the GridView control and add your readonly setter:
private void dataGridView1_DataBindingComplete(
object sender, DataGridViewBindingCompleteEventArgs e)
{
dataGridView1.Columns[0].ReadOnly = true;
}
In true TEK fashion, I figured out a solution to my own question:
To do this, you need to make use of the ColumnAdded event
datagridview.ColumnAdded += dataGridView_ColumnAdded;
Then in the event, you can check a column by name:
private void dataGridView_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
if (e.Column is DataGridViewColumn)
{
DataGridViewColumn column = e.Column as DataGridViewColumn;
column.ReadOnly = true;
if (column.Name == "first_name")
{
column.ReadOnly = false;
}
}
}
Make column read-only when column has been generated
private void Form1_Load(object sender, EventArgs e)
{
List<Student> allStudent = new List<Student>();
for (int i = 0; i < 10; i++)
{
allStudent.Add(new Student { Name = "Student" + i, Roll = i + 1 });
}
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = allStudent;
//Edited to show column count
MessageBox.Show("Column count is " + dataGridView1.Columns.Count);
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
column.ReadOnly = true;
}
}
public partial class Student
{
public string Name { get; set; }
public int Roll { get; set; }
}
I have these checkboxes those are created dynamically using C#. These are HTMLInputCheckboxes. They are kept under a panel called "panel_seat".
I want to get the values from these checkboxes to post them in database. How to do this? Either creating them as checkbox list or group?
If so please give me some code references please.
Jquery code:
$(document).ready(function () {
$("#btn_check").click(function () {
var str = "";
x = $("#frm").serializeArray();
str = JSON.stringify(x);
$('#<%=hdnSelectedTicket.ClientID %>').val(str);
});
});
Asp.net:
<asp:HiddenField ID="hdnSelectedTicket" runat="server" />
C# code: Using special dll for Json
using System.Web.Script.Serialization;
using Newtonsoft.Json;
namespace TestApp.components.seatfromdb
{
public class Test
{
public string name { get; set; }
public string value { get; set; }
}
public partial class seatfromdb : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btn_submit_Click(object sender, EventArgs e)
{
List<Test> myDeserializedObjList = (List<Test>)Newtonsoft.Json.JsonConvert.DeserializeObject(hdnSelectedTicket.Value, typeof(List<Test>));
int count = myDeserializedObjList.Count;
string[] chkarr = new string[count-4];
int j=0;
for (int i = 0; i < count; i++)
{
if (myDeserializedObjList[i].name.Substring(0, 6) == "check_")
{
chkarr[j] = myDeserializedObjList[i].name.Substring(6,1);
j++;
}
}
}
}
}
Checkbox group or list is not needed. I Used hidden field which carries the (stringified checkbox values) Json. charr[] array can be put into the database as by our need either as bytes or anything else.
Not found anything that directly answers my problem, so hopefully someone can shed some light on it.
I have two Composite Controls, lets call them BudgetTable and BudgetTableItem, where BudgetTable contains a list of BudgetTableItem.
So far everything works so long as I add new RowItems in the HTML View - when I add one programmatically it appears, but doesn't survive postback.
I can only assume I'm doing something boneheaded with ViewState, and would appreciate any pointers!
Thanks in advance.
The HTML:
<hea:BudgetTable runat="server" ID="btTest" MaximumFundingAvailable="7000" CssClass="bob">
<Items>
<hea:BudgetTableItem runat="server" Description="Test1" />
<hea:BudgetTableItem runat="server" Description="Test2" />
<hea:BudgetTableItem runat="server" Description="Test3" />
</Items>
</hea:BudgetTable>
The code behind:
[PersistenceMode(PersistenceMode.InnerProperty)]
[ParseChildren(true)]
public class BudgetTableItem : CompositeControl {
private TextBox _description = new TextBox();
private TextBox _cost = new TextBox();
private CheckBox _heaFunded = new CheckBox();
/*public delegate void AddRow();
public delegate void RemoveRow(BudgetTableItem item);
public event AddRow AddNewRow;
public event RemoveRow RemoveNewRow;*/
public string ItemName {
get {
var viewstate = ViewState["ItemName"];
return (viewstate is string) ? (string)viewstate : "default";
}
set {
ViewState["ItemName"] = value;
}
}
public bool ShowRemoveRow {
get {
var viewstate = ViewState["ShowRemoveRow"];
return (viewstate != null && viewstate is bool) ? (bool)viewstate : false;
}
set {
ViewState["ShowRemoveRow"] = value;
}
}
public bool ShowAddRow {
get {
var viewstate = ViewState["ShowAddRow"];
return (viewstate != null && viewstate is bool) ? (bool)viewstate : false;
}
set {
ViewState["ShowAddRow"] = value;
}
}
public string Description {
get {
return _description.Text;
}
set {
_description.Text = value;
}
}
public decimal Cost {
get {
decimal cost =0;
decimal.TryParse(_cost.Text, out cost);
return cost;
}
set {
_cost.Text = value.ToString();
}
}
public bool HeaFunded {
get {
return _heaFunded.Checked;
}
set {
_heaFunded.Checked = value;
}
}
protected override void CreateChildControls() {
Controls.Clear();
HtmlTableCell tableCell1 = new HtmlTableCell();
HtmlTableCell tableCell2 = new HtmlTableCell();
HtmlTableCell tableCell3 = new HtmlTableCell();
HtmlTableCell tableCell4 = new HtmlTableCell();
tableCell1.Attributes.Add("class", "col1");
tableCell2.Attributes.Add("class", "col2");
tableCell3.Attributes.Add("class", "col3");
tableCell4.Attributes.Add("class", "col4");
tableCell1.Controls.Add(_description);
tableCell2.Controls.Add(_cost);
tableCell3.Controls.Add(_heaFunded);
/*if (ShowAddRow || ShowRemoveRow) {
Button addNewButton = new Button();
addNewButton.Text = (ShowAddRow) ? "Add Row" : "Remove";
if (ShowAddRow) {
addNewButton.Click += new EventHandler(addNewButton_Click);
}
if (ShowRemoveRow) {
addNewButton.Click += new EventHandler(removeButton_Click);
}
tableCell4.Controls.Add(addNewButton);
}
else{*/
tableCell4.InnerHtml = " ";
//}
Controls.Add(tableCell1);
Controls.Add(tableCell2);
Controls.Add(tableCell3);
Controls.Add(tableCell4);
}
/*void addNewButton_Click(object sender, EventArgs e) {
if (AddNewRow != null) {
AddNewRow();
}
}*/
/*void removeButton_Click(object sender, EventArgs e) {
if (RemoveNewRow != null) {
RemoveNewRow(this);
}
}*/
protected override void RecreateChildControls() {
EnsureChildControls();
}
public override void RenderBeginTag(HtmlTextWriter writer) {
writer.Write("<tr>");
}
public override void RenderEndTag(HtmlTextWriter writer) {
writer.Write("</tr>");
}
}
Controls, custom or otherwise that require a ViewState and wish to receive events should be created in Init.
Http is stateless. Your entire page with all its controls is recreated on every postback. Controls that you add in the design view, are added to your designer.cs file, and created for you. When you add controls yourself, you must write code to recreate the controls on every PostBack that occurs later.
You can use the session to remember which controls were added by code and add them on later PostBacks.
Here are my requirements:
I have a dropdown list and text box (appName and profile).
I want to take the values from the dropdown and text box and add them to a table (or a control like gridview that renders into a
table)
At some point I want to be able to loop through the table and submit the values to a db.
My problem:
The postback caused by the onClick even is casing the table to only show the last value entered, and doesn't retain any of the previous
values.
Notes:
I tried to work arond this using a datalist bound to a datagrid, but no luck.
Code:
protected void addAppButton_Click(object sender, EventArgs e)
{
DropDownList appList = (DropDownList)newEntryFV.FindControl("appList");
TextBox profileTextBox = (TextBox)newEntryFV.FindControl("profileTextBox");
addAppsToTable(appList.SelectedValue.ToString(), profileTextBox.Text.ToString());
}
private void addAppsToTable(string appName, string profileName)
{
Table appsTable = (Table)newEntryFV.FindControl("appTable");
TableRow newRow = new TableRow();
TableCell appNameCell = new TableCell();
TableCell profileCell = new TableCell();
appNameCell.Text = appName;
profileCell.Text = profileName;
newRow.Cells.Add(appNameCell);
newRow.Cells.Add(profileCell);
appsTable.Rows.Add(newRow);
}
Code that solved my problem:
[Serializable]
public class securityApps
{
public string secAppID { get; set; }
public string secAppName { get; set; }
public string secProfile { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
BindApps();
}
protected void addAppButton_Click(object sender, EventArgs e)
{
DropDownList appList = (DropDownList)newEntryFV.FindControl("appList");
TextBox profileTextBox = (TextBox)newEntryFV.FindControl("profileTextBox");
addAppsToListVS(appList.SelectedValue.ToString(), appList.SelectedItem.Text.ToString(), profileTextBox.Text.ToString());
BindApps();
}
private void addAppsToListVS(string appID, string appName, string profile)
{
securityApps secApp = new securityApps();
secApp.secAppID = appID;
secApp.secAppName = appName;
secApp.secProfile = profile;
((List<securityApps>)ViewState["appsListVS"]).Add(secApp);
}
// Binds apps to Grid View
private void BindApps()
{
GridView appsListGV = (GridView)newEntryFV.FindControl("appsListGV");
if (ViewState["appsListVS"] != null)
{
appsListGV.DataSource = (List<securityApps>)ViewState["appsListVS"];
appsListGV.DataBind();
}
else
{
List<securityApps> appsListVS = new List<securityApps>();
ViewState["appsListVS"] = appsListVS;
}
}
How about storing a List of objects (they could even be simple key value pairs) in the ViewState. You can use that data as the DataSource for a GridView. I think that's the simplest way to go. If you need more details, let me know.
Edits-- Your solution above looks good-- I might just make it a little easier by setting up a property for your ViewState values..
List<securityApps> AppsListVS{
get
{
if(ViewState["AppListVS"] == null
this.AppListVS = new List(securityApps)();
return (List<securityApps>)ViewState["AppListVS"];
}
set
{
ViewState["AppListVS"] = value;
}
}
How to setup a multi value dropdownlist from a list collection...
Datasource: Listcollection which contains ColorCode and Description...
how to I setup a dropdown with Colorcode-Description like ORG-orange...
then how to capture these selected value as colorcode only by removing description for update purpose...
Now I am doing like this...
ddl.datesource=list<datasetvalues> // ...contains (colorcode, description)
ddl.DataTextField = "ColorCode";
ddl.DataValueField = "ColorCode";
ddl.databind();
then selected value should be like this...
ddlcolor.Items.FindByValue((DataBinder.Eval(formView1.DataItem,
"colorCode").ToString())).Selected = true;
for update:
ClassA.Color= ddl.selectedvalue();
Now what I need change to in the above code to get the combination of both..otherwise i need have textbox for description which syncs with ddl..which is bit complex for my level of programming...thanks..
There are a couple of solutions as per my knowlege.
1) You can concatenate the text like : Code + "-" + Value, while preparing the list using a For/Foreach... loop
2) If it is permitted as per your project, you may also consider overriding the string inside the entity but the selected value will be with a hyphenated(with a - inbetween code & value) string, which you need to string split in the code behind.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
List<CodeValue> colors = new List<CodeValue> {new CodeValue{Code="",Value="Select"},new CodeValue{Code="RD",Value="Red"},
new CodeValue{Code="BL",Value="Blue"}};
ddlColors.DataSource = colors;
ddlColors.DataBind();
}
}
protected void btnClick_Click(object sender, EventArgs e)
{
var item = ddlColors.SelectedValue;
var code = item.Split('-');
}
}
class CodeValue
{
public string Code { get; set; }
public string Value { get; set; }
public override string ToString()
{
return this.Code + "-" + this.Value;
}
}