Saturday, July 5, 2008

ASP.Net Sep 3

ASP.NET's @ OutputCache directive lets me cache different versions of a page based on varying input parameters, HTTP headers, and browser types. I'd also like to be able to cache based on varying session IDs. Is that possible?
You bet. Here's a sample page that uses a VaryByCustom attribute to cache different versions of a page based on session IDs:

<%@ Page Language="C#" %>
<%@ OutputCache Duration="10" VaryByParam="None" VaryByCustom="SessionID" %>

<% Session["MyData"] = "Foo"; // Make the session persistent Response.Write ("Your session ID is " + Session.SessionID); Response.Write ("
");
Response.Write ("The current time is " + DateTime.Now.ToLongTimeString ());
%>

In order for VaryByCustom="SessionID" to work, you must include in the application root a Global.asax file containing the following GetVaryByCustomString method:


GetVaryByCustomString is a mechanism for extending ASP.NET's page output cache. This implementation of GetVaryByCustomString, which overrides the one inherited from HttpApplication, responds to a VaryByCustom="SessionID" attribute in an @ OutputCache directive by returning the current session ID, if present. ASP.NET responds by caching different versions of the page if the session IDs differ.
Note that GetVaryByCustomString extracts the session ID from the session cookie, not from the session's SessionID property. That's because the request has yet to be associated with a session when GetVaryByCustomString is called. An unpleasant side effect is that this technique doesn't work with cookieless session state. Also note that the page won't be cached until a user requests the page for the second time, because the first request lacks a valid session cookie.

I've noticed that DataGrids round-trip tons of information in view state, decreasing the effective bandwidth of the connection. Is there anything I can do to reduce the DataGrid's view state usage?
You bet. Set the DataGrid's EnableViewState property to false, as shown here:

Once you make this change, you'll also have to reinitialize the DataGrid in every request (even during postbacks), because the DataGrid will no longer retain its state across postbacks. In other words, instead of doing this:

void Page_Load (Object sender, EventArgs e)
{
if (!IsPostBack) {
// TODO: Initialize the DataGrid
}
}

Do this:
void Page_Load (Object sender, EventArgs e)
{
// TODO: Initialize the DataGrid
}

The performance you lose may be more than compensated for by the effective bandwidth you gain--especially if instead of querying a database on every request, you query once, cache the data, and initialize the DataGrid from the cache.
Is it possible to create a DataGrid that uses scrolling rather than paging to provide access to a long list of items?
With a little help from a
tag, yes. The following ASPX file displays the "Products" table of SQL Server's Northwind database in a scrolling table:

<%@ Import Namespace="System.Data.SqlClient" %>

By default, ASP.NET's worker process uses the identity of a special account named ASPNET that's created when ASP.NET is installed. By including a user name and password in Machine.config's element, I can configure ASP.NET to run using an account of my choosing. However, my company has a strict policy against encoding plaintext passwords in configuration files. I want to specify the account that ASP.NET uses, but I also need to secure the password. Is that possible?
Out of the box, ASP.NET 1.0 requires the user name and password to be encoded in plaintext in Machine.config. However, you can obtain a patch from Microsoft that allows the user name and password to be stored in encrypted form in a secure registry key that's off limits to non-administrators. The hotfix also allows you to secure the credentials used to access ASP.NET's ASPState database when using SQL Server to store session state.

You'll find instructions for obtaining the hotfix (and information about a helper utility that encrypts user names and passwords for you) at http://support.microsoft.com/default.aspx?scid=kb;en-us;Q329250. Microsoft plans to build the hotfix into the "Everett" release of the .NET Framework, which will ship with Windows .NET Server. Be aware, however, that Windows .NET Server comes with IIS 6.0, which supports an alternative means for securing process credentials. Applications can be assigned to arbitrary application pools, and application pools can be assigned unique identities using encrypted credentials stored in the IIS metabase. As Microsoft security guru Erik Olsen recently noted, this is probably the better long-term direction for companies whose policies prevent them from storing plaintext credentials in CONFIG files.

I want to make DataGrid paging more efficient by using custom paging. Oracle makes custom paging easy by supporting query-by-row-number. SQL Server is just the opposite. There's no obvious way to ask SQL Server for, say, rows 101-150 in a 500-row result set. What's the best way to do custom paging when you have SQL Server on the back end?
You can use a query of the following form to retrieve records by row number from Microsoft SQL Server:


SELECT * FROM
(SELECT TOP {0} * FROM
(SELECT TOP {1} * FROM {2}
ORDER BY {3}) AS t1
ORDER BY {3} DESC) AS t2
ORDER BY {3}

Replace {0} with the page size (the number of records displayed on each page), {1} with the page size * page number (1-based), {2} with the name of the table you wish to query, and {3} with a field name. The following example retrieves rows 41-50 from the "Products" table of the Northwind database:


SELECT * FROM
(SELECT TOP 10 * FROM
(SELECT TOP 50 * FROM Products
ORDER BY ProductID) AS t1
ORDER BY ProductID DESC) AS t2
ORDER BY ProductID


You can combine this query technique with custom paging to make DataGrid paging more efficient. With default paging, you must initialize the data source with all records displayed on all pages. With custom paging, you can initialize the data source with just those records that pertain to the current page.
Is it possible to prevent a Web form from scrolling to the top of the page when it posts back to the server?
One way to do it is to add a SmartNavigation="true" attribute to the page's @ Page directive. That requires Internet Explorer 5.0 or higher on the client. To prevent unwanted scrolling in a wider range of browsers, you can use a server-side script that generates client-side script. The first step is to replace the page's tag with the following statements:


<% if (Request["__SCROLLPOS"] != null && Request["__SCROLLPOS"] != String.Empty) { int pos = Convert.ToInt32 (Request["__SCROLLPOS"]); Response.Write ("");
}
else {
Response.Write ("");
}
%>
Step two is to add the following line somewhere between the
and
tags:

How does it work? The server-side script block outputs a tag containing an onscroll attribute that keeps tabs on the scroll position and an onload attribute that restores the last scroll position following a postback. The scroll position is transmitted to the server in a hidden control named __SCROLLPOS. Note that this technique is compatible with Internet Explorer but not with Netscape Navigator.

If I use the same user control in two different pages and include an @ OutputCache directive in the ASCX file, will the user control be cached once or twice?
In ASP.NET version 1.0, the control will be cached twice. In version 1.1, you can include a Shared="true" attribute in the @ OutputCache directive to cache the control just once.

What's the best source of in-depth information on ASP.NET security?
Go to http://www.microsoft.com/downloads/release.asp?ReleaseID=44047 and download a free book (in PDF format) from Microsoft entitled "Building Secure ASP.NET Applications." At 608 pages, it's packed with more than you'll probably ever need to know about ASP.NET security. An awesome resource for ASP.NET developers!

How do I set the focus to a specific control when a Web form loads?
With a dash of client-side script. Upon loading, the following page sets the focus to the first of its three TextBox controls:


Is it possible to post a page containing a runat="server" form to a page other than itself? Adding an action attribute to the tag is futile because ASP.NET overrides it with an action attribute that points back to the same page.
ASP.NET forbids a runat="server" form from posting back to another page, but you may be able to accomplish what you're after by taking a slightly different approach. It turns out that if you remove runat="server" from the tag, ASP.NET won't alter the tag's action attribute. The bad news is that a form lacking a runat="server" attribute can't host Web controls. The good news is that it can host HTML controls, which means that if you can do without DataGrids and other rich controls, you can post back to other pages just fine.
The following page contains a login form that hosts three HTML controls. Note the runat="server" attributes adorning the controls but the absence of runat="server" in the tag:

User Name:
Password:
Clicking the Log In button submits the form to Welcome.aspx, which reads the user name that the user typed from the HTTP request and outputs it to the page. Here's Welcome.aspx:

Do the runat="server" attributes on the tags do anything useful? You bet. They make the controls visible to server-side scripts.
When configured to store session state in a SQL Server database, ASP.NET stores sessions in the Tempdb database, which means sessions are lost if the database server is rebooted. Is it possible to configure ASP.NET to store session in the ASPState database instead for added robustness?
You bet. Read Microsoft Knowledge Base article 311209, which is titled "Configure ASP.NET for Persistent SQL Server Session State Management" and located at http://support.microsoft.com/default.aspx?scid=kb;EN-US;q311209. The article contains a link for downloading InstallPersistSqlState.sql, an installation script that creates the ASPState database and configures it to store session data in ASPState tables rather than Tempdb tables. This simple configuration change enables session state to survive server restarts and is a boon for small- and medium-size sites that can't justify building clustered arrays of database servers on the back end.

I'm using an element in Web.config to redirect to a custom error page when a 404 error occurs. Inside the error page, I want to retrieve the URL that caused the 404 error so I can write it to a log file. How do I get that URL?
When ASP.NET redirects to a custom error page, it passes the original URL (the one that caused the error) in a query string. Here's an example in which PageNotFound.aspx is the custom error page and foo.aspx is the page that produced the error:
http://localhost/PageNotFound.aspx?aspxerrorpath=/foo.aspx
PageNotFound.aspx can therefore obtain the URL of the page that generated the error (in your case, the nonexistent page that resulted in a 404 error) like this:

string badurl = Request["aspxerrorpath"];


If you'd like to retrieve the IP address from which the request originated for logging purposes as well, read it from Request.ServerVariables["REMOTE_ADDR"] or Request.UserHostAddress.

How does System.Web.UI.Page's IsPostBack property work? How does it determine whether a request is a postback?
IsPostBack checks to see whether the HTTP request is accompanied by postback data containing a __VIEWSTATE or __EVENTTARGET parameter. No parameters, no postback.

Forms authentication protects ASPX files and other resources owned by ASP.NET, but it does not restrict access to HTML files and other non-ASP.NET resources. Is it possible to extend forms authentication to protect ordinary HTML files and other resources that don't belong to ASP.NET?
The best way to do it is map *.htm, *.html, and other file name extensions to Aspnet_isapi.dll in the IIS metabase. (You can use the IIS configuration manager to do the mapping.) Transferring ownership of these resources to ASP.NET degrades performance a bit, but it might be worth it for the added security.

How do I build a DataGrid that contains a column of check boxes?
The column of check boxes is created easily enough with a TemplateColumn. What's not so obvious is how to read the state of the check boxes following a postback. The solution is to reach into the DataGrid and extract references to the check boxes. The sample below, which uses the "Titles" table of SQL Server's pubs database to populate a DataGrid, demonstrates how it's done. Click the push button and a list of all the titles with check marks next to them appears at the bottom of the page.


I'm dynamically adding columns to a DataGrid at run-time, but am finding that the DataGrid's events don't fire properly. Interestingly, if I define the columns statically, the events work just fine. Any idea what I'm doing wrong?
You're probably adding the columns to the DataGrid in Page_Load. Add them in Page_Init instead and the events will fire just fine. In general, Page_Init is the ideal place to modify the page and its controls, while Page_Load is the place to modify control state. Keep this simple dictum in mind and you'll save yourself a lot of headaches down the road.

When hosting a Windows Forms control in a Web page, is it possible to connect the control's events to a handler implemented in client-side script?
You bet. But you, the control developer, have to do a little work to allow it to happen. Here's a summary of the steps required:
1) Declare in the control DLL an interface containing methods whose names and signatures match the events you wish to expose to client-side script. Attribute the interface [InterfaceType (ComInterfaceType.InterfaceIsDispatch)] so the Framework will expose it as an IDispatch interface to COM clients. Use [DispId] attributes to assign each of the interface's methods a unique dispatch ID.
2) Adorn the control class with a [ComSourceInterfaces] attribute naming the interface defined in step 1. This instructs the Framework to expose the interface's methods to COM clients as COM events.
3) Grant the assembly containing the control (the control DLL) full trust on the client. You can use the Microsoft .NET Framework Wizards found in Control Panel/Administrative Tools to grant the assembly full trust.
To demonstrate, here's a WebSlider control whose Scroll events (which are inherited from and fired by the TrackBar base class) can be processed in a browser:

using System;
using System.Runtime.InteropServices;


namespace Wintellect
{
[ComSourceInterfaces (typeof (IWebSliderEvents))]
public class WebSlider : System.Windows.Forms.TrackBar {}

[InterfaceType (ComInterfaceType.InterfaceIsIDispatch)]
public interface IWebSliderEvents
{
[DispId (1)] void Scroll (Object sender, EventArgs e);
}
}



And here's a Web page that you can use to test the control and prove that you can respond to its events in client-side script:

0


As you move the slider's thumb, the client-side event handler continually updates the positional value beneath the slider to reflect the thumb's latest position.

How does ASP.NET generate session IDs? Are they random? Predictable session IDs increase the risk of session hijacking.
Relax: ASP.NET uses random, non-sequential session IDs. Specifically, it uses the FCL's System.Security.Cryptography.RNGCryptoServiceProvider class to generate highly random 120-bit session IDs. Sessions can still be hijacked by stealing session cookies or, if cookieless session state is being used, by reading session IDs from the browser's address bar. But ASP.NET's use of random session IDs should preclude the possibility of hijacking sessions by guessing session IDs.

Running ASP.NET on a Web farm requires you to configure each server to use identical validation and encryption keys. Is there a tool available for producing those keys?
You can get the tool you're looking for right here. KeyGen is a command-line utility for producing validation and encryption keys of any length. Click here to download it, and here to download the C# source code. To run it, simply type KeyGen followed by a key length in bytes. The following command produces a 24-byte key:


keygen 24


KeyGen uses the .NET Framework Class Library's System.Security.Cryptography.RNGCryptoServiceProvider class to generate cryptographically strong keys. As such, it only runs on machines equipped with the .NET Framework.

How can I get the name of the Windows security principal that a request is executing as? Page.User.Identity.Name is no help at all when forms authentication is used.
The best way to do it is to use P/Invoke to call the Win32 GetUserName function. The ASPX file below demonstrates how. In order for this to work, your code must be running with a high-enough trust level to permit callouts to unmanaged code.

<%@ Import Namespace="System.Runtime.InteropServices" %>

Is it possible to generate the source code for an ASP.NET Web service from a WSDL contract?
Yes. Begin by running the Wsdl.exe tool that comes with the .NET Framework SDK with a /server switch and pointing it to the WSDL document, like this:
wsdl /server http://www.wintellect.com/wsdl/widget.wsdl


Wsdl.exe responds by generating a CS file containing a WebService-derived base class that you can further derive from to implement a Web service. The Wsdl.exe-generated class contains abstract methods representing the Web methods described in the WSDL document; you'll need to override and implement these methods in your derived class. You must also manually copy the attributes in the Wsdl.exe-generated source code file to the corresponding elements in your source code file.

When I call DataAdapter.Fill to fill a DataTable, the data comes back just fine, but any constraints placed on that data in the database do not. For example, if the table I query contains a unique key constraint, the resulting DataTable does not. If I modify the DataTable and violate a constraint, I don't learn about my mistake until I call DataAdapter.Update. Is there a reasonable way to read constraints from a database and apply them to a DataTable?
There is: it's called FillSchema. The page below demonstrates its use. The record added to the DataTable violates a uniqueness constraint because the "title_id" of the Pubs database's "titles" table is a primary key and it already contains a record with a "title_id" value of TC7777. Thanks to FillSchema, the call to Rows.Add throws an exception.


SqlDataAdapter adapter = new SqlDataAdapter (
"select * from titles",
"server=localhost;database=pubs;uid=sa"
);

DataSet ds = new DataSet ();
adapter.Fill (ds, "Titles");

DataTable table = ds.Tables["Titles"];
adapter.FillSchema (table, SchemaType.Mapped);
DataRow row = table.NewRow ();
row["title_id"] = "TC7777";
row["title"] = "Programming Microsoft .NET";
row["price"] = "59.99";
row["ytd_sales"] = "1000000";
row["type"] = "business";
row["pubdate"] = "May 2002";
table.Rows.Add (row); // Get ready for an exception!


As an aside, you generally want to call FillSchema after DataAdapter.Fill, not before. Placing constraints on a DataTable before filling it slows down the query.

I'm using Windows authentication in an ASP.NET intranet app to identify callers, and Windows authentication to authenticate callers to a back-end SQL Server database. However, it doesn't work: the caller's credentials are apparently not being propagated to the database. What's wrong?
I'll bet you're using Integrated Windows Authentication (IWA) to authenticate callers, and if that's the case, you've encountered the famous Windows "one-hop" problem. By default, Windows only allows security credentials to travel one hop over the network. If you're using IWA, you use up your one hop going from the browser to the Web server. Several solutions exist, but none are perfect. The best solution, assuming you want to continue using IWA, is to enable delegation. Here are some links to articles with more information:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adminsql/ad_security_2gmm.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetHT05.asp

I'm using a SqlDataAdapter to update my database and a SqlCommandBuilder to generate INSERT, UPDATE, and DELETE commands. Everything works fine until I try to update a table that has spaces in its name. Then, SqlDataAdapter.Update throws a SqlException. Why does this happen, and how do I fix it?
The problem is that CommandBuilder isn't smart enough to figure out that table names with embedded spaces must be delimited with special characters such as [ and ], even if your SELECT statement contains a table name surrounded by those characters. The solution lies in CommandBuilder's QuotePrefix and QuoteSuffix properties. A CommandBuilder used this way will fail when it encounters a table whose name includes spaces:

SqlCommandBuilder builder = new SqlCommandBuilder (adapter);
adapter.Update (table);

But a CommandBuilder used this way will work just fine with most databases:

SqlCommandBuilder builder = new SqlCommandBuilder (adapter);
builder.QuotePrefix = "[";
builder.QuoteSuffix = "]";
adapter.Update (table);


How can I use ASP.NET validation controls to check for leading and trailing spaces in user input and display an error message if either is present?
Here's a CustomValidator that checks a control named "Input" for leading and trailing spaces:


Why do uploads fail when I use an ASP.NET file upload control to upload large files?
ASP.NET limits the size of requests (and therefore file uploads) as a precaution against denial-of-service attacks. By default, ASP.NET won't accept requests whose size exceeds 4 MB. You can change that by modifying the maxRequestLength attribute of Machine.config's element. The following maxRequestLength attribute expands the permissible request size to 8 MB (8192K):


Can ASP.NET 1.0 and 1.1 coexist on the same server?
Yes. Installing version 1.1 of the .NET Framework doesn't wipe out version 1.0; both versions remain resident on the machine. Version 1.0 lives in the %Windows%\Microsoft.NET\Framework\v1.0.3705 directory, while version 1.1 lives in %Windows%\Microsoft.NET\Framework\v1.1.4322. By default, installing version 1.1 upgrades all ASP.NET apps to use ASP.NET 1.1. If you want certain apps to revert to ASP.NET 1.0 instead, you must configure them accordingly (see below).

I upgraded my company's Web server to ASP.NET 1.1 and it broke one of my apps. Can I revert to ASP.NET 1.0 for that application and leave others running under 1.1?
You bet. Simply modify the IIS metabase to point that application to version 1.0.3705 of Aspnet_isapi.dll instead of version 1.1.4322. The easy way to do it is to run the Aspnet_regiis utility that came with version 1.0 of the .NET Framework. The following command uses Aspnet_regiis to configure the application in the virtual directory named MyApp to use ASP.NET 1.0:

Aspnet_regiis -sn w3svc/1/root/myapp


If you later decide to migrate the application to ASP.NET 1.1, simply repeat the command, but this time use the Aspnet_regiis utility that came with version 1.1. For additional information, refer to the article entitled ASP.NET Side-by-Side Execution of .NET Framework 1.0 and 1.1 on the ASP.NET team's Web site.

Can an ASP.NET app figure out at run-time what version of ASP.NET it's hosted by?
Yes, by reading the static Version property of the Framework's System.Environment class. Here's a page that demonstrates how. When executed, the page displays the version of ASP.NET that it's running under:


Should you need them, build and revision numbers are also present in the System.Version object returned by the property's get accessor.

No comments: