Export (0) Print
Expand All
Expand Minimize

How To: Protect From Injection Attacks in ASP.NET

 
Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

patterns & practices Developer Center

patterns & practices Developer Center

J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley

Microsoft Corporation

May 2005

Applies To

  • ASP.NET version 1.1
  • ASP.NET version 2.0

Summary

This How To shows how you can validate input to protect your application from injection attacks. Performing input validation is essential because almost all application-level attacks contain malicious input.

You should validate all input, including form fields, query string parameters, and cookies to protect your application against malicious command injection. Assume all input to your Web application is malicious, and make sure that you use server validation for all sources of input. Use client-side validation to reduce round trips to the server and to improve the user experience, but do not rely on it because it is easily bypassed.

To validate input, define acceptable input for each application input field. A proven practice is to constrain input for length, range, format, and type. Use the list of acceptable characters to define valid input, instead of the list of unacceptable characters. Using the list of unacceptable characters is impractical because it is very difficult to anticipate all possible variations of bad input.

When you need to accept a range of HTML characters, make sure that you HTML-encode the data to make it safe prior to displaying it as output.

Contents

Objectives
Overview
Summary of Steps
Step 1. Use ASP.NET Request Validation
Step 2. Constrain Input
Step 3. Encode Unsafe Output
Step 4. Use Command Parameters for SQL Queries
Step 5. Verify that ASP.NET Errors Are Not Returned to the Client
Additional Resources

Objectives

  • Constrain input for length, range, format, and type.
  • Apply ASP.NET request validation during development to identify injection attacks.
  • Constrain input by using ASP.NET validator controls.
  • Encode unsafe output.
  • Help prevent SQL injection by using command parameters.
  • Prevent detailed error information from returning to the client.

Overview

You need to validate all untrusted input to your application. You should assume that any input from users is malicious. User input to your Web application includes form fields, query strings, client-side cookies, and browser environment values such as user agent strings and IP addresses.

Weak input validation is a common vulnerability that could allow your application to be exploited by a number of injection attacks. The following are common types of attacks that exploit weak or missing input validation:

  • SQL injection. If you generate dynamic SQL queries based on user input, an attacker could inject malicious SQL commands that can be executed by the database.
  • Cross-site scripting. Cross-site scripting (XSS) attacks exploit vulnerabilities in Web page validation by injecting client-side script code. This code is subsequently sent to an unsuspecting user's computer and executed on the browser. Because the browser downloads the script code from a trusted site, the browser has no way of determining whether the code is legitimate.
  • Unauthorized file access. If your code accepts input from a caller, a malicious user could potentially manipulate your code's file operations, such as accessing a file they should not access or exploiting your code by injecting bad data.
Note   Injection attacks work over HTTP and HTTPS Secure Socket Layer (SSL) connections. Encryption provides no defense.

The general approach for input validation is summarized here. You should apply this approach to any input that comes from the network, such as text boxes and other forms field input, query string parameters, cookies, server variables, and Web method parameters. Note that the strategy is to first allow only good input and then deny bad input. This is because you can easily define good input for your application, but you cannot realistically anticipate the format for all malicious input.

Check for valid input as follows:

  • Constrain: Check for known good data by validating the type, length, format, and range. To constrain input from server controls, use the ASP.NET validator controls. To constrain input from other sources, use regular expressions and custom validation.
  • Reject: Check for any known bad data and reject bad input.
  • Sanitize: Sometimes you also need to sanitize input and make potentially malicious input safe. For example, if your application supports free-format input fields, such as comment fields, you might want to permit certain safe HTML elements, such as <b> and <i>, and eliminate any other HTML elements.

Summary of Steps

To protect your ASP.NET application from injection attacks, perform the following steps:

  • Step 1. Use ASP.NET request validation.
  • Step 2. Constrain input.
  • Step 3. Encode unsafe output.
  • Step 4. Use command parameters for SQL queries.
  • Step 5. Verify that ASP.NET errors are not returned to the client.

The next sections describe each of these.

Step 1. Use ASP.NET Request Validation

By default, ASP.NET versions 1.1 and 2.0 request validation detects any HTML elements and reserved characters in data posted to the server. This helps prevent users from inserting script into your application. Request validation checks all input data against a hard-coded list of potentially dangerous values. If a match occurs, it throws an exception of type HttpRequestValidationException.

You can disable request validation in your Web.config application configuration file by adding a <pages> element with validateRequest="false" or on an individual page by setting ValidateRequest="false" on the @ Pages element.

If you need to disable request validation, you should disable it only on the affected page. An example of this is when you have a page with a free-format text field that accepts HTML-formatted input.

Confirm that ASP.NET Request Validation Is Enabled in Machine.config

Request validation is enabled by ASP.NET by default. You can see the following default setting in the Machine.config.comments file.

<pages validateRequest="true" ... />
  

Confirm that you have not disabled request validation by overriding the default settings in your server's Machine.config file or your application's Web.config file.

Test ASP.NET Request Validation

You can test the effects of request validation. To do this, create an ASP.NET page that disables request validation by setting ValidateRequest="false", as follows.

<%@ Language="C#" ValidateRequest="false" %>
<html>
 <script runat="server">
  void btnSubmit_Click(Object sender, EventArgs e)
  {
    // If ValidateRequest is false, then 'hello' is displayed
    // If ValidateRequest is true, then ASP.NET returns an exception
    Response.Write(txtString.Text);
  }
 </script>
 <body>
  <form id="form1" runat="server">
    <asp:TextBox id="txtString" runat="server" 
                 Text="<script>alert('hello');</script>" />
    <asp:Button id="btnSubmit" runat="server" OnClick="btnSubmit_Click" 
                 Text="Submit" />
  </form>
 </body>
</html>
  

When you run the page, "Hello" is displayed in a message box because the script in txtString is passed through and rendered as client-side script in your browser.

If you set ValidateRequest="true" or remove the ValidateRequest page attribute, ASP.NET request validation rejects the script input and produces an error similar to the following.

A potentially dangerous Request.Form value was detected from the client (txtString="<script>alert('hello...").
  
Note   Do not rely on ASP.NET request validation. Treat it as an extra precautionary measure in addition to your own input validation.

Step 2. Constrain Input

To constrain input, follow these guidelines:

  • Use server-side input validation. Do not rely on client-side validation because it is easily bypassed. Use client-side validation in addition to server-side validation to reduce round trips to the server and to improve the user experience.
  • Validate length, range, format and type. Make sure that any input meets your guidelines for known good input.
  • Use strong data typing. Assign numeric values to numeric data types such as Integer or Double. Assign string values to string data types. Assign dates to the DateTime data type.

For Web form applications that obtain input through server controls, use the ASP.NET validator controls to constrain the input. For other sources of input data, such as query strings, cookies, and HTTP headers, constrain input by using the Regex class from the System.Text.RegularExpressions namespace.

Explicitly Check Input from Form Fields

To constrain form field input received through server controls, you can use the following ASP.NET validator controls:

  • RegularExpressionValidator. Use this control to constrain text input.
  • RangeValidator. Use this control to check the ranges of numeric, currency, date, and string input.
  • CustomValidator. Use this control for custom validation, such as ensuring that a date is in the future or in the past.

To validate form field input received through HTML input controls, perform validation in server-side code and use the Regex class to help constrain text input. The following sections describe how to constrain a variety of common input types.

Validating Text Fields

  • To validate text fields, such as names, addresses, and tax identification numbers, use regular expressions to do the following:
  • Constrain the acceptable range of input characters.
  • Apply formatting rules. For example, pattern-based fields, such as tax identification numbers, ZIP Codes, or postal codes, require specific patterns of input characters.
  • Check lengths.

Using a RegularExpressionValidator

To use a RegularExpressionValidator, set the ControlToValidate, ValidationExpression, and ErrorMessage properties to appropriate values as shown in the following example.

<form id="WebForm" method="post" runat="server">
  <asp:TextBox id="txtName" runat="server"></asp:TextBox>
  <asp:RegularExpressionValidator id="nameRegex" runat="server" 
        ControlToValidate="txtName" 
        ValidationExpression="^[a-zA-Z'.\s]{1,40}$" 
        ErrorMessage="Invalid name">
  </asp:regularexpressionvalidator>
</form>
  

The regular expression used in the preceding code example limits an input name field to alphabetic characters (lowercase and uppercase), space characters, the single apostrophe for names such as O'Dell, and the period. In addition, the field length is constrained to 40 characters.

Note   The RegularExpressionValidator control automatically adds a caret (^) and dollar sign ($) as delimiters to the beginning and end of expressions if you have not added them yourself. You should add them to all of your regular expressions as good practice. Enclosing the expression in the delimiters ensures that the expression consists of the desired content and nothing else.

Using the Regex Class

If you are not using server controls (which means you cannot use the validator controls), or you need to validate input from sources other than form fields (such as from query string parameters or cookies), you can use a Regex class.

To use the Regex class

  1. Add a using statement to reference the System.Text.RegularExpressions namespace.
  2. Ensure that the regular expression is contained in the ^ and $ anchor characters (beginning of string, end of string).
  3. Call the IsMatch method of the Regex class, as shown in the following code example.
// Instance method:
Regex reg = new Regex(@"^[a-zA-Z'.\s]{1,40}$");
Response.Write(reg.IsMatch(txtName.Text));

// Static method:
if (!Regex.IsMatch(txtName.Text,@"^[a-zA-Z'.\s]{1,40}$")) 
{
  // Name does not match expression
}
  

If you cannot cache your regular expression for frequent use, you should use the static IsMatch method where possible for performance reasons, to avoid unnecessary object creation.

Validating Numeric Fields

In most cases, numeric fields should be checked for type and range. To validate the type and range of a numeric input field that uses a server control, you can use a RangeValidator control. The RangeValidator supports currency, date, integer, double, and string data types.

To use a RangeValidator, set the ControlToValidate, Type, MinimumValue, MaximumValue, and ErrorMessage properties to appropriate values as shown in the following example.

<asp:RangeValidator 
       ID="RangeValidator1" 
       Runat="server" 
       ErrorMessage="Invalid range. Number must be between 0 and 255."
       ControlToValidate="rangeInput" 
       MaximumValue="255" 
       MinimumValue="0" Type="Integer" />
  

If you are not using a server control, you can validate a numeric range by converting the input value to an integer and then performing a range check. For example, to validate that an integer is valid, convert the input value to a variable of type System.Int32 by using the new Int32.TryParse method (introduced in the Microsoft .NET Framework version 2.0). This method returns false if the type conversion fails.

Int32 i;
if (Int32.TryParse(txtInput.Text, out i) == false)
{
  // Conversion failed
}
  

If you are using an earlier version of the .NET Framework, you can use Int32.Parse or Convert.ToInt32 inside a try/catch block and handle any exceptions of type FormatException that are thrown if the input value is not the correct type.

The following code shows how to perform a type and range check for an integer entered through an HTML text input control.

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

<script runat="server">

  void Page_Load(object sender, EventArgs e)
  {
    if (Request.RequestType == "POST")
    {
      int i;
      if (Int32.TryParse(Request.Form["integerTxt"], out i) == true)
      {
        // TryParse returns true if the conversion succeeds
        if ((0 <= i && i <= 255) == true)
        {
          Response.Write("Input data is valid.");
        }
        else
          Response.Write("Input data is out of range");
      }
      else
        Response.Write("Input data is not an integer");
    }
  }
   
</script>

<html>
  <body>
    <form id="form1" action="NumericInput.aspx" method="post">
      <div>
        Enter an integer between 0 and 255:
        <input name="integerTxt" type="text" />
        <input name="Submit" type="submit" value="submit" />
      </div>
    </form>
  </body>
</html>
  

Validating Date Fields

You need to validate that date fields are of the correct type. In most cases, you also need to check them for range, for example to validate that they are in the future or past. If you use a server control to capture an input date, and if you also need to validate that a date falls within a specific range, you can use a RangeValidator control with its Type field set to Date. This control lets you specify a range by using constant date values. If you need to validate a date range based on today's date, for example to validate that a date is in the future or the past, you can use a CustomValidator control.

To use a CustomValidator control to validate a date, set the ControlToValidate and ErrorMessage properties and the OnServerValidate event to point to a custom method containing your validation logic. The following sample .aspx page code shows this approach.

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

<script runat="server">

 void ValidateDateInFuture(object source, ServerValidateEventArgs args)
 {
   DateTime dt;

   // Check for valid date and that the date is in the future
   if ((DateTime.TryParse(args.Value, out dt) == false) || 
       (dt <= DateTime.Today))
   {
     args.IsValid = false;
   }
 }

</script>

<html>
  <body>
    <form id="form1" runat="server">
      <div>
        <asp:Label ID="Label1" Runat="server" 
                   Text="Future Date:"></asp:Label>
        <asp:TextBox ID="futureDatetxt" Runat="server"></asp:TextBox>
        <asp:CustomValidator 
              ID="CustomValidator1" Runat="server" 
              ErrorMessage="Invalid date. Enter a date in the future."
              ControlToValidate="futureDatetxt"  
              OnServerValidate="ValidateDateInFuture">
        </asp:CustomValidator>
        <br />
        <asp:Button ID="submitBtn" Runat="server" Text="Submit"  />
      </div>
    </form>
  </body>
</html>
  
Note   The preceding code uses DateTime.TryParse, which is new to .NET Framework 2.0.

Sanitizing Free-Text Fields

To sanitize input, you make untrusted input safe by preventing it from being treated as code. For example, if your application handles user input that it cannot constrain or reads data from a shared database, you might need to sanitize the data or make the output safe when you write it on your page. Sanitize data prior to output by using HttpUtility.HtmlEncode.

Allowing Restricted HTML Input

If your application needs to accept a range of HTML elements—for example through a rich text input field such as a comments field—turn off ASP.NET request validation and create a filter that allows only the HTML elements that you want your application to accept. A common practice is to restrict formatting to safe HTML elements such as <b> (bold) and <i> (italic). Before writing the data, HTML-encode it. This makes any malicious script safe by causing it to be handled as text, not as executable code.

To allow restricted HTML input

  1. Disable ASP.NET request validation by the adding the ValidateRequest="false" attribute to the @ Page directive.
  2. Encode the string input with the HtmlEncode method.
  3. Use a StringBuilder and call its Replace method to selectively remove the encoding on the HTML elements that you want to permit.

The following .aspx page code shows this approach. The page disables ASP.NET request validation by setting ValidateRequest="false". It HTML-encodes the input and selectively allows the <b> and <i> HTML elements to support simple text formatting.

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

<script runat="server">

  void submitBtn_Click(object sender, EventArgs e)
  {
    // Encode the string input
    StringBuilder sb = new StringBuilder(
                            HttpUtility.HtmlEncode(htmlInputTxt.Text));
    // Selectively allow  and <i>
    sb.Replace("&lt;b&gt;", "<b>");
    sb.Replace("&lt;/b&gt;", "");
    sb.Replace("&lt;i&gt;", "<i>");
    sb.Replace("&lt;/i&gt;", "");
    Response.Write(sb.ToString());
  }
</script>

<html>
  <body>
    <form id="form1" runat="server">
      <div>
        <asp:TextBox ID="htmlInputTxt" Runat="server" 
                     TextMode="MultiLine" Width="318px"
                     Height="168px"></asp:TextBox>
        <asp:Button ID="submitBtn" Runat="server" 
                     Text="Submit" OnClick="submitBtn_Click" />
      </div>
    </form>
  </body>
</html>
  

Validate Query String Values

Validate query string values for length, range, format, and type. You usually do this by using a combination of regular expressions to:

  • Constrain the input values.
  • Set explicit range checks.
  • Specify the explicit type checks performed by converting the input value to its equivalent .NET Framework type and handling any ensuing conversion errors.

The following code example shows how to use the Regex class to validate a name string passed on a query string.

void Page_Load(object sender, EventArgs e)
{
  if (!System.Text.RegularExpressions.Regex.IsMatch(
       Request.QueryString["Name"], @"^[a-zA-Z'.\s]{1,40}$"))
    Response.Write("Invalid name parameter");
  else
    Response.Write("Name is " + Request.QueryString["Name"]);
}
  

Validate Cookie Values

Values maintained in cookies, such as query string parameters, can easily be manipulated by a client. Validate cookie values in the same way as you would for query string parameters. Validate them for length, range, format, and type.

Validate File and URL Paths

If your application has to accept input file names, file paths, or URL paths, you need to validate that the path is in the correct format and that it points to a valid location within the context of your application. Failure to do this can result in attackers persuading your application into accessing arbitrary files and resources.

Validating File Paths

To prevent a malicious user manipulating your code's file operations, avoid writing code that accepts user-supplied file or path input. For example:

  • If you must accept file names as input, use the full name of the file by using System.IO.Path.GetFileName.
  • If you must accept file paths as input, use the full file path by using System.IO.Path.GetFullPath.

Using MapPath to Prevent Cross Application Mapping

If you use MapPath to map a supplied virtual path to a physical path on the server, use the overload of Request.MapPath that accepts a bool parameter so that you can prevent cross-application mapping. The following code example shows this technique.

try
{
  string mappedPath = Request.MapPath( inputPath.Text, 
                                       Request.ApplicationPath, false);
}
catch (HttpException)
{
  // Cross-application mapping attempted
}
  

The final false parameter prevents cross-application mapping. This means that a user cannot successfully supply a path that contains ".." to traverse outside of your application's virtual directory hierarchy. Any attempt to do this results in an exception of type HttpException.

If you use server controls, you can use the Control.MapPathSecure method to retrieve the physical path to which the virtual path is mapped. Control.MapPathSecure uses code access security and throws an HttpException if the server control does not have permissions to read the resulting mapped file. For more information, see Control.MapPathSecure in the .NET Framework SDK documentation.

Using Code Access Security to Restrict File I/O

An administrator can restrict an application's file I/O to its own virtual directory hierarchy by configuring the application to run with Medium trust. In this event, .NET code access security ensures that no file access is permitted outside of the application's virtual directory hierarchy.

You configure an application to run with Medium trust by setting the <trust> element in Web.config or Machine.config.

<trust level="Medium" />
  

Validating URLs

You can filter for a valid URL format using a regular expression, such as the following.

^(?:http|https|ftp)://[a-zA-Z0-9\.\-]+(?:\:\d{1,5})?(?:[A-Za-z0-9\.\;\:\@\&\=\+\$\,\?/]|%u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2})*$
  

This constrains the input, but it does not validate whether the URL is valid in terms of the application boundaries. You should check whether the target is valid in the context of your application. For example, does it point to an authorized server that you expect your application to communicate with?

Step 3. Encode Unsafe Output

If you write text output to a Web page, encode it using HttpUtility.HtmlEncode. Do this if the text came from user input, a database, or a local file.

Similarly, if you write URLs that might contain unsafe characters because they have been constructed from input data or data from a shared database, use HttpUtilty.UrlEncode to make them safe.

Avoid the mistake of encoding the data early. Make sure you encode at the last possible opportunity before the data is displayed to the client.

Use HtmlEncode to Encode Unsafe Output

The HtmlEncode method replaces characters that have special meaning in HTML to HTML variables that represent those characters. For example, < is replaced with &lt; and " is replaced with &quot;. Encoded data does not cause the browser to execute code. Instead, the data is rendered as harmless text, and the tags are not interpreted as HTML.

To illustrate the use of HtmlEncode, the following page accepts input from the user and allows potentially unsafe HTML characters by setting ValidateRequest="false". Before writing the input back to the user, the code calls HttpUtility.HtmlEncode on the supplied input text. This renders any potentially unsafe HTML as harmless text.

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

<script runat="server">
  void submitBtn_Click(object sender, EventArgs e)
  {
      Response.Write(HttpUtility.HtmlEncode(inputTxt.Text));
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
  <body>
    <form id="form1" runat="server">
      <div>
        <asp:TextBox ID="inputTxt" Runat="server" 
             TextMode="MultiLine" Width="382px" Height="152px">
        </asp:TextBox>
        <asp:Button ID="submitBtn" Runat="server" Text="Submit" 
                    OnClick="submitBtn_Click" />
      </div>
    </form>
  </body>
</html>
  

To see the effect of the HTML encoding, place the preceding page in a virtual directory, browse to it, enter some HTML code in the input text box, and click Submit. For example, the following input is rendered as text.

Run script and say hello <script>alert('hello');</script>
  

It produces the following safe output.

Run script and say hello <script>alert('hello');</script>
  

If you remove the call to HtmlEncode and simply write back the input, the browser executes the script and displays a message box. Malicious script could pose a significant threat.

Use UrlEncode to Encode Unsafe URLs

If you need to write URLs that are based on input that you do not fully trust, use HttpUtility.UrlEncode to encode the URL string.

HttpUtility.UrlEncode( urlString );
  

Step 4. Use Command Parameters for SQL Queries

To help prevent SQL injection, use command parameters for SQL queries. The Parameters collection provides type checking and length validation. If you use the Parameters collection, input is treated as a literal value and SQL does not treat it as executable code. An additional benefit of using the Parameters collection is that you can enforce type and length checks. Values outside of the range trigger an exception.

Use Parameters Collection When You Call a Stored Procedure

The following code fragment illustrates the use of the Parameters collection when calling a stored procedure.

SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", 
                                     myConnection);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                                       "@LoginId", SqlDbType.VarChar, 11);
parm.Value = Login.Text;
  

Use Parameters Collection When Building Your SQL Statements

If you cannot use stored procedures, you can still use parameters, as shown in the following code fragment.

SqlDataAdapter myCommand = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", myConnection);
SQLParameter parm = myCommand.SelectCommand.Parameters.Add(
                           "@au_id" ,SqlDbType.VarChar, 11);
Parm.Value = Login.Text;
  

For more information about how to prevent SQL injection, see How To: Protect From SQL Injection in ASP.NET.

Step 5. Verify that ASP.NET Errors Are Not Returned to the Client

You can use the <customErrors> element to configure custom, generic error messages that should be returned to the client in the event of an application exception condition.

Make sure that the mode attribute is set to "remoteOnly" in the web.config file as shown in the following example.

<customErrors mode="remoteOnly" />
  

After installing an ASP.NET application, you can configure the setting to point to your custom error page as shown in the following example.

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />
  

Additional Resources

For more information about how to protect your application from injection attacks, see the following documents:

Feedback

Provide feedback by using either a Wiki or e-mail:

We are particularly interested in feedback regarding the following:

  • Technical issues specific to our recommendations
  • Usefulness and usability issues

Technical Support

Technical support for the Microsoft products and technologies referenced in this guidance is provided by Microsoft Support Services. For support information, please visit the Microsoft Support Web site at http://support.microsoft.com.

Community and Newsgroups

Community support is provided in the forums and newsgroups:

  • MSDN Newsgroups: http://msdn.microsoft.com/newsgroups/default.asp
  • ASP.NET Forums: http://forums.asp.net

To get the most benefit, find the newsgroup that corresponds to your technology or problem. For example, if you have a problem with ASP.NET security features, you should use the ASP.NET Security forum.

Contributors and Reviewers

  • External Contributors and Reviewers: Andy Eunson; Chris Ullman, Content Master; David Raphael, Foundstone Professional Services, Rudolph Araujo, Foundstone Professional Services; Manoranjan M. Paul
  • Microsoft Consulting Services and PSS Contributors and Reviewers: Adam Semel, Nobuyuki Akama, Tom Christian, Wade Mascia
  • Microsoft Product Group Contributors and Reviewers: Stefan Schackow, Vikas Malhotra, Microsoft Corporation
  • MSDN Contributors and Reviewers: Kent Sharkey, Microsoft Corporation
  • Microsoft IT Contributors and Reviewers: Eric Rachner, Shawn Veney (ACE Team), Microsoft Corporation
  • Test team: Larry Brader, Microsoft Corporation; Nadupalli Venkata Surya Sateesh, Sivanthapatham Shanmugasundaram, Sameer Tarey, Infosys Technologies Ltd
  • Edit team: Nelly Delgado, Microsoft Corporation; Tina Burden McGrayne, Linda Werner & Associates Inc
  • Release Management: Sanjeev Garg, Satyam Computer Services

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Show:
© 2014 Microsoft