Monday, March 30, 2009

The log or differential backup cannot be restored because no files are ready to rollforward.

If you have found this page, it is likely that you encountered the following error when you tried to restore a differential backup using Microsoft SQL Server 2005. Restore failed for Server ''. (Microsoft.SqlServer.Smo) Additional Information: System.Data.SqlClient.SqlError: The log or differential backup cannot be restored because no files are ready to rollforward. (Microsoft.SqlServer.Smo) What this error is telling you is that there is no database that was left in non-operational mode, and thus has not been cleaned up such that uncommitted transactions have not been rolled back. The easy way to reproduce this error is to backup your database using full recover model, and do full and differential backups. Once you have your full and differential backup files you, if you want to restore your database all you have to do is restore the full backup first, and then one of the differential files (differential backups have all the changes since the last full backup) that brings you up to the point you want to restore to. You will get the above error when you try to restore the differential backup (after you just restored the full backup). Unfortunately, you forgot one critical detail (just like I did at first). You MUST restore all but the last (in this case the full backup) with NORECOVERY option. In the Microsoft SQL Server Management Studio there are three options on the Option "page" when you restore a database. Option 1 (the default): Leave the database ready to use by rolling back uncommitted transactions. Additional transaction logs cannot be restored.(RESTORE WITH RECOVERY) Option 2: Leave the database non-operational, and do not roll back uncommitted transactions. Additional transaction logs can be restored.(RESTORE WITH NORECOVERY) To properly restore a database that is backup up using full recovery mode with full and differential backups, here are the steps you need to follow to not get the above error. Restore Full backup
  1. Open the Restore Database window in Microsoft SQL Server Management Studio
  2. Ensure the To database field is filled in with the name you want.
  3. Choose From device as the Source for restore.
  4. Choose the full backup file you want to restore. In most cases this is the most recent full backup file.
  5. Click the Options page on the left navigation.
  6. Choose Leave the database non-operational, and do not roll back uncommitted transactions. Additional transaction logs can be restored.(RESTORE WITH NORECOVERY). This is the most important step!!!
Restore Differential backup
  1. Open the Restore Database window in Microsoft SQL Server Management Studio
  2. Ensure the To database field is filled in with the name you want. The same that you specified in step 2 for the Restore Full backup
  3. Choose From device as the Source for restore.
  4. Choose the differential backup file you want to restore. In most cases this is the most recent differential backup file.
  5. Click the Options page on the left navigation.
  6. Choose the default: Leave the database ready to use by rolling back uncommitted transactions. Additional transaction logs cannot be restored.(RESTORE WITH RECOVERY) Make sure to choose this if you want to use your database after the restore.
That is it, no more error. If you are lucky :)

Thursday, March 26, 2009

LINQ - A brief overview

LINQ to SQL

I don't have a lot of experience with LINQ yet, but what I have read and played with is very impressive. Especially, when used with Visual Studio 2008. It is sooo easy to create a data access layer. If you want to create your own entities, then you can still do that just like you always have. You can even use stored procedures if you still want to use them. Though I think LINQ makes stored procedures not necessary since I usually use stored procs to prevent (dare I say) SQL injection, and I think LINQ handles that pretty well.

The question I always had was, why use LINQ. I think it is actually a very good thing now that I have read more about it. LINQ provides a uniform way to do Create, Read, Update, and Delete (CRUD) operations on data. Data could be Flickr, classes, any database by any vendor, or really anything if you extend or someone else has extended LINQ.

One of my initial thoughts was why do I need to learn LINQ I already know SQL and ADO.NET. That works really well. The simple answer is a consistent interface to other data as well. Really LINQ is just a conduit to allow you to do CRUD operations on nearly anything you can imagine. The advantage is that if the source of that data changes you code doesn't have to change much at all. It also means that if you are querying XML or the database, or whatever you can do it using the same syntax and same tool. Think of it just as you do a foreach statement. It is just another tool in your toolbelt. Instead of constantly having to implement different ways to do CRUD operations for each data source, now you really just need to learn or at least use one (LINQ).

Another argument I had initially was I like to use Custom entities that correspond to tables in the database. I used to use CodeSmith or MyGeneration to generate these entities. As it turns out that is what LINQ to SQL does. In fact, it produces very nice object model that I can use. The best part is that it is integrated into Visual Studio 2008, and I can visually see the model while in Visual Studio 2008, and can be edited there as well.

In the end, I think it is an excellent tool and worth learning. It is not difficult to learn, but it is a bit strange from a syntax point of view. Below are some of the basics to get you started. In this example, let's assume the name of the table is MyData.

Here is the SQL to create the table if you want to try these examples

CREATE TABLE [dbo].[MyData](
 [MyDataID] [int] IDENTITY(1,1) NOT NULL,
 [Test1] [nvarchar](50) NULL,
 [Test2] [int] NULL,
 [Test3] [datetime] NULL,
 CONSTRAINT [PK_MyData] PRIMARY KEY CLUSTERED
([MyDataID] ASC)
)

Assuming you created the table in some database, you need to add that database to the Server Explorer in Visual Studio 2008 (VS2008). 

Next we need to create a  DataContext. This is where LINQ objects live and communicate with the database. To create one of these, add a new item to your project called "LINQ to SQL Classes". I called my file MyDataClasses.dbml. Open this file in VS2008. You will see something similar to what you would see if you opened a DataSet. Find the MyData table in the Server Explorer that you added earlier. Simply drag the Table to the diagram. That is it! It is that simple! You now have your data access layer. You can add other tables and relationships just like DataSets, but we are just doing the one here.

NOTE: You may have to select the MyDataID field and change the Auto-Sync feature to OnInsert instead of Always. It should also have Primary Key set to true.

Continuing with example, assume you have a form of some kind that has four fields on it. One for each column in the database. Also, there are four buttons: Insert, Find, Update, and Delete.

Insert
To insert a new record, row, object, whatever you want to call it, you basically just create a new object, set the properties, insert it into the context, and tell the context to submit the changes. Here is the code to do that.

using (MyDataClassesDataContext ctx = new MyDataClassesDataContext())
{
MyData newRecord = new MyData();
newRecord.Test1 = txtString.Text;
newRecord.Test2 = Convert.ToInt32(txtInt.Text);
newRecord.Test3 = new DateTime?(Convert.ToDateTime(txtDateTime.Text));
ctx.MyDatas.InsertOnSubmit(newRecord);
ctx.SubmitChanges();
}


Find
Find is where the strange syntax is. There is a new kind of data type that LINQ makes use of and it is called var. It is more or less like Object, but not quite. In contrast you could use IEnumerable<MyData> instead of var. The choice is yours. All we do below is query the database. The first example is really for multiple rows being returned, and the second example is if you know exactly one will be returned. The second one is probably a better choice in this case since we are querying on the primary key.

using (MyDataClassesDataContext ctx = new MyDataClassesDataContext())
{
// this works
//var record2 = from d in ctx.MyDatas
//              where d.MyDataID == Convert.ToInt32(txtID.Text)
//             select d;

//MyData record = record2.ToList<MyData>()[0];

// This works better using a lymbda expression
MyData record = ctx.MyDatas.Single<MyData>(d => d.MyDataID == Convert.ToInt32(txtID.Text));

txtID.Text = record.MyDataID.ToString();
txtString.Text = record.Test1.ToString();
txtInt.Text = record.Test2.Value.ToString();
txtDateTime.Text = record.Test3.Value.ToShortDateString() + " " + record.Test3.Value.ToShortTimeString();
}

Update
For updating data you need to find the object you want to update, makes changes, and submit changes. This will by definition already be in the DataContext so you don't need to insert it on submit like you do when you insert. It is a mix between inserting a new object and finding an object.

using (MyDataClassesDataContext ctx = new MyDataClassesDataContext())
{
// this works
var record2 = from d in ctx.MyDatas
     where d.MyDataID == Convert.ToInt32(txtID.Text)
     select d;

MyData record = record2.ToList<MyData>()[0];

// This works better using a lymbda expression
//MyData record = ctx.MyDatas.Single<MyData>(d => d.MyDataID == Convert.ToInt32(txtID.Text));

record.Test1 = txtString.Text;
record.Test2 = Convert.ToInt32(txtInt.Text);
record.Test3 = new DateTime?(Convert.ToDateTime(txtDateTime.Text));

ctx.SubmitChanges();
}


A good places to start is:
http://msmvps.com/blogs/abu/archive/2008/06/30/introducing-linq-tutorial.aspx

Friday, March 6, 2009

ASP.NET GridView Scrollable area and Fixed Header Solution

I have search long and hard and spent much of my own time trying to find a good Fixed Header and Scrollable rows for the ASP.NET GridView. I think I have finally come up with a combination of my own efforts and the best of borrowed from other solutions. Unfortunately, I didn't keep track of where I got some of the code that I borrowed. My apologies to the original authors.

This solution attempts to remedy all the buggy solutions I found on the internet. Some of the common problems were

  • The solution did work for the GridView
  • The solution assumed we had more control of the generated html from the GridView
  • The solution required modifying the html generated by the GridView
  • The solution stopped working when windows is resized
  • The solution assumed that there was only one scrollable area on the page. (To be fair, my solution doesn't assume this, but it would require some duplication of style sheets, jscript code, etc.)

Here is a sample implementation of my solution. It works quite well with only one scrollable area that has a fixed header. You will need to change duplicate the JavaScript file and CSS and all references to "container" if want more than one scrollable area that has a fixed header. Another alternative is to generate a customized .css .js file based on a special url. That is beyond the scope of this though.

There are several key things I would like to point about the .aspx page. The .js and .css are just really items you need to reference and don't really require any changes (except as noted above). So, I really just want to highlight what you would need to add to your page (that has a working GridView that does not have any special Fixed column header) to make this solution work.

  1. The DOCTYPE line is VERY important. This is NOT the default that Visual Studio adds to your page. Replace the line that Visual Studio puts in your .aspx page with the one shown below.
  2. Copy and Paste the GridView1_PreRender event handler to your .cs file. If you have a different name for your GridView you will need to change references to it to make the name you gave it. You will also want to set the height and width that you want. A word of warning, I did not implement the width yet, so actually that doesn't do anything. Currently the width of the GridView is not set here. Don't forget to change the references to GridView1 in the literals to match the name you use. Also, un/comment the appropriate example. If you want the GridView to be the Maximum height available and expand as the window resizes use the SetFixedHeaderWithMaxHeight call. Otherwise, if you want a fixed height, use the SetFixedHeader example.
  3. Register the GridView1_PreRender event handler with the GridView1. One easy way to do this is to add the following line to the GridView1 tag.
    OnPreRender="GridView1_PreRender"
    NOTE: You will of course need to make this match what you specified in step 2.
  4. Create a .css file by copying the CSS lines below into its own file. Alternatively, you could just include it in a style tag in the head of the page, though I don't recommend this approach.
  5. Create a .js file by copying the JavaScript lines below into its own file. Alternatively, you could just include it in a script tag in the head of the page, though I don't recommend this approach.
  6. Reference the .js and .css file in the head of the page.
  7. Copy and Paste the HeaderStyle tag in the GridView.
  8. Copy and Paste the DIV tag that has the id="container" and is directly around the GridView. It is important that there is no other DIV tags or other tags in between the DIV and the GridView. If you change this, you will need to make changes to the CSS and JavaScript.

<%@ Page Language="C#" %>

<!-- This comment keeps IE6/7 in the reliable quirks mode -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "
http://www.w3.org/TR/html4/loose.dtd">


<script runat="server">
    protected void GridView1_PreRender(object sender, EventArgs e)
    {
        if (GridView1.Rows.Count > 0)
        {
            //This replaces <td> with <th> and adds the scope attribute
            GridView1.UseAccessibleHeader = true;

            //This will add the <thead> and <tbody> elements
            GridView1.HeaderRow.TableSection = TableRowSection.TableHeader;
        }

        GridView1.Style["border-collapse"] = "separate";

        string height = "450px";
        string width = "200px";
        string gv1FixedHeaderJScript = string.Format("SetFixedHeader('{0}', '{1}', '{2}');", GridView1.ClientID, height, width);

        // MAX Height Example
        string gv1FixedHeaderJScript = string.Format("SetFixedHeaderWithMaxHeight('{0}', '{1}', '{2}');", GridView1.ClientID, width, "20px");

        // Fixed Height Example
        //ScriptManager.RegisterStartupScript(this, this.GetType(), "gvGridView1FixedHeaderKey", gv1FixedHeaderJScript, true);


    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>GridView Test</title>
    <link href="FixedHeader.css" rel="stylesheet" type="text/css" />
    <script language="JavaScript" src="FixedHeader.js"></script>
</head>
<body>
    <form id="form1" runat="server">
   
     <div id="container" style="border-style:none;">
        <asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
            OnPreRender="GridView1_PreRender">
         <HeaderStyle CssClass="DataGridFixedHeader" />
        </asp:GridView>
       
    </div>
        <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetDelinquentActions"
            TypeName="DAL"></asp:ObjectDataSource>
    </form>
</body>
</html>


// JScript File

// This is the function that gets called to resize the scrollable area to the size we want.
// We have to use the setTimeout() with a 1ms delay IF we call this function
// from within the form tag instead of just before the bottom of the body end tag.
// The reason is that in IE any of the height and width (scrollHeight, offsetHeight, style.height)
// are not set until after the form has finished rendering. This means, that the call to the
// SetFixedHeader2 method must occur after the form has been rendered. When using master pages
// as we are doing the location we need can only be specified in the .aspx page of the master page.
// Since we use this setting for different pages we can't hard code it there.
// Also, we can't use the ScriptManager.RegisterStartupScript unless we use the
// setTimeout either. This is because using this method puts the code just before the form end tag,
// which is again not where we need it to be. However, using setTimeout allows us to use this method.
function SetFixedHeader (gvClientID, height, width)
{
    var expr = "SetFixedHeader2('" + gvClientID + "', '" + height + "', '" + width + "')"
    setTimeout(expr, 1);
}

// This is essentially the same as SetFixedHeader function except the height is
// always going to be the height on the body - bottomMargin - top of GridView.
// bottomMargin is the number of pixels that create the gap between the bottom of
// the window and the bottom of the GridView
function SetFixedHeaderWithMaxHeight (gvClientID, width, bottomMargin)
{
    // get the max height that the GridView can be
    var maxHeight = getMaxHeight() - parseInt(bottomMargin);
   
    // We need to resize the GridView when the Window is resized
    window.onresize = MaximizeGridViewScrollableArea;
       
    var expr = "SetFixedHeader2('" + gvClientID + "', '" + maxHeight + "', '" + width + "')"
    setTimeout(expr, 1);
   
    gridViewClientID = gvClientID;
    desiredHeight = maxHeight;
    desiredWidth = width;
    desiredBottomMargin = bottomMargin;
}

function MaximizeGridViewScrollableArea()
{
    SetFixedHeaderWithMaxHeight(gridViewClientID, desiredWidth, desiredBottomMargin);
}

var gridViewClientID = null;
var desiredHeight = null;
var desiredWidth = null;
var desiredBottomMargin = null;

// height - string - the height of the scrollable area in pixels (not percent) i.e. 330px
// width - string - the width of the scrollable area in pixels or percent i.e. 600px or 50%
function SetFixedHeader2 (gvClientID, height, width)
{   
    // get numeric values for height and width
    var heightNum = parseInt(height);
   
    // adjust the size since the grid view needs to be slightly smaller
    // than the container div
    var heightAdjustment = 40;
    heightNum = heightNum - heightAdjustment;
   
    // do we need scrolling or not?
   
    var gv = document.getElementById(gvClientID);
    var tbody = null;
   
    // loop through the four (or fewer) child nodes of the table
    // and find the tbody node
    for (var i=0; i<gv.childNodes.length; i++)
    {
        var child = gv.childNodes[i];
       
        if (child.tagName)
        {
            if (child.tagName.toUpperCase() == "TBODY")
            {
                tbody = child;
                // we found what we needed, exit the loop
                i = gv.childNodes.length;
            }
        }
       
    }
   
    if (tbody != null)
    {
   
        var gvDiv = GetDivGeneratedByGridView();
       
        // scrolling is needed
        if (parseInt(tbody.scrollHeight) > parseInt(heightNum))
        {
        //alert('needs scrolling');
            //tbody.style.height = height;
            tbody.style.height = (heightNum) + "px"
                     
            if (gvDiv != null)
            {
                // add the height adjustment back in for the container, so it is bigger
                gvDiv.style.height = (heightNum + heightAdjustment) + "px";
            }
        }
        // scrolling is NOT needed
        else
        {
        //alert('NO scrolling');
            tbody.style.height = '100%'
           
            if (gvDiv != null)
            {
               
                gvDiv.style.height = "100%";
            } 
        }        
    }
}

// returns the DIV surrounding the GridView.
// NOTE: This is NOTE the DIV with id="container" that we added.
// This is the DIV that is generated by the GridView when it is rendered.
// This the DIV between teh DIV with id="container" and the table that is
// generated by the GridView.
function GetDivGeneratedByGridView()
{
    // set the size of the container div to be just a little bigger
    // than the grid view
    var container = document.getElementById("container");
   
    var isIE = typeof container.children == 'object';
    var gvDiv = null;
   
    if (isIE)
    {
        gvDiv = container.children[0];   
    }
    else // Firefox
    {
        // NOTE: First childNode is a textnode that is a new line
        gvDiv = container.childNodes[1];   
    }
   
    return gvDiv;
}


// get the max height the GridView can have
// NOTE: This is based on where the GridView is vertically on the page
//       For example, if the GridView is 100 pixels from the top of the
//       top of the body, then this will return the height of the body - 100.
function getMaxHeight() {
  var div = GetDivGeneratedByGridView();
 
  myHeight = 0;
//  alert(document.body.topMargin);
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myHeight = window.innerHeight;
  } else if( document.documentElement && document.documentElement.clientHeight) {
    //IE 6+ in 'standards compliant mode'
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && document.body.clientHeight) {
    //IE 4 compatible
    myHeight = document.body.clientHeight;
  }

  var maxGridViewHeight = myHeight - div.offsetTop;

  return maxGridViewHeight;
 
 
}



/*** The Fixed Header Stylesheet ***/

.DataGridFixedHeader { POSITION: relative; TOP: expression(this.parentNode.parentNode.parentNode.scrollTop-1);}

#container div {
 overflow: auto; /* so the extra columns and rows flow as needed */
 margin: 0 auto;
 }

#container table {
 width: 99%;  /*100% of container produces horiz. scroll in Mozilla*/
 
 /* Gets rid of the 1 pixel space on the top of the header that shows through when scrolling */
 border: none ! important;
 }
 
#container table>tbody {  /* child selector syntax which IE6 and older do not support*/
 overflow: auto;
 overflow-x: hidden;
 }
 
#container thead tr {
 position:relative;
 top: expression(offsetParent.scrollTop); /*IE5+ only*/
 }
 
#container table tfoot tr { /*idea of Renato Cherullo to help IE*/
      position: relative;
      overflow-x: hidden;
      top: expression(parentNode.parentNode.offsetHeight >=
   offsetParent.offsetHeight ? 0 - parentNode.parentNode.offsetHeight + offsetParent.offsetHeight + offsetParent.scrollTop : 0);
      }

#container td:last-child {padding-right: 20px;} /*prevent Mozilla scrollbar from hiding cell content*/

#container thead td, thead th {
 
 /* the background color for the header to something other than transparent
  so that the rows don't show behind while scrolling */
 background-color:white;
 
 }
 
 
/*** Purely Cosmetics ***/

#container div {
 width: 99%;  /* table width will be 99% of this*/
 height: 50px;  /* a small efault value so user won't really see resize if delay rendering 1ms. it is changed by the SetFixedHeader() javascript function. Must be greater than tbody*/
 } 

/*** print style sheet ***/

@media print {

#container div {overflow: visible; }
#container table>tbody {overflow: visible; }
#container td {height: 14pt;} /*adds control for test purposes*/
#container thead td {font-size: 11pt; }
#container tfoot td {
 text-align: center;
 font-size: 9pt;
 border-bottom: solid 1px slategray;
 }
 
#container thead {display: table-header-group; }
#container tfoot {display: table-footer-group; }
#container thead th, thead td {position: static; }

#container thead tr {position: static; } /*prevent problem if print after scrolling table*/
#container table tfoot tr {     position: static;    }

}


/*** Global Print Styles ***/
@media print {

.noprint {display: none;}

body {
 font-family:"Palatino Linotype", Georgia, Garamond, serif;
 background-image: none;
 }
 
#container {
 border: none;
 padding: 0;
 }

}

Wednesday, March 4, 2009

Master-Detail GridViews using AJAX.

Recently, I had the need to have a GridView on the top half of a page, and GridView of related records on the bottom half of the page. This is the typical Master-Detail relationship. When the user clicks on a LinkButton on the Master GridView I want the Detail GridView to update. The trick is, I want this to happen without a postback. AJAX to the rescue.

I have attached an event handler for the RowCommand event of the Master GridView. In here I look for the CommandName that I specified in the TemplateField that has a ButtonLink in it. This allows me to handle the ButtonLink clicks in one place and quite simply.

Well, there are a few tricks I had to learn to get everything to work with AJAX. Here is what I did.

  1. Add the ScriptManager control to the ASP.NET page.
  2. Add the UpdatePanel control to the page.
  3. Set the UpdateMode to Conditional
  4. Move the Detail GridView into the UpdatePanel
  5. Added an event handler for RowCreated event on the Detail GridView.
  6. In the RowCreated event handler I used e.Row.FindControl() to find my ButtonLink on the row.
  7. Once I had the ButtonLink, I registered it using the ScriptManager1.RegisterAsyncPostBackControl(myLink) method to register my ButtonLink.

My failed attempts included trying to register the Master GridView using the ScriptManager1.RegisterAsyncPostBackControl(MyMasterGridView), and at first appeared to work. Then I noticed strange behavior. My column headers would not sort anymore, and another column that had a checkbox in it was firing it changed event for every row. I would love to know why registering the GridView did not work. I suspect it may have something to do with the callback based sorting the GridView can do out of the box.

Making a call to the server from JavaScript using ASP.NET Client Callback (Low Level AJAX)

The concept behind AJAX is really nothing new. The basic idea is that from JavaScript we want to call a server function and have the result returned to us. The result is returned by calling a JavaScript callback function such that the result from the server is sent via a parameter to this JavaScript callback function. Typically we want to display the result dynamically by updating the HTML page we are on without a postback. To do this, the JavaScript callback function typically will change the page using JavaScript.

In ASP.NET, you can use update panels, and other high level methods of doing this, but this is essentially what is going on behind the scene. Sometimes, you may want or need to do the same thing but at a very low level.  

To add this type of functionality to your ASP.NET page, here is a list of simple ways (not the only ways) you can add this low level AJAX to your page.

Make the class for your page implement System.Web.UI.ICallbackEventHandler interface.

This interface requires that you define the following two methods.

   
string result;
    // Define method that processes the callbacks on server.
    // NOTE: This is required by the ICallbackEventHandler interface
    public void RaiseCallbackEvent(String eventArgument)
    {
        result = eventArgument + "!!!!";
    }


    // Define method that returns callback result.
    // NOTE: This is required by the ICallbackEventHandler interface
    public string GetCallbackResult()
    {
        return result;
    }

Since AJAX is asynchronous that means that after the RaiseCallbackEvent method is called that processing can take a long time, and will not block further execution. This means that when the processing is done we need to have some way of knowing that, and then doing something with that result. In our example, I am simply adding "!!!!" to the value, but this could very complex long running code. That is what the GetCallbackResult method is for. It gets called only when the RaiseCallbackEvent method has completed. You don't call it, the framework does this for you.

You may notice that we use a result variable to communicate between the two methods. I think this is a bit strange, and would have expected the result to be passed as an parameter to GetCallbackResult, but it wasn't. :) I expect this approach is nearly as simple, and arguably more flexible.

Now that we have defined what will be happen when the AJAX request is received by the server, we need to define what will happen when data is passed back to the JavaScript page.

For this, let's assume we want to display the results in a span tag on our page. For this let's assume we have something like the following defined somewhere on our page.

<span id="MyMessage">Original Content Here</span>

We need to define one methods to do something with the result once it is sent back to the browser. The result shows itself as a parameter to the JavaScript function. It would be best to define one that will get called if there is an error with the AJAX call.

The JavaScript Function would be something like this.

function DoSomethingCallback(text)
{
  var myMessage = document.getElementById("MyMessage");
  MyMessage.innerHTML = text;
}

function DoSomethingErrorHandler(text)
{
  var myMessage = document.getElementById("MyMessage");
  MyMessage.innerHTML = 'An error has occurred.';
}

 
Now all we need to do is wire up the callback. This is done by asking ASP.NET to create the callback for us. Here is the C# code that must fire on EVERY page load. So, put it in the page load event or some other event that occurs EVERY time the page loads. If you don't do this on every page load, you will get postbacks instead of AJAX calls.

In the method below the first parameter is the page that implements the ICallbackEventHandler. The second parameter is in this case the name of parameter, but it could also be a global JavaScript variable, or a constant. If it is a string constant, you will need to use the embedded single quotes. The third parameter is the name of the JavaScript function we defined above that handles the returned data. The fourth parameter is the name of the JavaScript function we defined above that handles the error. The last parameter is to designate we are doing this asynchronously.

// Get the JavaScript Callback
String jscriptCallback = Page.ClientScript.GetCallbackEventReference(this, "val", "DoSomethingCallback", "",
"DoSomethingCallbackErrorHandler", false);


In theory we are done. You can now use the value of the jscriptCallback C# variable however you want. Now, however you want to get that JavaScript code to the browser is up to you. In this example, I am taking some shortcuts to make understanding the example a bit easier. However, in real life just like other JavaScript stuff in ASP.NET, you would want to use RegisterClientScriptBlock to define the JavaScript function, instead of using a property as I have done below.

For our example, I am adding a property like the following.

public string AjaxJavaScript
{
   get
   {
      // Get the JavaScript Callback
      String jscriptCallback = 
         Page.ClientScript.GetCallbackEventReference(this, "val", 
      "DoSomethingCallback", "",
      "DoSomethingCallbackErrorHandler", false);
      return jscriptCallback;
   }
}

Now I can use this anywhere in my .aspx page to get access to the code. In most cases you will need to wrap this code in a JavaScript function since the code generated by ASP.NET has both single and double quotes. This makes it very difficult to use directly in an event handler that is defined in quotes or double-quotes. Here is the example JavaScript function.

function GetMessageFromServer(val)
{
  <%= AjaxScript %>
}

If you view the source on the page, you would see that the code looks something like the following.

function GetMessageFromServer(val)
{
WebForm_DoCallback('__Page',val,DoSomethingCallback,"",DoSomethingCallbackErrorHandler,false);
}


Notice that the second parameter here is val which must match the parameter of the surrounding function.

Now for the exciting part. Now we can actually call the function from anywhere we can use JavaScript. In the below example, I am calling the JavaScript from two HTML buttons.

<input type="button" value="12345" onclick="GetMessageFromServer(12345)"/>
<input type="button" value="111" onclick="GetMessageFromServer(111)"/>



Here is the complete source for easy copy and paste:


<%@ Page Language="C#" %>
<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<script runat="server">
   
    string result;
    // Define method that processes the callbacks on server.
    // NOTE: This is required by the ICallbackEventHandler interface
    public void RaiseCallbackEvent(String eventArgument)
    {
        result = eventArgument + "!!!!";
    }


    // Define method that returns callback result.
    // NOTE: This is required by the ICallbackEventHandler interface
    public string GetCallbackResult()
    {
        return result;
    }



public string AjaxJavaScript
{
    get
    {
       // Get the JavaScript Callback
       String jscriptCallback = Page.ClientScript.GetCallbackEventReference(this, "val",
       "DoSomethingCallback", "",
       "DoSomethingCallbackErrorHandler", false);
    return jscriptCallback;
    }
}
   
</script>


 


<html>
<head>
    <title>Low Level AJAX Example</title>
   
    <script type="text/javascript">
function DoSomethingCallback(text)
{
  var myMessage = document.getElementById("MyMessage");
  myMessage.innerHTML = text;
}


function DoSomethingCallbackErrorHandler(text)
{
     var myMessage = document.getElementById("MyMessage");
  myMessage.innerHTML = 'An error has occurred.';
}


function GetMessageFromServer(val)
{
     //Example: WebForm_DoCallback('__Page',val,DoSomethingCallback,"",DoSomethingCallbackErrorHandler,false);
     <%= AjaxJavaScript %>
}


</script>
</head>
<body>
    <form id="Form1" runat="server">
  <span id="MyMessage">Original Content Here</span>
  <input type="button" value="12345" onclick="GetMessageFromServer(12345)"/>
  <input type="button" value="111" onclick="GetMessageFromServer(111)"/>
    </form>
</body>
</html>