| Scott on Writing |
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.
1/8/2010
|
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.
12/8/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
12/1/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:
- The MailMessage object that the PasswordRecovery control is about to send is available via e.Message, and
- 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!
11/10/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
11/5/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.
10/26/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 <, >, and ", 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 <a target="_blank" href="http://www.4guysfromrolla.com/">4GuysFromRolla.com</a>!">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>!")
10/23/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!
10/15/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.
10/2/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!
10/1/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.
9/28/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.
9/10/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.
9/8/2009
|
Ruminations on Multi-Tenant Data Architectures
One of the key advantages of web applications is that they can be deployed as a hosted service and accessed over the Internet rather than needing to be locally installed at a customer's site. Over the years I've helped numerous clients build web applications of this sort. When building such an application there are bound to be questions on the best way to model and store data from the different customers that access the hosted application. Do you co-mingle customer data in a single database with a single schema? Do you use a single database, but separate customer data into different schemas? Or do you create a separate database with separate tables for each customer?
These three data architectures are examined and discussed in detail in the article Multi-Tenant Data Architecture. The authors introduce three different multi-tenant data architectures, which they name:
- Separate Databases - in this architecture each customer's data is stored in a separate database. The databases may all be on the same database server or they could be partitioned across multiple database servers. This approach provides for maximum isolation of customer data. If we were building a hosted application using the Northwind database and had three customers then with this approach we would have three databases - Northwind01, Northwind02, and Northwind03 - and each database would have the same tables, views, stored procedures, and so on. Customer 1's data would all be located in the Northwind01 database, while Customer 2's data would be over in the Northwind02 database.
- Shared Databases, Separate Schemas - SQL Server 2005 introduced the concept of schemas, which offer a way to group a set of tables. With this approach you can have a single database with one schema for each customer. Returning to the Northwind example, with this approach there would be a single database, but there would be three schemas - Customer01Schema, Customer02Schema, and Customer03 schema. Each schema would have the same set of tables, views, stored procedures, and so forth. Customer1's product information would be found in the Customer01Schema.Products table, whereas Customer2's product information would be stored in Customer02Schema.Products.
- Shared Database, Shared Schema - here we have only a single database and a single schema. There would be only one Products table. To differentiate one customer's products from another we'd need to add a NorthwindCustomers table that would have a record for each customer and then add a NorthwindCustomerID foreign key to the Products table (and to the other pertinent tables).
The Multi-Tenant Data Architecture examines these three approaches in much more detail with screen shots, T-SQL snippets, and more in-depth examples, and is definitely worth reading.
I'd like to talk a little bit about my “from the trenches” experience with multi-tenant data architectures. First, a little background.
Medical Software In 2003 I started work with a client we'll call Acme Medical, which was building a hosted, ASP.NET medical software application. This application had been in existence for a couple of years by this point, but as a Microsoft Access application that was installed and run locally from a couple of hospitals and clinics. I joined the project with the aim of moving the application from Microsoft Access to ASP.NET and SQL Server. We chose to use a Separate Databases architecture. I continue to work on this project today. Originally, there were two customers, each with a few thousand records. The data model contained maybe 20-30 tables. Today there are more than 15 customers, each with hundreds of thousands if not millions of records. There are nearly 350 database tables, hundreds of ASP.NET pages, and several automated backend processes.
Print Management Another client, Acme Printers, was a prominent player in the print shop marketplace, selling in-house applications to large-scale print shops. I helped this company build an online, hosted version for order placement and fulfillment. We used a Shared Database, Shared Schema data model. In a nutshell, there is a Customers table with a CustomerID primary key value uniquely identifying each customer. Every other database table has a CustomerID field that identifies what data belongs to what customer. When a user signs on, we look up what customer the user is associated with and then store this CustomerID in session. This CustomerID variable is then used in other pages on the site to pull back the data pertinent to that customer.
I have never used the Shared Database, Separate Schema approach. I recently spoke with Michael Campbell about this topic, and he shared the following advice: “I'd recommend against using shared DBs and separate schemas. In my experience that almost NEVER works out as advertised, adds all sorts of difficulty in terms of disaster recovery, management, and isolation, and really doesn't offer any worthwhile benefits. It also becomes absolutely insane in terms of managing permissions/security as well.”
The factors that should influence whether you go with a Separate Database or Shared Database, Shared Schema architecture are not technical, but rather are regulatory-, security-, or business-related. If you find yourself deciding to use one architecture over another because of some technological reason you're probably asking the wrong questions and evaluating the wrong criteria.
Here's how I tackle this problem. I start by assuming that I'm going to use the Shared Database, Shared Schema architecture, as it's usually the best fit for the types of projects I work on. It easier to setup and implement and test and debug and scale up (to a point) than the Separate Database architecture. For the majority of web applications, the Shared Database, Shared Schema architecture is the best approach when weighing just the technological- and development-related factors.
Sadly, in the real-world there are many factors that outweigh technological ones. While the Separate Databases architecture has more friction associated with it than the Shared Database, Shared Schema, it does have certain advantages in terms of security, privacy, isolation, and so on. When evaluating which architecture to use, I like to ask my client the following questions:
-
How important is the security and privacy of the data?
-
For the medical software, security and privacy is paramount. Customers (the hospitals using the software) are entering social security numbers, lab test results, diagnoses from specialists and psychologists, and so on. The data is rife with very personal and sensitive patient information, not to mention information about the health care practitioners, including social security numbers, license information, and disciplinary actions.
-
Are there any regulatory reasons for choosing one architecture over the other?
-
Depending on the line of business, there may be governmental or professional regulations that require a clean separation of data. For medical providers in the US there is a regulation known as HIPAA, which defines guidelines for patient privacy. It's been several years since I've explored HIPAA, but from my recollection HIPAA does not require that a Separate Database architecture be used, but using one ensures that patient data in one hospital is isolated from the doctors and staff at other hospitals.
-
Do your customers want/need access to the database?
-
In some industries, customers using a hosted application demand access to the data. For the medical software, there have been a handful of clients who, before signing up, stipulated that they have access to a weekly backup of their data, which they could store on their computer system. With the Separate Databases approach it has been easy to service these requests - schedule a weekly backup for that customer's database and add an ASP.NET page that allows that customer's administrative users the ability to see past backups and download those backup files to their computer.
-
How many customers do you expect to be using the system?
Note that the above questions don't touch on technological or development issues. Rather, they are focusing on security, privacy, regulations, and the end user's needs or expectations.
While the Separate Databases architecture provides a higher degree of security and privacy through data isolation, there are a couple of challenges worth noting.
- Rolling out changes to the data model is more difficult. Imagine that you've added a new feature, which entailed adding two database tables, a new view, and six new stored procedures. In a Shared Database, Shared Schema architecture, rolling out that change is a matter of adding those database objects to the production database. With a Separate Databases architecture, you need to make sure to roll out those changes to all of the databases. With a systematic, tested, scripted process this is not much of a challenge, but if you don't have such a system defined - if this work is done manually, for instance - you're asking for trouble as you're undoubtedly going to have a scenario where the database changes get rolled out to some databases, but not all. This can be especially challenging if you have decided to customize the data model on a per-customer basis. If database X has certain tables or fields that are not found in database Y then this makes rolling out changes in an automated fashion more challenging. For this reason I would discourage making table-level customizations per customer if at all possible.
- 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.
- Adding a new customer requires creating a new database. Signing up a new customer is great news for the company's bottom line, but what changes does it entail in the data model? For a Shared Database, Shared Schema architecture, adding a new customer is as simple as adding a new record to the Customers table. For a Separate Databases architecture, adding a new customer means creating a new database and adding the appropriate database objects and any initial data in these tables. Using the SQL Server model database you can simplify this process, but in order to do so you need to make sure that any changes to the data model - new stored procedures, tables, views, UDFs, etc. - need to also be added to the model database. Similarly, any changes to the initial state - new default records in a lookup table, say - need to also be added to the appropriate tables in model.
Given the pros and cons of these two multi-tenant architectures, you can guess what architecture a company is using based on their price point and their targeted industry. If there is a high cost to sign up to the site then chances are the company has only a “few” clients (maybe just a few, maybe dozens, but probably not hundreds or thousands), and if the application is for medical, financial, or legal purposes then they probably use a Separate Databases architecture. For web applications with a lower price point and geared to industries where data privacy and security is less important and less encumbered by regulations, chances are a Shared Database, Shared Schema approach is being used. As you might expect, the medical software has around 15 customers, each paying thousands of dollars per month to use the service, whereas the print shop software has a couple hundred clients who pay a one time setup fee with the option to buy an annual support contract, which amortizes to hundreds of dollars per month per cusotmer.
Whether you choose the Shared Database, Shared Schema or Separate Databases architecture depends largely on non-technological factors. As I noted earlier, I typically choose the Shared Database, Shared Schema architecture by default, only switching to a Separate Databases architecture if there are particular security, privacy, regulatory, or other business needs that necessitate it.
8/19/2009
|
Range-Specific Requests in ASP.NET
The HTTP/1.1 protocol include support for range-specific requests, which allow a client to optionally request a particular range of bytes rather than request the entire file. This functionality is most commonly used by download manager programs, which allow users to pause and resume downloads. In a nutshell, a download manager will start by asking for the entire contents of a file (the default behavior). If the user pauses the download or if the download manager is shut down, the last downloaded byte is remembered. Later, the download manager can resume the download by sending a request to the server for the file starting from the last downloaded byte.
The following diagram illustrates the range-specific request workflow just described when downloading a large file named DancingHampsters.zip. The diagram shows what happens when the download is paused (or interrupted) after having downloaded the first 500,000 bytes, and how using range-specific requests the client can resume the download starting from a specified location in the file (rather than having to re-download the file in its entirety).
While IIS natively handles range-specific requests, ASP.NET does not. If you are serving binary content from an ASP.NET HTTP Handler and if you want (or need) to support range-specific requests then you'll need to add such functionality yourself. I bumped into this requirement when working on a project that serves videos to Apple's handheld devices - the iPhone and iPod Touch. The iPhone and iPod Touch request video files using range-specific requests. Therefore, if you are serving these videos straight from the file system via IIS then everything will work as expected, but if you serve the video from an HTTP Handler in order to implement authorization rules or if the files are dynamically generated then you'll need to write code in your HTTP Handler that will handle the range-specific requests sent by the Apple devices.
In my research I stumbled upon an article by Alexander Schaaf titled Tracking and Resuming Large File Downloads in ASP.NET, which presented Visual Basic code showing how to implement range-specific requests in an ASP.NET 1.x application. I took this code, refactored it, ported it to C#, and utilized a number of language enhancements added since the .NET 1.x days. This code, along with a discussion on how range-specific requests work and a look at how to use the code, is now available on DotNetSlackers.
Read more at: Range-Specific Requests in ASP.NET.
Happy Programming!
8/14/2009
|