Wednesday, June 20, 2007

In IE 7, the window.open function isn't what it used to be

So here's the scenario. You have a website with a link that opens a new window. When opening this new window, you use the javascript:window.open() function, and you specifically state "location=no, statusbar=no" within the open function. When you test it locally, it works wonderfully. Then, you make your push to production, and users see the location and statusbar in the new window. What's the deal!?

IE 7, Firefox, and Safari have all preached user security for quite some time, so it may not be much of a shock to you to hear that the address and status bars are always visible, no matter what parameters you feed it via window.open(). Granted, the address bar in the above scenario cannot be used for navigation as it is "greyed out", but these toolbars are always visible to ensure that user's aren't sent to Phishing sites, etc.

So why did my window.open() function work when I tested it?

Simple, when testing on your development PC, or internal network, you're withing your "Intranet" group according to your browser. The Intranet group has lower level security permissions that allow such functions to work. However, when you make your push to production, the site is now in the "Internet" group, which obviously has higher level security.

This FAQ here states how IE 7, Firefox, and Safari take to the window.open() parameters.

This post, albeit brief, hopefully saves you some time and energy on not only troubleshooting the scenario above, but also saves you in development time as you can no longer rely on this technique to open new windows with total control over their look and feel.

Labels: , , ,

Thursday, May 17, 2007

5 Minutes to Forms Authentication

In case you couldn't tell by the last few posts I've made... I really like Forms Authentication! I wasn't very organized and just threw some random posts out here to share some idea's, but sometimes all you need in life is simplicity... so that's what this post will be, a simple Forms Authentication setup.

I recently had to add some type authentication to an existing web application, nothing fancy at all, bare-bones, just a username, a password, and no database connectivity. That's where this post comes in... when all you need is something quick.

We'll cover this in 3 steps. The first is creating your login form, second being your form's code behind, and the third being your web.config setup.

Step 1 - Create Your Web Form

We won't do anything fancy here, all you want is:
  • A Username Textbox
  • A Password Textbox
  • A Button to submit your form
  • A placeholder to display error messages
Let's get to it!

Create a new web form in your project. In our case, we will call our form "login.aspx". Next, go to the login.aspx page and create your form. We want to create elements for each of the items above, so our end result is this:
Please login to use this application
Username:
Password:

Our HTML to create our web form is:


<table>
<tr>
<td colspan="3" id="tdMessage" runat="server"></td>
</tr>
<tr>
<td colspan="3">Please login to use this application</td>
</tr>
<tr>
<td>Username:</td>
<td> </td>
<td><asp:TextBox ID="txtUsername" Runat="server" /></td>
</tr>
<tr>
<td>Password:</td>
<td> </td>
<td>
<asp:TextBox ID="txtPassword" Runat="server"
TextMode="Password" /></td>
</tr>
<tr>
<td colspan="3"><asp:Button ID="btnSub" Runat="server"
Text="Login"></asp:Button></td>
</tr>
</table>

Above we have 5 rows in our table. Row 1 is our TD tag (named tdMessage) that will house our error messages. Row 2 is static text asking the user to log in. Row 3 contains our Username textbox (named txtUserName), Row 4 has our Password textbox (named txtPassword), and Row 5 has our button (named btnSub)That's it, our HTML is built!

Step 2 - Your Code-Behind

Ahh yes, our almighty code-behind... an ASP.NET page is almost useless without it! Our code-behind will remain very simple, as there is no database connectivity needed (as you'll see in step 3)!

For starters, Import the System.Web.Security NameSpace into your login.aspx.vb file:

Imports System.Web.Security

Next, we want to handle all login events in our btnSub Click events. So, within the btnSub click event handler, we place the following code:

If Me.txtUsername.Text = "" Or Me.txtPassword.Text = "" Then
Me.tdMessage.InnerHtml = "<font color=""red"">Please enter proper login
credentials</font><br>"
Else
If FormsAuthentication.Authenticate(Me.txtUsername.Text, Me.txtPassword.Text) Then
FormsAuthentication.RedirectFromLoginPage(Me.txtUsername.Text, True)
Else
Me.tdMessage.InnerHtml = "<font color=""red"">Invalid Login, please try again</font><br>"
End If
End If

Just like everything else in this project, the logic is simple. If a username or password isn't supplied, set the error message to ask for proper login credentials. If both a username and a password are supplied, hand-off the username and password values to the FormsAuthentication.Authenticate function, which returns a true/false value on the credentials. If the username/password authenticates, call RedirectFromLoginPage, which sets the the Forms Authentication cookie, and let's the user go on their merry little way. Otherwise, the login credentials were invalid, so we display an error message asking the user to try again.

Step 3 - Setup Forms Authentication in your Web.Config

Last but not least, we need to setup Forms Authentication in our Web.Config, otherwise all that cool code you wrote above is worthless.

So, open your web.config file in your project, within the system.web node, add the following XML to the Authorization section:

<deny users="?" />

This means that all users who are not authenticated are denied, and therefore are sent to be authenticated.

Secondly, in our Authentication section, add the following XML:

<authentication mode="Forms">
<forms
name="ourFormsAuth"
path="/"
loginUrl="login.aspx"
protection="All"
timeout="30">
<credentials passwordFormat="Clear">
<user name="ourUser" password="ourPassword"/>
</credentials>
</forms>
</authentication>

And that's all it takes! We set our authentication mode to "Forms", meaning that now our app expects a form on the website to authenticate our user. The Forms XML has a name attribute, which is the name of the cookie, the path of where the cookie is stored, the Url of the login form (in our case login.aspx), our protection mode (set to "All" to validate and encrypt the data), and our timeout of the cookie, in minutes). And, if you wish, you can add additional user credentials:

<user name="ourUser1" password="ourPassword1"/>
<user name="ourUser2" password="ourPassword2"/>
<user name="ourUser3" password="ourPassword3"/>

Now when you run your project, a login form will appear, asking you for proper credentials. If you dont enter a proper combination of what you entered in the "credentials" section, it doesn't let you in. As I stated in the beginning of this post... pretty simple!

Labels: , , , ,

Thursday, May 10, 2007

Creating Excel Reports with ASP.NET 2.0

Someone recently stumbled across this article on creating excel reports with ASP.NET 2.0, and they were king enough to forward it my way. It's nice because it makes use of the inherent COM objects from Microsoft Office and Excel, and much of the magic is actualy based around creating your appropriate DataSet, and the Excel piece is kind of just the whipped-cream topping...

Labels: , , , , ,

Sunday, May 06, 2007

Creating a Time control in .NET

Recently I was working on an ASP.NET application that required the end user to enter and submit a time on the web form. I wasn't comfortable leaving it up to a textbox, since I would need both an hour, a minute, and an AM/PM. Leaving this to a textbox entry would require validation for both the user to use a ":" as well as enter an "AM" or "PM" If you ask me... this sounds like a pain in the rear-end.

When I thought about my options a little more thoroughly, I realized that I could use drop down lists for the user to enter their time, and I can convert it to a date type, and in essence with only a little effort. If we add a calendar control to our form (which this example will assume you did already), you can get a specific date and time.

1. Create our drop down lists

Our end result is to have the following:
Select Class Start Time::


To do so, create three ASP drop down lists, the first for your hours, the second for your minutes (I only needed 15 minute intervals), and the third for your "AM" or "PM" selection.

2. Create your function

How does that song go, "conjunction junction, what's your function?" Sorry, got a little off track there ;)

Converting our selections into a specific time, there is some logic to consider. First of all, keep in mind that the default time to a date datatype is 12:00 AM, so that is your time to start with. So, with that in mind, the way you handle a selection of "12:30 AM" is very different than how you would handle "4:15 PM".

I know, I know.. less talky more codey.. ok, here's your function:


Function ConvertToDate(ByVal HrCtrl As System.Web.UI.WebControls.DropDownList, _
ByVal MinCtrl As System.Web.UI.WebControls.DropDownList, _
ByVal AMPMCtrl As System.Web.UI.WebControls.DropDownList, _
ByVal StartDate As Date) As Date

'Set the date of the class, StartDate is passed in from the selected date from our Calendar control, which is NOT depicted in this example!
Dim ourDate As Date = StartDate
'Get the hour selected
Dim Hour As Double = CDbl(HrCtrl.SelectedValue)
'Get the Minutes selected
Dim Min As Double = CDbl(MinCtrl.SelectedValue)
'A boolean value for AM/PM... remember, by default the time is 12:00 AM
Dim PM As Boolean = False
'Is our selected AM/PM value PM?
If AMPMCtrl.SelectedValue = "PM" Then PM = True
'If the selected time was 12:00 AM, we do nothing with the hours
If (Hour = 12 And Not PM) Then
'Just add any minutes.. if any
ourDate = ourDate.AddMinutes(Min)
Else
'Add any hours selected in the hour select box
ourDate = ourDate.AddHours(Hour)
'Now add any minutes
ourDate = ourDate.AddMinutes(Min)
'If the selected AM/PM is PM, we MAY have to add 12 hours to the current time
If PM Then
'If the selected hour is NOT 12, then we add 12 hours, otherwise adding 12 hours to
'the additional 12 hours would cause the time to be incorrect

If Hour <> 12 Then
ourDate = ourDate.AddHours(CDbl(12))
End If
End If
End If

Return
ourDate

End Function


As you can see, the function above isn't very complicated. It's parameters are your ASP.NET controls you created in step 1, so it's a little more sophisticated and flexible than just accepting plain text values.

It starts out by converting your hours and minuted to double precision values. The reason for this is because the AddHours and AddMinutes functions both accept a double datatype. Next, we check to see if both the hour "12" and "AM" was selected. If so, all we want to do is add any minutes selected, since adding 12 hours to the default time of 12 AM would make the time 12 PM, which is obviously incorrect. If 12:xx AM wasn't selected, we add any hours and minutes to our time. Finally, we check if "PM" was selected. If it was, and "12" wasn't selected, we add 12 additional hours to our time, making it a "PM" time.

3. Call our function

The hardest part is done, all we have to do now is call our function above. You can do so by using code similar to the following.

Dim ourSelectedDate As Date = ConvertToDate(Me.OurHourControl, Me.OurMinuteControl, _
Me.OurAMPMControl, Me.OurCalendarControl.SelectedDate)

As stated throughout this example, we assume that you have a Calendar control on your page, and we pass the date as our last parameter. If you don't need a Calendar control, then just pass a date in for that parameter instead. With this function created, if you have a need to create a date/time range from select boxes, you can use one function for all of your needs. And, it's fairly efficient to boot!

Besides that, that's all you need to return a date/time from a series of drop down lists. Pretty simple, eh???

Labels: , , , ,

Wednesday, May 02, 2007

Easing into XML - An easy way to work with XML, Datasets, and .NET

.Net has a lot of built in support for XML as a data structure, and they provde some pretty direct methods to traverse nodes and attributtes with XML. However, especially as of late, I've been sharing data to-from other applications via XML. During this process, sometimes I just want to get at the data... I don't want to loop through any nodes, I just want my data... either to throw at a datagrid, or perhaps just to check a value of something. Additionally, I already have my XML data sitting in a string, its not saved anywhere in a file, it's already accessible in my application.

In my travels, I was able to eventually figure out this formula for success. It accepts a string of XML (properly formed... of course) and reads it into a dataset, which in turn is read into a datatable, which in turn can be accessed via a datarow.

'I know it wont exactly win any awards, but here's some sample XML
Dim xmlText As String = "<OurData><customerID>08459684</customerID></OurData>"

'a datatable to hold our data
Dim dt As New DataTable
'a dataset to convert our xml into a datatable
Dim ds As New DataSet
'a datarow to read our datatable
Dim dr As DataRow

'Datasets can read XML in via a StringReader. If we already have our XML, and
'its not being provided to us via an external document, we can create a new
'StringReader on the fly, supplying our XML when it is instantiated
ds.ReadXml(New System.IO.StringReader(xmlText))

'Now that we have a dataset, use our datatable to receive our "table", which
'in this case is "OurData" based on the sample XML above

dt = ds.Tables(0)
'Now we can read in any "rows" from our XML. We only have one row, so I'll
'for-go writing any logic to loop through the rows...
dr = dt.Rows(0)

'Finally, we can directly reference our node and get our value based
'on the current row

Dim custID as String = CStr(dr.Item("customerID"))

Yes... it IS that simple...

Labels: , , ,

Tuesday, May 01, 2007

Custom Forms Authentication - Cleaning up

My last few posts revolved around custom forms authentication. After stumbling and bumbling through the process, I had to go back and perform some minor cleanup. So lets grab a mop and bucket, a few cold ones, and get our hands dirty...

Why is my Global.asax's Application_AuthenticateRequest called multiple times?

I ended up "googling" a similar question to the one above, and it's one of those things that if you take the time to think about it, you know the answer. The Application_AuthenticateRequest routine is used on every page request. So even though your user may attempt to go to Default.aspx, this can actually invoke two or three page requests.

So to circumvent this "problem" (I hate to call something a problem when it's doing what it's supposed to do), we just check if the current user is known at the beginning of every request, like so:

if (HttpContext.Current.User = null)
{
We dont know who they are, so authenticate
}
else
{
We know who they are
}

What good is Custom Forms Authentication if I dont show a login form?

The reason I like doing Custom Forms Authentication is because it's tweaking an existing security principle from Microsoft. They built in all code, functions, classes, and settings to globally authenticate a user. If we make the proper entries in our web.config to use Forms Authentication, we can make use of the Global.asax's Application_AuthenticateRequest to perform our authentication routine for our end-user.

For example, let's say you authenticate your user into your application by performing a Form POST of data from your host site, and this is the only way you want your users to enter your app. If your Application_AuthenticateRequest doesn't recognize the user, your authentication routine can check for a Form POST of data from your host site. If it's there... authenticate them, if it's not.. dont authenticate them, it's that simple.

And the coup de grace, you can make use of the "loginForm" parameter in your web.config's Forms Authentication settings to actually make your "You are not authorized page". Therefore, if the user enters, and the user is not authorized to be on the site, the user is still redirected to page referenced in the loginForm parameter.

How can I keep my users authenticated without a login form?

I wanted my users to eventually timeout, unless they remained active within the site. To perform this, I made use of the slidingExpiration parameter. Place this in your web.config's Forms Authentication settings, like so:

forms name="foo" loginUrl="notAuthorized.aspx" protection="All" timeout="30" path="/" slidingExpiration="true"

With sliding expiration, everytime a request is made, the clock "resets" on the forms authentication cookie. So, with my settings above, that means that every time the user uses my site, their identity ticket's expiration is set to the current time+30 minutes.


I hope you enjoyed my last few posts regarding Forms Authentication. It is certainly a well-built tool for ASP.NET that requires nothing more than comfort with using cookies on your site.

Labels: , , ,

Monday, April 30, 2007

Custom Forms Authentication - Pass User Data using userData!

My last post shared some information on custom forms authentication. I was able to use the information I shared to create a custom authentication scenario where I didn't necesarily have a login form, but I was able to still centralize all authentication in my web application's Application_AuthenticateRequest module in the Global.asax code behind.

One of my struggles with this method, however, was my attempt to pass any supplemental User Data I may need for the user's web experience. In other words, if I need the users customer number, where can I store this information? Well, you can store user's data in userData!

This wasn't immediately apparent to me, as any examples I came across talked about storing "Roles" in the userData field, but from what I have gathered, it really is an open ended field that you can store any supplemental data into.

So, when creating our FormsAuthenticationTicket, in two of our overloaded functions we have the ability to add userData. Here is one example:

FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(version, name, issueDate, expiration, isPersistent, string userData, cookiePath);

So, when we create our ticket, and store it as a cookie, later we can retrieve the ticket from the cookie and make use of the userData property to get our user's data.

Finally, let's say we have a variety of information we want to store, such as customer number, address, and ZIP code. You can always just store this information as a delimited string:

Customer Number:12345678|Address:123 Foo Lane|ZIP Code:98765

Now, when you retrieve the authentication ticket, throw your delimited data into an array, parse the array into a HashTable, and then request your information as you need it. (By the way, if the forms auth ticket code looks familiar, it's because I made use of Dr. Bromberg's example from my last post!)

//Get our users identity
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
//Get all user info from the Forms Auth Ticket
FormsAuthenticationTicket ticket = id.Ticket;
//Get our user data, from the UserData property of the Forms Auth Ticket
string userData = ticket.UserData;
//Our data in userData is pipe delimited, so split it into an array
string[] info = userData.Split('|');

//Create a new HashTable to store our information
System.Collections.Hashtable ht = new Hashtable();
//Loop through our array
foreach(string s in info)
{
//Split each string at the ":", to give us a key/value pair
string[] splitme = s.Split(':');
//Now add our key/value pair to the HashTable
ht.Add(splitme[0].ToString(),splitme[1].ToString());
}

//Finally, our data is available to us as we need it!
string CustNum = ht["Customer Number"].ToString();
string CustAddress = ht["Address"].ToString();
string CustZIP = ht["ZIP Code"].ToString();

Labels: , , ,

Friday, April 27, 2007

Custom Forms Authentication

Forms Authentication in .NET is a great feature and easy to implement. Its an automatic, site-wide, mechanism to ensure that your user's are authenticated on your site. If they're not authenticated, theyre sent to a login form to authenticate... hence Forms Authentication.

But lets say, for example, that you want to make use of all of the benefits of Forms Authentication, without having a login form. This article below gives an excellent example of how to implement Custom Forms Authentication.

Custom Forms Authentication - Egghead Cafe - Author: Peter A. Bromberg, Ph.D.

Labels: , , , ,

Friday, April 20, 2007

Exceptional Exception Handling

Here's some new guidelines from MSDN regarding Exception Handling in .NET

http://msdn2.microsoft.com/en-us/library/ms229005.aspx

Labels: , ,