Xerratus
Happily stressed out, since 1974


 
Sunday, August 03, 2008

Over the weekend I helped launch a website that was located in the US but opted to host the site on a Canadian dedicated server.  One of the first things I did while setting up the server was to change the regional settings from "en-CA" to "en-US" (control panel -> regional and language options).  After pushing the site up to the server, I noticed during my testing that everywhere I was displaying dates (using the built-in .ToLongDateString() function of the DateTime object), the incorrect format was showing up.  Further more, transaction dates were being entered into the database incorrectly as well.

en-CA formats:

Short date: "dd/MM/yyyy"
Long date: "MMMM-dd-yy"

en-US formats:

Short date: "M/d/yyyy"
Long date: "dddd, MMMM dd, yyyy"

So, the computers region was setup correctly but the incorrect dates were still being shown.  Since the dates were being driven by a .NET object, I next looked at the .NET Framework (I'm running 2.0).  The solution I found was to modify the globalization element of the web.config by adding the following to the <system.web> element:

<globalization
   fileEncoding="utf-8"
   requestEncoding="utf-8"
   responseEncoding="utf-8"
   culture="en-US"
   uiCulture="en-US"
/>

Furthermore, since this was a dedicated server and all of the sites were going to be US sites we moved this from the web.config to the machine.config so that all new sites would not run into this problem.

Thursday, July 31, 2008

Here's one I just had to solve.  I had an xml string that was being passed to a sproc that used sp_xml_preparedocument to insert the values using OPENXML.  Problem was that the sproc was throwing the following error:

'An invalid character was found in text'

The odd thing was that the xml in question was valid in Visual Studio before it passed it to the sproc and did not have any illegal characters.  What I found was that by simply changing the input value from varchar(max) to nvarchar(max) sp_xml_preparedocument was able to parse the xml just fine. 

For a quick refresher, read up on nchar and nvarchar in T-SQL.

Friday, January 11, 2008

This one has been plaguing me for years: What is the best way to create a dynamic stored procedure that allows null inputs to filter results?  To solve this, over the years, I've tried a few things, below are just a couple:
  • Build a SELECT statement string together concatenating non-null values then execute the string.
  • Use IF statements to narrow down the type of filtering requested and run the pre-built SQL statement with the corresponding non-null values.
The problem with the above solutions is that neither rely on one solid select statement.

Today, while working on another request to come up with a dynamic stored procedure for a specific report a client wants, I had an epiphany.  If I use isnull() in conjunction to with the actual value, I can get the desired results, no matter what input(s) are null -if any or all.

Here's the magic line that allows just that:

WHERE D1.MarketID = isnull(@MarketID, D1.MarketID)

Now, we all know that isnull() returns the first non-null value.  So, if @MarketID is null, then we just return the actual value of the row in question; D1.MarketId.  For a fact, D1.MarketID will ALWAYS equal D1.MarketID.  That's it!  

So, if we create a sproc that takes in five different values (filters), all of which can be null, utilize the above isnull() trick, we can let out business layer decide which results to return simply by passing in an actual value or a DBNull.Value to the call.  The stored procedure finally becomes dumb and all of the logic is finally in the hands of the business logic, where it should be, in a non-kludgie (pronounced "Hacked") way.

Here is a quick example:

CREATE PROC Reporting_Impressions
    @MarketID int = NULL,
    @StartDate datetime = NULL,
    @EndDate datetime = NULL,
    @Page varchar(25) = NULL,
    @LocationID uniqueidentifier = NULL
AS

SELECT D1.LocationId, D1.LocationName, D1.MarketId, D1.MarketName,
D1.DisplayedOnPage, count(D1.DisplayedDate) AS TotalImpressions
FROM PremiumMerchant_Displays AS D1 WITH(NOLOCK)
INNER JOIN PremiumMerchant_Displays AS D2 WITH(NOLOCK)
ON D1.LocationId = D2.LocationId AND D1.MarketId = D2.MarketId AND
D1.DisplayedOnPage = D2.DisplayedOnPage AND D1.ImpressionId <> D2.ImpressionId
WHERE D1.MarketID = isnull(@MarketID, D1.MarketID)
AND D1.DisplayedOnPage = isnull(@Page, D1.DisplayedOnPage)
AND D1.LocationId = isnull(@LocationID, D1.LocationId)
AND D1.DisplayedDate >= isnull(@StartDate, D1.DisplayedDate)
AND D1.DisplayedDate <= isnull(@EndDate, D1.DisplayedDate)
GROUP BY D1.LocationId, D1.LocationName, D1.MarketId, D1.MarketName, D1.DisplayedOnPage

GO

And here are some various ways to call this procedure:

--Specific market id
EXEC Reporting_Impressions 4, NULL, NULL, NULL, NULL

--All, no filters
EXEC Reporting_Impressions NULL, NULL, NULL, NULL, NULL

--Date range
EXEC Reporting_Impressions NULL, '1/1/2008 7:00:00 AM', '2/15/2008 11:00:00 AM', NULL, NULL

--Specific page
EXEC Reporting_Impressions NULL, NULL, NULL, 'Home', NULL

--Specific Location
EXEC Reporting_Impressions NULL, NULL, NULL, NULL, 'B40B82C9-A41A-49DA-8950-3CBFF4488AEA'

--Specific market id and page for a given date range
EXEC Reporting_Impressions 4, '1/1/2008 7:00:00 AM', '2/15/2008 11:00:00 AM', 'Directory', NULL

As you can see, there is only one stored procedure with many different results, all of which are dependent on the number of non-null inputs and snuggled within a nice, clean, single select statement.

Thursday, January 10, 2008

Nice little trick if you want to return results sorted randomly from a SQL 2000/2005 database:

SELECT * FROM Customers ORDER BY newid()

So say you want to return 10 random customers from the database, you'd modify the statement as such:

SELECT TOP 10 * FROM Customers ORDER BY newid()

For those who don't know, newid() generates a new guid, just as Guid.NewGuid() does in .NET.  By ordering a uniquely generated guid, we're guaranteed random results.

Monday, August 20, 2007

So, for the past couple of weeks, while working on a project that involves integration with Community Server 2007 I've run into the annoying and ambiguous message "Unable to start debugging on the web server".



Now, I've set up IIS sites before and I know to check the usual suspects; app pool, Windows authentication, etc.  But with the tight deadline of this project, I just haven't had the time to research this one.  Googling this usually just brings up forums where "Unable to start debugging on the web server" is brought up that are run on Community Server.  Yeah, helpful.

So, today I need (stress the word NEED) to debug this application because the blog web service is not returning consistent data.  That's a whole other WTF, but for now we'll stick with the issue at hand; getting the damn thing to debug.

With that, I have to figure this out.  Within a few minutes I find myself in this forum which is telling me that it's my IIS is corrupt and that I have to uninstall everything .NET then reinstall it all.  Bullshit I say!  BULLSHIT!  So I continue to scroll down when I see a reply from a "David B" which simply says to change the debug attribute in the web.config to true.  YES!  Community Server distributes the SDK with this option set to false.  Damn, I need to remember this!

So, for those of you in the same boat, DO NOT reinstall IIS and everything .NET, just change the web.config to the following:

<compilation debug="true" defaultLanguage="c#">

Works like a charm.
Wednesday, August 15, 2007

Working on a project today that has some aspects that integrate with Community Server, I ran into a slight problem.  I needed to replace the submit resource link button for adding comments with an image button.  At first it sounded easy.  Just replace the <CSControl:ResourceLinkButton /> with an <asp:ImageButton />.  Not so fast, the compiler said.  The <CSBlog:WeblogPostCommentForm /> was looking for a link button that inherits from CSLinkButton.  Of course, the built in image button does not do that.  So my solution, create my own custom composite control that inherits CSLinkButton but generates a link button.

Here's how I did it:

First, create a new control in the CommunityServerControls20 project (anywhere is fine but I put it in a new folder called "Custom") and call it CustomImageLinkButton.cs.

Add the following code:

using System;
using System.ComponentModel;
using CommunityServer.Components;

namespace CommunityServer.Controls
{
    
public class CustomImageLinkButton : CSLinkButton
    {
        [
Bindable(true)]
        
public virtual string ImageUrl
        {
            
get
            {
                
object state = ViewState["ImageUrl"];
                
if(state != null)
                {
                    
return (string)state;
                }
                
return "";
            }
            
set { ViewState["ImageUrl"] = value; }
        }

        
protected override void OnPreRender(EventArgs e)
        {
            
base.OnPreRender(e);

            
if(!Globals.IsNullorEmpty(ImageUrl))
            {
                System.Web.UI.WebControls.
Image image = new Image();
                image.ImageUrl = ImageUrl;
                
this.Controls.Add(image);
            }
        }
    }
}

When you create the control in the UI and pass in an ImageUrl, the control creates an image object and adds it to the links control array.  That's it.

Here is the UI code in action:

<CSBlog:WeblogPostCommentForm ID="WeblogPostCommentForm1" runat="server">
    ... snip ...
    <FormTemplate>
         <div class="CommonFormArea">
             ... snip ...
            
<div class="CommonFormField">
                <br />
                <CSControl:CustomImageLinkButton runat="server" ID="btnSubmit" ImageUrl="http://localhost/project1/images/submit.jpg" ValidationGroup="CreateCommentForm" />
             </div>
        </div>
    </FormTemplate>
</
CSBlog:WeblogPostCommentForm>

Enjoy!


Wednesday, August 01, 2007

In my years of programming, I've always found one thing that's been fairly consistent; developers love to put the baseUrl in the web.config.  While this is nice and works well good, it does pose it's problems especially in local development environments where the baseUrl isn't always the same.  One day a while back, I decided to write something that grabbed the baseUrl from the Request object.  It didn't take much time at all and works great. 

Today I needed to use it again for another project and thought I'd share while I had it open.

For those of you interested, here is the method I created:

private string GetBaseUrl()
{
    
HttpContext context = Context;
    
string url = context.Request.Url.AbsoluteUri;
    
string baseUrl = url.Replace(context.Request.Url.AbsolutePath, "") + context.Request.ApplicationPath;
    
    
return (baseUrl.EndsWith("/")) ? baseUrl : baseUrl + "/";
}

To use, simply place this into the application_start event:

this.Application.Add("BaseUrl", GetBaseUrl());

Now, to use from within your sites codebase you can access it as such:

string baseUrl = (string)Page.Application["BaseUrl"];

No web.config to worry about and works with different IIS settings when developers are working with the code.

Saturday, July 28, 2007

Like a lot of services or software, when a new release comes out, I like to try out the new version.  When I upgraded DasBlog to version 1.9, I found that it added "This comment has not been screened by an external service." to then end of all of my comments.  This is something that I personally didn't like and/or want on my site. 

I looked around the configuration page to see if there was a way to remove it with no luck.  After that I Googled for a solution; no luck there either.  So, not giving up, I viewed the source of one of my pages and found that this text was convienently wrapped with a <div class="commentSpamStateStyle"></div>.  Knowing CSS, I just added the following style to my dasBlog.css file:

div.commentSpamStateStyle
{
    display: block;
}

There, problem solved.

Friday, July 27, 2007

Since I'm a programmer, I usually find myself needing to write regular expressions from time to time.  Now, I'm no expert but I think I can hold my own... with the help of my trusty ReGex cheat sheet.  The problem has always been that I'd have to implement the ReGex() code, then write the expression and test to be sure it worked.  Not ideal.  So today, while finding myself, once again, needing to write a regular expression I stumbled across this very nice and simple online RegEx validator.

I know what you're thinking, "I could write that in 10 lines of code or less" -but alas, you didn't!  So until you, or I for that matter, write one of our own and put it out there for the world to see, this is a nice alternative.

Wednesday, November 22, 2006

Somebody recently asked me this question and I thought that a some others out there might not know this little trick; SQL gurus please ignore:

When editing data in a table view from within SQL Enterprise Manager (2000 or 2005) you can NULL the field by hitting Ctrl+0 when the cell has focus.  Deleting the data completely does not do it.  Typing in "NULL" does not do it either.

Tuesday, November 21, 2006

While working an ASP.NET 2.0 project today I ran across a problem with the OnClientClick event within the Button WebControl.  The problem is that .NET renders this button:

<asp:Button ID="btnAddChoice" runat="server" Text="Add" OnClientClick="AddToListBox(lbxChoices, txtChoiceName);" />

as

<input type="submit" name="btnAddChoice" value="Add" onclick="AddToListBox(lbxChoices, txtChoiceName);" />

So when I click on the button, my JavaScript fires correctly but the page then submits -not what I want it to do.  Thinking fast, I added a snippet to return false after my client-script ran, like so:

<input type="submit" name="btnAddChoice" value="Add" onclick="AddToListBox(lbxChoices, txtChoiceName);return false;" />

This however, does not work; the page still submits.  After looking at the rendered HTML I can see the problem right away -the button type is submit.  What I need is for it to be a type of button.  

An input control of type button, will not automatically submit the form.  But how do I get .NET to render one?

What I do not want to to is capture it just before it renders and change it there.  And I also do not want to try to manipulate the type attribute after it's already been rendered.  The solution I found; .NET has a added an attribute (boolean) to the Button WebControl called UseSubmitBehavior.  By setting it to false in the WebControl, like so:

<asp:Button ID="btnAddChoice" runat="server" Text="Add" OnClientClick="AddToListBox(lbxChoices, txtChoiceName); return false;" UseSubmitBehavior="false" />

Renders the input control like I want it to, as a button:

<input type="button" name="btnAddChoice" value="Add" onclick="AddToListBox(lbxChoices, txtChoiceName); return false;__doPostBack('btnAddChoice','')" />

Notice that I still need to stop the execution of the JavaScript by returning false because .NET added  __doPostBack('btnAddChoice','') to the onclick event.  

But the main difference is that by returning false the page DOES NOT submit.  Now, I can go back to writing my client-script to manipulate the page and capture those changes when the real submit button is clicked and the server code takes over.

Tuesday, October 31, 2006

A few weeks ago, I posted a solution I came up with a way to utilize a remember me checkbox in .NET 2.0 that, when selected, pre-populated just the username in the username textbox during subsequent visits.  The problem I found was that signing out of FormsAuthentication wiped out any and all cookies I had set during or prior to logging a user in.  So, the simple act of looking at the cookie to see if the user wanted to be remembered became quite an ordeal.  My solution was to set/get the cookie using JavaScript.  This worked and I was happy.

This is a shot of the cookie collection when the user signs out and returns to the login page:



Notice that the count of the cookie collection is 0.

There are some skeptics out there though.  So, for them, I've created a very simple website solution that tries to set and get the remember me cookie as explained above.  Now, the solution DOES NOT work.  Once a user logs out, the remember me cookie is gone, along with the saved username, and there is no way to tell if the user had selected to have his username saved. 

The challenge is to modify the solution so that it DOES work.

Post updates in the comments.  When and if a solution is found and posted in the comments, I'll add to the solution and re-post so that we can finally get around this issue.

Note: The solution is NOT using the Login control on purpose.  The goal is to have the username pre-populated but still make them login.  From my limited usage of the Login control, it appears to actually keep the user logged in.  If you can get this solution to work with the Login control, I say go for it... and more kudos to you for doing so.

The winner receives bragging rights.

Download the solution: FormsAuthenticationRememberMeChallenge.zip (5.01 KB)

(the username and password to login into the form is "test"/"1111")

UPDATE: That was fast!  Adi has come up with a winning solution.  Congrats. 

Download it here: FormsAuthenticationRememberMeSolution.zip (5.55 KB)

Wednesday, October 25, 2006

We've all been there.  We develop a web application that uses the querystring to pass data from a master list page to a detail page.  Usually just the ID is passed (just a good programming technique) and you even check and double check the value and type of data so that only the user who is supposed to see it, sees it.  But the temptation to change the id for the user is too great and nobody ever likes seeing any type of identifier in the querystring.

The solution I've developed simply encodes the querystring name/value pairs so that it is unreadable but can be retrieved and created with relative ease. 

Note: This is solution does not encrypt/decrypt the values.  Rather it uses Base64 encoding to render the string unreadable.  It is possible for somebody with knowledge to hack this but you're a good programmer... and you've solved for this, just as you did if a user simply changed the ID; right?

So back to the solution:

First, we need to create a simple class to encode and decode Base64 values. 

public sealed class Base64
{
    
public static string Encode(string data)
    {
        
try
        {
            
byte[] encData_byte = new byte[data.Length];
            encData_byte =
Encoding.UTF8.GetBytes(data);
            
string encodedData = Convert.ToBase64String(encData_byte);
            
return encodedData;
        }
        
catch(Exception e)
        {
            
throw new Exception("Error in base64Encode " + e.Message);
        }
    }

    
public static string Decode(string data)
    {
        
try
        {
            
UTF8Encoding encoder = new UTF8Encoding();
            
Decoder utf8Decode = encoder.GetDecoder();

            
byte[] todecode_byte = Convert.FromBase64String(data);
            
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
            
char[] decoded_char = new char[charCount];
            utf8Decode.GetChars(todecode_byte,
0, todecode_byte.Length, decoded_char, 0);
            
string result = new String(decoded_char);
            
return result;
        }
        
catch(Exception e)
        {
            
throw new Exception("Error in base64Decode " + e.Message);
        }
    }
}

Next, we create a QueryString class and give it an indexer to easily retrieve the values using an ordinal.  The getter returns null if the ordinal is empty, the querystring is not found or if the ordinal does not match up with any items found.  If Request.QueryString["q"] is found, we decode the string, split the pairs, loop thru each pair splitting the name/value out, and match on the ordinal.  If a match is found, we simply return the value of the item back.

public string this[string ordinal]
{
    
get
    {
        
if(ordinal == "")
        {
            
return null;
        }

        
if(HttpContext.Current.Request.QueryString["q"] == null)
        {
            
return null;
        }

        
// decodes to name/value pairs: id=1&something=what&test=true
        string decode;
        
try
        {
            decode =
Base64.Decode(HttpContext.Current.Request.QueryString["q"]);
        }
        
catch(Exception)
        {
            
return null;
        }

        
string[] pairs = decode.Split('&');

        
foreach(string pair in pairs)
        {
            
string[] item = pair.Split('=');

            
if(item[0].ToLower() == ordinal.ToLower())
            {
                
return item[1];
            }
        }

        
return null;
    }
}

For encoding, we supply a simple encode() method.

public string EncodePairs(string data)
{
    
if(data == "")
    {
        
return "";
    }

    
return Base64.Encode(data);
}

Now, for the UI:

To retrieve the querystring items, create an instance of QueryString (I chose to make it a disposable object so I could use the using pattern), check to see if the ordinal exists.  If it does, cast it to whatever datatype your system calls for.

// Retrieve the querystring values
using(QueryString q = new QueryString())
{
    IdLabel.Text = (q[
"id"] != null) ? q["id"] : "";
    NameLabel.Text = (q[
"name"] != null) ? q["name"] : "";
    ActiveLabel.Text = (q[
"active"] != null) ? q["active"] : "";
}

To encode pairs, build up the querystring like you normally would (leaving off the starting "?") and use the encode() method to encode it.  Then, append it to whatever page you are redirecting to as q=encodedstring. 

string pairs = String.Format("id={0}&name={1}&active={2}", IdTextBox.Text, NameTextBox.Text, ActiveRadioButtonList.SelectedValue);
string encoded;

// Set the querystring values
using(QueryString q = new QueryString())
{
    encoded = q.EncodePairs(pairs);
}

Response.Redirect(
"~/Default.aspx?q=" + encoded);

This is just a quick demonstration how one can use encoding to limit tampering when passing querystring data to another page.  There are many ways to achieve this without even using a querystring but for those instances where you either have to or want to, you now have another option. 

One last note:  This can be taken one step further if encryption is needed.  Just extend the QueryString class to encrypt/decrypt rather than encode/decode.  There are risks there as well but that's for you to decide.

Download the solution: QueryStringEncoding.zip (4.13 KB)
Friday, October 20, 2006

One of the subtle differences I've found while working with web projects in Visual Studio 2005 is the Global.asax code is placed in script tags within the actual .asax page, not a codebehind file, as it was with .NET 1.1.  This article describes how to set up the Global.asax file to utilize the Global.asax.cs codebehind file.

First off, go to files->add new and select the "Global Application Class" file.  You'll notice right off the bat that the option to put the code in a separate file is grayed out.  Ignore this and click "Add".

Next, go back to files->add new and select a "Class" file.  Type in the name "Global.asax.cs" and click "Add".  Visual Studio will prompt you to add this file to the App_Code directory, select "Yes" and continue. 

Now that the files are in place, we need to do some slight manipulation of the existing code to get this solution to compile.

To start, the Global class in the Global.asax.cs file needs to implement HttpApplication.

public class Global : System.Web.HttpApplication

Next, move the code within the script tags from the Global.asax file to the Global.asax.cs file -within the Global class.

void Application_Start(object sender, EventArgs e)
{
    
// Code that runs on application startup
}

...snip...

void Session_End(object sender, EventArgs e)
{
    
// Code that runs when a session ends.
}

Once the code has been moved, remove the script tags from Global.asax:

<script runat="server">
</script>

Add the following to the Application directive at the top of the same page:

CodeBehind="Global.asax.cs" Inherits="Global"

The entire contents of the Global.asax file should look like:

<%@ Application Language="C#" CodeBehind="Global.asax.cs" Inherits="Global" %>

The solution should compile and the Global events should fire correctly as well.

Download the solution: GlobalCodeBehindWebSiteExample.zip (2.25 KB)

Wednesday, October 18, 2006

All of the ASP.NET web projects that I've ever worked on relied on some appSetting within the web.config file.  When ever the variable was needed, some code was used to grab the variable in question than modify the type as needed or used simple string comparisons.  The problem I found early on working on a small team was that every member had their own twist on grabbing and using the data from within the web.config file.  While no method was wrong, the differences in using the same variable degraded their integrity.  

My solution was to encapsulate the appSettings in a static class that returned the variables as properties of the correct type.  This encapsulation also isolated the place where all appSettings were obtained making changes much easier while, at the same time, normalizing their usage.  It takes a few minutes longer of up front work when adding new settings but the ease of usage diminishes that ten-fold.  

Another plus is that a missing setting won't throw an error since we check for it's existence before we actually try and cast the value.  In my experience, not all developers check first, they assume it'll be there -bad idea.  

Note: The below example is utilizing .NET 2.0 but the same results can be obtained using 1.1 by simply changing ConfigurationManager.AppSettings[""] to ConfigurationSettings.AppSettings[""].

First off, here are a few differnet appSettings that have varying usages and types:

<add key="PromoExpiration" value="11/1/2006"/>
<
add key="IntroText" value="This is some random text ..."/>
<
add key="DisplayExtra" value="true"/>
<
add key="LoopCount" value="16"/>

Next I created a class called ConfigController where the getters for the above settings are obtained.  Here are two examples; the first demonstrates a bool while the second shows an int data type:

public static bool DisplayExtra
{
    
get
    {
        
if(ConfigurationManager.AppSettings["DisplayExtra"] != null)
        {
            
return bool.Parse(ConfigurationManager.AppSettings["DisplayExtra"]);
        }
        
else
        {
            
return false;
        }
    }
}

public static int LoopCount
{
    
get
    {
        
if(ConfigurationManager.AppSettings["LoopCount"] != null)
        {
            
return int.Parse(ConfigurationManager.AppSettings["LoopCount"]);
        }
        
else
        {
            
return -1;
        }
    }
}

To further ensure that no errors are thrown, a try catch block can be used when casting the data.  In my opinion it's overkill but when you work on a project where the client can and does edit the web.config file outside of your control, exception handling might be in order.

if(ConfigurationManager.AppSettings["PromoExpiration"] != null)
{
    
try
    {
        
return DateTime.Parse(ConfigurationManager.AppSettings["PromoExpiration"]);
    }
    
catch(Exception)
    {
        
return DateTime.MinValue;
    }
}
else
{
    
return DateTime.MinValue;
}

Once the controller is in place, it's usage is as simple as:

PromoPanel.Visible = (DateTime.Now < ConfigController.PromoExpiration);

IntroLabel.Text =
ConfigController.IntroText;

ExtraPanel.Visible =
ConfigController.DisplayExtra;

for(int i = 0; i < ConfigController.LoopCount; i++)
{
    
Literal l = new Literal();
    l.Text =
String.Format("Count: {0}<br />", i);

    LoopPlaceHolder.Controls.Add(l);
}

In summary, keep access to the web.config settings in one place and return the resulting values using their respective types to ensure proper usage throughout your solution.

Download the solution: ConfigControllerWebSiteExample.zip (5.3 KB)
Monday, October 16, 2006

A new service by Hidden Network, that I've noted in a prior article Want a new way to find a job... read my blog, has just ended it's Beta testing.  Over the weekend, I found out that my blog was one of the chosen to use the new service to display job postings (see the right hand side of this page under the heading "Job Postings"). 

From what I've heard, employers are signing up, posting ads, and liking the resulting applicants even from these early stages. 

For my avid readers

Continually check the listings posted under the "Job Postings" heading for new opportunities or browse for more jobs.  Even if you are not actively looking, better opportunities may be just around the corner. 

For employers interested in posting ads

Start posting jobs today.

For bloggers interested in using the service

I encourage you to create an account and try the service out.  For those who don't want to jump right in, check back for a review of Hidden Network and my opinion of the service to date.

Wednesday, October 04, 2006

The other day I tried to add a simple "Remember me" checkbox to the login.aspx page for a site I'm working on.  Simple enough in theory that I didn't think much of it and gave myself an hour to add it in. 

Here's what I needed it to do:

  • Pre-populate the txtUserName textbox of the last user IF they checked the "Remember me" checkbox.
  • Remember the state of the "Remember me" checkbox so that the user doesn't have to check it each time they log in (or uncheck it).

Sounds simple enough right?  Just add a cookie and check it when login.aspx loads, right?  Wrong.

The problem became clear very early on; the cookies were being cleared when the user logged out.  Since my goal was to access the cookie BEFORE the user logged in, I could not rely on the cookie.  But I HAD to use a cookie, there was no way around it. 

So, after some searching and trial-and-errors, I came up with a simple solution: use JavaScript cookies.  Now it sounds like a bit of a hack but it's not really.  JavaScript has been able to read and write to cookies from the get go. 

So first we need to write some simple script that will read/write to cookies:

function CreateCookie(name, value, days)
{
    
if(days)
    {
        
var date = new Date();
        date.setTime(date.getTime() + (days*
24*60*60*1000));
        
var expires = "; expires=" + date.toGMTString();
    }
    
else var expires = "";
    
    document.cookie = name +
"=" + value + expires + "; path=/";
    
}

function ReadCookie(name)
{
    
var nameEQ = name + "=";
    
var ca = document.cookie.split(';');
    
for(var i = 0; i < ca.length; i++)
    {
        
var c = ca[i];
        
while (c.charAt(0)==' ') c = c.substring(1, c.length);
        
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    
    
return null;
}

function EraseCookie(name)
{
    CreateCookie(name,
"", -1);
}

Next, we create 2 new custom methods; one to read the cookie when the page loads and the other to write/remove the cookie when the checkbox is checked:

function CheckRememberMe()
{
    
if(ReadCookie("rememberMe") != null)
    {
        document.getElementById(
"txtUserName").value = ReadCookie("rememberMe");
        document.getElementById(
"RememberMe").checked = true;
        document.getElementById(
"txtPassword").focus();
    }
}

function ToggleRememberMe(checked)
{
    
if(checked)
    {
        CreateCookie(
"rememberMe", document.getElementById("txtUserName").value, 30);
    }
    
else
    {
        EraseCookie(
"rememberMe");
    }
}

Now, all that is left is to add an onload event to the body tag and an onclick event to the checkbox:

<body onload="CheckRememberMe();">

(note; since the checkbox value is not used anywhere but the client, I chose not to use a .NET server control)

<input type="checkbox" name="RememberMe" id="RememberMe" onclick="ToggleRememberMe(this.checked);" /><label for="RememberMe">Remember me on this computer</label>

Viola, you're done.  Simple, effective and it can be used by multiple browsers.

Monday, October 02, 2006

Recently I created my own custom macro object for dasblog 1.9 but ran into a slight problem; it acted like the dll I created wasn't there.  Following this article (Creating custom macros for dasBlog) to a "T" still faired no better.  Not wanting to spend too much time on this and thinking that I was doing something wrong, I emailed Scott Hansleman (one of the main contributers to dasBlog) to see if he could spot my problem quickly.  Scott was great and we corresponded for most of the day trying different things to try and solve it.  He took a special interest in this because he said that this was the second inquiry in the same week about this error.

2006-09-29 14:00:18 PM
1
Error:
Error executing Macro: System.ArgumentNullException: Value cannot be null. Parameter name: type at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) at newtelligence.DasBlog.Web.Core.MacrosFactory.CreateCustomMacrosInstance(SharedBasePage page, Entry item, String name) at newtelligence.DasBlog.Web.Core.TemplateProcessor.ProcessTemplate(SharedBasePage page, Entry entry, String templateString, Control contentPlaceHolder, Macros macros)
while processing .

2006-09-29 14:00:18 PM
1
Error:
Error executing macro: Test("hello", 1)|Extras. Make sure it you're calling it in your BlogTemplate with paratheses like 'myMacro()'. Macros with parameter lists and overloads must be called in this way. Exception: System.MissingMemberException: Member newtelligence.DasBlog.Web.Core.Macros.Test not found. at newtelligence.DasBlog.Web.Core.TemplateProcessor.InvokeMacro(Object obj, String expression) at newtelligence.DasBlog.Web.Core.TemplateProcessor.ProcessTemplate(SharedBasePage page, Entry entry, String templateString, Control contentPlaceHolder, Macros macros)
while processing .

From the looks of everything, it seemed that dasBlog could not find my custom class.  But why?  I made sure the changes in the web.config were correct because I thought for sure that was where the problem was.

Scott was able to duplicate the error once, then realized that he forgot to put his macro dll into the bin folder.  Emailing me again, he asked - as nicely as possible I might add - if I had forgot to put the dll in the bin directory.  Nope, I replied and even sent a screen shot of the servers bin directory.  To top it off, I sent him my solution to see if he could find anything wrong with my code or references.

While he was working away on solving this (thinking that there was something wrong with the macro factory code) I switched gears to see if building the macro using .NET framework 1.1 had anything to do with it.  I say this because the article on how to create custom dasBlog macros was using the .NET framework 2.0 so I did the same; why not, if the article did it in 2.0 why couldn't I?

Well the 1.1 solution worked!  Just then, I got another email from Scott asking if I was running dasBlog under 2.0 or not.  You see, creating macros under the 2.0 framework NEEDS dasBlog to be running under the 2.0 framework as well... DUH (as I smack myself in the forehead).

The problem all along was not with my code, not with the blog engine code but rather with the fact that I was trying to run a macro intended for .NET 2.0 under the 1.1 framework.

2 solutions presented themselves: A) Switch dasBlog to run under 2.0 or B) create the macro under 1.1.  Why is B even an option?  Because it is backward compatible with 2.0.  That's right, the 1.1 macro has more flexibility if I want to distribute this dll to others who, for one reason or another, are still running dasBlog under the 1.1 framework.

In summary, if you are trying to create a custom dasBlog macro and run into a problem, check to see that your website is running under the same .NET framework version as your solution (or lower).

A big thanks to Scott Hanselman for taking his time to help me with solve this dilema.

Thursday, August 17, 2006

Well it's about time! 

Being a technical blogger, I occasionally like to post code snippets for code that I find useful or helpful for a myriad of things.  The part I hate though is copying my code, which is nicely formatted in Visual Studio 2005, to FreeTextBox, which is the rich text editor for my blog.  Yes, there are some nice sites that allow you to copy and paste in your code that generate html for you but going back and forth is sometimes a hassle.  Also, I have my colors set up a tad bit different than some of the code-to-html generators so the conversion is never like I have it set up.  There was always my trusty fall back; paste the selection into Microsoft Word then copy it again and THEN paste it into FreeTextBox.  This worked but there was one very annoying problem; it wrapped each line with <p></p> tags.  Because of this, the code always looked double-spaced.  To manually go in and remove was a pain for larger chunks of code.

This is what I need/want?
  • The ability to select the code snippet I want right in visual studio.
  • Copy it.
  • Paste it straight into my blog rich textbox WITH the same colors that visual studio has.
After a little searching yesterday, I found EXACTLY what I was looking for; A macro written by Jeff Atwood (www.codinghorror.com/blog).  This allows me to do what I need and I can present my code in a matter of minutes now, formatted just like I like it.  Small, simple and complete... perfect.  For those of you who need it, I implore you to try it.

Here's a quick snippet to demonstrate:

public static Control FindControlByID(ControlCollection controls, string id)
{
    
Control found = null;

    
foreach(Control control in controls)
    {
        
if(control.HasControls())
        {
            found = FindControlByID(control.Controls, id);
            
if(found != null)
            {
                
break;
            }
        }

        
if(control.ID == id)
        {
            found = control;
            
break;
        }
    }

    
return found;
}

Ahhhhhh, that's nice.

Wednesday, August 09, 2006

For when you need to access a control from within a user control where the ID of said control is passed in as a parameter.  That was a mouthful.  Basically, if you create a custom user control that takes in the ID of another control (let's say a TextBox) the user control cannot find a handle to that control using the standard Page.FindControl("TextBox1").  If TextBox1 exists on the main page (whether or not it's on a master page) and your custom control needs to access TextBox1, it cannot be accessed thru the Page.FindControl method directly.  Luckily, there is recursion. 

Here is the method to find the control:

public Control FindControlByID(ControlCollection controls, string id)
{
    Control found =
null;

    
foreach(Control control in controls)
    {
        
if(control.HasControls())
        {
            found = FindControlByID(control.Controls, id);
            
if(found != null)
            {
                
break;
            }
        }

        
if(control.ID == id)
        {
            found = control;
            
break;
        }
    }

    
return found;
}


An example of this would be if you had a user control that had to populate a textbox that was passed in as a property.

Widget.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Widget.ascx.cs" Inherits="_controls_Widget" %>

Widget.ascx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;