irob's blog.

Software Developer @ Engage Software. DotNetNuke Training Instructor

December 2008 - Posts

St. Louis Day of .NET

Last weekend I attended a local .NET Developer conference - the St. Louis Day of .NET. It was a regular work day (9-5) of .NET-centric one-hour sessions. Most of the sessions were based (to varying degrees) on content from the recent Professional Developer's Conference (PDC) in Los Angeles.

DODN_Banner

There were two main reasons I couldn't pass it up, aside from the fact that there were going to be a few interesting sessions. The first was that Engage Software was one of the sponsors, so I wanted to represent our company at the conference. The other reason was that it was located in Washington University's CAIT Facilities in Clayton, MO about 1.5 miles from my house. If I was going to get up early and do "work like stuff" on a Saturday - at least it was close!

busyevent_logoI was impressed at how well the conference was put together. The three guys responsible (Kevin, Jeff, and Scott) rounded up BusyEvent to help with the details. I must say it worked out very well. Registration was especially painless, and except for hearing groans about running out of coffee at one point, I didn't really hear anyone talking about logistics of any kind, just the sessions (which is a good thing). Thought I haven't been to too many conferences, I get the sense that people not complaining about the logistics is unusual. So kudos to everyone who helped put this one together.

New ASP.NET Stuff

The session I enjoyed the most was on a subject that I'm already a bit familiar with - ASP.NET 3.5 SP1 New Features. The speaker was knowledgeable, the presentation was straightforward, the demos worked, the content was relevant -- it was everything I could want out of a presentation/session.

The session really consisted of introductions to the ADO.NET Entity Framework, ADO.NET Data Services, and Dynamic Data. I am really excited for these technologies to become more mainstream (and no matter how they stack up compared to non-Microsoft-issue tools doesn't even really matter because it's a ton better than hand coding or using DataSets for everything!)

The Entity Framework (EF) hit home for me, because I've been writing data access layers from scratch for a few years now and I'm really looking for something to do all that busy work for me. Given that EF is supposed to be Microsoft's Way Forward in the ORM Space and that it looks to be pretty quick and painless to use - I'm ready to dig in.

I'm going to be experimenting a bit over the next week and will sum up the results in presentation format for the rest of the Engage crew before we leave for Christmas break. If you're lucky I'll share with you as well. I'm especially interested in figuring out how to make it work well in the DotNetNuke world.

A related side-note - Brandon Haynes has what looks to be a fun little project going on CodePlex.

image

Design? Design!

In addition, I attended the Windows 7: Design Principles for Windows 7 session. This was a great session because I got to see that Microsoft is finally catching on.  Things that seem trivial or minor simply aren't. Your product is evaluated not just by functionality but by the experience and how the product makes the user feel.

This is great news because I personally believe that the flexibility complexity of Windows is what your grandma is really scared of - not the fact that it's a computer and all technology is scary - Windows itself can be pretty scary!

The tweaks that they've done to the Windows operating system for Windows 7 are very subtle and each are pretty minor on their own, but when you add things up, we're going to get a much better experience out of the next OS - about time! Check out the video (wmv) from the original PDC talk.

Other Sessions

I also went to the Expression Blend 2 Tips and Tricks session, which was cool because I haven't messed with Expression Blend at all yet. I did get a better feel for the tool through the presentation, and some insight into some of its capabilities. One of my favorite ways to learn is by watching someone who is better than me, and even watching the presenter click around a bit helped me get my head around the features. Now I wish I had an excuse to fire Blend up at work!

Finally, I caught the first 1/3rd of the Future of C# session before I headed out. It was a very promising topic, as it's something I'm very interested in, but because it was packed (standing room only), hot, and the projected image of the presenter's computer wasn't even viewable (by me) from the back of the room, so I ducked out early. I did see the part about how dynamic languages are all the rage, and that (better or worse) C# 4.0 is headed, in-part, in that direction. Cool. I'll keep an eye on my Google reader for updates :)

Until next year STLDODN! Oh yeah -- check out what twitter folks are saying about the conference.

If you went - what sessions did you see? What were your thoughts?

"Improving the Secured File UX" Part Two

In Improving the Secured File Download UX for Unauthenticated Users I elaborated on a workaround hack to display a friendly error message and redirect to the login page when trying to access a file that had been through DotNetNuke's file ticket system.

If you're in a situation where that solution makes sense for you - great. But what about future releases of the application? Should this functionality exist within the framework itself? Is it too trivial? Does it make sense for everyone that uses the framework so much so that it should be a part of it?

To find out, I put the issue in DotNetNuke's Gemini.

I suggest that when you encounter a workaround/hack/enhancement such as this:

  • Don't just fix it for now
  • These "fringe areas" are one of the places where the "community at large" can be of most help. Let the core team focus on the big stuff.
  • So...put it in Gemini!
  • And, ideally, try to give the core team a head start.

So, in an effort to follow the above path, I cracked open my handy DotNetNuke source code and made a few changes.

update: I'm providing the following information for reference only, I really don't think you should make this change to your DNN application. My intention is just to remind you that the core code is there, we can easily make changes, test them out, and suggest to the DotNetNuke team that they incorporate those changes into the application.

Background

When the user requests the file, the LinkClick.aspx "page" is requested. (e.g. "http://localhost/DotNetNuke_2/LinkClick.aspx?fileticket=XKDsP6pHvtQ=&tabid=36&mid=410"). This really isn't a page, but is a handler registered in the application's web.config.

The actual class that handles the file request is called "FileServerHandler" and is found in Library\Components\FileSystem\FileServerHandler.vb in version 4.9 and \Library\Services\FileSystem\FileServerHandler.vb in DotNetNuke 5.0 RC2.

While some slight refactoring has been done to this class between 4.9 and 5.0, the portion we're concerned with has not changed:

' serve the file
If TabId = Null.NullInteger Then
    If Not FileSystemUtils.DownloadFile(_portalSettings.PortalId, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
Else
    If Not FileSystemUtils.DownloadFile(_portalSettings, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
End If

The above code is responsible for the original behavior of showing the localized file permission error message.

Goals for Improvement

If the user is not able to download the file:

  1. If they are not logged in, let's take them to the current portal's login URL with a return URL for the file ticket.
  2. If they are logged in, display the localized FilePermission.Error message, as usual.

Adding a Utility Method

First, let's add a method to help us get the current portal's login URL:

Private Function GetLoginUrl(ByVal portalSettings As PortalSettings, ByVal request As HttpRequest) As String
    If Not portalSettings Is Nothing AndAlso Not request Is Nothing Then
        Dim tabId As Integer = portalSettings.ActiveTab.TabID
        Dim controlKey As String = "Login"
        If Not Null.IsNull(portalSettings.LoginTabId) AndAlso String.IsNullOrEmpty(request.QueryString("override")) Then
            controlKey = String.Empty
            tabId = portalSettings.LoginTabId
        ElseIf Not Null.IsNull(portalSettings.HomeTabId) Then
            tabId = portalSettings.HomeTabId
        End If
        Return DotNetNuke.Common.Globals.NavigateURL(tabId, controlKey)
    Else
        Throw New ArgumentNullException(If(portalSettings Is Nothing, "portalSettings", "request"))
    End If
End Function

This method is intended to yield the correct login page whether a custom one is specified or not. It could potentially use the current tab, or the home tab as well. Just playing it safe.

Adding the core enhancement

Now, we need to figure out whether or not the user is logged in, and perform one of two actions. Either redirect them to the login page, or let them know that they don't have sufficient privileges to view the file.

Private Sub RedirectToLoginOrDisplayErrorMessage(ByVal context As HttpContext, ByVal _portalSettings As PortalSettings)
    If (context.Request.IsAuthenticated = False) Then
        Dim loginPage As String = GetLoginUrl(_portalSettings, context.Request)
        Dim returnUrl As String = HttpUtility.UrlEncode(HttpContext.Current.Request.Url.PathAndQuery)
        context.Response.Redirect(loginPage + "?returnurl=" + returnUrl)
    Else
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
End Sub

Wiring it Up

Now, without changing the original code much, we can replace a couple lines of code, set a few break points, and try this thing out.

' serve the file
If TabId = Null.NullInteger Then
    If Not FileSystemUtils.DownloadFile(_portalSettings.PortalId, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        RedirectToLoginOrDisplayErrorMessage(context, _portalSettings)
    End If
Else
    If Not FileSystemUtils.DownloadFile(_portalSettings, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        RedirectToLoginOrDisplayErrorMessage(context, _portalSettings)
    End If
End If

Summary

Mission accomplished. Now to see if it gets picked up anywhere along the way. Let me know what you think of this enhancement or the process in general. Also, I did enjoy picking apart this one trivial thing, so if you have any suggestions for future experiments, please let me know.

Improving the Secured File Download UX for Unauthenticated Users

DotNetNuke has extensive security features. One of which is enforcing role based permissions when accessing files. The general workflow is to say that "Registered Users" get to see a particular file, you then create a link to that file using DNN, and as a matter of course, the roles are enforced. This uses what's known as a file ticket to request the file, so that it is not linked to directly.

This is great if you are only providing those links to currently authenticated users. However, if you want to provide these links to unauthenticated users, you get a pretty unfriendly experience. Granted, this is an edge use-case, and an argument could be made against doing this in the first place, but every project is different, and this just might make sense for you.

Background

There are a couple of ways to get yourself into this situation - the one I set up was the following:

  • Drop a links module on the page,
  • add a new link to a file
  • Go to the file manager, and make that file's folder available only to registered users.
  • Make sure the module is visible to all users

Currently, in DNN 4.9, you get a blank white page (even if you view source) with the following content:

You do not have permission to view this file.

Getting better

We could easily make that better by localizing it with a link to the login page. This improves the user experience a little bit. But really, we can put anything we want in here. Including a full HTML page with web site branding to give the user a more pleasant experience.

The value to localize can be found in GlobalResources\SharedResources.resx under the "FilePermission.Error" key. Note that any time you make a change to this file, the app restarts. So try to be patient when making changes.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>My Temporary Page</title> 
</head> 
<body> 
    <p>Dear user, please <a href="http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx">Login</a> to view the file you have requested. Thanks, Admin.</p> 
</body> 
</html>

Actually useful?

To take it a step further, we could add a timed redirect to the page, using a meta refresh. This will give the user the ability to see a custom informational page, as well as automatically be redirected.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>My Temporary Page</title> 
    <meta http-equiv="REFRESH" content="10;url=http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx" /> 
</head> 
<body> 
    <p>Dear user, please <a href="http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx">Login</a> to view the file you have requested. You will be redirected in 10 seconds. Thanks, Admin.</p> 
</body> 
</html>

Probably a lot better now...

Now, if we wanted to get really fancy, we could ditch the meta refresh, and enhance our HTML page to use JavaScript to redirect. The great benefit of this is that we can tack on a returnurl parameter to our redirect with the file ticket's URL. This will tap into the core login behavior and deliver the appropriate content after the user has logged in.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>My Temporary Page</title> 
    <script type="text/javascript"> 
        <!-- 
        function redirectToLogin() { 
            var l = document.location.href; 
            if (l.toLowerCase().indexOf("fileticket") != -1) { 
                var virtualDirectory = ""; 
                var loginPage = "/Default.aspx?ctl=login&returnurl=";  
                var returnUrl = encodeURIComponent(l.substr(l.lastIndexOf("/"))) 
                window.location = "/" + virtualDirectory + loginPage + encodeURIComponent("/") + virtualDirectory + returnUrl; 
            } 
        } 
        --> 
    </script> 
</head> 
 
<body onload="setTimeout('redirectToLogin()', 5000)"> 
    <p>Header with site branding, navigation</p> 
    <p><a href="javascript:redirectToLogin();">Dear user, go to the login page!</a></p> 
    <p>Footer with site branding, navigation?</p> 
</body> 
 
</html>

There are a few things that you may want to modify above for your implementation:

  • timeout (on body onload)- this is how fast your page will redirect to the login page/control. If you don't want a "landing page", set this to 0.
  • virtualDirectory - if you are using a virtual directory (e.g. http://localhost/dotnetnuke_2/), you'll want to set this to "dotnetnuke_2"
  • loginPage - relative link to the login page/control

Summary

When we want to provide a link for unauthenticated users to a file and enforce role security, we now have the ability to:

  • provide that user with more information when they click on the link
  • implement consistent branding on that informational page
  • automatically redirect them to the login screen
  • supply a return URL so that after they login, they are taken directly to the file.

As this is just a quick and dirty implementation, I'd love to see someone with a need for this actually make it awesome. (yes, that's right...I don't even have a project to implement this on...)

And as always, if you have a project to share with the community, you should submit it to dnnGallery!

A note about unwanted redirects

One catch with the meta refresh is that the DNN language editor shows you a preview of the content. So after you localize it with a meta refresh the first time, ever subsequent time you visit the SharedResources language file in the editor, the page will redirect.

I don't think the same is true of the JavaScript, but because different browsers could handle this differently, I went ahead and included a check so that the JavaScript redirect will only occur if the URL contains "FileTicket". Feel free to experiment.

More Posts