# Friday, May 29, 2009

A few days ago, I was trying to figure out what to do about a user control that had multiple fieldsets on one page...  The target page already had a few little forms (fieldsets and/or div with input fields and buttons) and when I added my user control to the page, the ENTER key would always point to the first button the page.  The was really messing me up, since there was ~4 different forms that each had their own Submit button.  I came up with a hard-coded solution that worked perfect, but seemed a bit excess.  Somebody suggested I make a jQuery Plug-In, so I tweaked it a bit more and came up with the following plug-in below.

Here are a few examples when you could use this...

1. You have 2+ fieldsets with their own button to perform an action: $("FIELDSET").AssignEnterKey();

2. You have a div called Search that provides site wide search: $("#Search").AssignEnterKey();

3. You have a bunch of lookups on one page that have the class name Lookup: $(".Lookup").AssignEnterKey();


/*
* jQuery AssignEnterKey Plug-in
*
* Summary       This plug-in will attach a click/blur event to all input elements inside a parent container,  
*                        the events will add and remove a class to the parent container that is used to associate  
*                        the enter key with the a button or submit button.  
*                
* Notes             Using the options, you can target a different controls for both triggering the focus and the target to be executed.
*
* Example         $("FIELDSET").AssignEnterKey();
*
* Developed by  Zachary.Hunter@gmail.com
* Date                2009-05-29 23:05
*/
(function($) {
    $.fn.AssignEnterKey = function(options) {

        var defaults = {
            triggerExp: ":input",
            targetExp: ":submit,:button"
        };

        var options = $.extend(defaults, options);

        $(document).keypress(function(e) {
            if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
                $(".EnterKeyAssigned")
					.find(options.targetExp)
					.click();
                return false;
            }
        });

        return this.each(function() {
            var obj = $(this);

            obj.find(options.triggerExp)
                    .click(function() { obj.addClass("EnterKeyAssigned"); })
                    .blur(function() { obj.removeClass("EnterKeyAssigned"); });
        });
    };
})(jQuery); 

I think this came out much more usable than my original code (see post below).  I setup an two parameters that you can over-ride to have it look for specific trigger controls or target controls.  Overall, this is very basic plug-in and I can already see a few things that need to be address (What to do when nothing is selected?  What to do when you have multiple buttons in a container?).  I think I'll add a few more defaults options to address these.  I'm very happy with what I got, since this was my first attempt at developing a jQuery plug-in... Feedback is always appreciated!!!

[EDIT 5/19] by Zach : I have submitted this to the jQuery website as a plug-in. Hopefully somebody else finds this useful...

jquery.assignenterkey.js (1.26 KB)
Friday, May 29, 2009 10:57:49 PM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [2]  | 
# Wednesday, May 27, 2009

I'm working on a legacy applicaition that is built using ASP.NET 2.0 and relies on classic hard-coded javascript to do client side interactions.  I decided to replace the javascript with jQuery a few months ago, and it's been smooth sailing ever since.  I recently had an issues where I needed to host multiple fieldsets in a signle web user control / page.  The problem with having multiple fieldsets that each contain their own controls (text boxes, check boxes, submit buttons), is the behavior of the ENTER key.  By default the ENTER key is assigned to the firt button on the page.  Since my page is full of buttons, I needed a way to assign the ENTER key to the fieldset a user was actively using.  This is easy using ASP.NET panels (defaultbutton="Button1"), but not as easy if you are using standard HTML controls and fieldsets.  After a bit of tinkering, I came up witht he following code... The first two lines looks pretty redundant to me, but I really wanted to target them by specific ID's since I wasn't sure a specific dynamic page would look like...

$("#<%= SearchCustomers.ClientID %>").find(":input").click(function() { $(this).parents("FIELDSET").addClass("default"); }).blur(function() { $(this).parents("FIELDSET").removeClass("default"); });
$("#<%= SearchGroups.ClientID %>").find(":input").click(function() { $(this).parents("FIELDSET").addClass("default"); }).blur(function() { $(this).parents("FIELDSET").removeClass("default"); });


$(document).keypress(function(e) {
	if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
		$(".default").find(":submit,:button").click();
		return false;
	}
});

The code above, adds a Click / Blur event to every input item in a specific fieldset.  The Click event adds a class to the parent fieldset when an input field is clicked and removes it on the blur event.  The assumes the user will always click a input control before pressing enter.

	
<fieldset id=" runat=" SearchCustomers? server?><legend>Invoicing Manager - Customer Settings</legend>
<p>
	<label for="<%=customerCode.ClientID %>" class="right">Customer Code :</label>
	<input type="text" id="customerCode" class="textBoxMedium" runat="server" />
	<asp:Button ID="Search" runat="server" Text="Search" CssClass="button" OnClick="Search_Click" />
</p>
</fieldset>

<!-- Field Set 2, uses input type=button -->
<fieldset id="SearchGroups" runat="server">
<legend>Invoicing Manager - Group Members</legend>
<p>
	<label for="<%=groupName.ClientID %>" class="right">Group Name :</label>
	<input type="text" id="groupName" class="textBoxMedium" runat="server" />
	<input id="groupSearch" runat="server" type="button" value="Search" class="button" />
</p>
<p>
	<ol id="groupResults" style="width: 90%; text-align: left;" runat="server">
	</ol>
</p>
</fieldset>

Above is what my HTML fieldsets looked like.  Pretty generic stuff, Fieldset #1 uses a input type=button as the target of the ENTER key and Fieldset #2 uses a asp:button control.

[EDIT 5/17] by Zach : I posted this on StackOverflow.com to see if anybody had an idea on how I could improve on the code to increase re-useability and somebody suggested replacing specific selectors with a generic based on class or element.  Both are great ideas and are easy ways to apply this to any HTML page (including non ASP.NET pages like PHP/HTML/etc..).

Wednesday, May 27, 2009 11:00:11 AM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [0]  | 
# Tuesday, May 12, 2009

I'm really surprised that the jQuery auto complete plug-in does not support XML by default. Almost all web services have the option to transmit XML, that it should be a requirement that every Ajax plug-in or widget consumes XML. If your using ASP.NET 2.0 and you want to use the jQuery autocomplete plug-in, you'll notice this isn't an easy task.  Since the default plug-in requires a flat file with a list of values seperated by new row "\r".  To support this natively, you can use a ASHX handler, but I wanted to use my existing web services.  To get around the problem, I created a "parseXML" function and added an if statement in the $ajax results function.

The change really came down to adding an if statement for dataType = "xml" and added a new property datakey to support the parsing of the xml file.  This way, I can parse ASMX 2.0 Web Service calls or static XML files.  [UPDATE] There is a catch to "STATIC" xml data, the default plug-in is based on the assumption that your results (aka: XML data) will only contain filtered results.  If you data contains all possible results, you'll need to filter the data... See comments for instructions!  The XML parsing is done using jQuery, so you don't have to worry about dealing with broswer specific XML engines.

Here is the modified request function and the new parseXML fuction that I added to the "jQuery.autocomplete.js" plugin:

        function request(term, success, failure) {
            if (!options.matchCase)
                term = term.toLowerCase();
            var data = cache.load(term);
            // recieve the cached data
            if (data && data.length) {
                success(term, data);
                // if an AJAX url has been supplied, try loading the data now
            } else if ((typeof options.url == "string") && (options.url.length > 0)) {

                var extraParams = {
                    timestamp: +new Date()
                };
                $.each(options.extraParams, function(key, param) {
                    extraParams[key] = typeof param == "function" ? param() : param;
                });

                $.ajax({
                    // try to leverage ajaxQueue plugin to abort previous requests
                    mode: "abort",
                    // limit abortion to this input
                    // port: "autocomplete" + input.name,
                    dataType: options.dataType,
                    url: options.url,
                    data: $.extend({
                        q: lastWord(term),
                        limit: options.max
                    }, extraParams),
                    success: function(data) {
                        var parsed = [];

                        // Added Logic by Zach
                        // If dataType = "XML" use the parseXML fuction
                        if (options.dataType == "xml") {
                            parsed = parseXML(data);
                        }
                        else {
                            parsed = options.parse && options.parse(data) || parse(data);
                        }
                        cache.add(term, parsed);
                        success(term, parsed);
                    }
                });
            } else {
                // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
                select.emptyList();
                failure(term);
            }
        };
   
        // Added Logic by Zach
        // Added XML Parse Function
        function parseXML(xml) {
            var parsed = [];

            $(xml).find(options.datakey).each(function() {
                parsed[parsed.length] = {
                    data: [$(this).text()],
                    value: $(this).text(),
                    result: options.formatResult && options.formatResult($(this).text(), [$(this).text()]) || [$(this).text()]
                };
            });
            return parsed;
        }

In order to use this new functionality, you'll need to add the following options when you bind to a XML datasource. 

    $(document).ready(function() {
        $("#customerCode").autocomplete("Customers.xml", { dataType: "xml", datakey: "customer" });
    });

Here is the sample XML file used in the auto complete instance above.


    Amy
    Brian
    Charlie
    Zach
    Zachary

The only requirement for your ASMX web service, is your method signature must accept a string paramters called "q".  You can add additional parameters on your method, by default the autocomplete sends (q,limit,timestamp) but only "q" is required.

Here is a link to the offical jQuery autocomplete plug-in.

Here is a complete ASP.NET 2.0 Web Applicaiton sample with the modified auto complete plug-in jQueryAutocompleteASMX.zip (62.92 KB)

[EDIT 5/14] by Zach :

        $(document).ready(function() {
            $("#textboxCustomer").autocomplete("/Autocomplete.asmx/GetCustomers", {
                parse: function(data) {
                    var parsed = [];

                    $(data).find("string").each(function() {
                        parsed[parsed.length] = {
                            data: [$(this).text()],
                            value: $(this).text(),
                            result: [$(this).text()]
                        };
                    });
                    return parsed;
                },
                dataType: "xml", datakey: "string", max: 5
            });
        });

Using the function above, you can replace the built-in autocomplete parse function with a dynamic parser.  The only requirement is returning string array, which is the same object returned by the default function.  I think the original example is useful if you are using the jQuery plug-in with ASP.NET and .NET Web Services, but if I was only going to use the plug-in a few times... I'd use this soltuion.  -Zach

[EDIT 5/19] by Zach:

Don't forget the following lines should be added to your web.config under "<system.web>" to support GET & POST requests for your web services.

       
        
            
                
                
            
        
Tuesday, May 12, 2009 4:01:53 PM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [9]  | 
# Friday, May 08, 2009

Over the past few weeks I've been on a quest to learn more about WCF and RESTful services.  I found lots of information along with a lot of incomplete or broken demonstrations.  I'm think the beta worked a little different and a lot of blog posts still exist that reference development with the beta of .NET 3.5 and WCF.  I got really lost for a few days because of this, and looked really hard for a working proof of concept that pointed out the key "setup" points so I could compare against my service and figure out what I was missing.  I never found one, but after reading enough blog posts and piecing together the information from multiple source, spent 2-3 days reproducing the setup to make sure I had mastered the process.  My last demo was designed to give people a basic working demo of the main changes need to setup RESTful communications between a WCF Server/Web Service and jQuery using JSON. 

Demo 1-4 shows you the ASMX and the WCF equivalents for doing the basics (Get String, Get Array, Send String, Send Array), and Demo 5 shows you how to use a WCF Data Contract with a Wrapped/Bare message format.  To make things easier to see, I log all JSON requests/results to the webpage for inspection.  This also allows you to see how requests are non-blocking and can be received out of order depending on processing time.

I'm by far no expert on these technologies, so if anybody has suggestions on what I could be doing better... Let me know!  Hopefully  seeing a working demo will help somebody else get a little more REST at work!

Demo Screen Shot

WCFDemo.zip (113.66 KB)

How to add a ASMX Web Service (ASP.NET Web Applicaiton w/.NET 3.5 SP1 Framework)

  1. Add New Item - Web Service
  2. Uncomment "[ScriptService]"

That's it, your done.  To access your web service via jQuery, use the following syntax.

    $.ajax({
        type: "POST",
        url: "WebService1.ASMX/HelloWorld",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        data: "{}",
        success: function(res) {
        // Do your work here.
        // Remember, the results for a ASMX Web Service are wrapped
        // within the key "d" by default. e.g. {"d" : "Hello World"}
        }
    });

How to add a WCF Service (ASP.NET Web Applicaiton w/.NET 3.5 SP1 Framework)

  1. Add New Item - WCF Service
  2. Right-click on the service and choose "View Markup".
  3. Add the following line to the ServiceHost declaration: Factory="System.ServiceModel.Activation.WebServiceHostFactory"
  4. Add a reference to the project for "System.ServiceModel.Web".
  5. Open the interface associated with the service (e.g. IService1.cs) and add the following using statement "using System.ServiceModel.Web;".
  6. Inside the interface, set the attributes on the method to support JSON & POST or GET request types.

    [OperationContract]
    [WebInvoke(
     Method = "POST",
     ResponseFormat = WebMessageFormat.Json)]
    string SampleReturnValue();
  7. Open the web.config and comment out the following lines:
      <!--<behaviors>
       <endpointBehaviors>
        <behavior name="Web.Service1AspNetAjaxBehavior">
         <enableWebScript />
        </behavior>
       </endpointBehaviors>
      </behaviors>
      <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
      <services>
       <service name="Web.Service1">
        <endpoint address="" behaviorConfiguration="Web.Service1AspNetAjaxBehavior"
         binding="webHttpBinding" contract="Web.Service1" />
       </service>
      </services>-->

That's it, your done.  To access your web service via jQuery, use the following syntax.

    $.ajax({
        type: "POST",
        url: "Service1.svc/DoWork",
        contentType: "application/json",
        dataType: "json",
        success: function(res) {
        // Do you work here.
        }
    });

I found a lot of blog posts pointing to WCF Service Factory projects, which require a completely different setup.  The sample above is for adding a WCF Service to a Web Applicaiton.

While debugging this, I used Firebug for Firefox and Fiddler to see the ajax & JSON data fired between the client and server.  When you use fiddler to debug a local website, you need to add a "." after the domain name to cause fiddler to catch the traffice (e.g. http://localhost.:1234/WebSite1).  Hopefully the demo compiles and runs without problems, so you can see a quick 1-2-3 on how to get WCF up and running quickly!

Friday, May 08, 2009 8:28:04 AM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [0]  | 
# Monday, April 27, 2009

I was working on a email invoicing application a few days ago, and I needed a way to consolidate multiple PDF invoices (approximately ~50-200 invoice p/week).  I wanted something that was free and worked with C#, I found PdfSharp and after looking at the demo's I came up with a quick solution.  The process is really simple and can be summarized as (Create a new empty PDF, open each existing PDF and copy their pages into the new PDF).  Here is a code snippet of how I combined the PDF files, using a DataReader that has the source URL of each invoice:

	// ********************************************************************************************* //
	// This is just a simplified excerpt, I was looping over all the invoices from the previous week
	// by customer, and aggergrating their PDF invoices into a signle document.  In addition to the
	// generating the PDF, I used the DataReader to also generate a CSV file of their invoice details.
	// ********************************************************************************************* //

	// Combined PDF
	var customerPdf = new PdfDocument();
	
	// Hold copy of last document
	string lastDocumentUrl = string.Empty;

	while (dr.Read())
	{
		// Get current document
		string documentUrl = dr["InvoiceURL"].ToString();
	
		// Check if last document added is the same as current document
		if (lastDocumentUrl != documentUrl)
		{
			// Set the last document equal to the current document
			lastDocumentUrl = documentUrl;

			// Get the current file (e.g. \\\\\.pdf)
			string filePath = documentUrl;

			// Read in the existing document
			PdfDocument inputDocument = PdfReader.Open(filePath, PdfDocumentOpenMode.Import);

			// Get # of pages
			var count = inputDocument.PageCount;

			// Add each page to "Combined PDF"
			for (int idx = 0; idx < count; idx++)
			{
				PdfPage page = inputDocument.Pages[idx];
				customerPdf.AddPage(page);
			}
		}
	
	}
Monday, April 27, 2009 9:08:41 PM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [0]  | 
# Thursday, December 18, 2008

I love to dabble in new things, about 3 months ago I started helping a friend fix up their Cold Fusion 7 website.  I noticed a bunch of things I consider important missing from the site and was curious about the user demographics/stats (who are they, what browser are they using, what is the max resolution we can use on the site, etc...).  After talking a bit, I decided to implement the following items to gather the data:

  1. Implement AWSTAT to monitor the website logs.
  2. Implement a custom internal stat tracker to gather info about users.

Here are the fields we required:

  1. User Name
  2. Browser Type & Version (e.g. IE5, IE6, IE7, FF3, Chrome ???)
  3. Operating System  (e.g. Linux, Max, Windows)
  4. Default Language Supported (e.g. en, jp ) 
  5. Screen Resolution (e.g. monitor resolution 1024x768, 1280x1024, or 800x600)
  6. Screen Colors (e.g. 16bit, 24bit, 32bit)
  7. IP Address
  8. URL (with only the first query string parameter, since he uses FuseBox)
  9. Date
  10. Time

Some of the variables in our list are not available in Cold Fusion, they are browser based data that the DOM has available via JavaScript.  To get the data to the server I created a a page called "count.cfm" to accept a query string parameters with the data from the client.  This is just JS 101, as far as I know there is no other solution to getting client side information about the user to the server.  There are a lot more stats you can obtain from the client, to find them look at the JavaScript "screen" and "navigator" objects .  I started to stub out a little extra in my code below (e.g. Navigator.javaEnabled ), if you want to use this data you'll need to add the logic to add the data in a GET request to "count.cfm".

    var file = '/fusebox/count.cfm';
    w = screen.width;
    h = screen.height;
    v = navigator.appName;
    j = navigator.javaEnabled();

    if (v != 'Netscape') {
        c = screen.colorDepth;
    }
    else {
        c = screen.pixelDepth;
    }
    
    info = 'res=' + w + 'x' + h + '&js=' + j + '&col=' + c;

    document.write('');

After the data is received in the "count.cfm" page, I normalize the data by replace some of the data with foreign keys.  This keeps the stat log normalized and decreases the table size.

function LogStats()
{
if(isDefined("session.userid"))
{
    UserId = session.userid;
    UserName = session.username;
    AuthorityLevel = session.authoritylevel;
}
else
{
    UserName = "Anonymous";
    UserId = "";
    AuthorityLevel = "";
}

OS = getOs(CGI.HTTP_USER_AGENT);
BrowserType = getBrowser(CGI.HTTP_USER_AGENT);

if(isDefined("CGI.HTTP_ACCEPT_LANGUAGE"))
{
    Language = CGI.HTTP_ACCEPT_LANGUAGE;
            
    if(Language.IndexOf(";") GT 0)
    {
        Language = Mid(Source, 1, Source.IndexOf(";"));
    }
}
else
{
    Language = "(Unknown)";
}


IP = CGI.REMOTE_ADDR;
LogDate = DateFormat(Now(),"mm/dd/yyyy");
LogTime = TimeFormat(Now(), "hh:mm");

if(isDefined("CGI.HTTP_REFERER"))
{
    Source = CGI.HTTP_REFERER;
            
    if(Source.IndexOf("&") GT 0)
    {
        Source = Mid(Source, 1, Source.IndexOf("&"));
    }
}
else
{
    Source = "(Home Page)";
}

if(isDefined("CGI.HTTP_REFERER"))
{
    Referrer = CGI.HTTP_REFERER;
}
else
{
    Referrer = "";
}

if(isDefined("url.res"))
{
    Resolution = url.res;
}
else
{
    Resolution = "(Unknown)";
}

if(isDefined("url.js"))
{
    JavaScriptEnabled = url.js;
}
else
{
    JavaScriptEnabled = "(Unknown)";
}

if(isDefined("url.col"))
{
    Colors = url.col;
}
else
{
    Colors = "(Unknown)";
}

IdOs = GetIdOS(OS);
IdColors = GetIdColors(Colors);
IdRes = GetIdRes(Resolution);
IdBrowser = GetIdBrowser(BrowserType);
IdLang = GetIdLang(Language);
IdPath =  GetIdPath(Source);

Insert = "SET NOCOUNT ON;
          INSERT INTO tbl_Stats_Stats
          (username, [date], [time], ip, osid, colorid, browserid, resid, pathid,languageid)
          VALUES 
          ('#UserName#', '#LogDate#', '#LogTime#', '#IP#', #IdOs#, #IdColors#, #IdBrowser#, #IdRes#, #IdPath#, #IdLang#);
          SELECT @@identity as LanguageId from tbl_stats_Languages";
InsertResults = cfquery(dsn="",sqlstring=Insert);
return InsertResults.LanguageId;
}

This probably took about 2 hours to throw together.  I'm sure there is more than can be done to optimize the code but this way my first time programming with Cold Fusion.  Additionally, since my script is targeted for an intranet application, I didn't implement parameter validation which is a must for external websites!  The largest amount of work was building  the logic to parse the USER_AGENT so I could determine the browser and OS.  These are very important, since our goal was to determine how we could redesign without negatively impacting the users.

This was deployed by putting the JS into the footer template (footer.cfm).  The database script is included in the source code download, along with a data reset script.

Hopefully the logic or code can help you solve your problem.

ColdFusionWebUserStats.zip (3.96 KB)
Thursday, December 18, 2008 11:37:02 AM (Pacific Standard Time, UTC-08:00)  #     |  Comments [0]  | 
# Tuesday, November 25, 2008

This logic can be used to give you a SINGLE SIGN-ON / AUTOLOGIN solution with ASP.NET 2.0 or higher using a combination of authentication providers on IIS 5.1 (Windows 2000).  The demo is using C# and does require a network administrator account to make the magic work.  I've tried using other accounts, but was only able to get this to work with a "Domain Administrator" account.

I tried so many solutions (applicaiton sub folder using Windows auth, web service, every possible IIS 5.1 settings, WMI query) and nothing worked.  After trying to figure this out for a few weeks, I realized that I had a single sign-on solution being used for our SonicWall content filtering system appliances.  I looked into how this was being done and I decided to put the same logic in the page_load event of my login control.

After an hour of playing around, I had a working ASP.NET single sign-on solution.  Using impersonation, I create a thread that uses a domain administrator account (this is a requirement, which is also required by the SonicWall solution) to perform a remote call to the client workstation to obtain the currently logged on user(s).  The magic is done by using "netapi32.dll", by passing the NetWkstaUserEnum method a hostname your able to obtain an array of users logged into the computer.  I always take the last user in the list, since the machine account "domain\machinename$" is always first.  I've not had it return the wrong person yet.  Even if a user is not automatically logged in, they can still manually enter their login/password to access the site.

** On my development machine, when I checked the array results I would see a bunch of accounts that were generated each time I ran VS.  Each time I ran my demo applicaiton, I'd see a different account like (ASPNET) as the last user.  I think this has something to do with the thread VS users for the built-in webserver.

Too keep my original Forms Authentication solution in place, I parse the account (e.g. DOMAIN\User => User) and I check the Forms Authentication database for the user's name, skipping the password check.  If they exist, I creates a FormsAuthenticationTicket and the process is complete.

** This code is part of a C#, ASP.NET Website Project.  I've tested this code on a few websites all using C# and this does work.  The cavaet of requiring a high-security account is a pain but it works.  If anybody knows of another way to pull this off, let me know...

protected void Page_Load(object sender, EventArgs e)
{
    
if (!IsPostBack) { // Attempt Autologin
        
string account = String.Empty;
        
string domain = String.Empty;
        
string user = String.Empty;
        
string email = String.Empty;


        
try
         {

             ImpersonateUser iu
= new ImpersonateUser();
            
if (iu.impersonateValidUser("<DOMAIN ACCOUNT>", "<DOMAIN NAME>", "<DOMAIN ACCOUNT PASSWORD>"))
             {

                 NetWorkstationUserEnum nws
= new NetWorkstationUserEnum();
                
string host = nws.DNSLookup(Request.UserHostAddress);

                
string[] users = nws.ScanHost(host);

                
if (nws.ScanHost(host).Length > 0)
                 {
                     account
= users[users.Length - 1];
                     domain
= account.Substring(0, account.IndexOf("\\"));
                     user
= account.Substring(account.IndexOf("\\") + 1, account.Length - account.IndexOf("\\") - 1);
                 }

                 iu.undoImpersonation();
             }
         }
        
catch (Exception logex)
         {
             Log.Debug(
"Autologin Failure: " + logex.Message);
         }


        
if (!String.IsNullOrEmpty(account))
         {
             Log.Info(
"Account: " + account);

            
if (domain.ToUpper() == "<DOMAN NAME>")
             {
                 email
= user.ToLower() + "@<DOMAIN ACCOUNT EMAIL>";
                 String strRole
= AssignRoles(email);

                
if (!String.IsNullOrEmpty(strRole))
                 {
                     FormsAuthentication.Initialize();

                    
//The AddMinutes determines how long the user will be logged in after leaving
                    
//the site if he doesn't log off.
                     FormsAuthenticationTicket fat
= new FormsAuthenticationTicket(1,
                         email, DateTime.Now,
                         DateTime.Now.AddDays(7),
true, strRole,
                         FormsAuthentication.FormsCookiePath);

                     HttpCookie ck;
                     ck
= new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat));
                     ck.Expires
= fat.Expiration;
                     ck.Path
= FormsAuthentication.FormsCookiePath;

                     Response.Cookies.Add(ck);
                     Response.Redirect(FormsAuthentication.GetRedirectUrl(email,
true));
                 }
                
else
                 {
                     Log.Info(
"Unable to create FAT, user does not have any roles assigned.");
                 }
             }
         }

     }
}

By default, the login control is only displayed when a user is not authenticated.  This is becaus the auto login logic is hooked onto the modules load event.  This solution is currently working on Windows 2000 with IIS 5.1, for a portal developed with ASP.NET 2.0 and C#.  The portal uses forms authentication but existing on an internal domain.  The site has a a few hundred users, some are remote agents that connect via VPN and are not part of the domain.

This has made a huge difference, since we now set the company default webpage to the site and key that they'll be able to see everything since we've automatically logged them in.  The first day I made this change, I got 3 emails about the some major changes users saw done to the site.  I found this comment really funny, since we didn't make any changes to the content... it just so happened that this was the first time they had ever logged in!

Good References for the soltuion:

http://www.codeproject.com/KB/IP/LoggedOnUsersPart2.aspx  (had a good note about security)

Other Notes:

I found another soltuion saying that a WMI Query should be able to obtain the same data, but when I tried this using impersontation it did not work.  I read that WMI queries, even when run via Impersation, are limited by the ASPNET WP thread.  I can't explan the details but I did find two posts about this and took them at face value. 

You can download the C# ASP.NET code here.  This is an excerpt from my project but it's working code.  If anybody else has a working soltuion that does not require a domain admin acount, let me know.  Having a Single Sign-On ("SSO") solution for an ASP.NET intranet site is awesome!

FormsWindowsMixxed_Source.zip (1.99 KB)

Tuesday, November 25, 2008 5:45:51 PM (Pacific Standard Time, UTC-08:00)  #     |  Comments [0]  | 

A few weeks ago I was working on a project that required a report to be sent to a Line Printer (Oki Data DotMatrix Printer) that had a built in 14.4K modem.  After a few hours of researching my options and a few more hours researching AT commands, I had a working solution.  The only one caveat, was the speed of processing versus the speed of communication which manifested itself by causing the communications stream to break because the process would complete before all the data was buffered to the serial port.  To get around this, I setup the program to check for a completion status (OK, ERROR, CONNECT) or wait a maximum amount of time before it exits out of routine.  If anybody has a better idea, I'm all ears!

This is the core function used to send the report, it's implemented as part of a Transport class... since the project required FTP & Modem communcations.

public static string PrintReport(string ReportData, string PhoneNumber, SerialPort port)
        {
            StringBuilder responsMessage = new StringBuilder();

            try
            {
                port.Open();
                responsMessage.Append(SendAT("AT", port));
                responsMessage.Append(SendAT("ATE0", port));
                responsMessage.Append(SendAT("ATS31=1", port));
                responsMessage.Append(SendAT("ATDT" + PhoneNumber, port, 60000));
                responsMessage.Append(SendTextData(port, ReportData));
                responsMessage.Append(SendCommandSignal(port));
                responsMessage.Append(SendAT("ATH", port, 5000));
                port.Close();
            }
            catch (Exception e)
            {
                responsMessage.Append("*** EXCEPTION MESSAGE ***" + "\r\n");
                responsMessage.Append(e.Message.ToString() + "\r\n");
                responsMessage.Append("*************************" + "\r\n");

            }

            return responsMessage.ToString();

        }

To call this method, I use the following code that includes some checkes to make sure there is a modem on the port and that I'm able to communicate with it.

            SerialPort port = new SerialPort();

            port.Parity = Parity.None;
            port.StopBits = StopBits.One;
            port.DataBits = 8;
            port.Handshake = Handshake.None;
            port.DtrEnable = true;
            port.RtsEnable = true;

            try
            {
                port.PortName = ComPort;
                bool IsConnected = Modem.CheckStatus(port);

                if (!IsConnected)
                {
                    return "Unable to communicate with the modem on " + ComPort;
                }

                string modemResults = Modem.PrintReport(Reports, PhoneNumber, port);

                if (modemResults.Contains("EXCEPTION"))
                {
                    return modemResults;
                }
                else
                {
                    return "SUCCESS";
                }

            }
            catch (Exception er)
            {
                return er.Message;
            }

I found the SerialPort.DtrEnable and SerialPort.RtsEnable settings to being the key to getting the modem to working.  Without setting both of these to true, I couldn't get the modems to communicate at all.  I found various articles about using one or ther other, but after setting both of these settings to true I was able to get a Serial/Internal PCI/Laptop modem to communicate with the printer!

Zach

ProjectX.Transport.Modem.cs (8.78 KB)

Tuesday, November 25, 2008 5:03:30 PM (Pacific Standard Time, UTC-08:00)  #     |  Comments [0]  | 
# Thursday, September 04, 2008

This is working code that demostrates how you can allow users to manage their DataGrid row colors (can also be easily applied to a GridView), using a a few cool ASP.NET Ajax & Custom Control items.  This idea can easily be applied to many other things and is the base of a DataGrid Rule Manager I've been tinkering with developing.  I've put notes all over the code and referenced other articles used while developing.

I'm not a dedicated programmer as I'm sure you'll notice from my style of coding.  Even though I don't program all day, it doesn't mean I don't appreciate good habbits/standards.  My biggest peeve is a bad UI.  I originally planned to use a few ASP.NET server controls, but was disappointed by their lack of features/bugs.  I ended up using a FREE 3rd party control (obout ColorPicker) for the color picker because it was super easy to use.  It did exactly what I need with just a few lines of code.  After having the tool completed, I was still disappointed with the DropDownList's inability to apply styles and did a little research to find an MSDN article on how to build a custom DropDownList to work around this problem.  I ended up using 97% of the MSDN example code had (minor tweaks/adjustments) to replace the stock DropDownList server control and get the colors working.  The result was awesome, the picture below shows a pretty DropDownList with background colors set based on the assigned color.  This custom DropDownList could easily be used for the color picker as well, but I thought I'd leave the obout control in the example, in case you wanted to see a more advanced/dynamic color picker.

Overall, I'm very pleased with the results..  I'm making a few extra DB hits to load my ColorRules table that is bound to the DropDownList, because I was being lazy and wanted my DropDownList sorted without using a SortList (by default, ListItems added to the DropDownList are put at the bottom).

This sample was built from parts of my live tool.  It was implemented as a portal module, and supports advanced features like multi-table coloring.  I picked the pastel colors, because my form gets printed on a color printer and they need to see the text without difficulties.

Source Code: ColoredTableManager.zip (1017.72 KB)

Live Demo: http://www.zachhunter.net/sandbox/colortablemanager/default.aspx

Thursday, September 04, 2008 12:25:22 PM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [0]  | 
# Wednesday, July 27, 2005

I created a Web User Control to be used as a Module in a portal I support.  While building the UI, the user requested a few features that would require us to dynamically assign values to a second dropdown list based on their choice in the first.  Since these values are static, and there is only 5, I wanted this to be a client side event.  This seemed simple enough, until I realized that I would be adding this control dynamically to different web pages, multiple times.  This has the effect, of auto naming your web user controls (example: Drag two Web User Controls to a page and their names will be Control1 and Control2).  The problem is when you try to reference a form control in a web user control, you must use the fully qualfied name of the control "<Web User Control Name>:<Control Name>".  I decided the fastest way to do this was to extract the "<Web User Control Name>:" from the source control, and use it when refering to the second control.

Below, is my code for updating a second "HtmlSelect" control based on the selection of the first "HtmlSelect" control.  Both controls have been set to RunAt="server" since they will be programmatically accessed.

Here is my JavaScript code that I regisered in my Web User Control.  I also assigned an OnChange event to Control1 to call this function and pass in the current object.

Calling Function Code Reference:
-------------------------------------
OnChange = "ClientSideTrigger(this);"


JavaScript Code Block:
--------------------------------------
1: <script>
2: function ClientSideTrigger(controlObj){
3:
4:
5:    
// *** PARSE the dyanmic WebControl Name **
6:
    var userControl = controlObj.name.substring(0, controlObj.name.indexOf(":")+1);
7:
8:    
// *** BUILD the destination control name to be Updated (the second HtmlSelect box)
9:
    var destinationControl = userControl + "toWarehouseList";
10:
11:    
// *** GET the currently selected index of control ***
12:
    var myindex = document.getElementById(controlObj.name).selectedIndex;
13:
14:    
// *** ERASE contents of the 2nd control ***
15:
    for (var i=document.getElementById(destinationControl).options.length-1; i>=0; i--)
16:       { document.getElementById(destinationControl).options[i] = null; }
17:

18:     // *** SET the default item of 2nd control to "" ***
19:
    document.getElementById(destinationControl).selectedIndex = -1;
20:
21:
22:
// *** Logic to build contents of 2nd control ***
23:
if(myindex==0) {
24:   document.getElementById(destinationControl).add(new Option("",""));
25:   document.getElementById(destinationControl).add(new Option("7300","7300"));
26:   document.getElementById(destinationControl).add(new Option("USA00","USA00"));
27:   document.getElementById(destinationControl).add(new Option("USA01","USA01"));
28:   document.getElementById(destinationControl).add(new Option("USA02","USA02"));
29:   document.getElementById(destinationControl).add(new Option("USA03","USA03"));

30: } else
if(myindex==1) {
31:   document.getElementById(destinationControl).add(new Option("",""));

32:   document.getElementById(destinationControl).add(new Option("USA00","USA00"));
33:   document.getElementById(destinationControl).add(new Option("USA01","USA01"));
34:   document.getElementById(destinationControl).add(new Option("USA02","USA02"));
35:   document.getElementById(destinationControl).add(new Option("USA03","USA03"));
36: } else
{
37:   document.getElementById(destinationControl).add(new Option("",""));
38:   document.getElementById(destinationControl).add(new Option("7300","7300")); }
39: }
40: </script>


Wednesday, July 27, 2005 11:45:50 PM (Pacific Daylight Time, UTC-07:00)  #     |  Comments [0]  |