Scott on Writing

Musings on technical writing...

Tuesday, March 09, 2010 #

Determining Whether a String Is Contained Within a String Array (Case Insensitive)

About once every couple of months I need to write a bit of code that does one thing if a particular string is found within an array of strings and something else if it is not ignoring differences in case. For whatever reason, I never seem to remember the code snippet to accomplish this, so after spending 10 minutes of research today I thought I'd write it down here in an effort to help commit it to memory or, at the very least, serve as a quick place to find the answer when the need arises again.

So without further adieu, here it is:

Visual Basic Version:

If stringArrayName.Contains("valueToLookFor", StringComparer.OrdinalIgnoreCase) Then
    ...
Else
    ...
End If

C# Version:

if (stringArrayName.Contains("valueToLookFor", StringComparer.OrdinalIgnoreCase))
    ...
else
    ...

Without the StringComparer.OrdinalIgnoreCase the search will be case-sensitive. For more information on comparing strings, see: New Recommendations for Using Strings in Microsoft .NET 2.0.

Happy Programming!

posted @ 2:14 PM | Feedback (1)

Wednesday, February 17, 2010 #

FIX: It is an error to use a section registered as allowDefinition='MachineToApplication' beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS

I teach two six week courses on ASP.NET at the University of California - San Diego Extension. The first class serves as an introduction to ASP.NET and as such many of the students create their first ASP.NET websites in class. During the first meeting students create a simple ASP.NET website. At the end of the first evening, those students who did not bring their own laptop need to work on their website from home or work; they may copy the files to a thumb drive or ZIP up the files and send them via email. In either case, it's not uncommon for students to bump into the following error when opening the website to bump into the following error message:

It is an error to use a section registered as allowDefinition='MachineToApplication' beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.

This error message isn't the most sensible error message, especially those to ASP.NET, so let me try to explain it.

Configuration information for an ASP.NET website is defined in one or more Web.config files. The configuration settings are applied in a hierarchical manner. There's a “global” Web.config file that spells out the baseline configuration information for all websites on the web server; this file lives in the %WINDIR%\Microsoft.Net\Framework\version\CONFIG folder. You can also have a Web.config file in the root folder of your website. This Web.config file can override settings defined in the “global” Web.config file, or add new ones. Additionally, you may have Web.config files in the subfolders of your website, which define new configuration settings or override configuration settings defined in Web.config files higher up in the hierarchy.

Certain configuration elements in Web.config cannot be defined beyond the application level, meaning that they must be defined in the “global” Web.config file or in the Web.config file in the website's root folder. The <authentication> element is one such example. The above error message indicates that there is a Web.config file in one of the website's subfolders than has one of these configuration elements that cannot be defined beyond the application level.

This problem most commonly arises when you open the website in Visual Studio but accidentally open the parent of the root folder. For example, imagine that you have a website located at C:\MyProjects\Website1, where the Website1 folder is the root of the website. When you open this website from Visual Studio you are asked to specify the website's root folder - it is imperative that choose the Website1 folder. If you accidentally select the MyProjects folder then the Web.config file in the Website1 folder is now in one of the website's subfolders. Because that Web.config file includes the <authentication> element, among other application-level settings, you'll receive the above error.

The fix to this error (in most cases), then, is to close your project and reopen it from Visual Studio, making sure that you select the appropriate folder.

Happy Programming!

posted @ 1:39 PM | Feedback (2)

Friday, January 08, 2010 #

January's Toolbox Column Now Online

My (final) Toolbox column in the January 2010 issue of MSDN Magazine is available online and includes the following reviews:

  • Balsamiq Mockups For Desktop - Balsamiq Mockups is a lightweight, easy-to-use UI mockup tool that combines the speed, simplicity, and low-tech look and feel of paper mockups with the archival and sharing benefits inherent in computer-generated mockups. Go from a blank canvas to an intuitive and detailed mockup in a matter of minutes.
  • Blogs of Note: UX Booth Blog - with submissions from several noted UX design authors, trainers, and consultants, the UX Booth Blog offers advice and insight on how to improve your application's user experience.
  • JSLint - JSLint is a free static JavaScript Code Quality Tool created by Douglas Crockford. JSLint searches your script and reports various warnings, such as the use of global variables, statements not terminated by semicolons, and unreachable code, among many other potential pitfalls. Also check out JSLint.VS, a free Visual Studio Add-In that lets you run JSLint directly from the Visual Studio IDE.

This issue reviewed Microsoft SQL Server 2008 Reporting Services Unleashed, by Michael Lisin, Jim Joseph, and Amit Goyal. An excerpt follows:

Like with any enterprise-grade platform, SSRS is expansive in its features and use cases. I recently helped a client evaluate and get started with SSRS and found “Microsoft SQL Server 2008 Reporting Services Unleashed” (Sams, 2009) to be an invaluable guide for learning the ins and outs. The book is divided into five parts. The first part provides a light overview of SSRS, highlights common user scenarios, introduces the SSRS architecture, and compares and contrasts different report deployment scenarios. There’s also a short chapter on installing SSRS, with step-by-step instructions and plenty of screenshots. ... “Microsoft SQL Server 2008 Reporting Services Unleashed” is an excellent introduction to SSRS for administrators, DBAs and users. The depth of material is a little light in some areas, but this book does an excellent job conveying the most important aspects and exploring the breadth of features and functionality available in SSRS.

Enjoy! - http://msdn.microsoft.com/en-us/magazine/ee291539.aspx

After four years and nearly 50 columns, this one marks my last Toolbox column in MSDN Magazine. Microsoft recently tapped 1105 Media to run MSDN Magazine and TechNet Magazine, who decided to go in a new direction with regards to this column. I don't know if the Toolbox column will be retired or whether it will continue with another columnist, but I wanted to sign off saying thanks to those who regularly read the column and to the hundreds of people who sent in suggestions for tools, technologies, and books to review.

posted @ 9:57 AM | Feedback (6)

Tuesday, December 08, 2009 #

SOLUTION: Outlook Is Stripping Line Breaks From Plain-Text Emails Auto-Generated From My ASP.NET Application!

Today I was working on an ASP.NET application that sends out plain-text emails to site administrators when certain conditions unfold. One problem I unearthed today is that when viewing these emails in Microsoft Outlook many of the carriage returns were being removed, making the email difficult to read. The code to generate the body of these emails is very simple and straightforward, looking something like:

StringBuilder body = new StringBuilder();
body.Append("Information of interest:").Append(Environment.NewLine);
body.AppendFormat("Item #1: {0}", someValue).Append(Environment.NewLine);
body.AppendFormat("Item #2: {0}", someOtherValue).Append(Environment.NewLine);
body.AppendFormat("Item #3: {0}", yetAnotherValue).Append(Environment.NewLine);
...

When viewing the message in Outlook it would come out like so:

Information of interest:
Item #1: blah Item #2: blah blah Item #3: blah blah blah

Note how the carriage returns between each of the three items has been removed. Boo. The behavior is not due to the code that generates the email, but rather how Outlook displays a message. (For instance, GMail displays the carriage returns as you'd expect.) Outlook strips out what it identifies an unnecessary carriage returns. If you open the email in Outlook you'll see a little informational bar at the top of the email explaining that Outlook has removed extra line breaks and that you can click it to add them back in. Furthermore, you can configure Outlook to never remove such carriage returns by going to Tools --> Options, clicking the E-mail Options button, and then unchecking “Remove extra line breaks in plain text messages.“

Of course, it's never a good strategy to tell you customers that the problem is on their end and that they need to do something to fix the problem. The good news is that there appears to be a way that you can format the line breaks so that Outlook won't automatically remove them. I got this tip from member scarecrow-rye at the ASP.NET Forums, in the post Email text message line breaks not working:

Try including an empty space character at the end of each line. Certainlty seems to work for me.

And it worked for me, too. With this little adjustment to the code, Outlook stopped stripping out what it had earlier deemed as extra line breaks:

StringBuilder body = new StringBuilder();
body.Append("Information of interest:").Append(Environment.NewLine);
body.AppendFormat("Item #1: {0} ", someValue).Append(Environment.NewLine);
body.AppendFormat("Item #2: {0} ", someOtherValue).Append(Environment.NewLine);
body.AppendFormat("Item #3: {0} ", yetAnotherValue).Append(Environment.NewLine);
...

Note the extra space between the value and the end of the string.

posted @ 11:36 AM | Feedback (3)

Tuesday, December 01, 2009 #

December's Toolbox Column Now Online

My Toolbox column in the December 2009 issue of MSDN Magazine is available online and includes the following reviews:

  • Huagati DBML/EDMX Tools - a Visual Studio Add In that improves the LINQ to SQL and Entity Framework Designers, allowing you to quickly rename objects, update the object model to reflect the latest relational model changes, and auto-document the object model based on column descriptions in the database.
  • Blogs of Note: Oren Eini a/k/a Ayende Rahien - Oren Eini is the creator of Rhino Mocks and a contributor to the NHibernate project. On his blog you'll find posts covering a spectrum of topics, and his output - at times Oren writes three or more posts per day - is impressive. A must-read for .NET developers and architects.
  • SQL Multi Script - SQL Multi Script is a desktop application for executing one query against multiple databases. This tool is useful for DBAs who manage several databases and with Software as a Service-type applications that use separate databases for each customer.
  • Tabs Studio - a Visual Studio Add In that improves the tabbed user interface. Unlike VS's default tabbed interface, with Tabs Studio tabs are stacked vertically, if needed, so that you can see a tab for every open document. Tabs Studio also groups related documents into a single tab and the appearance of all tabs, the currently selected tab, and the previously selected tab can be customized.

There was no book review in this issue.

Enjoy! - http://msdn.microsoft.com/en-us/magazine/ee819138.aspx

posted @ 12:52 PM | Feedback (3)

Tuesday, November 10, 2009 #

Configuring the PasswordRecovery To Send Email to an SSL-Enabled SMTP Client

ASP.NET's PasswordRecovery provides a mechanism for users to recover their forgotten passwords. Depending on how user passwords are saved, the PasswordRecovery control will either email a user their password or it will reset the user's password to a new, auto-generated one and email them this new password. In either case, the PasswordRecovery control sends an email message. To accommodate this, your web application should have the SMTP settings defined in Web.config's section as described in this article: Sending Email in ASP.NET.

Certain SMTP servers (such as GMail's) only accept connections over SSL. Unfortunately, you cannot specify whether to send emails via SSL from the section; rather, you have to do it when you instantiate the SmtpClient object, via its EnableSsl property. This is problematic if you need to have the PasswordRecovery control send the user her password via an SMTP server that requires SSL. The workaround is to create an event handler for the PasswordRecovery control's SendingMail event, where you create your own SmtpClient object, set its EnableSsl property, and use it to send the MailMessage object the PasswordRecovery control is getting ready to send.

Protected Sub prResetPwd_SendingMail(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.MailMessageEventArgs) Handles prResetPwd.SendingMail
    Dim smtp As New SmtpClient
    smtp.EnableSsl = True

    Try
        smtp.Send(e.Message)
    Catch ex As Exception
        'Decide how to handle SMTP errors
    End Try

    'Instruct the control to cancel sending the email itself, since we just sent it
    e.Cancel = True
End Sub

Two things to note:

  1. The MailMessage object that the PasswordRecovery control is about to send is available via e.Message, and
  2. It is important to set e.Cancel to True. This tells the PasswordRecovery control to not send the email, which is what we want because we have already sent it with the SmtpClient object created in this event handler.

This concept can be extended to other ASP.NET Web controls that can automatically send emails (such as the CreateUserWizard control).

Happy Programming!

posted @ 12:54 PM | Feedback (2)

Thursday, November 05, 2009 #

November's Toolbox Column Now Online

My Toolbox column in the November 2009 issue of MSDN Magazine is available online and includes the following reviews:

  • BI Documenter - point BI Documenter to the schema of your database and select which database objects to include. Then, with the click of a button, BI Documenter will create a Compiled Html Help File (.chm) or HTML pages that documents the selected database objects. You can also add rich database diagrams created using BI Documenter's built-in diagramming tool, as well as include your own image and Microsoft Word files.
  • Blogs of Note: Beth Massi - Microsoftie Beth Massi has a great blog with tips, tricks, and tutorials on Office development, WPF, ADO.NET Data Services, and more. What makes Beth's blog unique is her passion for Visual Basic - all of her code samples are in VB and she often posts about upcoming language features and enhancements.
  • CuttingEdge.Conditions - CuttingEdge.Conditions is an open source library that enables developers to define pre- and post-conditions using a fluent interface, which is an API design style that aims at maximizing readability through the use of descriptive names and method chaining. For example, you can define a pre-condition on the input parameter id like so:
public void DoSomething(int id)
{
    // Check all preconditions:
    Condition.Requires(id, "id")
            .IsInRange(1, 999)         // ArgumentOutOfRangeException on failure
            .IsNotEqualTo(128);        // throws ArgumentException on failure

    ...
}

This issue reviewed ASP.NET MVC Framework Unleashed, by Stephen Walther. An exerpt from my review follows:

Getting started with ASP.NET MVC involves a bit of a learning curve, even for experienced ASP.NET developers, because of the numerous differences between the two frameworks. For example, when creating an ASP.NET MVC application in Visual Studio, you are prompted to create a unit test project. With ASP.NET MVC, you design your Web pages using HTML along with a few helper classes—there are no Web controls to drag and drop onto the page. And unlike Web Forms, there are no baked-in postback or Web control event models. In short, there are a lot of new techniques to learn when moving from Web Forms to ASP.NET MVC. If you are an intermediate to experienced ASP.NET developer who wants to learn ASP.NET MVC, check out Stephen Walther’s latest book, “ASP.NET MVC Framework Unleashed” (Sams, 2009). Walther does an excellent job introducing new concepts and showing how they’re used—without overwhelming the reader with an avalanche of information.

Enjoy! - http://msdn.microsoft.com/en-us/magazine/ee335714.aspx

posted @ 12:18 PM | Feedback (6)

Monday, October 26, 2009 #

TIP: How To Generate a Fully Qualified URL in ASP.NET (E.g., http://www.yourserver.com/folder/file.aspx)

Imagine that you've got an ASP.NET page that is generating an email message that needs to include links back to the website. Perhaps you're writing the next greatest online message board application and when someone replies to a thread you want to send out emails to the other thread participants indicating that a new message has been posted along with a link to view the just-posted message. You know that the URL to view a particular thread is, say, ~/Threads/View.aspx?ThreadId=threadId. But how do you turn that relative URL into an absolute URL like http://www.yourserver.com/Threads/View.aspx?ThreadId=threadId?

The simplest answer is to hard code the domain name into the URL when generating the email's content, but that approach is naive and wouldn't work if you were building an application that others could buy and install on their servers. It also would cause a headache if you change your domain name. Finally, it's a headache because oftentimes an ASP.NET application has different URLs depending on the environment. From the development environment the URL might be http://localhost:somePortNumber/MyMessageboard/Threads/View.aspx?ThreadId=threadId, whereas on the staging environment it might be http://stagingserver/MB/Threads/View.aspx?ThreadId=threadId. Ideally, the email generated from a particular environment would contain the appropriate URL back to said environment.

The good news is that generating a fully qualified URL in ASP.NET is quite simple. The following code will pick up the correct HTTP or HTTPS setting, the correct server name - be it localhost, localhost:portNumber, serverInIntranet, an IP address, or a domain name (www.example.com) - and the applicaiton path, if any. Here's the code in both C# and VB:

// C#
string relativeUrl = "~/Threads/View.aspx?ThreadId=" + threadId;
string fullyQualifiedUrl = Request.Url.GetLeftPart(UriPartial.Authority) + Page.ResolveUrl(relativeUrl);
' VB
Dim relativeUrl As String = "~/Threads/View.aspx?ThreadId=" + threadId
Dim fullyQualifiedUrl As String = Request.Url.GetLeftPart(UriPartial.Authority) & Page.ResolveUrl(relativeUrl)

That's it! Pretty easy.

The Request.Url property returns the fully qualified URL of the currently requesting page. The GetLeftPart(UriPartial.Authority) method returns just the protocol and domain name, which is then concatenated with the resolved relative URL to give us the fully qualified URL for the relative URL.

For more on Request.Url, along with a look at the many other path-related properties available of the HttpRequest class, see Rick Strahl's blog entry, Making Sense of ASP.NET Paths.

posted @ 3:59 PM | Feedback (6)

Friday, October 23, 2009 #

Rich Tooltips With jQuery

I was recently working on an application and needed the ability to present the user with a list of links when they hovered over a particular line of text. HTML elements include a title attribute that, when set, displays the attribute's value in a small yellow window when the user hovers over the element. Problem is, the tooltip is a text-only interface. There's no way to embed a hyperlink or image or other rich markup into the tooltip.

Since we are using jQuery in this project I spent some time exploring various techniques for implementing rich tooltips in jQuery. Specifically, I needed a solution that met the following criteria:

  • The tooltip would appear near or next to the text that the user was hovering over, and not on some fixed, predefined spot on the screen.
  • I could, from server-side code, specify what elements would display tooltips when hovered over.
  • I could, from server-side code, customize the tooltip message.
  • The tooltip could include any sort of HTML markup I wanted to throw into it - links, images, etc. - and the user could interact with this markup (click on a link, for instance).
  • The tooltip would remain displayed for a specified amount of time (say, 500 milliseconds) after the user moused off of the element that displayed the tooltip.

Unfortunately, I was unable to find a pre-built solution that would meet our needs, so I created my own, which I'd like to share now.

Please keep in mind that I know just enough JavaScript to shoot myself in the foot. Also, my experience with jQuery is limited, to be kind.

In order to display rich tooltips using my approach you need to do three things.

First, add the following CSS to those pages that use the rich tooltips:

.skmTooltipHost
{
 cursor: help;
 border-bottom: dotted 1px brown;
}

.skmTooltipContainer
{
    padding-left: 10px;
    padding-right: 10px;
    padding-top: 3px;
    padding-bottom: 3px;
    display: none;
    position: absolute;
    background-color: #ff9;
    border: solid 1px #333;
    z-index: 999;
}

Those elements that should display a tooltip when hovered over need to use the skmTooltipHost class. The skmTooltipContainer class defines the styles for the tooltip itself.

Next, add the following $(document).ready function to your page (and add a script reference to the appropriate jquery.js file, if you haven't already):

<script type="text/javascript">
  $(document).ready(function() {
     $(".skmTooltipHost").hover(
       function() {
       $(this).append('<div class="skmTooltipContainer">' + $(this).attr('tooltip') + '</div>');
  
       $(this).find('.skmTooltipContainer').css("left", $(this).position().left + 20);
       $(this).find('.skmTooltipContainer').css("top", $(this).position().top + $(this).height());
        $(".skmTooltipContainer").fadeIn(500);
      },
 
       function() {
        $(".skmTooltipContainer").fadeTo(500, 1.0, function() { $(this).remove(); });
       }
     );
  });
</script>

Finally, decorate those HTML elements that you want to display a tooltip with the skmTooltipHost class and add an attribute named tooltip that contains the content to display in the tooltip. For example, imagine I had a document with the following markup:

<p>ASP.NET simplifies the process of creating dynamic web applications!</p>

I could turn add a tooltip to the text “ASP.NET” by adding the additional markup:

<p><span class="skmTooltipHost" tooltip="ASP.NET was created by Microsoft in 2001.">ASP.NET</span> simplifies the process of creating dynamic web applications!</p>

To get rich markup in the tooltip attribute you'll need to escape the <, >, and " characters with &lt;, &gt;, and &quot;, respectively. For instance, we could add a hyperlink in the above tooltip like so:

<p><span class="skmTooltipHost" tooltip="ASP.NET was created by Microsoft in 2001. Learn more at &lt;a target="_blank" href=&quot;http://www.4guysfromrolla.com/&quot;&gt;4GuysFromRolla.com&lt;/a&gt;!">ASP.NET</span> simplifies the process of creating dynamic web applications!</p>

You can see these rich tooltips in action here - http://scottonwriting.net/sowBlog/CodeProjectFiles/JQueryTooltipDemo.htm

And lastly, to do this programmatically from server-side code, you would add a Label Web control to your page named, say, lblASPNET. You'd set its CssClass property to skmTooltipHost. To set its tooltip attribute add the following (untested) code to the Page_Load event handler:

lblASPNET.Attributes.Add("tooltip", Server.HtmlEncode("ASP.NET was created by Microsoft in 2001. Learn more at <a target="_blank" href="http://www.4guysfromrolla.com/">4GuysFromRolla.com</a>!")

posted @ 3:17 PM | Feedback (6)

Thursday, October 15, 2009 #

SOLUTION: JSLint.VS Add-In Always Reports "No Errors" Even For Invalid JavaScript Files

JSLint is a free JavaScript code quality tool created by Douglas Crockford. At the JSLint.com website you can paste in a block of JavaScript code and JSLint will examine the code and warn you when it encounters any script that violates its list of rules. Many of the rules JSLint checks against are configurable, and include checks for usage of undefined variables, use of the eval function, statements not terminated with semicolons, and other 'trouble waiting for a place to happen' coding patterns.

JSLint.VS is a free, open source Visual Studio Add-In created by Predrag Tomasevic that brings JSLint to the Visual Studio IDE.

Today I installed JSLint.VS for the first time on a machine. The installation went smoothly, but anytime I used the Add-In it reported No Errors, even though the JSLint.com website was finding errors with the same block of JavaScript code. The good news is that I was able to identify the problem and fix it.

Just the Workaround, Please
For JSLint.VS to work the .js extension must be associated with the JScript WSH engine. Chances are, you have the JScript WSH engine already installed, but another program has claimed the association. In my case, the .js extension was associated with UltraEdit. Once I reassociated it with the JScript engine, the JSLint.VS Add-In worked as expected.

To see what program the .js extension is associated with, drop to the command line and enter:

assoc .js

The output should be: .js=JScript. If it's not, enter:

assoc .js=JScript

Now try JSLint.VS again.

For Those Who Care... The Why...
When you launch JSLint.VS from within the IDE it creates three files in the Application Data folder (My Documents\User\Application Data):

  • wsh.js - contains the JSLint JavaScript code, which you can download from http://www.jslint.com/fulljslint.js
  • temp.js - contains the JavaScript code to check with JSLint
  • csh.cmd - a batch file that executes the wsh.js file, passing in the temp.js contents as input.

Specifically, JSLint.VS executes the following command:

csh.cmd wsh.js < temp.js

If the .js extension is not mapped to the JScript WSH engine the above command will result in the following error message: “There is no script engine for file extension .js”

In the face of that error, JSLint.VS simply returns, “No Errors.” Once you associate the .js extension with the JScript WSH engine you should be good to go!

posted @ 3:49 PM | Feedback (6)

Friday, October 02, 2009 #

October's Toolbox Column Now Online

My Toolbox column in the October 2009 issue of MSDN Magazine is available online and includes the following reviews:

  • nukeationMachine - nukeationMachine is a Visual Studio Add In that streamlines implementing WPF, WinForms, and ASP.NET user interfaces. nukeationMachine includes 1,600 UI bits, which are common groupings of user interface elements, such as Ok, Cancel, Retry buttons. You can add any UI bit to a design surface with a click of the mouse and in a fraction of the time it would take to add and position each user interface element separately.
  • Podcasts of Note: .NET Rocks - each week hosts Carl Franklin and Richard Campbell spend about an hour exploring a particular technology, product, or person of interest to the .NET community. Franklin and Campbell are both natural interviewers and do a great job picking the brains of their guests while maintaining a smooth and natural flow to the discussion. Listening to a .NET Rocks podcast is a lot like overhearing a conversation among experienced developers at a user group meeting or conference - you're bound to learn something new, hear an interesting anecdote or two, and discover how other knowledgeable developers are using .NET and related technologies in their daily jobs.
  • Fluent NHibernate - NHibernate is a popular ORM for .NET whose relational to domain mapping is typically specified via XML. Fluent NHibernate is an open source library that enables developers to specify these mappings using a fluent interface, an API design style that aims at maximizing readability through the use of descriptive names and method chaining.

This issue reviewed The Nomadic Developer, by Aaron Erickson. An exerpt from my review follows:

If you currently work for a technology consulting company, or are weighing the pros and cons of doing so, then Aaron Erickson's book "The Nomadic Developer" is for you. ... [It] should be required reading for anyone currently seeking a job at a consulting company, especially those who have not previously worked as consultants. The book's second chapter, "The Seven Deadly Firms," describes seven dysfunctional traits that, if present, will negatively impact your time at the company. For each dysfunction, Erickson supplies a detailed description, explains what life is like at a consulting firm when that trait is present, and provides tips for spotting the dysfunction during a job interview. There are also chapters on the top 10 traits a technology consulting firm is looking for in applicants, and another chapter that suggests questions applicants should ask during the interview process.

Enjoy! - http://msdn.microsoft.com/en-us/magazine/ee309513.aspx

As always, if you have any suggestions for products, blogs, or books to review for the Toolbox column, please send them to toolsmm@microsoft.com.

posted @ 10:10 AM | Feedback (2)

Thursday, October 01, 2009 #

Deleting All Records In a Table EXCEPT For the N Most Recently Added Records

I recently ran into a situation where I needed to delete all records from a table except for the 1,000 most recently added records. Specifically, I was working on a site that used Error Logging Modules And Handlers (ELMAH), a free, open source error logging library for ASP.NET applications. ELMAH doesn't automatically prune its error log. If you are using the database as a log source and if your web hosting provider has a database quota in effect, it is possible to have ELMAH's error log grow so large that the quota is surpassed and the database is taken offline. To help mitigate this problem, it is a good idea to take steps to keep ELAMH's error log size in check:

As with any sort of logging service is is important that ELMAH's log be periodically pruned. If you let ELMAH's log grow unchecked it can reduce performance when querying the log and suck up disk space, which is especially important in hosted environments where there are typically hard disk quote limits for each user on the database server. The good news is that there are a number of techniques you can employ to help ensure that your ELMAH error log stays a reasonable size.

  • Use error filtering.
  • Setup a weekly job in SQL Server to delete entries older than, say, three months.
  • Update the ELMAH_LogError stored procedure to delete old log entries.

Keep in mind that trimming the error log brings with it a tradeoff: you are removing error log entries that might be important for analysis later in time. If this is the case, if you think you might need to review that error log from more than 90 days in the past, then before deleting records from the error log you should archive them somewhere.

While these approaches are certainly good measures to implement, they don't handle the case where there's a flood of errors in a short amount of time. If ELMAH's error log is being inundated with errors from the past 24 hours, a script that deletes error log entries older than three months is going to have no effect.

An alternative approach is to delete all entries in ELMAH other than the most recently added N error log entries. This ensures that the error log never exceeds a maximum number of records (N), while keeping the N most recent records. The following query will do the trick:

DELETE FROM ELMAH_Error
WHERE ErrorId NOT IN
        (SELECT TOP 1000 ErrorId
         FROM ELAMH_Error
         ORDER BY TimeUtc DESC)

The ErrorId column is the primary key in the ELMAH_Error table, and TimeUtc stores the UTC date/time the error was recorded. The subquery gets the first 1,000 ErrorId values ordered by the time the error was logged, from the most recently logged to the oldest. The query deletes all error records whose ErrorId values are not in that set of the 1,000 most recently added errors.

If you are using SQL Server 2005 or beyond, you can use the ROW_NUMBER() keyword in a query like so:

DELETE FROM ELMAH_Error
WHERE ErrorId IN (
      SELECT ErrorId
      FROM
        (
           SELECT ErrorId, ROW_NUMBER() OVER(ORDER BY TimeUtc DESC) AS RowIndex
           FROM ELMAH_Error
        ) AS ErrorsWithRowNumbers
      WHERE RowIndex > 1000
)

The innermost query uses the ROW_NUMBER() keyword to assign a row number to each record from ELMAH_Error, numbering the most recently logged error 1, the next one 2, and so forth, such that the oldest error will have the largest row number assigned. This innermost query is used as a derived table. The query that uses it gets the ErrorId values whose row number is greater than 1,000, which returns those ErrorId values for errors that are “ranked” 1,001 or higher. In other words, it returns those ErrorId values for errors that are not one of the 1,000 most recently added errors. Finally, this set of ErrorIds are deleted from the table.

I ended up using the second query (the one with ROW_NUMBER()), placing it in the ELMAH_LogError stored procedure (along with a DELETE statement that removes any error log entries older than 3 weeks). If you use either one of these queries, consider adding an index on the TimeUtc column sorted in descending order.

Happy Programming!

posted @ 8:50 AM | Feedback (6)

Monday, September 28, 2009 #

PROBLEM: CSS Styles No Longer Apply For Anonymous Users

I teach two ASP.NET courses as the University of California - San Diego Extension. In the second course students use forms-based authentication and ASP.NET's membership framework to create a web application that supports user accounts. Invariably, at least one student bumps into the following scenario:

  • The website's formatting and layout is defined via CSS rules, which are located in one or more .css files.
  • The URL authorization settings have configured such that web pages on the site can only be accessed by authenticated users. That is, the <authorization> section in the Web.config file in the root folder contains the following markup:

    <authorization>
        <deny users="?" />
    </authorization>


    Some students lock down the entire site to authenticated users only. Others may have one or more <location> elements that open up access to specific pages to anonymous users, or have a separate folder with its own <authorization> settings that allow anonymous access to the pages within.
  • The website is served using the ASP.NET Development Server, which is the lightweight web server that ships with Visual Studio and is launched when you run a file system-based web application from the IDE.

When the above conditions hold, students find that when visiting their site those CSS formatting and layout rules defined in the .css file(s) are not applied for anonymous visitors. For example, when visiting the login page, the website's colors and fonts and layouts defined in the .css file(s) are not in effect. However, once the visitor signs into the site, the CSS rules take effect.

What's going on here?

The reason the CSS rules are not applied when the site is visited by an anonymous user is because of how the ASP.NET Development Server handles web requests. In a nutshell, every single request that arrives to the ASP.NET Development Server - be it for an ASP.NET page or a CSS file - is dispatched to the ASP.NET engine for processing. Consequently, the URL authorization rules defined in Web.config apply to the CSS files as well as the ASP.NET pages. So when an anonymous user visits, the page includes the <link> elements to the CSS files and the browser requests them, but the web server responds with a redirect response to the login page. As a result, the browser does not get the CSS content and that's why CSS rules do not apply for anonymous users (and why they start applying once the visitor signs in).

The simplest workaround is to configure the URL authorization rules to allow anonymous users to access the CSS files. If you have the CSS files in a separate folder (such as /Styles), then you can simply add a Web.config file to that folder with the following <authorization> settings:

<authorization>
    <allow users="*" />
</authorization>

If the CSS files are in the root folder, then you will need to add a <location> element in the root folder's Web.config file for each CSS file and use the above markup to permit anonymous users to access those files. For more on using the <location> element, refer to location Element (ASP.NET Settings Schema).

Finally, keep in mind that the behavior described here only occurs when the web server dispatches requests for CSS files to the ASP.NET engine. As aforementioned, this is the behavior of the ASP.NET Development Server; however, this is not the default behavior of IIS. By default, IIS handles request for static content itself, meaning that ASP.NET's URL authorization rules will not apply to CSS files, JavaScript files, images, ZIP files, and so on, although it is possible to instruct IIS 7 to integrate it's security checks with ASP.NET's configuration via the Integrated Pipeline mode. See Apply ASP.NET Authentication and Authorization Rules to Static Content with IIS 7.0's Integrated Pipeline Feature for more information.

posted @ 8:34 AM | Feedback (1)

Thursday, September 10, 2009 #

September's Toolbox Column Now Online

My Toolbox column in the September 2009 issue of MSDN Magazine is available online and includes the following reviews/discussions:

  • Improving Web Application Performance With Distributed Caching - provides an overview of distributed caching for web applications, with discussions on three products: memcached, an open source option that powers many high-profile sites like LiveJournal, Wikipedia, and SourceForge; Velocity, which is Microsoft's foray into the distributed caching market; and third-party commercial implementations, like ScaleOut Software's ScaleOut StateServer and Alachisoft's NCache, which was reviewed in the October 2007 issue.
  • Blogs of Note: Udi Dahan - Udi is a speaker, trainer, and consultant on software architecture and design of distributed systems. He has worked on several large-scale, service-oriented applications for enterprises, and blogs about his experiences building enterprise applications on his blog. Check out the 'First time here?' page on Udi's blog, which has links to his most popular and engaging articles and blog posts.
  • AutoMapper - it's not uncommon to need to transfer objects of one 'shape' into a different shape. This is particularly common when exposing data through a service layer. Internally, you work with your entities in terms of business objects that model your domain, but when exposing this data you may need to return a more appropriate object type that contains fewer properties. These objects used in the service layer are referred to as Data Transfer Objects (DTOs). Writing the domain object to DTO mapping code is tedious. AutoMapper, a free, open source project - helps relieve that tedium by making such object-object mappings as easy as writing two lines of code.

This issue did not include a book review.

Enjoy! - http://msdn.microsoft.com/en-us/magazine/ee413550.aspx

As always, if you have any suggestions for products, blogs, or books to review for the Toolbox column, please send them to toolsmm@microsoft.com.

posted @ 8:12 AM | Feedback (1)

Tuesday, September 08, 2009 #

A Tool For Querying Multiple Databases

I recently blogged about different multi-tenant data architectures, comparing and constrasting the Separate Databases and Shared Database, Shared Schema architectures, as well as noting what sorts of questions to ask when trying to ascertain which model to use. One of the disadvantages of the Separate Databases approach is that it can be difficult to view data aggregated across the databases:

Viewing data aggregated across the databases is difficult. I've touched upon this topic in an earlier blog post, Running the Same Query Against Multiple Databases. When you find a bug on one database and need to see whether it affects data in other databases there are not many tools at your disposal. One poor man's tool is sp_msForEachDb, but it's less than ideal.

And reader John Chapman added his two cents in the comments regarding this issue:

For lots of applications you actually have situations where there are users who need to be able to see data from multiple customers at the same time. For example you may have situations where you have external customers who see only their data, but yet have internal liaisons who need to oversee the activities of multiple customers. Therefore necessitating that they see data from multiple customers on a single screen.

I have ran into these situations before, which was a key reason why we used a single database shared schema. The application we replaced used separate databases and was unable to provide this sort of functionality.

Over the years I have created a very (very!) rough tool for querying multiple databases in a Separate Databases architecture. In short, you enter a query, select which databases to query against, and then the tool runs that query against each selected database and combines the results into a single <table> on the page. As you can see from the screen shot below (click for a larger version), the tool includes a multi-line textbox for entering the query to execute and a CheckBoxList of the databases to query. The results are included in a single <table>.

While the above screen shot shows a query that just returns a scalar value (one column, one row), it certainly works with queries that return multiple rows and columns. And with a little bit of legwork the tool could be enhanced to include rollup-type functionality, showing subtotals per database and grand totals across all selected databases for numeric columns.

To learn more about how I created this tool, check out my latest 4GuysFromRolla.com article: Querying a Multi-Tenant Data Architecture.

posted @ 3:15 PM | Feedback (6)

My Links

Ads Via DevMavens

Archives

Post Categories

 

I am a Microsoft MVP for ASP.NET.
I am an ASPInsider.
<March 2010>
SMTWTFS
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Comment Stats

DayTotal% of Total
Sunday 2056.8%
Monday 42514.1%
Tuesday 51917.2%
Wednesday 55518.4%
Thursday 58019.2%
Friday 54718.1%
Saturday 1886.2%
Total 3019100.0%

Hour1Total% of Total
12:00 AM 782.6%
1:00 AM 812.7%
2:00 AM 682.3%
3:00 AM 822.7%
4:00 AM 692.3%
5:00 AM 1264.2%
6:00 AM 1183.9%
7:00 AM 1816.0%
8:00 AM 1926.4%
9:00 AM 1585.2%
10:00 AM 1886.2%
11:00 AM 1936.4%
12:00 PM 2016.7%
1:00 PM 1846.1%
2:00 PM 1695.6%
3:00 PM 1354.5%
4:00 PM 1153.8%
5:00 PM 1073.5%
6:00 PM 1013.3%
7:00 PM 1073.5%
8:00 PM 923.0%
9:00 PM 882.9%
10:00 PM 913.0%
11:00 PM 953.1%
Total 3019100.0%

Comments by Blog Entry Date/Time

Day Entry MadeAvg.Total
Sunday 4.97159
Monday 4.80384
Tuesday 4.04477
Wednesday 7.39680
Thursday 6.26676
Friday 5.07466
Saturday 4.78177
Total 5.403019

Hour1 Entry MadeAvg.Total
12:00 AM 5.2937
1:00 AM 1.002
5:00 AM 0.000
7:00 AM 3.8550
8:00 AM 3.72134
9:00 AM 6.06297
10:00 AM 5.63276
11:00 AM 4.22194
12:00 PM 6.16351
1:00 PM 3.09133
2:00 PM 4.89230
3:00 PM 7.64321
4:00 PM 4.00108
5:00 PM 6.07170
6:00 PM 4.64116
7:00 PM 8.95188
8:00 PM 8.63164
9:00 PM 5.00115
10:00 PM 6.31101
11:00 PM 4.5732
Total 5.403019

Learn More About Comment Stats
1 - All times GMT -8...


Blog Stats

Favorite Web Sites

My Books

My MSDN Articles