Sunteți pe pagina 1din 17

Expert Reference Series of White Papers

10 ASP.NET Tips To
Increase Productivity
and Simplify
Maintenance

1-800-COURSES www.globalknowledge.com
10 ASP.NET Tips To Increase
Productivity and Simplify Maintenance
Dan Wahlin, Microsoft MVP, ASP Insider, Silverlight Insider, MCT, MCAD

Introduction
ASP.NET provides a robust framework for building Web applications that minimizes the amount of code that’s
required to be written. Although most developers rely on Visual Studio or Web Developer Express when build-
ing applications, there’s no substitute for knowing the framework when it comes to increasing productivity. The
more you know about built-in functionality in the .NET framework and ASP.NET, the less code you have to write,
which ultimately makes your applications more maintainable.

This white paper outlines several different tips to enhance developer productivity and reduce the amount of
code that needs to be written in many cases. Tips range from simple concepts, such as setting default focus on a
control to creating your own FindControl() method. Although the tips focus on ASP.NET Web Forms, there is also
information about Microsoft’s latest ASP.NET MVC framework as well. Let’s get started.

Tip #1 - Strongly Typed Access to Cross-Page Postback


Controls
ASP.NET 2.0 and higher supports the ability to post data from one page to another using cross-page postback
functionality. This is useful when you don’t want a page to postback to itself. For example, you may have a
search user control defined in a master page, and when the end user searches, you want to take them to a
search results page.

The built-in cross-page postback functionality in ASP.NET works great out of the box but doesn’t provide
strongly-typed access to controls in the previous page. Out of the box, you’ll normally have to resort to FindCon-
trol() method calls to locate the controls. There’s another option that can be used, though, to access controls in
a strongly-typed manner, which minimizes the amount of strings in your code and allows the compiler to catch
errors more easily.

If you have a page called Default.aspx that exposes a public property that returns a Textbox defined in the page,
the page that data is posted to (let’s call it SearchResults.aspx) can access the property in a strongly-typed man-
ner by adding the PreviousPageType directive into the top of the page:

<%@ PreviousPageType VirtualPath=”Default.aspx” %>

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 2


By adding this directive, the code in SearchResults.aspx can access the TextBox defined in Default.aspx in a
strongly-typed manner. The following example assumes the property defined in Default.aspx is named Search-
TextBox.

C#
TextBox tb = PreviousPage.SearchTextBox;

Visual Basic
Dim tb as TextBox = PreviousPage.SearchTextBox

This code obviously only works if the previous page is Default.aspx. PreviousPageType also has a TypeName
property as well where you can define a base type that one or more pages derive from to make this technique
work with multiple pages.

Tip #2 – Use the Dynamic Data Feature in .NET 3.5 SP1


If you’ve ever had to create database admin pages by hand, you know that it can be a time-consuming process.
By using built-in controls such as SqlDataSource or ObjectDataSource, you can certainly simplify the process, but
creating the pages and hooking the data to the controls still takes time. Visual Studio 2008 with SP1 installed
adds a new project type into the mix called Dynamic Data that can be used to automatically create insert, up-
date and delete pages to modify data in a database. Dynamic Data is very customizable but even out of the box,
it gives you a great deal of functionality.

Here are the basic steps to get started using Dynamic Data.
1. Install .NET 3.5 SP1 and Visual Studio 2008 SP1
2. Create a new project and select Dynamic Data Web Application from the templates
3. Add a new LINQ to SQL Classes item into your project
4. Open up the Server Explorer, add your target database and drag the desired tables onto the LINQ to
SQL designer surface
5. Right-click on Global.asax and select View Code from the menu
6. Locate the model.RegisterContext line in the code file and uncomment it
7. Locate the string “YourDataContextType” here and replace it with the name of your LINQ to SQL data
context object. If you named your LINQ to SQL file Northwind, then your data context object will be
called NorthwindDataContext
8. Set the ScaffoldAllTables property to true
9. Save the file and run Default.aspx in the browser
10. You now have instant access to insert, update and delete records in your database tables. An example
of a Dynamic Data page is shown below.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 3


Tip # 3 – Set the Default Button When an End User Hits
the Enter Key
Causing the appropriate button’s “click” event on the server-side when an end user hits the enter key to submit
a form can be tricky. Fortunately, you can now use the HtmlForm control’s DefaultButton property to set which
button should be clicked when the user hits enter. This property is also available on the Panel control in cases
where different buttons should be triggered as a user moves into different Panels on a page. Here’s an example
of using the DefaultButton property to define a button named btnSubmit as the default button:

<form id=”frm” DefaultButton=”btnSubmit” runat=”server”>


...
</form>

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 4


Tip # 4 – Set the Default Focus When a Page Loads
Have you ever visited a page to login and had to click in the first textbox in order to type? From an end user
perspective, it’s much more efficient to automatically set the focus to the first control (such as a user name
TextBox) as a page loads. That way they can start typing right away.

While setting the focus to a control can be done by writing custom JavaScript, ASP.NET has a built-in feature
to handle this for you with no scripting code. This is accomplished by using the DefaultFocus property of the
HtmlForm control. By giving it the ID of the control that should have focus, it will automatically handle all of the
behind-the-scenes code to make that happen. Here’s an example of using DefaultFocus.

<form id=”frm” defaultfocus=”txtUserName” runat=”server”>


...
</form>

The Page class also provides a way to programmatically set the focus to a control. Here’s an example of setting
the focus to a control named txtUserName during Page_Load.

C#
protected void Page_Load(object sender, EventArgs e)
{
this.SetFocus(“txtFirstName”);
}

Visual Basic
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Me.SetFocus(“txtFirstName”)
End Sub

Focus can also be set directly on controls by calling the target control’s Focus() method. Here’s an example of
calling a TextBox control’s Focus() method.

C#
txtFirstName.Focus();

Visual Basic
txtFirstName.Focus()

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 5


Tip #5 – Finding ASP.NET Server Controls
ASP.NET 3.5 prefixes nested controls with the ID of the parent control if the parent implements an interface
named INamingContainer. This is done to ensure that unique IDs are rendered in the HTML. For example, the
TextBox with an ID of txtTarget shown next will be given a client-side ID of frmView_txtTarget in the HTML that
is sent to the browser.

<asp:Panel ID=”pnlItems” runat=”server”>


<asp:FormView ID=”frmView” runat=”server”>
<ItemTemplate>
<asp:TextBox ID=”txtTarget” runat=”server” Text=’<%#
Eval(“FirstName”) + “ “ + Eval(“LastName”) %>’ />
</ItemTemplate>
</asp:FormView>
</asp:Panel>

Here’s what the rendered HTML looks like.

<div id=”pnlItems”>
<table cellspacing=”0” border=”0” id=”frmView”
style=”border-collapse:collapse;”>
<tr>
<td colspan=”2”>
<input name=”frmView$txtTarget” type=”text”
value=”Joe Doe” id=”frmView_txtTarget” />
</td>
</tr>
</table>
</div>

You can use this naming convention to your advantage. If you need to set the default focus to the TextBox when
the page first loads, you can add the following code on the <form> element.

<form id=”form1” runat=”server” defaultfocus=”frmView_txtTarget”>


… child controls
</form>

This allows the default focus to be set on a nested control without resorting to writing C# or Visual Basic code in
the code-beside file.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 6


In addition to giving the control a client-side ID of frmView_txtTarget, the control is also given a name of
frmView$txtTarget as well once rendered as HTML (note that the $ character is used instead of the underscore
character). This pattern can be used in conjunction with ASP.NET’s FindControl() method to locate nested con-
trols on the server-side.

FindControl() only locates immediate children of a given parent control. This means that if you search for a Text-
Box within a form but the TextBox is nested inside of another control (such as a FormView or DetailsView), then
FindControl() won’t be able to find it. As a result, FindControl() can be painful to use when controls are deeply
nested within other controls.

In the past, I have used the parentControlID$childControlID pattern with SqlDataSource parameters to identify
TextBox controls nested within a DetailsView control without resorting to writing C# or Visual Basic code. This
type of syntax allows the page to query the parent control and then locate the target child control. This tech-
nique also works with the Page class’s FindControl() method. Using the parentID$childID syntax, FindControl()
will walk the page’s control hierarchy. For example, to find the nested txtTarget TextBox control shown earlier
the following syntax can be used (this example searches from the root of the page).

C#
TextBox tb = this.FindControl(“form1$frmView$txtTarget”) as Text-
Box;
if (tb != null)
{
//Do something with the TextBox
}

Visual Basic
Dim tb As TextBox = TryCast(Me.FindControl(“form1$frmView$txtTarg
et”), _
TextBox)
If tb IsNot Nothing Then
‘Work with TextBox control
End If

This is, of course, a loosely bound operation (quotes are used so the compiler won’t catch typos in the quotes),
so it’s not recommend if a strongly-typed technique is available for a given situation. However, it’s quite useful
in situations where you need to access controls nested in parent controls, such as DetailsView, FormView, etc.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 7


Tip #6 – Build a Recursive FindControl() Extension
Method
Although FindControl() is limited to finding immediate children of a parent control, there’s nothing to stop you
from building a more robust method that can search for deeply nested controls. Both C# 3.0 and Visual Basic 9
allow extension methods to be created that can “extend” an existing class even if you don’t have access to the
class’s source code. By using extension methods you can extend the Page class’s functionality quite easily and
create a recursive FindControl() method. In fact, you can even make the method return a strongly-typed object
so that you don’t have to perform casting operations as with the built-in FindControl() method.

To allow FindControl() to recursively search for children the following type of extension method can be written.

C#
public static class PageHelpers
{
public static T FindControl<T>(this Page page, Control parent-
Container,
string ctlID) where T : Control
{
T childCtl = parentContainer.FindControl(ctlID) as T;
//Control isn’t an immediate child so start a recursive op-
eration
if (childCtl == null)
childCtl = FindChildControl<T>(parentContainer, ctlID);
return childCtl;
}

private static T FindChildControl<T>(Control startingControl,


string ctlID) where T : Control
{
T childCtl = null;
foreach (Control ctl in startingControl.Controls))
{
childCtl = ctl as T; //Attempt to cast to target con-
trol’s type
if (childCtl == null ||
(string.Compare(ctlID, childCtl.ID, true) != 0))
childCtl = FindChildControl<T>(ctl, ctlID);
if (childCtl != null) //Found control so break out of
loop

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 8


break;
}
return childCtl;
}
}

Visual Basic
Public Module PageHelpers
<System.Runtime.CompilerServices.Extension()> _
Public Function FindControl(Of T As Control)(ByVal page As Page,
_
ByVal parentContainer As Control, ByVal ctlID As String) As T
Dim childCtl As T = TryCast(parentContainer.
FindControl(ctlID), T)
‘Control isn’t an immediate child so start a recursive opera-
tion
If childCtl Is Nothing Then
childCtl = FindChildControl(Of T)(parentContainer, ctlID)
End If
Return childCtl
End Function

Private Function FindChildControl(Of T As Control)( _


ByVal startingControl As Control, ByVal ctlID As String) As T
Dim childCtl As T = Nothing
For Each ctl As Control In startingControl.Controls
childCtl = TryCast(ctl, T)
‘Attempt to cast to target control’s type
If childCtl Is Nothing OrElse (String.Compare(ctlID, _
childCtl.ID, True) <> 0) Then
childCtl = FindChildControl(Of T)(ctl, ctlID)
End If
If childCtl IsNot Nothing Then
‘Found control so break out of loop
Exit For
End If
Next
Return childCtl
End Function
End Module

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 9


This code starts by attempting to find the target control in the parent container’s child controls collection using
the built-in FindControl() method. If the control isn’t found, the FindChildControl() method is called recursively
to locate the child based upon its ID. Because generics are used, the method is strongly-typed and automatically
returns the appropriate type of child object if it is found.

To call the extension method the following code can be written. This example looks for a TextBox named txt-
Target.

C#
TextBox txt = this.FindControl<TextBox>(this.Form, “txtTarget”);
if (txt != null)
{
//Work with TextBox control
}

Visual Basic
Dim tb As TextBox = Me.FindControl(Of TextBox)(Me.Form, “txtTarget”)
If tb IsNot Nothing Then
‘Work with TextBox control
End If

Tip #7 - Maintain the Position of the Scrollbar on


Postbacks
When ASP.NET was first released, it was difficult to maintain the position of the scrollbar when doing a post-
back operation without resorting to custom JavaScript. This was especially true when you had a grid on the
page and went to edit a specific row. Instead of staying on the desired row, the page would reload, and you’d
be placed back at the top and have to scroll down. In ASP.NET 2.0 or higher, you can add the MaintainScroll-
PostionOnPostBack attribute to the Page directive to allow ASP.NET to automatically track the position of the
scrollbar.

<%@ Page Language=”C#” MaintainScrollPositionOnPostback=”true”


AutoEventWireup=”true” CodeFile=”...” Inherits=”...” %>

By using this simple trick you can not only be more productive as a developer, but also allow end users to be
more productive since the amount of scrolling that they perform will be reduced.

Tip # 8 – Use ASP.NET Validation Groups


It’s quite common for web-based business applications to have pages with multiple controls and buttons in
them. When one of the buttons is clicked, you want specific ASP.NET Validator controls to be evaluated rather

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 10


than all of the validators defined on the page. ASP.NET 2.0 and higher adds a ValidationGroup property to all
validator controls and buttons (Button, LinkButton, etc.) that allows specific validators to fire while ignoring
others.

If you have a TextBox at the top of a page that has a RequiredFieldValidator next to it and a Button control, you
can fire that single validator when the button is clicked by setting the ValidationGroup property on the button
and on the RequiredFieldValidator to the same value. Any other validators not in the defined ValidationGroup
will be ignored when the button is clicked. Here’s an example of using ValidationGroup.

<form id=”form1” runat=”server”>

Search Text: <asp:TextBox ID=”txtSearch” runat=”server” />

<asp:RequiredFieldValidator ID=”valSearch” runat=”Server”


ControlToValidate=”txtSearch” ValidationGroup=”SearchGroup” />

<asp:Button ID=”btnSearch” runat=”server” Text=”Search”


ValidationGroup=”SearchGroup” />

....
Other controls with validators and buttons defined here
....
</form>

Tip #9 – Use ASP.NET MVC to Automatically Map


Control Values to Object Properties
Although the majority of tips in this white paper focus on ASP.NET Web Forms, there are additional frameworks
that Microsoft supports such as ASP.NET MVC. ASP.NET MVC (Model-View-Controller) is a different way to build
web applications. It focuses on providing a testable framework that allows developers to more easily unit test
different features in an application. Although a complete discussion of ASP.NET MVC is beyond the scope of this
white paper, here’s a quick tip on some of the ways ASP.NET MVC can increase productivity.

Must developers don’t enjoy writing tedious code and are always coming up with ways to minimize and refactor
code where possible. One of the nice features in the ASP.NET MVC framework is the ability to automatically map
control values that are submitted by an end user to customer object properties. With ASP.NET Web Forms, you
find yourself doing the following quite often (although some controls, such as DetailsView when used with the
<%# Bind %> expression, can do some mapping for you).

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 11


C#
protected void btnSubmit_Click(object sender, EventArgs e)
{
Person p = new Person();
p.FirstName = FirstName.Text;
p.LastName = LastName.Text;
UpdatePerson(p);
}

Visual Basic
Protected Sub btnSubmit_Click(sender as Object, e as EventArgs)
Dim p as new Person()
p.FirstName = FirstName.Text
p.LastName = LastName.Text
UpdatePerson(p)
End Sub

This code is a simple example that can quickly get more complex, depending upon the number of controls in
your forms. ASP.NET MVC uses specialized objects called Controllers to serve up pages (called Views) and within
a controller class, you can write methods (called Actions). Actions can capture the form data submitted by an
end user and automatically map it to custom object properties without having to write any mapping code as
shown above. For example, consider the following ASP.NET MVC controls defined with a View named EditCus-
tomerProfile (only a portion of the view is shown for the sake of brevity).

<table width=”640” cellspacing=”5”>


<tr>
<td style=”width:30%”><label for=”Customer.FirstName”>
First Name:</label></td>
<td style=”width:70%”>
<%= Html.TextBox(“Customer.FirstName”,
Model.Customer.FirstName)%>
<%= Html.ValidationMessage(“FirstName”, “*”)%>
</td>
</tr>
<tr>
<td><label for=”Customer.LastName”>Last Name:</label></td>
<td>
<%= Html.TextBox(“Customer.LastName”, Model.Customer.Last-
Name) %>
<%= Html.ValidationMessage(“LastName”, “*”)%>

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 12


</td>
</tr>
<tr>
<td><label for=”Customer.Company”>Company:</label></td>
<td>
<%= Html.TextBox(“Customer.Company”, Model.Customer.Compa-
ny)%>
<%= Html.ValidationMessage(“Company”, “*”)%>
</td>
</tr>
<tr>
<td><label for=”Customer.Phone”>Phone:</label></td>
<td>
<%= Html.TextBox(“Customer.Phone”, Model.Customer.Phone)%>
<%= Html.ValidationMessage(“Phone”, “*”)%>
</td>
</tr>
<tr>
<td colspan=”2”>
<input type=”submit” value=”Update Profile” />&nbsp;&nbsp;
<span class=”StatusMessage”
style=’color:<%= Model.StatusColor %>’>
<%=Model.StatusMessage %>
</span>
</td>
</tr>
</table>

The first thing you’ll notice is that controls are defined differently in ASP.NET MVC than they are in ASP.NET Web
Forms. Examine the ID for each control and notice that they all have “Customer” prefixing the name (Customer.
FirstName, Customer.LastName, Customer.Company, etc.). This is all the mapping code you need. The method
(Action) that handles the posted data simply needs to define a class that has properties that map to the form
fields and the mapping happens automatically. You can even customize the mapping, if desired, or perform map-
ping using built-in controller methods such as UpdateModel().

Here’s what the EditCustomerProfile() method (Action) that handles the posted data looks like. You can see that
it accepts a Customer parameter named customer that will automatically receive the posted control values and
update the appropriate properties. The parameter name is important here since that is what makes the auto-
mapping possible (recall that each control was prefixed with “Customer”, case doesn’t matter here). There’s a
lot more that could be said about this particular topic, but this should give you an idea of how the auto-map-
ping works in ASP.NET MVC and how it can save time by minimizing code.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 13


C#
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditCustomerProfile(Customer customer)
{
try
{
if (customer.IsCustomerValid)
{
OperationStatus opStatus =_CustomerRepository.
Update(customer);
if (opStatus.Status)
{
ViewData[“Status”] = “true”;
return View(“EditCustomerForm”,
new CustomerViewModel(customer,”Navy”,
“Your profile information was saved.”));
}
}
}
catch
{ //Log
}
ViewData[“Status”] = “false”;
ModelState.AddModelErrors(customer.GetRuleViolations());
return View(“EditCustomerForm”, new
CustomerViewModel(customer, “Red”,
“There was a problem updating your profile.”));
}

Visual Basic
<AcceptVerbs(HttpVerbs.Post)> _
Public Function EditCustomerProfile(customer As Customer) As Ac-
tionResult
Try
If customer.IsCustomerValid Then
Dim opStatus As OperationStatus = _
_CustomerRepository.Update(customer)
If opStatus.Status = True Then
Return View(“EditCustomerForm”, _
New CustomerViewModel(customer, “Navy”, _

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 14


“Your profile information was saved.”))
End If
End If
Catch
‘Log
End Try
ModelState.AddModelErrors(customer.GetRuleViolations())
Return View(“EditCustomerForm”, _
New CustomerViewModel(customer, “Red”, _
“There was a problem updating your profile.”))
End Function

Tip #10 – Use JavaScript Libraries in AJAX-Enabled


Applications
Rich Internet Applications (RIAS) are quite popular nowadays since they provide end users with a more desktop-
like experience right in the browser. ASP.NET 3.5 has built-in AJAX features (see http://www.asp.net/ajax for
more details) that can be used to minimize postbacks and allow users to access data more quickly compared
to traditional web applications. Out of the box, you can use controls such as the UpdatePanel to easily AJAX-
enable an ASP.NET application without being forced into writing a lot of code. The ASP.NET AJAX script library
also includes several short-cut JavaScript functions that can be used when writing client-side code.

One of the most useful script library functions is $get(). It’s used to locate client-side objects loaded into the
DOM (Document Object Model) in the browser and is really just a wrapper around document.getElementById()
that is normally used to locate objects. By using $get() you can easily locate objects in a more compact manner.

var div = $get(‘divCustomers’);

The only problem with $get() is that you have to know the exact ID of the object you’d like to locate. What if you
don’t know the object’s ID, but do know the name of the CSS class that the object uses?, $get() can’t be used in
those situations, unfortunately.

In situations where you need more flexibility in writing client-side code and finding DOM objects it’s recom-
mended that you use freely available script libraries such as Prototype (http://www.prototypejs.org) or jQuery
(http://www.jQuery.com). By using these script libraries, you can significantly minimize the amount of client-
side code that has to be written. I personally use jQuery in nearly every client project that I work on due to the
amount of functionality that it provides. For example, if I need to locate all objects that reference a CSS class
named “BigText” and change the HTML content to “This object uses the BigText CSS Class” I can write the fol-
lowing code using jQuery.

$(‘.BigText’).html(‘This object uses the BigText CSS class’);

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 15


This code uses a jQuery selector to locate objects using the BigText CSS class. The .BigText portion of the code is
the “selector” with the period character instructing jQuery to locate objects that have that specific CSS class. IDs
can also be selected as well (much like $get()), although you use the # character. Here’s an example of selecting
a TextBox named txtFirstName and changing the value of it to “John”.

$(‘#txtFirstName’).val(‘John’);

There’s much more that can be said about ASP.NET AJAX, jQuery, and other script libraries, but the main point is
that you can increase your productivity while writing AJAX-enabled applications by using the functionality that
these script libraries include.

Summary
ASP.NET provides a powerful framework that can be used to create rich and robust web applications. In this
white paper, you’ve seen several tips that can be applied to the development process to minimize code, increase
productivity, and simplify future application maintenance. Whether it’s locating controls, working with AJAX, or
even using the new ASP.NET MVC framework, these tips can be applied quickly and easily without a large learn-
ing curve.

Learn More
Learn more about how you can improve productivity, enhance efficiency, and sharpen your competitive edge.
Check out the following Global Knowledge courses:
Advanced Web Application Technologies with Microsoft Visual Studio 2005
Core Web Application Technologies with Microsoft Visual Studio 2005
Introduction to Programming Microsoft .NET Applications with Microsoft Visual Studio 2005
Web Application Technologies with Microsoft Visual Studio 2005
Developing Microsoft ASP.NET Web Applications Using Visual Studio .NET
Visual Studio 2008: ASP.NET 3.5

For more information or to register, visit www.globalknowledge.com or call 1-800-COURSES to speak with a
sales representative.

Our courses and enhanced, hands-on labs offer practical skills and tips that you can immediately put to use. Our
expert instructors draw upon their experiences to help you understand key concepts and how to apply them to
your specific work situation. Choose from our more than 700 courses, delivered through Classrooms, e-Learning,
and On-site sessions, to meet your IT and management training needs.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 16


About the Author
Dan Wahlin (Microsoft Most Valuable Professional for ASP.NET and XML Web Services) founded The Wahlin
Group (http://www.TheWahlinGroup.com) which specializes in .NET, Silverlight, and SharePoint consulting and
training solutions. He also created the XML for ASP.NET Developers Web site (http://www.xmlforasp.net), which
focuses on using ASP.NET, XML, AJAX, Silverlight, and Web Services in Microsoft’s .NET platform. Dan is on the
INETA Speaker’s Bureau, speaks at conferences and user groups around the world, and is recognized as an au-
thority on .NET technologies. Dan has written several books on .NET, including Professional Silverlight 2 for ASP.
NET Developers, Professional ASP.NET 3.5 AJAX, ASP.NET 2.0 MVP Hacks and Tips, and XML for ASP.NET Devel-
opers. Dan blogs at http://weblogs.asp.net/dwahlin.

Copyright ©2009 Global Knowledge Training LLC. All rights reserved. 17

S-ar putea să vă placă și