Thursday, 1 June 2006
10:51 PM
One of the many cool things in ASP.NET 2.0 is that you can declaratively set a default button. This makes the pain of the various hacks (#) pretty much go away, bye-bye.
With this in mind, I set the default button for my main blog page (yer on it right now) to be the Search button, using this syntax:<form runat="server" DefaultButton="buttonSearch"> I quick-like tested it and it worked just fine.
But I soon got a complaint that when the page first opened, it was scrolling down a bit. I had a look, and sure enough. Now that I had a default button for the form, when the page loaded, it set the focus on the button, which was below the fold. That's not what I wanted.
The DefaultButton attribute can be applied to containers, so to fix this, I removed the attribute from the <form> tag. The box you see as Search is a <td> element in a table. So within the <td> element I added an <asp:panel> element and set its DefaultButton attribute to point to the Search button.
The morals of the story are that a) setting a default button will by default (haha) also set focus, and that therefore and/or maybe b) you want to either move your default button to an appropriate location or scope it to a suitable container. (Does this make sense? Hope so.) Anyway, I humbly submit this as your tip of the day. :-)
[categories]
aspnet, whidbey
|
link
|
Monday, 8 May 2006
04:57 PM
In a comment on another post about validating parameters for the SqlDataSource control, someone asks about how to handle errors that might occur within the SqlDataSource control -- e.g. "query timeout, SQL Server not running, mistake in query," to quote the comment. I thought it was worthwhile to promote the discussion from the comments to a separate entry. (If you read those comments, there's nothing new here for you ...)
Eilon confirmed that the data source controls don't raise their own error events. You can trap errors like the ones listed, but you have to do so slightly indirectly.
You have (at least) two choices. One is to perform data binding yourself and put the DataBind call into a try-catch block:Sub Page_Load() If Not IsPostBack Then Try GridView1.DataSourceID = "SqlDataSource1" GridView1.DataBind() Catch ex As Exception ' Hopefully better error handling that this ... Response.Write("Error data binding") End Try End If End Sub You can also catch the error in the Page_Error handler (documented here). Handle the Page_Error event and then redirect (using Server.Transfer) to an error page. Here's the handler:Sub Page_Error() Server.Transfer("DataBindError.aspx") End Sub And a possibility for the target page:Sub Page_Load() Label1.Text = Server.GetLastError.Message End Sub This will catch all unhandled errors in the page, which I guess is a good thing or a bad thing, depending.
PS You can also create an application-level error handler. (#)
[categories]
aspnet, whidbey
|
link
|
Sunday, 30 April 2006
02:56 PM
For whatever reason, creating custom ASP.NET controls is something that is ... uh ... underrepresented in my skill set. As with certain other corners of the product, my theoretical knowledge outstrips my practical knowledge. So when I was messing around with a couple of things recently, I thought that they might be good practice scenarios for creating some custom controls.
The thing about custom controls is that the concept is really quite easy to grasp. You inherit from, say, WebControl and you override Render, and you're in business. And if you're working in VS2005, the thing practically writes itself. (haha) The base control class does an amazing amount of heavy lifting and -- again, in outline -- you only need to add your control-specific logic.
As happens, though, the difficulties lie in the details. I have found that to be true even with the very simple controls I've done -- there's always a gotcha or three that makes (at least for the less-experienced, like me) for a certain two-steps back type of development. Which I guess is where all the fun is.
The first control I fooled around with was a "dynamic" email control. (I'm pretty sure that an email control is the first thing most people fool around with, the "Hello, World" of control development.) In my case, I wanted a control that implemented the supposed anti-email-spambot feature that I learned about some time ago. The idea is that instead of setting the href attribute in an <a> tag to a mailto: value, you set it to a JavaScript function that dynamically constructs the mailto: value on the fly (on onclick and onmouseover) and assigns it to the href attribute. Look, ma, nothing for the spambot to find. (At least, not for dumb spambots.) I've done this procedurally in a page, so now it was time to encapsulate the behavior into a control.
I thought about listing the source here, but it's slightly too long, so I put it into an .htm page. These are the points to call out, I guess.
The control requires that JavaScript is enabled on the browser. There is no self-contained test for this (that I know of), so oh, well. We just assume.
When the user clicks the control, it does whatever mailto: does on their machine.
The control exposes these properties:
- EmailLinkText -- what the user sees. If no value is specified, the control constructs the link text out of the target email name and displays it as
EmailName AT Domain DOT Extension . (The strings " AT " and " DOT " are configurable.) Note that the user doesn't have to translate this back into an email address, coz the link is clickable.
- FullEmailAddress -- the email address for that link. Data-bindable, thanks to the Power of Attributes. You can specify the recipient using a complete email address, or you can specify ...
- EmailName, Domain, and Extension separately. (Also bindable.) Your choice.
- DomainDelimiterText and ExtensionDelimiterText. In case the strings
" AT " and " DOT " are not to your liking. The control has to emit a JavaScript function, of course. Per Nikhil's and Vandana's book (I think it was), I did this by overriding OnPreRender and calling RegisterClientScriptBlock.
You can play with the control on this page: DynamicEmailTest.aspx, which enables you to see how the JavaScript is set and how setting different properties affects the control.
One of the things that I had to investigate was how to render only an <a> element. By default, the base control class wants to emit a <span> element. A most excellent article on MSDN explained that I would have to override the base TagKey property and return HtmlTextWriterTag.A. I also had to override AddAttributesToRender in order to set the href attribute (to an empty string) and the onclick and onmouseover attributes to call the JavaScript function. These two requirements took me afield from the basic strategy I mentioned earlier of simply overriding Render. In fact, in the end, the only thing I had to explicitly render was the link text.
I had initially tried to implement a feature that ended up costing me about 90% of the time I spent screwing around with this. I had thought that I could (should) make the FullEmailAddress and EmailName/Domain/Extension properties reciprocal -- setting one would populate the other(s). Boy, as far as I can tell, there's no clean way to do this within the control itself. For starters, you don't want to overwrite any values that the user sets, so you don't want to just barge in there and set the properties willy-nilly. Instead, I had a notion that if and only if FullEmailAddress was not set, it would return the combination of EmailName/Domain/Extension (and vice versa), without necessarily setting those properties explicitly. This is codable inasmuch as you can look for If ViewState("FullEmailAddress") Is Nothing , but the problem is that it doesn't stay Nothing for long. In my test page, once the text boxes had been populated with one of these based-on-the-other property values, it was as if the user had entered those property values, and on postback, ASP.NET happily set those properties, so they were no longer null. (Make sense?) In the end, I gave up on the idea of reciprocal properties, although there might be a way to do these. As a compromise, and because this seemed like it might be useful in some circumstances, I implemented the read-only properties FullEmailAddressEmailName, FullEmailAddressDomain, and FullEmailAddressExtension, which return the parsed components of FullEmailAddress.
There is one 2.0-specific feature in this code. The RegisterClientScriptBlock call, which is exposed via the ClientScript page property, has a new 2.0-specific overload. But you can use uncomment the corresponding 1.1 version and it seems to work fine -- I tested it on Web Matrix, yahoo!
So that's the first control. Another one another time.
[categories]
aspnet, whidbey
|
link
|
Friday, 21 April 2006
02:24 PM
My homies on the Web Development Tools team -- the folks who brought you Visual Web Developer and Visual Web Developer Express -- have started a team blog.
Cute picture, although it's kinda small. :-)
[categories]
aspnet, whidbey
|
link
|
Tuesday, 4 April 2006
09:21 PM
In his blog, Karl Seguin has a thoughtful rant (is that an oxymoron?) about code examples in documentation. He shows some examples from (whew) the Macromedia documentation for .NET.[1]
He finds some examples of particularly egregious bad practices, including:- Insecure code.
- Poor error handling.
- Non-meaningful variable names.
- Inefficient and inelegant code.
In a follow-on comment, he notes one point in particular: One concern I had with the Macromedia stuff is I think (and I could totally be wrong) that there'll be a high percentage of copy/pasting. This is material focused towards developers not primarily focused on .NET (or similar languages). They don't know that it's wrong/incomplete. They want to get their flash app working and taking the sample to modify the connection string and select statement might be all the work they'll put into it. Why should they do any more? This does come from Macromedia after all. We've rassled with a lot of these issues. When we write code examples, there is a constant tension, let's call it, between functionality and readability. We very much bear in mind Karl's point about copy-n-paste, one of the behaviors that defines the so-called Mort persona.[2]
Consider database access. We went round and round about how to illustrate this. To accommodate our Morts, we would ideally offer nearly copy-n-paste examples. In that case, it would be easiest to show a) an explicit connection string, and b) explicit credentials. That would minimize the number of changes that the Mort developer would have to make, and it would reduce the cognitive burden required to get the example working.
But these are bad, bad practices. Bad. So we opted for good practice, and upped the work that Mort would have to do to get things running. These days, we show connection strings only when that's somehow essential to the example, and then we show only integrated security. What we mostly do is advise people to keep their connection strings in the config file, which we illustrate. We also somewhat monotonously keep pounding on the message that they should encrypt their connection strings. Doing these non-illustrated tasks is, as they say, left as an exercise for the reader. (We link, of course.) But not doing it -- showing stuff explicitly -- is bad pedagogy.
We also look for places where we illustrate user input, typically with a TextBox control. This one is more subtle. We could always show using Server.HtmlEncode to encode user input. But there are a couple of arguments against doing this always. One, you don't always have to do it. If the example just echoes user input back to the user, there's no point in encoding it. (Yeah, I know -- hold that thought.) Two, if we use it when it's not needed, we clutter the example and perhaps raise questions in the reader's mind about why we're doing it. Three, by default, ASP.NET pages have ValidateRequest turned on, so (at least by default), Mort has a safety net. What we've done is show Server.HtmlEncode in places where you'd obviously use it always. In situations where it doesn't seem mandated, we add a security note (example) that is a heads-up to the reader -- "Hey, this is user input, which you shouldn't trust. Go read about it here."
We've had similar discussions about the use of try-catch and about illustrating exception handling. For example, just how granular is the exception handling in an example? (Catch every exception? Do we unpack possible inner exceptions?) What does the example code in a given catch block do? Although we want to illustrate some sort of non-dumb exception handling, we have to assume that at some point the reader takes over and (we hope) adds some sort of application-appropriate code.
The actual mechanics of creating and vetting code examples have gotten better since the early days of .NET. These days, almost all the code examples are in separate files that are run through FxCop and are compiled (Hey, we can break builds, too!), and are injected into the topics only when we build the docs. Aside from the obvious advantages, this gives us some nice reuse.
I would hardly claim that the code examples are uniformly great. I'm sure people would be delighted to send me all sorts of questionable stuff. (Although pace Kent's comment in Karl's post, it should be pretty darn hard at this point to come up with sightings of username="sa" password="" .) But making code examples better, and adding more of them, was a major goal for Whidbey.
As for elegance, that's a tough one. The ASP.NET writers are all coders, some of them quite experienced. Certainly in newer code samples, the lameness factor should be pretty low. In some cases, there are probably examples where we compromised something for readability, perhaps just to keep the example to a reasonable size. There are some very good examples -- our man Doug wrote up a series of example topics for creating custom providers (membership, roles, etc.) which are not just highly functional, but IMO really excellent example code as well. There are many other such as well.
We're also doing our darndest these days to come up with examples that are non-trivial. A pattern that we try to follow for the reference (API) docs is to have one or more in-depth examples in the class overviews, and then extract relevant excerpts for the individual member topics. Even so, the task can be daunting. Imagine trying to come up with code examples to illustrate the many capabilities of, say, the GridView control.
One final comment about variable names. This was another discussion. For the most part, we try to follow .NET coding conventions. When we show ASP.NET controls, we actually use the convention followed by Visual Studio when it creates controls -- Button1 , Label2 , etc. Theory is this will be consistent with what users will see when working in the product. As I say, that's the theory. One thing I'm reasonably confident about is that you won't find many (any?) variables named foo and bar . :-)
[categories]
aspnet, whidbey, writing
|
link
|
Monday, 3 April 2006
02:31 AM
Having a) implemented a comments feed b) as a handler (pat self on back), I thought it would be "elegant" to deliver it as URL that ended in .rss. And so I have: the comment feed is now BlogCommentsFeed.rss. Check it out:
http://www.mikepope.com/blog/blogcommentsfeed.rss?id=1458
It wasn't hard, but I had to bang on the side of the box a few times before everything was working. What it really was was a lesson in how much ASP.NET does for you under the covers.
I started with a default handler (BlogCommentsFeed.ashx). The .ashx extension is, haha, handled automatically by ASP.NET; ASP.NET assumes that it's dealing with an HTTP handler. Alls you have to do is create a class that implements IHttpHandler, implements a ProcessRequest method, and implements an IsReusable property. Using VS 2005 makes all of this trivially easy. The template in VS also adds an @ WebHandler directive at the top of the file, which looks a lot like an @ Page directive.
Do all this and make sure the file has an .ashx extension, and you can put the file anywhere. When ASP.NET sees the .ashx extension, it knows what to do.
But ASP.NET does not, of course, know what to do with an .rss extension. What I did was:
- In IIS, I mapped the .rss extension to the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll ISAPI. This tells IIS to hand the .rss requests to ASP.NET.
- In ASP.NET, I edited the Web.config file and added a mapping for the handler:
<httpHandlers> <add verb="GET" path="blogcommentsfeed.rss" type="BlogCommentsFeed" /> </httpHandlers> This tells ASP.NET that when it sees a request for blogcommentsfeed.rss, invoke the BlogCommentsFeed class.
- I renamed BlogCommentsFeed.ashx to BlogCommentsFeed.rss.
Ok, let's hold up right here.
Test your chops. Did this work? Answer: no. Error: can't find BlogCommentsFeed class.
Hmm, hmm.
But of course: there is no such class! I never compiled any such class and put it in Bin. A reminder, let's call it, about how ASP.NET not only knows how to handle handlers named with .ashx, but it knows to compile them.
I copied BlogCommentsFeed.rss to App_Code. Did it work? Uh ... no. ASP.NET compiles class files in that folder, as long as it knows what the heck they are. But this mysterious .rss extension, says ASP.NET, I know nothing about that. Rename .rss extension to .vb, sheepishly.
I assume you had all this sussed out.
After I moved the file to the App_Code folder and renamed it to .vb, did it work? Mmmm, no. Why not? Compiler barfed on the @ WebHandler directive. The directive is useful for when ASP.NET is going to throw a parser at the file, but for plain jane class files (as in the App_Code folder), it's illegal.
After I removed the @ WebHandler directive, did it work? Well, no. I was getting 404-ish errors. Hmm, hmm. Aha! The request is for mikepope.com/blog/BlogCommentsFeed.rss. but there's no such file, right? Just BlogCommentsFeed.vb, which is (will be, should be) invoked in response to requests for the now non-existent BlogCommentsFeed.rss file.
And here I surprised myself by remembering the fix for this. Over in IIS, you can tell it not to look for a physical file, and just let the ISAPI handle things, thank you:
And then, finally, all was fixed, as you can see.
I must put in a good word here for ... shhh ... our docs, which walked me through this. (Well, most of it.) The whole bidness is described right here: How to: Create Synchronous HTTP Handlers. Yay for us.
[categories]
aspnet, whidbey
|
link
|
Tuesday, 14 March 2006
11:41 AM
The marketing folks at work have thunk up a contest to encourage inventive ways to use the Visual Studio/SQL Server Express Edition products:
| Made in Express Contest Do you like to use technology to build cool and useful stuff? Do you think you could do it with Visual Studio Express and/or SQL Server Express? Would you like $10,000 cash? Well then we have a contest for you! Learn more at www.MadeInExpressContest.com. |
$10,000, dang. :-)
[categories]
aspnet, whidbey
|
link
|
Thursday, 9 March 2006
11:50 PM
The redoubtable Eilon Lipton came up with another nice nugget of data-source-control goodness in answer to a question at work today. Here's the scenario. You are using a SqlDataSource control to run a query like this:
Select FirstName, LastName From Employees Where EmployeeId = ?
(Here, the EmployeeId field is an integer.) You want to be able to pass the EmployeeId to the SqlDataSource control directly from the query string. No problem; the data source configuration wizard walks you right through setting that up. It ends up looking something like this:<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:EmployeesConnectionString %>" SelectCommand="SELECT [FirstName], [LastNamee] FROM [Employees] WHERE ([EmployeeID] = @EmployeeID)"> <SelectParameters> <asp:QueryStringParameter Name="BlogID" QueryStringField="ID" Type="Int32" /> </SelectParameters> </asp:SqlDataSource> But what if someone (those darn users) passes in a query string value that's not an integer? You get one of those "Input string not in correct format" errors.
So you want to validate that the query string value is an integer. You can do this by handling the SqlDataSource control's Selecting event. There, you can extract the parameter value, test it, and cancel the event (and thus the query) if something's awry. Here's one way you might check that the query string value is an integer (or integer-able, anyway):Protected Sub SqlDataSource1_Selecting(ByVal sender As Object, _ ByVal e As System.Web.UI.WebControls.SqlDataSourceSelectingEventArgs) Dim idString As String = e.Command.Parameters(0).Value.ToString() Dim id As Integer If Int32.TryParse(idString, id) = False Then labelStatus.Text = "Invalid ID in query string" e.Cancel = True End If End Sub Pretty straightforward, eh? Problem is that it doesn't work. Or more precisely, even with this handler, you'll still see the invalid-format error.
Eilon had the workaround: in the declaration for the SqlDataSource control's parameter collection, leave the data type blank. So it looks like this:<SelectParameters> <asp:QueryStringParameter Name="BlogID" QueryStringField="ID" /> </SelectParameters> Look, ma, no data type. And now it works. Note that we're converting the EmployeeID to an integer in the Selecting handler, so the type is ok when the query ultimately runs. But if the parameter declaration has no type, the SqlDataSource control won't try to force the parameter value into a particular type, and no error.
[categories]
aspnet, whidbey
|
link
|
Thursday, 9 March 2006
04:54 PM
When you read a page on MSDN, you can both rate it and enter comments. Other people can see the ratings, but the comments are not visible to all.
Now and again we'll have a look at a topic and wonder about the ratings. (Well, we generally wonder only about bad ratings. :-) ) There have been a few that we thought were pretty useful topics that have gotten low ratings. Then when we look at the comments, we'll find out that, oh, say, the code in the topic has a bug in it. There's a way to get yerself a low rating.
And do you want a really, really low rating? How about if you leave the sample code out altogether? Uh ... oops. We did this in a topic I was looking at today[1]:
How to: Locate the Web Forms Controls on a Page by Walking the Controls Collection
The comments were surprisingly un-vicious, considering. Anyway, here's the missing code:' VB <%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server"> <title>Locate the Web Forms Controls on a Page by Walking the Controls Collection</title> </head>
<script runat="server"> Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim allTextBoxValues As String = "" Dim c As Control Dim childc As Control For Each c In Page.Controls For Each childc In c.Controls If TypeOf childc Is TextBox Then allTextBoxValues &= CType(childc, TextBox).Text & "," End If Next Next If allTextBoxValues <> "" Then Label1.Text = allTextBoxValues End If End Sub </script>
<body> <form id="form1" runat="server"> <asp:Button id="Button1" runat=server /> <asp:Label id=Label1 runat=server></asp:Label> </form> </body> </html> // C# <%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server"> <title>Locate the Web Forms Controls on a Page by Walking the Controls Collection</title> </head> <script runat="server"> private void Button1_Click(object sender, System.EventArgs e) { string allTextBoxValues = ""; foreach (Control c in Page.Controls) { foreach (Control childc in c.Controls) { if (childc is TextBox) { allTextBoxValues += ((TextBox)childc).Text + ","; } } } if (allTextBoxValues != "") { Label1.Text = allTextBoxValues; } }
</script> <body> <form id="form1" runat="server"> <asp:Button id="Button1" runat=server /> <asp:Label id=Label1 runat=server></asp:Label> </form> </body> </html>
[categories]
aspnet, whidbey, work
|
link
|
Saturday, 25 February 2006
05:03 PM
Someone at Microsoft has posted improved versions of HtmlEncode and UrlEncode in what they call the "Microsoft Anti-Cross Site Scripting Library V1.0." The library includes two methods that are essentially the same as the corresponding methods in HtmlServerUtility:public static string HtmlEncode(string s); public static string UrlEncode(string s); Thus:String s = Microsoft.Security.Application.AntiXSSLibrary.HtmlEncode(TextBox1.Text); The difference between the HtmlUtility and the Anti-XSS library versions of the methods is that the former encodes only a specific a set of characters, whereas the new version encodes everything but a specific set of characters. IOW, the former uses a blacklist, the latter a whitelist. In security terms, this means the new version is that much harder to get around.
(All of this information lifted directly from the docs and samples included with the library.)
To use the new library, download the .msi from the download page and run it. The installer puts the library by default at x:\Program Files\microsoft\Anti-Cross Site Scripting Library V1.0 . The installation includes some minimal docs, some samples, and an assembly containing the class. The easiest way to use it, probably, is to copy the assembly to the Bin folder of any app where you want to use it. Have a look at the .rtf file in the Documentation folder for a little more information than what's listed here.
Via Mike Gunderloy.
[categories]
aspnet, whidbey
|
link
|