Setting AspxComboBox.SelectedIndex not working after using LinqDataSource.Selecting. Any ideas? - asp.net

I've posted this on the DevExpress forums as well, but you can't beat stackoverflow for good answers.
I seem to be having a problem with the DevExpress AspxComboBox control.
I'm using DevExpress 9.1.11 controls with Visual Studio 2008.
Here are my controls:
<asp:LinqDataSource ID="ContactsDataSource" runat="server"
ContextTypeName="DAL.MorrisDataContext"
Select="new (Id, FullName)"
TableName="Contact">
</asp:LinqDataSource>
<dxe:ASPxComboBox ID="SignerComboBox" runat="server" ToolTip="Select a Contact to use."
AutoPostBack="True" DataSourceID="ContactsDataSource" TextField="FullName"
ValueField="Id" ValueType="System.String" Width="140px" SelectedIndex="0">
</dxe:ASPxComboBox>
I am handling the Selecting event of the LinqDataSource to filter the list of items used in the ComboBox as follows:
Protected Sub ContactsDataSource_Selecting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.LinqDataSourceSelectEventArgs) Handles ContactsDataSource.Selecting
Dim Contacts As IList(Of Contact) = iEntity.Contacts.ToList
e.Result = Contacts
End Sub
iEntity.Contacts is just a LINQtoSQL object containing a child collection of LINQtoSQL objects known as "Contacts". All of this works perfectly.
The problem comes when I try to add a new Contact to the collection of contacts for the Entity and then try to update the ComboBox to reflect the addition as follows:
Private Sub SignerUpdate()
Dim m_Last = SignerContactLastTextBox.Text
Dim m_First = SignerContactFirstTextBox.Text
If m_Last <> "" OrElse m_First <> "" Then
Dim m_Middle = SignerContactMiddleTextBox.Text
Dim m_Suffix = SignerContactSuffixTextBox.Text
Dim m_ContactIndex As Int32 = SignerComboBox.SelectedIndex
Dim m_Contact As Contact = New Contact
If m_Last <> "" Then m_Contact.LastName = New Identifier With {.Value = m_Last}
If m_First <> "" Then m_Contact.FirstName = New Identifier With {.Value = m_First}
If m_Middle <> "" Then m_Contact.MiddleName = New Identifier With {.Value = m_Middle}
If m_Suffix <> "" Then m_Contact.Suffix = New Identifier With {.Value = m_Suffix}
iEntity.Contacts.Add(m_Contact)
SignerComboBox.DataBind()
SignerComboBox.SelectedIndex = SignerComboBox.Items.Count - 1
'SignerComboBox.SelectedIndex = 3
Else
SignerContactSuffixTextBox.ErrorText = "Must have First or Last name."
SignerContactSuffixTextBox.IsValid = False
End If
End Sub
The key lines being these:
iEntity.Contacts.Add(m_Contact)
SignerComboBox.DataBind()
SignerComboBox.SelectedIndex = SignerComboBox.Items.Count - 1
'SignerComboBox.SelectedIndex = 3
The first line adds a new Contact to the collection.
The second line rebinds the ComboBox which results in the Selecting event from above being fired and getting the updated list including the new contact.
Everything is still working fine. I can put a watch on SignerCombBox.Items and see that the new item is there after the bind. It was not there prior to the bind.
All is good.
Then we come to the third line. This is where we have a problem. I expect this to select the last item in the list of items. The results of SignerComboBox.Items.Count is correct. We get a count back that does include the new item.
What doesn't work, is that when we try to use that result to set the SelectedIndex property. It will not accept it.
The fourth line is where I've tried to do everything manually to make sure I'm not losing my mind. Turns out I'm not. As you can see this line is commented out.
Even though I have added another line to Items and I can see that line in Items. It can not be set as selected using SelectedIndex. When I try to set it, it just remains set to whatever it was set to previously (in my case 0). I've also tried setting it with SignerComboBox.SelectedItem = SignerComboBox.Items(SignerComboBox.Items.Count - 1) or SignerComboBox.SelectedItem = SignerComboBox.Items(3) to no avail.
So, for example. With my test data I start out with 3 items in the collection. 0 - 2 are in Items.
At this point I can set SignerComboBox.SelectedIndex to 0, 1, or 2. It works just fine.
I then add another item.
Rebind to get the updated Items.
Check the Items. They now contain 4 items. 0, 1, 2, and 3
Set SignerComboBox.SelectedIndex = 3. It will not work. It will remain 0. Even though I know that item is in the Items list of the control.
Set SignerComboBox.SelectIndex = 2. Works just fine. It will be set to 2.
It's as if it's deciding whether or not it's a valid index based on something other than it's own Items list.
I even tried inserting the new one higher in the list and using an index that had already existed. Once that had been done though, that index would not be accepted.
I really don't understand it. I'm pretty sure it must be a bug. Even that doesn't make much sense to me though, because it seems like the kind of bug that would have been reported about a million times over already. So my only guess is that perhaps it has something to do with the fact that I'm handling the Selecting event of the DataSource and giving it a custom result. I don't see why that would matter, but I can't see how anything else is the least bit out of the ordinary.
I've been pounding on this problem for a while now and I'm out of ideas. So any help would be greatly appreciated.
Thanks,
Tory
EDIT:
I thought maybe I could phrase the question a bit more succintly.
Here's what it comes down to:
Bind a combobox to a LinqDataSource based on a LinqToSql object that
is related as a child to another LinqToSql object.
Create an instance of the parent object.
Use the LinqDataSource.Selecting event to use the contents of the
parent object instance's child object collection, as the results of
the LinqDataSource in the form of an IList(Of ChildObject).
Show the contents of the ComboBox.
Add a new Child object to the child object collection of the parent
object instance.
Databind the ComboBox again in order to pick up the newly added
member of the collection.
Attempt to set the SelectedIndex of the ComboBox to the index of the
newly created member of it's contents, using something like this:
SignerComboBox.SelectedIndex = SignerComboBox.Items.Count - 1
You will not be able to.
It will show up in the ComboBox.Items list, but you will not be able
to assign it's index to the SelectedIndex property.

Related

Can you access the ID of a Control added to an asp page in code-behind

I am adding nodes to a Treeview in an asp via code-behind. Is there a way of getting the id of the node after I've added it, example code:
e.Node.ChildNodes.Add()
How can I grab the id of the Node I'm adding (or added)? Just to be clear I'm trying to access the id that will be on the item on the page and could be used by client-code.
Code snippet of adding the Nodes:
Private Sub navTree_TreeNodePopulate(sender As Object, e As TreeNodeEventArgs) Handles navTree.TreeNodePopulate
Dim TestNode As New TreeNode
TestNode.Text = "Test"
e.Node.ChildNodes.Add(TestNode)
but .add has no return value
In VB, each TreeNode has a collection of child nodes in its ChildNodes property. The Add method returns void, so that's of no help in identifying the new node from your code behind. Microsoft's docs describe a TreeNode's Value property as "a non-displayed value used to store any additional data about the node, such as data used for handling postback events", and give an example of using it to store an ID (follow the link below to see their example)
Something like this might work in your case:
//to add the node
Private Sub navTree_TreeNodePopulate(sender As Object, e As TreeNodeEventArgs) Handles navTree.TreeNodePopulate
Dim TestNode As New TreeNode
TestNode.Text = "This is a test node."
TestNode.Value= 123 //your data's ID
e.Node.ChildNodes.Add(TestNode)
//to find the node by its ID later you'll have to loop through the collection and look for `Value == 123`
More info:
https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.treenode.value?view=netframework-4.8

User text box input is rewritten by default value

Consider the following .aspx page:
Title:
<asp:TextBox runat="server" ID="TitleTB"></asp:TextBox>
Details:
<asp:TextBox runat="server" ID="DetailsTB"></asp:TextBox>
<asp:Button runat="server" ID="Btn" Text="Submit" OnClick="Btn_click"/>
Note that I minimized the code to be legitimate so a lot of lines are missing (irrelevant lines, <br/> for example).
In the C# file, I usually post the details to the database (inserting them), but if I have a certain field in my query string (to_edit, per se) I need to update the, already existing, record.
Obviously, this task is overall simple. The thing is, that when that field is included, I initially (in the Page_Load event) set the Title and the Details fields to the values already found in the record (so the user won't have to input from zero, he'd have the ones he already entered to edit).
A debug would show, though, that when I post these back to the database (UPDATE query, which looks a bit like this UPDATE Table SET Title = #Title, Details = #Details WHERE ID=#ID, where I checked #ID - which is completely valid. #Title corresponds to TitleTB.Text and #Details to DetailsTB.Text, both added with SqlCommand.AddWithValue()) that DetailsTB.Text and TitleTB.Text are, for some reason, the same as I assigned them in the Page_Load although I deleted the whole text box content in my browser and refilled it with a different string.
Here are chosen parts of my Code Behind:
//A part of my Page_Load()
//reader is an SqlDataReader, the connection is valid, the values here are valid, and the output is as planned.
TitleTB.Text = (string)reader["Title"];
DetailsTB.Text = (string)reader["Details"];
And up to now, everything seems fine.
//Relevant parts of Btn_click()
cmd.Connection = conn; //valid connection
cmd.CommandText = "UPDATE QuestionsTable SET Title = #Title, Details = #Details WHERE ID=#ID";
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Title", TitleTB.Text);
cmd.Parameters.AddWithValue("#Details", DetailsTB.Text);
cmd.Parameters.AddWithValue("#ID", Request.QueryString["to_edit"]);
conn.Open();
int affected = cmd.ExecuteNonQuery(); //affected is now 1, as expected.
conn.Close();
//There's a redirection over here.
But in the code shown above, TitleTB.Text and DetailsTB.Text are the same, it's not that the update query doesn't work. It's that, for some reason, the text boxes values won't change even when I clearly change them on my browser.
Any idea what could happen?
EDIT:
One remark is that when I use the OnClientClick event to alert the values (and then returning true, it was only for a test) - the values are identical to what I typed (not to the default value).
One of the possible reasons for such behavior is that you forgot to check if Page_Load is caused by a post back and you overwrite text both values in both get and post.
Just try to add an obvious condition:
//A part of my Page_Load()
//reader is an SqlDataReader, the connection is valid, the values here
if ( !this.IsPostback )
{
TitleTB.Text = (string)reader["Title"];
DetailsTB.Text = (string)reader["Details"];
}
Note that control values are stored in the viewstate and thus there is no need to update them during consecutive post backs.
Wrap your Page_Load event code in If(!IsPostBack){ }. The postback property of the button is refreshing the page with default value.
The code should be as follows -
If(!IsPostBack) {
//A part of my Page_Load()
//reader is an SqlDataReader, the connection is valid, the values here are valid, and the output is as planned.
TitleTB.Text = (string)reader["Title"];
DetailsTB.Text = (string)reader["Details"];
}
A good read may be helpful - http://www.codeproject.com/Articles/811684/Understanding-The-Complete-Story-of-Postback-in-AS

Way to encapsulate methods from default.aspx.vb page to another class

This question is about structure :
I have a Default.aspx page which holds references to (XML)services, and handles innerHTML of HTML objects. The ammount of buttons is based on services output.
Since this is an long and complex algorithm, I would like to encapsulate this in another class, to divide it into smaller and more readable chunks of code.
The problem is I do not know what the best option is, should I copy the reference of the used objects(service as well as the HTML items) into the new class ?
Since the ammount and origin of items it does not look to my like an elegant option.
I searched on the internet but could not find anything that suits this(I would think) common situation
This is the function I would like to transfer to another class. Currently it is in Default.aspx
and uses rep(ort)Service,defaultPath,path,selectionScreen and Image2 objects to draw the menu dynamically.
''' <summary>
''' Dynamically builds the square menu where the reports can be selected from.
''' It is based on the number of reports available
''' Show list of available reports on reporting server as HyperLinks
''' </summary>
''' <remarks></remarks>
Private Sub BuildReportSelectionArea(Optional path As String = "")
Dim items As CatalogItem() = repService.ListChildren(path, False)
Dim items2 As CatalogItem() = repService.ListChildren(path, False)
'Ensure that folders are shown first
Dim maxFolder = 0
For i = 0 To items.Count - 1 Step 1
If (items(i)).TypeName = "Folder" Then
items(i) = items2(maxFolder)
items(maxFolder) = items2(i)
maxFolder += 1
End If
' Some other code
End Sub
'TODO :Ensure the alfabetical order is preserved
Next
I would first generally comment on the code:
this means you are 2 times accessing the service, but the second array is later used to "sort" the items catalogItem, it seems a waste of resources to call the service twice
Dim items As CatalogItem() = repService.ListChildren(path, False)
Dim items2 As CatalogItem() = repService.ListChildren(path, False)
Reordering you could simply achieve using
Dim items As New List(Of CatalogItem)(RepService.ListChildren(path, False))
items.Sort(Function(item1 As CatalogItem, item2 As CatalogItem)
If String.Equals(item1.TypeName, item2.TypeName) Then
Return item1.FileName.CompareTo(item2.FileName)
End If
If item1.TypeName = "Folder" Then
Return -1
End If
Return 1
End Function)
which would sort by folders first, then by filename (you might have to update some of your properties to match)
you could further extract by either creating a module or a shared class that accepts the repService as an attribute and the path, and returns your output code
though creating a user control / webpart so you could add this functionality to each page you would like, would be a very good option as well, and the generally accepted way to refactor complex code...

Setting values when a user updates a record using Entity Framework 4 and VB.NET in ASP.NET application

this is my first post, hopefully I'm following the rules!
After a few weeks of banging my head against brick walls and endless hours of internet searches, I have decided I need to make my own post and hopefully find the answers and guidance from all you experts.
I am building an ASP.NET application (in Visual Studio 2010) which uses an SQL 2008 database and Entity Framework 4 to join the two parts together. I designed the database as a database project first, then built the Entity Model as a class project which is then referenced in the main ASP.NET project. I believe this is called Database First in Entity Framwork terms?
I'm fairly new to Web Apps (I mainly develop WinForms apps) and this is my first attempt at using Entity Framwork.
I can just about understand C# so any code samples or snippets you might supply would be preferred in VB if possible.
To give some background on where I am so far. I built a regsitration page called register.aspx and created a wizard style form using ASP:Wizard with ASP:TextBox, ASP:DropdownList and ASP:CheckBoxList it's all laid out inside a HTML table and uses CSS for some of the formatting. Once the user gets the the last page in the wizard and presses "Finish" I execute some code in the VB code behind file as follows:
Protected Sub wizardRegister_FinishButtonClick(sender As Object, e As System.Web.UI.WebControls.WizardNavigationEventArgs) Handles wizardRegister.FinishButtonClick
Dim context As New CPMModel.CPMEntities
Dim person As New CPMModel.Person
Dim employee As New CPMModel.Employee
Dim newID As Guid = Guid.NewGuid
With person
.ID = newID
.UserID = txbx_UserName.Text
.Title = ddl_Title.SelectedValue
.FirstName = txbx_FirstName.Text
.MiddleInitial = txbx_MiddleInitial.Text
.FamilyName = txbx_FamilyName.Text
.Gender = ddl_Gender.SelectedValue
.DOB = txbx_DOB.Text
.RegistrationDate = Date.Now
.RegistrationMethod = "W"
.ContactMethodID = New Guid(ddl_ContactMethodList.SelectedValue)
.UserName = txbx_UserName.Text
If Not (My.Settings.AuthenticationUsingAD) Then
.Password = txbx_Password.Text ' [todo] write call to salted password hash function
End If
.IsRedundant = False
.IsLocked = False
End With
context.AddToPeople(person)
With employee
.ID = newID
.PayrollNumber = txbx_PayrollNumber.Text
.JobTitle = txbx_JobTitle.Text
.DepartmentID = ddl_DepartmentList.SelectedValue
.OfficeNumber = txbx_OfficeNumber.Text
.HomeNumber = txbx_HomeNumber.Text
.MobileNumber = txbx_MobileNumber.Text
.BleepNumber = txbx_BleepNumber.Text
.OfficeEmail = txbx_OfficeEmail.Text
.HomeEmail = txbx_HomeEmail.Text
.IsRedundant = False
.RedundantDate = Nothing
'--------------------------
.FiledBy = Threading.Thread.CurrentPrincipal.Identity.Name
.FiledLocation = My.Computer.Name
.FiledDateTimeStamp = Date.Now
'----------------------------
End With
context.AddToEmployees(employee)
context.SaveChanges()
End Sub
The above works fine, seemed to be a sensible way of doing it (remember I'm new to Entity Framework) and gave me the results I extpected.
I have another page called manage.aspx on this page is a tab control, each tab page contains a asp:DetailsView which is bound to an asp:EntityDataSource, I have enabled Update on all the Entity Data Sources and on some I have also enabled Insert, none of the have delete enabled.
If I build and run the app at this stage everything works fine, you can press the "edit" link make changes and then press "update" and sure enough those updates are displayed on the screen instantly and the database does have the correct values in.
Here's the problem, I need to intercept the update in the above code (the bold bit) notice there is a column in my database called FiledBy, FiledLocation, and FiledDateTimeStamp. I don't want to show these columns to the user viewing the ASP.NET page but I want to update them when the user presses update (if there were any changes made). I have tried converting the ASP:DetailsView into a template and then coding the HTML side with things like;
<## Eval(User.Name) #>
I did manage to get it to put the correct values in the textboxes when in edit mode but I had the following problems;
It didn't save to the database
I have to show the textboxes which I don't want to do
I have read on several other posts on here and other websites that you can override the SaveChanges part of the Entity Framwork model one example of this is in the code below;
public override int SaveChanges()
{
var entities = ChangeTracker.Entries<YourEntityType>()
.Where(e => e.State == EntityState.Added)
.Select(e => e.Entity);
var currentDate = DateTime.Now;
foreach(var entity in entities)
{
entity.Date = currentDate;
}
return base.SaveChanges();
}
The problem is, that whilst I understand the idea, and can just about transalate it to VB I have no idea how to implement this. Where do I put the code? How do I call the code? etc.
I would ideally like to find a solution that meets the following requirements:
Doesn't get overwritten if I was to regenerate / update the Entity Model
Doesn't need to be copy and pasted for every table in my model (most but not all have the audit columns in)
Can be used for other columns, for example, if a user make a selection in a check list I may want to write some value into another (not exposed to the user) column.
Am I taking the right approach to displaying data in may webpages I certainly find the DataView and GridView limiting, I did think about creating a form like the registration form in table tags but have no Idea how to populate the textboxes etc with values from the database nor how to implement paging as many of the tables have one to many relationships, although saving changes would be easy if I did populate textboxes on a table.
Thank you all in advance to anyone who offers there support.
Kevin.
Ok so I have now got closer, but still not got it working correctly. I've used the following code to edit the value in the DetailsView on the Databound event. If writes the correct timestamp into the read only textbox but does not right this value back to the database when you press the Update button on the DetailsView control.
Protected Sub dv_Employee_DataBound(sender As Object, e As EventArgs) Handles dv_Employee.DataBound
If dv_Employee.CurrentMode = DetailsViewMode.Edit Then
For Each row In dv_Employee.Rows
If row.Cells(0).Text = "FiledDateTimeStamp" Then
row.Cells(1).Text = Date.Now()
End If
Next
End If
End Sub
I like the idea of overriding the save changes method. Your context should be a partial class, so you just need to define another class marked as partial with the same name in the same namespace and add the override to it.
Namespace CPMModel
Partial Public Class CPMEntities
...Your override here
End Class
Since this is an override of the default SaveChanges method you do not need to make any changes in how it is called. You will need to find a way to gain access to entity level properties.
Since this is the method that commits changes for all entities you will not be able to access the properties like you do in your example, since the entity class does not define an of your concrete properties. So you need to create partial classes for your entities and have them all implement an interface that defines the properties you would like to interact with.
Create interface:
Interface IDate
Property Date() as DateTime
End Interface
Create partial class for each of your entities that implements the IDate interface. (The same rules apply for these partial classes they must be marked partial, have the same name as the class they extend and live in the same namespace) Then in your SaveChanges override
public override int SaveChanges()
{
var entities = ChangeTracker.Entries<Entity>()
.Where(e => e.State == EntityState.Added)
.Select(e => e.Entity);
var currentDate = DateTime.Now;
foreach(var entity in entities)
{
***Now cast entity to type of IDate and set your value ***
entity.Date = currentDate;
}
return base.SaveChanges();
}
I don't write in Vb so I left the syntax in C#.
That should fulfill all of your requirements.
Ok, so this isn't exactly how I imagined it would work but it has infact met my needs. Kind of!
What I did was Write a Protected Sub in the code behind file of the aspx file.
Protected Sub CustomEntityUpdate_Employee(sender As Object, e As
EntityDataSourceChangingEventArgs)
Dim emp As CPMModel.Employee = e.Entity
' Check if the entity state is modified and update auditing columns
If emp.EntityState = EntityState.Modified Then
emp.FiledDateTimeStamp = Date.Now()
emp.FiledBy = Threading.Thread.CurrentPrincipal.Identity.Name
emp.FiledLocation = My.Computer.Name
End If
End Sub
Then in the aspx page I edited the Entity datasource code to call the above sub routine
#nUpdating="CustomEntityUpdate_Employee"
<asp:EntityDataSource
ID="eds_Employee"
runat="server"
ConnectionString="name=CPMEntities"
DefaultContainerName="CPMEntities"
EnableFlattening="False"
EnableUpdate="True"
EntitySetName="Employees"
AutoGenerateWhereClause="True"
Where=""
EntityTypeFilter="Employee"
**OnUpdating="CustomEntityUpdate_Employee">**
<WhereParameters>
<asp:SessionParameter
Name="ID"
SessionField="UserID" />
</WhereParameters>
</asp:EntityDataSource>
The Protected Sub, checks if the entity has been modified and if it has updates the entity auditing columns with the values I wanted.
It works but it will certainly make for a lot more code, I ideally would like a way to package this up perhaps into a class and just call the sub from the various aspx pages via the OnUpdating event and have the class figure out which entity was making the call and handle all the logic there.

DropDownList Binding items

I have DDL in custom control that in the default case I insert new item with certain value.
but in a certain case I insert another item and want to the previous item to be deleted.
I tried to remove the first item and insert the another
I tried to change text and value of the inserted item
the 2 ways failed because:
in the first load of page, it take the value of the first item and text of the second,
then if I select another thing from DDL and then return to my Item, it take its correct value.
I need the second to take the correct value in the first load of the page.
Im not sure what you mean exactly,but let me try to help.
Page Load, the DDL has Item 1 loaded e.g. ItemText:Stack ItemValue:Overflow
Then on some action the above is deleted and a new Item is Inserted?
If so, could you try somthing like,
VB
With me.DDL
'Remove all Items from the DDL
.Items.Clear()
'Insert New Items
.Items.Insert(0, New ListItem("VALUE",TEXT"))
End With
C#
//Remove all Items from the DDL
this.DDL.Items.Clear();
//Insert New Items
this.DDL.Items.Insert(0, new ListItem("VALUE", "TEXT"));

Resources