Tuesday, June 15, 2010

Just Published The IconizedButton Control Set on CodePlex

Open the floor for criticism... I just published an open source control library on CodePlex titled IconizedButton Control Set. CodePlex description: Replaces the dull Button/LinkButton/HyperLink controls with styling and left and right aligned icons (via FamFamFam icon set). Contains built-in control styles/skins. Available customized user-configured styles/skins via CSS.

The IconizedButton Control Set documentation is location here.

I hope someone find the control library helpful and useful.

I'd appreciate any feedback you can provide.

Thanks for reading...

IconizedButton ASP.NET Control Set Documentation

Iconized Button Documentation

Contents

  • Summary
  • Goals
  • Features
  • Properties
  • Examples
  • Customization
  • Configuration
  • Known Issues (and Solutions)
  • Design (and Performance) Considerations
  • Credits
  • Downloads

Summary

IconizedButton is an ASP.NET Web Forms control set providing buttons and hyperlink controls with icons and styles. IconizedButton extends the standard ASP.NET LinkButton control, while IconizedHyperLink extends the standard ASP.NET Hyperlink. Extending the standard controls makes using the IconizedButton controls painless and familiar. Use the controls as if they were the standard controls.

Goals

  • Extend the standard ASP.NET control to include icons and skinning (without including user-defined resources);
  • no JavaScript dependencies; however, allow the user to include scripting;
  • easy deployment and integration is existing/new Web Projects;
  • light (as possible) footprint; and,
  • those features included in the 'Features' section.
  • Include text or Solo (no text) buttons

Features

  • Visual Studio Integration and Design Support
  • Buttons and hyperlinks with icons - ~ 1000 included icons (see FamFamFam in 'Credits' below)
  • Built-In Button Skinning - 20 skins
  • Button with or without text
  • Left or right align the icons (relative to the button text)

Properties

VS Properties
  • ButtonSkin : enumeration of button skins - Skin the button using one of 20 built-in skins, or set to 'Custom' and use your own via CSS. See 'Customization' section for more information on customizing the button using user-defined styles and icons.
  • CustomCssClass : string - Add custom button skinning and/or additional CSS classes.
  • CustomIconType : string - Set this property to your own custom icon via CSS class. IconType property must be equal to IconType.Custom or this property will be ignored.
  • DisplyMouseOutStyle : boolean - Whether (or not) to display the background skin/style on mouse out - meaning no initial background and no background on mouse out.
  • IconPosition : enumeration of icon positioning - Positions the icon. Enum values:

    • None - no icon. Button with text only.
    • Left - left align the icon relative to the button text.
    • Right - right align the icon relative to the button text.
    • Solo - icon only button.
    • SmallSolo - icon only button with a smaller footprint than the Solo value.
  • IconType : enumeration of icon types - Choose from ~1000 icons to render with the button.
  • RoundCorners : boolean - Whether (or not) the button should render with round corners. Implemented via CSS3; therefore, no available in IE.

Examples

  • The project page on CodePlex - IconizedButton - contains a sample Web application that you can download and use to test/sample the control set.
  • The following markup gives you the IconizedButton in the screenshot:

    <shp:IconizedButton 
                        runat="server" 
                        id="ibReturnHome" 
                        Text="Return Home" 
                        ButtonSkin="OfficeBlue" 
                        IconPosition="Left" 
                        IconType="HouseGo" 
                        RoundCorners="true"                            
                    />
                    
    IconizedButton Screenshot

Customization

  • The following markup gives you the customized IconizedButton in the screenshot (shout out to the troops). Custom styles and images are included in the sample Web application on CodePlex.

    <shp:IconizedButton
                        runat="server" 
                        id="ibArmyStrong" 
                        IconType="Custom" 
                        IconPosition="Left" 
                        ButtonSkin="Custom" 
                        CustomIconType="army-of-one" 
                        CustomCssClass="army" 
                        Text="Army Strong"                          
                    />
                    
    IconizedButton Screenshot

Configuration

  • Toolbox - IconizedButton control set can be added to the Visual Studio Toolbox. IconButton controls have their own icons that will help identify the controls from other controls in your toolbox.

    VS Toolbox
  • Page/User Control Registration - Dragging and dropping a control from the toolbox (like standard ASP.NET controls) will add the control to the page/user control. It will register the IconizedButton control on the top of the page/user control.
  • web.config Registration - for site/project wide registration, register the IconizedButton control set in the web.config as a child element of the <pages><controls></controls></pages> element.

    <add tagPrefix="shp" namespace="Shp.Web.UI.WebControls.Iconized" assembly="Shp.Web.UI.WebControls.Iconized"/>

Known Issues (and Solutions)

  • The button set controls are floated left (float: left). This means that you must clear the float in a container control. Or use a clearfix implementation - my favorite is from Perishable Press

Design (and Performance) Considerations

  • Button Skin Sprites - The button skins are image sprites. This helps minimize HTTP traffic and increases server and client performance. For more info, check this link out: CSS Sprites: What They Are, Why They're Cool, and How To Use Them
  • Icons are NOT Sprites - I created sprites for the icons categorized by alphabetical groups. While this task was tedious and time consuming, the resulting image files got a bit big for my taste (each sprite ~ 25 KB), so I decides to go with one image file per icon (~600 B). E.g. Lets say you had three iconized button on your page. The IconTypes started with 'A', 'C', and 'S' respectively. Using the sprite implementation, the resource hit would be ~ 75 KB, while using individual icon images, your resource hit would be ~ 1.8 KB. Make sense? A design decision that I didn't take lightly.
  • No JavaScript - One of my goals was to have no JavaScript dependencies. Understanding that the .NET Framework will add JavaScript to perform PostBacks, validations, etc..., IconizedButton control set adds no additional JavaScript resources. This also helps when the user/developer wants to customize the buttons an not have to hack around JavaScript dependencies. Just use the buttons (and additional JavaScript) as you would use LinkButton and HyperLink controls.
  • Visual Studio Designer - Selecting an IconType will display a default icon for the control. During the IconizedButton control set development, rendering the icon associated with the IconType gave me the infamous Visual Studio design-time delay. The design time delay (the pause in Visual Studio while it retrieves, processes, and renders a resource) is probably my biggest gripe with Visual Studio. I love the IDE; however, this really annoys me. Rendering only a default icon at design-time significantly improves design-time performance.

Credits

  • The icons are designed, developed, and licensed by Mark James of FamFamFam. The specific FamFamFam library of icons used in the IconizedButton control set is the Silk Icon set. If you need icons for your application, check out FamFamFam. FamFamFam icons are licensed under the Creative Commons Attribution 3.0 License.

Downloads

Friday, June 11, 2010

Test code for my previous post Telerik RadWindow Client-Side Content Container.  Sorry, the formatting is a little off.
CSS:
ul#actionList, ul#actionList li {margin: 0; padding: 0; list-style: none; line-height: 20px;}
ul#actionList { margin: 5px 10px; } ul#actionList li { margin-bottom: 5px; }
div#echoConsole { margin: 20px; border: 1px solid #ababab; background-color: #fbfec2; padding: 5px; }
p { padding: 5px; margin: 0; font-family: Arial; font-size: 12px; }
.mar-b-10 {margin-bottom: 10px;}
   
JavaScript:
$(document).ready(function() {
      // attach the click event to the buttons - button will open the new RadWindow dialog
      $('ul#actionList button').click(function(e) {
         e.preventDefault();
         var btnID = $(this).get(0).id;
         switch (parseInt(btnID.substring(btnID.length - 1))) {
            case 0:
               radWindowExtensions.openDialog({ width: 400, height: 400, title: 'This is the Dialog Title', content: $('div#hiddenDiv'), resizable: false, onOpen: dialogHandlers.onOpen, onClosing: dialogHandlers.onClosing, onClosed: dialogHandlers.onClosed });
               break;
            case 1:
               radWindowExtensions.openDialog({ width: 500, height: 600, title: 'This is the Dialog Title', content: $('div#hiddenDiv').html(), resizable: true, onOpen: dialogHandlers.onOpen, onClosing: dialogHandlers.onClosing, onClosed: dialogHandlers.onClosed });
               break;
            case 2:
               radWindowExtensions.openDialog({ xPos: '100', yPos: 10, onOpen: dialogHandlers.onOpen, onClosing: dialogHandlers.onClosing, onClosed: dialogHandlers.onClosed });
               break;
            case 3:
               radWindowExtensions.openDialog();
               break;
         }
      });
   });

   // test handlers object - will echo event handler messages to a div tag
   var dialogHandlers = {
      _getConsole: function() { if (!dialogHandlers._console) { dialogHandlers._console = $('div#echoConsole').show(); } return dialogHandlers._console; },
      _echo: function(msg) { var c = dialogHandlers._getConsole(); c.append($('<div/>').addClass('mar-b-10').html(msg)); },
      onOpen: function(content) {
         // use onOpen event handler to modify the content - e.g. add event handlers to buttons, etc...
         dialogHandlers._echo('onOpen event fired on RadWindow with ID of ' + this.get_name());
         $(content).find('p:first').css('color', 'red');
      },         
      onClosing: function(content) {
         // use onClosing event handler to clean up content - e.g. unbind event handlers.
         dialogHandlers._echo('onClosing event fired on RadWindow with ID of ' + this.get_name());            
         dialogHandlers._echo('number of paragraphs: ' + $(content).find('p').size());
      },
      onClosed: function(content) {
         dialogHandlers._echo('onClosed event fired on RadWindow with ID of ' + this.get_name());
         dialogHandlers._echo('number of paragraphs: ' + $(content).find('p').size());
      }
   };
HTML:
  • Content set to a jQuery wrapped object, non-resizable
  • Content set to HTML
  • No Content, positioned dialog
  • No Content, no options (default) dialog
Morbi scelerisque urna aliquam sapien dictum eget lacinia justo eleifend. Morbi eu nibh nibh? Morbi non justo id magna fermentum accumsan. Donec nunc mi, facilisis a venenatis eu, congue in ligula? Praesent nisl turpis, aliquet vitae eleifend non, auctor id erat. Nam a felis ac mauris ornare lacinia. Cras a massa sit amet leo tempor tincidunt. Curabitur pellentesque enim id est porta ornare. Morbi orci leo, sodales et lobortis vel, egestas at magna. Nam a nisi odio, suscipit ullamcorper quam. Pellentesque ullamcorper est vel quam blandit vel laoreet libero euismod. Nam orci sem, condimentum id interdum feugiat, laoreet nec mi. Duis varius cursus dolor id porttitor. Nulla id ligula sit amet leo imperdiet sollicitudin. Donec pulvinar, sem quis condimentum pellentesque, orci nisl placerat tellus, at egestas ante arcu nec lectus. Nullam cursus porta molestie. Sed in auctor metus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce volutpat vestibulum erat quis ultrices.

Telerik RadWindow Client-Side Content Container

Telerik RadControls for ASP.NET AJAX is a powerful set of .NET controls for Web Forms (they also have control sets for MVC, Silverlight, Windows Forms, WPF, and other productivity tools).  We use 'RadControls' all the time.  RadControls provide near Windows Forms-based controls functionality.  The RAD ability of RadControls is untouchable.

The caveat with RadControls is that if you want to go out-of-box Telerik box and extend the functionality or use it in a way that different for the Telerik way of developing Web interface, you're often let down after hours of 'trying' to make the controls work 'your' way.  Case in-point: RadWindow.

RadWindow is your basic inline popup that provide an IFRAME for rendering external content.  Until last year, displaying inline content (Telerik describes it at 'internal content') where the RadWindow's source is not an external page was close to impossible.  Rendering inline content in a RadWindow still isn't documented all that well.

I'll save you the details on the Telerik recommended solution to providing inline content (you can read about it here and somewhere in here).  Well, you've probably realized that this did not meet my requirements.  I wanted to have client-side RadWindow support to render dialogs with content retrieved from Web services, inline hidden elements, JavaScript, etc...  So, I created a small library that I can include as a reference in my pages to give me my desired functionality.

Before I post my solution, I need to give you a quick background on my design and code structure for Web Forms.  I tend to have a Master Page that includes common libraries (jQuery, Telerik Core, project specific, etc...).  When I use RadWindows I add a RadWindowsManager to my Master Page.  This provides all the Telerik RadWindow client-side libararies that I need.  My Master Pages also implement an Interface that contain references to .NET controls on the Master Page.  This gives Page controls type-safe access to the Master Page and the controls contained with in the Master Page.

So, here's the code.  It's fully documented, so you shouldn't have a problem using it.

/*
* File Name: telerik.radwindow.extensions.js
* Requirements:
* - Telerik.Web.UI.RadWindowManager client-side library
*   - adding a RadWindowManager control to the top of the page will add all the necessary libraries, include Telerik Core and jQuery.
*/

// namespace this libarary to avoid namespace clashes.
var radWindowExtensions = (radWindowExtensions || {});

radWindowExtensions.openDialog = function(options) { /* @options: extend the 'defaults' object map of the function. */
   // get the rad window manager - throw exception if RadWindowManager client-side library is not loaded.
   var rwm = null; try { rwm = GetRadWindowManager(); } catch (e) { throw new Error('Telerik.Web.UI.RadWindowManager client-side library is not loaded.'); }
   var defaults = {
      width: 500, /* outer width of rad window */
      height: 500, /* outer height of rad window */
      modal: true, /* is modal? */
      center: true, /* center rad window? */
      resizable: true, /* is rad window resizable? */
      title: '', /* rad window title */
      content: '', /* rad window content */
      xBorderSize: 16, /* the size of the horizontal borders */
      yBorderSize: 61, /* the size of the vertical borders */
      xPos: Number.NaN, /* x-position of the rad window */
      yPos: Number.NaN, /* y-position of the rad window */
      // event handler 'this' reference is to the current RadWindow.  @content parameter is a reference to the jQuery wrapped content
      onOpen: function(content) { }, /* onOpen event handler */
      onClosing: function(content) { }, /* onClosing event handler */
      onClosed: function(content) { } /* onClosed event handler */
   };
   // merge @options with default settings
   options = $telerik.$.extend({}, defaults, (options) ? options : {});

   // parse the x and y positions
   options.xPos = parseInt(options.xPos); options.yPos = parseInt(options.yPos);

   // create a rad new window (and open - this is required)
   var win = rwm.open(null, null);

   // remove the iframe from the default rad window
   $(win._iframe).remove();

   // render the window as modal
   if (options.modal) { win.set_modal(options.modal); }

   // set the window width
   win.set_width(options.width);

   // set the window height
   win.set_height(options.height);

   // set the window xy coordinates or center the window or use default positioning
   if ((!isNaN(options.xPos)) && (!isNaN(options.yPos))) win.moveTo(options.xPos, options.yPos);
   else if (!!options.center) win.center();

   // set the window title
   win.set_title(options.title);

   // create the content and wrap in a parent div
   var $content = $telerik.$('<div></div>').width(options.width - options.xBorderSize).height(options.height - options.yBorderSize).css('overflow', 'auto').append(options.content);
// destroy on close
   win.set_destroyOnClose(true);

   // add onClosing and onClosed event handlers
   win.add_beforeClose(function() { options.onClosing.call(win, $content); });
   win.add_close(function() { options.onClosed.call(win, $content); });

   // set the window behaviors (Move and Close)
   win.set_behaviors(Telerik.Web.UI.WindowBehaviors.Move + Telerik.Web.UI.WindowBehaviors.Close + ((!!options.resizable) ? Telerik.Web.UI.WindowBehaviors.Resize : 0));

   // add the wrapped content to the rad window - use the set_contentElement(content) function
   setTimeout(function() {
      win.set_contentElement($content.get(0));
      // fire the onOpen event handler
      if (options.onOpen) options.onOpen.call(win, $content);
   }, 0);
};

When using this library, I use a minimized version of the file.  To minimize JavaScript and CSS files, I use Yahoo! UI Compressor Library: YUI Compressor for .NET located on CodePlex or I use the Online YUI Compressor located here.

EDIT:  the formatting for the JavaScript above is parsed incorrectly and displaying two code blocks.  If you are copying and pasting the code, copy both blocks and create a single code file.  Sorry about that...

Thanks for reading...

Thursday, June 10, 2010

Redirect to HTTPS

I got asked a question today from one of my System Engineers with respect to redirecting HTTP traffic to HTTPS.  The Web server is IIS, but it shouldn't make a difference.
Explicit Requirements:
  • Web Server Global Redirect (vs. per individual Web application).
  • Present to the user a generic message stating that the URI has changed and suggesting to the user update any associated bookmarks.
  • Minimize any IIS metabase modifications.
Implicit Requirements:
  • Web request discovery so that redirect is identical to the original request with the exception of the protocol - this eliminates a metadata redirect.
  • Redirect delay - present the user with the aforementioned redirect and once a configured delay has expired, redirect the user using the secure protocol.
Solution:
Searching around the Internet, I found a few excellent examples; however, I could not find a solution that fulfilled all of our requirements, so I designed a solution. We decided to create a custom 403 error page.  This error page includes the aforementioned generic message  and some JavaScript  for redirect.  The JavaScript includes an optional parameter for redirect delay.  The JavaScript implementation is below:
var rdcore = {
    redirectToSsl: function(delay){ /* @delay:int or null - delay in seconds before the redirect is executed. */
        delay = (delay) ? (delay * 1000) : 0;
        setTimeout(function(){
            var oldUrl = window.location.hostname + window.location.pathname + window.location.search;
            window.location = 'https://' + oldUrl;
        }, delay);
    }
};
// parameter is the delay in seconds (integer or null) before the redirect is executed.
rdcore.redirectToSsl(10);

Monday, June 7, 2010

SSRS - SQL Server Reporting Server - External Image Issue (and Solution)

The other day I encountered some complicated issues with SQL Server Reporting Server (SSRS) and including external images from a file store that was not directly accessible to users (or SSRS).  I've always assumed that the credentials that you pass to SSRS to generate reports where the contextual credentials in which the reports were generated (aka impersonation).  Well, that assumption FAILED to materialize.

The intent of this post is to hopefully assist others with similar issues and to document my findings for future use and reference.

So, here the scenario and configuration (all in a TEST environment which parallels the PRODUCTION environment):
  • Box 1: Web Server
    • IIS 6
    • Web App: MyWebApp
    • File Store Virtual Directory: MyWebAppFileStoreVd
  • Box 2: SQL Server
  • Box 3: SSRS
  • Box 4: File Store
    • MyWebApp File Store: MyWebAppFileStore
As a developer, I had access to the file store (Windows permissions).  So, while creating reports in Visual Studio's Report Designer with hard coded image paths (for testing, because dynamic wasn't working - this is the issue), I could view the images in the report.  However, once the reports were deploy to SSRS, the image placeholders contained no path information and the infamous 'red X.'  This was also the case when the report was request and render via the Web app.

Ahhhh!  What's the problem?  The file store contained permissions for the SSRS user with the credentials passed to SSRS from the Web app.  After a couple of day of 'banging head on desk, computer, floor, etc...), I had an epiphany - maybe the SSRS user is not impersonated???  That was it - the user is NOT impersonated!  Reports are actually generated via the service account of SSRS - this makes perfect sense now; however, I had always assumed that impersonation was taking place.  I digress...

I've read that Microsoft's solution is to run the SSRS service as a domain account and give that domain account whatever permissions it needs on the file store.  Well, that is NOT a solution in our environment.  So, what did we do to solve the issue?  Well, we...

We decided to create a virtual directory on the Web Server (Box 1) that points to the aforementioned File Store (Box 4) and restricts access (over http) to a specific computer via IP - the SSRS IP (Box 3).  This essentially creates a File Store (Box 4) web share proxy on the Web Server (Box 1) accessible only to SSRS (Box 3) over http.

The following is the process and associated steps taken to successfully enable this scenario:
  1. Create a Virtual Directory outside of the MyWebApp Virtual Directory that points to the MyWebAppFileStore File Store - /MyWebAppFileStoreVd/
  2. Set MyWebAppFileStoreVd Virtual Directory’s Directory Security to ‘Enable Anonymous Access’ and clear all ‘Authenticated Access’ checkboxes.
    1. Virtual Directory Properties -- Directory Security Tab - Edit button (red highlighted button).

    2. Check the ‘Enable Anonymous Access’ checkbox.
    3. Clear ‘Authenticated Access’ checkboxes.
    4. Click ‘Ok’ and then ‘Apply.’

  3. Restrict access by the Report Server’s IP.
    1. Virtual Directory Properties -- Directory Security Tab -- Edit button (green highlighted button).

    2. Select the ‘Denied Access’ radio button within the ‘IP Address Access Restrictions’ group box.
    3. Click the ‘Add…’ button.

    4. Select the ‘Single Computer’ radio button in the ‘Grant Access’ dialog box.
    5. Enter the Report Server’s IP address in the IP Address masked textbox.

    6. Click ‘Ok’, ‘Ok’, ‘Apply’, and ‘Ok.’
That should about do it...

While this implementation works and works well for my specific scenario, this may not be the best implementation for your specific scenario.  I hope that this post saves at least one person the hours of troubleshooting and headaches that I incurred getting the implementation to work...

Thanks for reading...