(UPDATE #2: When I wrote this post a couple of years ago, I thought this was a pretty brain-dead way to handle things. Turns out that I really know what I’m talking about when the subject is my own incompetence. There is a better way: create a custom update procedure to store the value before it’s saved to the database.
Incidentally, adding JavaScript event handlers in this way can still do very powerful things. For example, I use the OnChange event on a name field to populate a “sortable” name field, changing “Graham Powell Jr.” to “Powell Jr., Graham”.
It’s just a retarded way to handle THIS problem.)
Or, the second entry in my unofficial series “Confessions of an ASP.NET newbie” (first entry is here).
I was recently working on an ASP.NET 2.0 project where I wanted to be able to click an LinkButton control and have it change the values of a number of fields. Google found a number of posts on various message boards asking for help doing the same thing. The most common response was, “Why would you want to do that?”
So I had to figure it out on my own. There are probably better ways of doing this – possibly during data validation, for instance – but this is the simplest method I could come up with.
Caveat: This method requires JavaScript. If it’s disabled in the client’s browser, this code won’t work.
Let’s approach this problem backwards. First we’ll create a JavaScript function that carries out the actions we want, then we’ll figure out how to call it from the LinkButton.
This simple function takes the control ID of a checkbox and a boolean value, then sets the “checked” property of the control:
<script language="javascript">
<!--
function SetCheckbox(ControlID, ControlState)
{
var CheckControl = document.getElementById(ControlID);
CheckControl.checked = ControlState;
}
-->
</script>
Please note that this control ID is from the JavaScript document object model, not from ASP.NET. Place this function in the <head> section of your ASP.NET page.
Now let’s look at the settings for the LinkButton control. Normally you would use this control to initiate a PostBack for a web form, but there is also a property called OnClientClick that carries out an action in the client browser before the form is submitted (ASP.NET does this by supplying both OnClick and href attributes in the HTML sent to the client). So we just put in a call to the SetCheckbox function and provide the target field’s ID, right?
Not so fast. In order to maintain globally unique IDs within each web page, the ClientID field of an ASP.NET control is defined both by the ID of the field itself and of any containers the field is within. This value is not obvious at design time and may change if the design is modified, so we don’t want to try to statically code it.
Happily, there’s a simple solution: the ASP.NET page itself knows what this client ID will be, and sticks it in a property called ClientID.
So at runtime you can use the following code to dynamically determine the client ID of the checkbox field and to set the OnClientClick property of the LinkButton control appropriately (this example assumes the checkbox is contained with a FormView control named ‘ExampleFormView’):
Dim ExampleLinkButton as LinkButton
Dim ExampleCheckbox as CheckBox
Dim ExampleID as String
ExampleLinkButton = ExampleFormView.FindControl("ExampleLinkButton")
ExampleCheckbox = ExampleFormView.FindControl("ExampleCheckbox")
ExampleID = ExampleCheckbox.ClientID
ExampleLinkButton.OnClientClick = "javascript:SetCheckbox('" & ExampleID & "', true)"
This code is in the Page_PreRenderComplete event handler in my page because the visible property of the LinkButton is data-bound, so I need to make changes to this control’s properties after databinding is complete. In most cases you should be able to use it in the Page_Load handler. Please note the single quotes around the client ID parameter – it’s easy to lose sight of them up against the double quotes marking the string literals.
If the target field (a checkbox, in this case) is data-bound, this function will only change the value but will not save it in the datasource. If you want to immediately save the value, just set the LinkButton’s CommandName value to “Update”. ASP.NET will first execute the client click, which will update the checkbox’s value, and will then post the changed data back to the server with instructions to update the database.
UPDATE: I found an error in the description above. Specifically, a LinkButton always causes a PostBack round-trip, so if you don’t want to lose any changes you made in JavaScript, you must specify that the CommandName is “Update”. This will save your changes to the underlying database. If this is not what you want to do, use a HyperLink object instead of a LinkButton, and put the JavaScript command into the NavigateUrl property instead of into OnClientClick. Otherwise the code works as described.
If you find this code helpful, please leave a note in the comments.
Off and on for the past couple of weeks I’ve been using Microsoft’s free Visual Web Developer 2005 Express to redesign a little Access database-driven website at work. I had hoped to make it simpler and easier to maintain, by using standard components instead of the custom code I had been using.
It turned out to be a little more complicated than I had thought. In particular I discovered a big problem with Access autoincrement fields. An autoincrement field is a long (32-bit) integer that is automatically assigned a unique ID when a new record is inserted. But when you are inserting records with ASP.NET, there’s no good way to retrieve this value.
This is not as much of a problem with SQL Server, because you can create stored procedures, or even simple queries, that carry out both INSERT and SELECT statements – that is, they send and receive data in one operation. Access doesn’t do this.
Background. In ASP.NET 2.0, there’s a free-form data bound container called a FormView. This is similar to the GridView and DetailsView objects, but it is more flexible, as you are not limited in how you can lay out the controls within it (text boxes, drop-down boxes, etc.).
Each FormView contains templates for different views, such as an Item view (read-only), and ItemEdit view (updates existing records), and an ItemInsert view (just what it says). Each template contains markup defining the data fields and their layouts.
In my case, this FormView is connected to a control called an AccessDatasource control. This datasource has the location of the data file and SQL statements for the SELECT, UPDATE, INSERT, and DELETE procedures. This control also has events that are triggered during these procedures, which will become important when we get to the code.
Now, most ASP.NET pages only post data back to themselves, but I am using separate pages for viewing data and for editing it, and I’m using a standard HTML input field to pass the ID between them. This is called “cross-posting” in ASP.NET, and I’m sure it’s a terrible idea and an example of my inexperience and general cluelessness, but I have good reasons for doing it that way.
To handle this, my Page_Load event handler contains the following code (all examples in VB):
Dim IDParam As New Parameter
If Not IsPostBack Then
If String.IsNullOrEmpty(Request.Form("ID")) Then
ServerFormView.ChangeMode(FormViewMode.Insert)
Else
IDParam = ServerDatasource.SelectParameters.Item("ID")
IDParam.DefaultValue = Request.Form("ID")
End If
End If
This FormView control is set to default to Edit mode. The first line basically says that if no ID form field is suppled, put the FormView control in Insert mode. If a parameter is supplied, it’s added to the data source. I could have used a FormParameter in the data source definition, but I want to be able to filter in other situations, as we’ll see in a minute.
INSERTing records. So let’s assume we are now in Insert mode. We fill in all the fields and click an Insert button to add the new record to the database. What happens next?
Among other things, the AccessDatasource_Inserting event fires. This event is raised once an INSERT procedure has started, but before the record is actually inserted. Here’s the code to handle this event – note that “e” is the event arguments passed to this procedure automatically:
Dim InsertCommand As DbCommand
Dim InsertConnection As DbConnection
Dim InsertTransaction As DbTransaction
InsertCommand = e.Command
InsertConnection = InsertCommand.Connection
InsertConnection.Open()
InsertTransaction = InsertConnection.BeginTransaction
InsertCommand.Transaction = InsertTransaction
All we’re really doing here is starting a transaction. Although my testing indicates that this may not always be required, using transactions guarantees that the data source won’t be polluted by other database operations during this insert.
After this event completes, the record is inserted and the AccessDatasource_Inserted event fires. This is where the work gets done:
Dim InsertCommand As DbCommand
Dim InsertTransaction As DbTransaction
Dim ID As Int32
Dim IDParam As New Parameter
InsertCommand = e.Command
InsertTransaction = InsertCommand.Transaction
InsertCommand.CommandText = "SELECT @@IDENTITY AS [ID]"
ID = InsertCommand.ExecuteScalar()
InsertTransaction.Commit()
IDParam = ServerDatasource.SelectParameters.Item("ID")
IDParam.DefaultValue = ID
ServerFormView.ChangeMode(FormViewMode.Edit)
ServerDatasource.DataBind()
So, what are we doing here? First, we’re grabbing the INSERT command and the transaction we set in the Inserting event handler. Then we’re reusing the command to run a SELECT statement that retrieves the @@IDENTITY (autoincrement) value for the last record inserted. Once that is complete, we commit the transaction.
Next we set the same parameter that we used in the Page_Load procedure to the value of the new row. We no longer want to be in Insert mode, so we change the FormView’s mode to Edit. Lastly, we refresh the data using the DataBind command.
And that’s all there is to it. Just make sure that the FormView’s ItemEdit template has a form field named “ID” that is bound to the data source’s “ID” field, and you are good to go.
If you’re code objects to manipulate Access data, you will run into a similar problem. You can handle it by handling the DataAdapter’s RowUpdated event. It’s similar to the procedure above.
If you get this to work for you, please leave a note in the comments so I can see if anyone’s actually using this code.