Evolution of a Programmer

A Programmer in High School/Jr.High

10 PRINT “HELLO WORLD”
20 END

A Programmer in First year in College
program Hello(input, output)
begin
writeln(‘Hello World’)
end.

A programmer in Senior year in College
(defun hello
(print
(cons ‘Hello (list ‘World))))

A programmer when first starting his job
#include <stdio.h
void main(void)
{
char *message[] = {“Hello “, “World”};
int i;

for(i = 0; i < 2; ++i)
printf(“%s”, message[i]);
printf(“n”);
}

Seasoned professional
#include <iostream.h
#include <string.h

class string
{
private:
int size;
char *ptr;

public:
string() : size(0), ptr(new char(”)) {}

string(const string &s) : size(s.size)
{
ptr = new char[size + 1];
strcpy(ptr, s.ptr);
}

~string()
{
delete [] ptr;
}

friend ostream &operator <<(ostream &, const string &);
string &operator=(const char *);
};

ostream &operator<<(ostream &stream, const string &s)
{
return(stream << s.ptr);
}

string &string::operator=(const char *chrs)
{
if (this != &chrs)
{
delete [] ptr;
size = strlen(chrs);
ptr = new char[size + 1];
strcpy(ptr, chrs);
}
return(*this);
}

int main()
{
string str;

str = “Hello World”;
cout << str << endl;

return(0);
}

Master Programmer
[
uuid(2573F8F4-CFEE-101A-9A9F-00AA00342820)
]
library LHello
{
// bring in the master library
importlib(“actimp.tlb”);
importlib(“actexp.tlb”);

// bring in my interfaces
#include “pshlo.idl”

[
uuid(2573F8F5-CFEE-101A-9A9F-00AA00342820)
]
cotype THello
{
interface IHello;
interface IPersistFile;
};
};

[
exe,
uuid(2573F890-CFEE-101A-9A9F-00AA00342820)
]
module CHelloLib
{

// some code related header files
importheader(<windows.h);
importheader(<ole2.h);
importheader(<except.hxx);
importheader(“pshlo.h”);
importheader(“shlo.hxx”);
importheader(“mycls.hxx”);

// needed typelibs
importlib(“actimp.tlb”);
importlib(“actexp.tlb”);
importlib(“thlo.tlb”);

[
uuid(2573F891-CFEE-101A-9A9F-00AA00342820),
aggregatable
]
coclass CHello
{
cotype THello;
};
};

#include “ipfix.hxx”

extern HANDLE hEvent;

class CHello : public CHelloBase
{
public:
IPFIX(CLSID_CHello);

CHello(IUnknown *pUnk);
~CHello();

HRESULT  __stdcall PrintSz(LPWSTR pwszString);

private:
static int cObjRef;
};

#include <windows.h
#include <ole2.h
#include <stdio.h
#include <stdlib.h
#include “thlo.h”
#include “pshlo.h”
#include “shlo.hxx”
#include “mycls.hxx”

int CHello::cObjRef = 0;

CHello::CHello(IUnknown *pUnk) : CHelloBase(pUnk)
{
cObjRef++;
return;
}

HRESULT  __stdcall  CHello::PrintSz(LPWSTR pwszString)
{
printf(“%wsn”, pwszString);
return(ResultFromScode(S_OK));
}

CHello::~CHello(void)
{

// when the object count goes to zero, stop the server
cObjRef–;
if( cObjRef == 0 )
PulseEvent(hEvent);

return;
}

#include <windows.h
#include <ole2.h
#include “pshlo.h”
#include “shlo.hxx”
#include “mycls.hxx”

HANDLE hEvent;

int _cdecl main(
int argc,
char * argv[]
) {
ULONG ulRef;
DWORD dwRegistration;
CHelloCF *pCF = new CHelloCF();

hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

// Initialize the OLE libraries
CoInitializeEx(NULL, COINIT_MULTITHREADED);

CoRegisterClassObject(CLSID_CHello, pCF, CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE, &dwRegistration);

// wait on an event to stop
WaitForSingleObject(hEvent, INFINITE);

// revoke and release the class object
CoRevokeClassObject(dwRegistration);
ulRef = pCF-Release();

// Tell OLE we are going away.
CoUninitialize();

return(0);
}

extern CLSID CLSID_CHello;
extern UUID LIBID_CHelloLib;

CLSID CLSID_CHello = { /* 2573F891-CFEE-101A-9A9F-00AA00342820
*/
0x2573F891,
0xCFEE,
0x101A,
{ 0x9A, 0x9F, 0x00, 0xAA, 0x00, 0x34, 0x28, 0x20 }
};

UUID LIBID_CHelloLib = { /* 2573F890-CFEE-101A-9A9F-00AA00342820
*/
0x2573F890,
0xCFEE,
0x101A,
{ 0x9A, 0x9F, 0x00, 0xAA, 0x00, 0x34, 0x28, 0x20 }
};

#include <windows.h
#include <ole2.h
#include <stdlib.h
#include <string.h
#include <stdio.h
#include “pshlo.h”
#include “shlo.hxx”
#include “clsid.h”

int _cdecl main(
int argc,
char * argv[]
) {
HRESULT  hRslt;
IHello        *pHello;
ULONG  ulCnt;
IMoniker * pmk;
WCHAR  wcsT[_MAX_PATH];
WCHAR  wcsPath[2 * _MAX_PATH];

// get object path
wcsPath[0] = ”;
wcsT[0] = ”;
if( argc 1) {
mbstowcs(wcsPath, argv[1], strlen(argv[1]) + 1);
wcsupr(wcsPath);
}
else {
fprintf(stderr, “Object path must be specifiedn”);
return(1);
}

// get print string
if(argc 2)
mbstowcs(wcsT, argv[2], strlen(argv[2]) + 1);
else
wcscpy(wcsT, L”Hello World”);

printf(“Linking to object %wsn”, wcsPath);
printf(“Text String %wsn”, wcsT);

// Initialize the OLE libraries
hRslt = CoInitializeEx(NULL, COINIT_MULTITHREADED);

if(SUCCEEDED(hRslt)) {

hRslt = CreateFileMoniker(wcsPath, &pmk);
if(SUCCEEDED(hRslt))
hRslt = BindMoniker(pmk, 0, IID_IHello, (void **)&pHello);

if(SUCCEEDED(hRslt)) {

// print a string out
pHello-PrintSz(wcsT);

Sleep(2000);
ulCnt = pHello-Release();
}
else
printf(“Failure to connect, status: %lx”, hRslt);

// Tell OLE we are going away.
CoUninitialize();
}

return(0);
}

Apprentice Hacker
#!/usr/local/bin/perl
$msg=”Hello, world.n”;
if ($#ARGV = 0) {
while(defined($arg=shift(@ARGV))) {
$outfilename = $arg;
open(FILE, “” . $outfilename) || die “Can’t write $arg:$!n”;
print (FILE $msg);
close(FILE) || die “Can’t close $arg: $!n”;
}
} else {
print ($msg);
}
1;

Experienced Hacker
#include <stdio.h
#define S “Hello, Worldn”
main(){exit(printf(S) == strlen(S) ? 0 : 1);}

Seasoned Hacker
% cc -o a.out ~/src/misc/hw/hw.c
% a.out

Guru Hacker
% cat
Hello, world.
^D

New Manager
10 PRINT “HELLO WORLD”
20 END

Middle Manager
mail -s “Hello, world.” bob@b12
Bob, could you please write me a program that prints “Hello,
world.”?
I need it by tomorrow.
^D

Senior Manager
% zmail jim
I need a “Hello, world.” program by this afternoon.

Chief Executive
% letter
letter: Command not found.
% mail
To: ^X ^F ^C
% help mail
help: Command not found.
% damn!
!: Event unrecognized
% logout

Advertisements

ASP.NET Forms Authentication – Security

ASP.NET Forms authentication allows users to identify themselves by entering credentials (a user name and password) into a Web Form. Upon receipt of these credentials, the Web application can authenticate the user by checking the user name and password combination against a data source.

This How To describes how to authenticate users against the Microsoft Active Directory directory service by using the Lightweight Directory Access Protocol (LDAP). It also describes how to retrieve a list of security groups and distribution lists to which the user belongs, how to store this information in a GenericPrincipal object, and how to store this into the HttpContext.Current.User property that flows with the request through the ASP.NET Web application. This can subsequently be used for .NET role-based authorization.

Summary of Steps

This How To includes the following steps:

  • Step 1. Create a Web Application with a Logon Page
  • Step 2.Configure the Web Application for Forms Authentication
  • Step 3.Develop LDAP Authentication Code to Look Up the User in Active Directory
  • Step 4.Develop LDAP Group Retrieval Code to Look Up the User’s Group Membership
  • Step 5.Authenticate the User and Create a Forms Authentication Ticket
  • Step 6.Implement an Authentication Request Handler to Construct a GenericPrincipal Object
  • Step 7.Test the Application

Step 1. Create a Web Application with a Logon Page

This procedure creates a simple C# Web application that contains a logon page that allows a user to enter a user name and password and a default page that displays the identity name and group membership information associated with the current Web request.

To create a Web application with a logon page

  1. Start Microsoft Visual Studio® .NET and create a new C# ASP.NET Web Application named FormsAuthAD.
  2. Use Solution Explorer to rename WebForm1.aspx as Logon.aspx.
  3. Add a new assembly reference to System.DirectoryServices.dll. This provides access to the System.DirectoryServices namespace that contains managed types to help with Active Directory querying and manipulation.
  4. Add the controls listed in Table 1 to Logon.aspx to create a simple logon form.Table 1. Logon.aspx controls
    Control Type Text ID
    Label Domain Name:
    Label User Name:
    Label Password
    Text Box txtDomainName
    Text Box txtUserName
    Text Box txtPassword
    Button Log On btnLogon
    Label lblError
  5. Set the TextMode property of txtPassword to Password.
  6. In Solution Explorer, right-click FormsAuthAd, point to Add, and then click Add Web Form.
  7. In the Name field, type default.aspx, and then click Open.
  8. In Solution Explorer, right-click default.aspx, and then click Set As Start Page.
  9. Double-click default.aspx to display the page load event handler.
  10. Add the following code to the event handler to display the identity name associated with the current Web request.
    Response.Write( HttpContext.Current.User.Identity.Name );

Step 2. Configure the Web Application for Forms Authentication

This procedure edits the application’s Web.config file to configure the application for Forms authentication.

To configure the Web application for forms authentication

  1. Use Solution Explorer to open Web.config.
  2. Locate the <authentication> element and change the mode attribute to Forms.
  3. Add the following <forms> element as a child of the authentication element and set the loginUrl, name, timeout, and pathattributes as shown in the following.
    <authentication mode="Forms">
      <forms loginUrl="logon.aspx" name="adAuthCookie" timeout="60"
        path="/">
      </forms>
    </authentication>
  4. Add the following <authorization> element beneath the <authentication> element. This will allow only authenticated users to access the application. The previously establish loginUrl attribute of the <authentication> element will redirect unauthenticated requests to the logon.aspx page.
    <authorization>
      <deny users="?" />
      <allow users="*" />
    </authorization>
  5. Save Web.config.
  6. Start the IIS Microsoft Management Console (MMC) snap-in.
  7. Right-click the application’s virtual directory, and then click Properties.
  8. Click the Directory Security tab, and then click the Edit button in the Anonymous access and authentication control group.
  9. Select the Anonymous access check box and clear the Allow IIS to control password check box.
  10. Because the default anonymous account IUSR_MACHINE does not have permission to access Active Directory, create a new least privileged account and enter the account details in the Authentication Methods dialog box.
  11. Click OK, and then click OK again to close the Properties dialog box.
  12. Return to Visual Studio .NET and add an <identity> element beneath the <authorization> element in Web.config and set the impersonate attribute to true. This causes ASP.NET to impersonate the anonymous account specified earlier.
    <identity impersonate="true" />

    As a result of this configuration, all requests to the application will run under the security context of the configured anonymous account. The user will provide credentials through the Web form to authenticate against Active Directory, but the account used to access Active Directory will be the configured anonymous account.

Step 3. Develop LDAP Authentication Code to Look Up the User in Active Directory

This procedure adds a new helper class to the Web application to encapsulate the LDAP code. The class will initially provide an IsAuthenticated method to validate a supplied domain, user name, and password against an Active Directory user object.

To develop LDAP authentication code to look up the user in Active Directory

  1. Add a new C# class file called LdapAuthentication.cs.
  2. Add a reference to the System.DirectoryServices.dll assembly.
  3. Add the following usingstatements to the top of LdapAuthentication.cs.
    using System.Text;
    using System.Collections;
    using System.DirectoryServices;
  4. Rename the existing namespace as FormsAuthAD.
  5. Add two private strings to the LdapAuthenticationclass; one to hold the LDAP path to Active Directory and the other to hold a filter attribute used for searching Active Directory.
    private string _path;
    private string _filterAttribute;
  6. Add a public constructor that can be used to initialize the Active Directory path.
    public LdapAuthentication(string path)
    {
      _path = path;
    }
  7. Add the following IsAuthenticated method that accepts a domain name, user name and password as parameters and returns bool to indicate whether or not the user with a matching password exists within Active Directory. The method initially attempts to bind to Active Directory using the supplied credentials. If this is successful, the method uses the DirectorySearcher managed class to search for the specified user object. If located, the _path member is updated to point to the user object and the _filterAttributemember is updated with the common name attribute of the user object.
    public bool IsAuthenticated(string domain, string username, string
      pwd)
    {
      string domainAndUsername = domain + @"" + username;
      DirectoryEntry entry = new DirectoryEntry( _path,
                                                 domainAndUsername,
                                                   pwd);
    
      try
      {
        // Bind to the native AdsObject to force authentication.
        Object obj = entry.NativeObject;
        DirectorySearcher search = new DirectorySearcher(entry);
        search.Filter = "(SAMAccountName=" + username + ")";
        search.PropertiesToLoad.Add("cn");
        SearchResult result = search.FindOne();
        if(null == result)
        {
          return false;
        }
        // Update the new path to the user in the directory
        _path = result.Path;
        _filterAttribute = (String)result.Properties["cn"][0];
      }
      catch (Exception ex)
      {
        throw new Exception("Error authenticating user. " + ex.Message);
      }
      return true;
    }

Step 4. Develop LDAP Group Retrieval Code to Look Up the User’s Group Membership

This procedure extends the LdapAuthentication class to provide a GetGroups method, which will retrieve the list of groups that the current user is a member of. The GetGroups method will return the group list as a pipe separated string, as in the following.

"Group1|Group2|Group3|"

To develop LDAP group retrieval code to look up the user’s group membership

  1. Add the following implementation of the GetGroups method to the LdapAuthenticationclass.
    public string GetGroups()
    {
      DirectorySearcher search = new DirectorySearcher(_path);
      search.Filter = "(cn=" + _filterAttribute + ")";
      search.PropertiesToLoad.Add("memberOf");
      StringBuilder groupNames = new StringBuilder();
      try
      {
        SearchResult result = search.FindOne();
        int propertyCount = result.Properties["memberOf"].Count;
        String dn;
        int equalsIndex, commaIndex;
    
        for( int propertyCounter = 0; propertyCounter < propertyCount;
             propertyCounter++)
        {
          dn = (String)result.Properties["memberOf"][propertyCounter];
    
          equalsIndex = dn.IndexOf("=", 1);
          commaIndex = dn.IndexOf(",", 1);
          if (-1 == equalsIndex)
          {
            return null;
          }
          groupNames.Append(dn.Substring((equalsIndex + 1),
                            (commaIndex - equalsIndex) - 1));
          groupNames.Append("|");
        }
      }
      catch(Exception ex)
      {
        throw new Exception("Error obtaining group names. " +
          ex.Message);
      }
      return groupNames.ToString();
    }

Step 5. Authenticate the User and Create a Forms Authentication Ticket

This procedure implements the btnLogon_Click event handler to authenticate users. For authenticated users, you will then create a Forms authentication ticket that contains the user’s group list. You will then redirect the user to the original page that they requested (before being redirected to the logon page).

To authenticate the user and create a forms authentication ticket

  1. Return to the Logon.aspx form and double-click the Log On button to create an empty btnLogon_Click event handler.
  2. At the top of the file add the following using statement beneath the existing using statements. This provides access to the FormsAuthenticationmethods.
    using System.Web.Security;
  3. Add code to create a new instance of the LdapAuthenticationclass initialized to point to your LDAP Active Directory, as shown in the following code. Remember to change the path to point to your Active Directory server.
    // Path to you LDAP directory server.
    // Contact your network administrator to obtain a valid path.
    string adPath =
      "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com";
    LdapAuthentication adAuth = new LdapAuthentication(adPath);
  4. Add the code that follows to perform the following steps:
    1. Authenticate the caller against Active Directory.
    2. Retrieve the list of groups that the user is a member of.
    3. Create a FormsAuthenticationTicket that contains the group list.
    4. Encrypt the ticket.
    5. Create a new cookie that contains the encrypted ticket.
    6. Add the cookie to the list of cookies returned to the user’s browser.
    try
    {
      if(true == adAuth.IsAuthenticated(txtDomainName.Text,
                                        txtUserName.Text,
                                        txtPassword.Text))
      {
        // Retrieve the user's groups
        string groups = adAuth.GetGroups();
        // Create the authetication ticket
        FormsAuthenticationTicket authTicket =
            new FormsAuthenticationTicket(1,  // version
                                          txtUserName.Text,
                                          DateTime.Now,
                                          DateTime.Now.AddMinutes(60),
                                          false, groups);
        // Now encrypt the ticket.
        string encryptedTicket =
          FormsAuthentication.Encrypt(authTicket);
        // Create a cookie and add the encrypted ticket to the
        // cookie as data.
        HttpCookie authCookie =
                     new HttpCookie(FormsAuthentication.FormsCookieName,
                                    encryptedTicket);
        // Add the cookie to the outgoing cookies collection.
        Response.Cookies.Add(authCookie);
    
        // Redirect the user to the originally requested page
        Response.Redirect(
                  FormsAuthentication.GetRedirectUrl(txtUserName.Text,
                                                     false));
      }
      else
      {
        lblError.Text =
             "Authentication failed, check username and password.";
      }
    }
    catch(Exception ex)
    {
      lblError.Text = "Error authenticating. " + ex.Message;
    }

Step 6. Implement an Authentication Request Handler to Construct a GenericPrincipal Object

This procedure implements the Application_AuthenticateRequest event handler within global.asax and creates a GenericPrincipal object for the currently authenticated user. This will contain the list of groups that the user is a member of, retrieved from the FormsAuthenticationTicket contained in the authentication cookie. Finally, you will associate the GenericPrincipal object with the current HttpContext object that is created for each Web request.

To implement an authentication request handler to construct a GenericPrincipal object

  1. Use Solution Explorer to open global.asax.cs.
  2. Add the following usingstatements to the top of the file.
    using System.Web.Security;
    using System.Security.Principal;
  3. Locate the Application_AuthenticateRequest event handler and add the following code to obtain the cookie that contains the encrypted FormsAuthenticationTicket, from the cookie collection passed with the request.
    // Extract the forms authentication cookie
    string cookieName = FormsAuthentication.FormsCookieName;
    HttpCookie authCookie = Context.Request.Cookies[cookieName];
    
    if(null == authCookie)
    {
      // There is no authentication cookie.
      return;
    }
  4. Add the following code to extract and decrypt the FormsAuthenticationTicketfrom the cookie.
    FormsAuthenticationTicket authTicket = null;
    try
    {
      authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    }
    catch(Exception ex)
    {
      // Log exception details (omitted for simplicity)
      return;
    }
    
    if (null == authTicket)
    {
      // Cookie failed to decrypt.
      return;
    }
  5. Add the following code to parse out the pipe separate list of group names attached to the ticket when the user was originally authenticated.
    // When the ticket was created, the UserData property was assigned a
    // pipe delimited string of group names.
    String[] groups = authTicket.UserData.Split(new char[]{'|'});
  6. Add the following code to create a GenericIdentity object with the user name obtained from the ticket name and a GenericPrincipalobject that contains this identity together with the user’s group list.
    // Create an Identity object
    GenericIdentity id = new GenericIdentity(authTicket.Name,
                                             "LdapAuthentication");
    
    // This principal will flow throughout the request.
    GenericPrincipal principal = new GenericPrincipal(id, groups);
    // Attach the new principal object to the current HttpContext object
    Context.User = principal;

Step 7. Test the Application

This procedure uses the Web application to request the default.aspx page. You will be redirected to the logon page for authentication. Upon successful authentication, your browser will be redirected to the originally requested default.aspx page. This will extract and display the list of groups that the authenticated user belongs to from the GenericPrincipal object that has been associated with the current request by the authentication process.

To test the application

  1. On the Build menu, click Build Solution.
  2. In Solution Explorer, right-click default.aspx, and then click View in Browser.
  3. Enter a valid domain name, user name, and password and then click Log On.
  4. If you are successfully authenticated, you should be redirected back to default.aspx. The code on this page should display the user name of the authenticated user.To see the list of groups the authenticated user is a member of, add the following code at the end of the Application_AuthenticateRequest event handler in the global.aspx.cs file.
    Response.Write("Groups: " + authTicket.UserData + "<br>");

International currency facts of SQL Server (2000, 2005)

SQL Server’s currency data types have some interesting international features. And some of the intricacies of those features have some interesting international implications. I figured as long as we were here I could talk about some of them….

The money and smallmoney topic in MSDN gives the basics of the datatypes:

money

Monetary data values from -2^63 (-922,337,203,685,477.5808) through
2^63 – 1 (+922,337,203,685,477.5807), with accuracy to a ten-thousandth of a monetary unit. Storage size is 8 bytes.

smallmoney

Monetary data values from – 214,748.3648 through +214,748.3647, with accuracy to a ten-thousandth of a monetary unit. Storage size is 4 bytes.

Both numbers are pretty much scaled integer types rather than floating point values (the latter would freak out a lot of people when it comes to money, so it makes sense to build the types this way). Though of course if you need more than four decimal places it is recommended that use the Decimal data type (there are apparently currencies for whom it is recommended to store more than four decimal places to help with complex calculations).

You also cannot include currency grouping separators (commas for en-US) unless you pass the value as a string — in which case you will want to be sure that the currency grouping and decimal separators all match the language of the session you are in. I usually like to put in straight numbers and not worry about dependencies on the language settings, myself.

But the really interesting information is in the topic entitled Using Monetary Data. What this datatype allows is any currency symbol to be put in front of the number, even if it is not in a string (enclosed by single quotes) in a Transact-SQL clause. Basically, all of the following currency signs are supported:

Codepoint

Symbol

Name

U+0024

$

DOLLAR SIGN

U+00a3

£

POUND SIGN

U+00a4

¤

CURRENCY SIGN

U+00a5

¥

YEN SIGN

U+09f2

BENGALI RUPEE MARK

U+09f3

BENGALI RUPEE SIGN

U+0e3f

฿

THAI CURRENCY SYMBOL BAHT

U+20a1

COLON SIGN

U+20a2

CRUZEIRO SIGN

U+20a3

FRENCH FRANC SIGN

U+20a4

LIRA SIGN

U+20a6

NAIRA SIGN

U+20a7

PESETA SIGN

U+20a8

RUPEE SIGN

U+20a9

WON SIGN

U+20aa

NEW SHEQEL SIGN

U+20ab

DONG SIGN

U+20ac

EURO SIGN

As a bit of trivia, if you look at the Using Monetary Data topic, it has the same table as above, sorted in code point order, with one exception: the Euro (U+20ac) is placed just before U+20a1. The reason for this is that once upon a time (in the original Books Online topic that shipped with the initial release of SQL Server 2000), the documentation listed U+20a0 as the euro.

Now the code in SQL Server did not do this (since that was not really the euro), and if you tried to use U+20a0 (, a.k.a. EURO-CURRENCY SIGN) as a currency sign in a money or smallmoney column, it would not work.

When they finally fixed the documentation, it was I suppose easier to update the table by updating the two entries without moving stuff around in the table….

Interestingly. I just looked in SQL Server 2005 Books Online and this table has not been updated there, either to add new entries or to fix that one ordering issue. Oops. But that is kind of minor, no sense worrying about that….

Now for the real problems — you knew there would be real problems, didn’t you? 🙂

There are 22 characters in the Currency Symbols block (only 11 of which SQL Server recognizes in this case). Most importantly, there are 41 characters in the Sc (Symbol, Currency) general category (only 18 of which SQL Server recognizes in this case). For both of these you can look to the links to see the list of currency symbols….

I would be a lot happier if SQL Server were looking for a return of UnicodeCategory.CurrencySymbol from CharUnicodeInfo.GetUnicodeCategory or some other convenient way of getting the currency symbols and treating them that way, of course.

Or alternately, it would be cool if they removed some of the items that no longer really exist since they have converted to the euro now, and maybe added some more in.

However, I will now take a step back and not ask for those features just yet….

Note that you can just insert your currency values with any of these currency symbols in front of them. And the values will be inserted. As Is. Which may not be what you want if you deal with €100 vs. ¥100 vs. ₩100 for example (since €100 is about ¥13,541 or ₩126,178 by today’s fix!).

Now that currency symbol is not stored, either — the currency’s identity is eliminated after the insert. Fill in your own disaster sequence on this one — and make sure to be careful of what you insert in your application….

Now I have worked with the Cloanto Currency Server several times, and would highly recommend them to people who would want to deal with different types of currencies and do conversions. It is pretty cool having the results at your fingertips and available through both automation and .NET, too. 

Anyway, the best practice for SQL Server is just keep the money and smallmoney columns with a single currency, or store the currency type in another field. It will keep you from doing something you did not intend to do with the database. Big mistakes (where big is defined as scope of effect; the actual mistake is usually a small design issue) in these sorts of columns are the surest way to find oneself looking for another job….

Accessing the network printer from ASP.NET VB.NET

I managed to put together several pieces of code from the web to allow network printing. Most of the examples I found were written in C#.

This script can be useful for placing orders with warehouses etc that allow access to their network printers.. etc.

<%@ outputcache location="None" %>
<%@ Page Language="VB" Debug="True"%>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.Collections.Specialized" %>
<%@ Import Namespace="System.ComponentModel" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Printing " %>
<%@ Import Namespace="System.Text" %>
<%@ Import Namespace="System.Web" %>


Sub Submit_Click(ByVal sender As Object, ByVal e As EventArgs)
Try
Dim pd as New PrintDocument()

' Set the printer name.
pd.PrinterSettings.PrinterName = "NS5hpoffice"
AddHandler pd.PrintPage, AddressOf Me.pd_PrintPage
pd.Print()

Catch ex as Exception
Response.Write("Error: " & ex.ToString)

End Try
End Sub

Private Sub pd_PrintPage(ByVal sender as Object, ByVal ev as PrintPageeventArgs)
Dim yPos as Single = 250
Dim leftMargin as Single = ev.MarginBounds.Left
Dim topMargin as Single = ev.MarginBounds.Top
Dim printFont = New Font("Arial", 10)
Dim sb As StringBuilder =  New StringBuilder()

' Page title and date/time.
sb.Append("Warehouse Shipment Request")
sb.Append(Environment.NewLine)
sb.Append("DateTime: " + DateTime.Now.ToString()+ Environment.NewLine)

' Iterate submitted form fields and get field names.
Dim fieldValue As String
Dim fieldName As String

' Exclude viewstate and submit button.
For Each fieldName In HttpContext.Current.Request.Form
If fieldName = "__VIEWSTATE" Or fieldName = "Submit" Then
Else
    ' Get the field values.
    fieldValue = HttpContext.Current.Request.Form(fieldName)
    ' Add the field names and values to the page.
    ' Break the field values into 50 character segments so it will fit on the paper.
    ' Currently, this only accounts for fields of l50 characters or less.
     ' ISSUE: breaks in the middle of words instead of spaces
    If fieldValue.Length > 100 Then
              sb.Append(fieldName + ": " + fieldValue.Substring(0,50) + Environment.NewLine)
              sb.Append("            " + fieldValue.Substring(50,50) + Environment.NewLine)
              sb.Append("            " + fieldValue.Substring(100,fieldValue.Length - 100) + Environment.NewLine)
    Else If fieldValue.Length > 50 Then
              sb.Append(fieldName + ": " + fieldValue.Substring(0,50) + Environment.NewLine)
              sb.Append("            " + fieldValue.Substring(50,fieldValue.Length - 50) + Environment.NewLine)
    Else
              sb.Append(fieldName + ": " + fieldValue + Environment.NewLine)
    End If
 End If
Next
ev.Graphics.DrawString(sb.ToString(), printFont, Brushes.Black, leftMargin, yPos, New StringFormat())
End Sub



<html>

<head>
    <title>Network Printing</title>
</head>
<body>
    <form runat="server">
        <table width="456" border="1" align="center" cellpadding="3" cellspacing="0">
            <tbody>
                <tr>
                    <td colspan="2">
                        <p>Network Printing</p>
                  </td>
                </tr>
                <tr>
                    <td width="148">
                        Quantity&nbsp;:</td>
                    <td width="290">
                        <asp:TextBox id="Quantity" width="25" text="0" runat="server" />

                    </td>
                </tr>
                <tr>
                    <td>
                        Inventory&nbsp;Number&nbsp;:</td>
                    <td>
                        <asp:TextBox id="InvNumber" text="12345" runat="server" />
                    </td>
                </tr>
				<tr>
                    <td>
                       Price&nbsp;:</td>
                    <td>
                        <asp:TextBox id="Price" text="12.50" runat="server" />
                    </td>
                </tr>
			<tr>
                    <td>
                        Description&nbsp;:</td>
                    <td>
                        <asp:TextBox id="Description" Text="Were having fun now!" runat="server" />
                    </td>
                </tr>
				   <tr>
                    <td colspan="2">
                        <asp:Button id="Submit" onclick="Submit_Click" runat="server" Text="Submit" />
                   </td
               </tr>
           </tbody>
      </table>
</form>
</body>
</html>

Seems to work great on Intranet. Haven’t had the opportunity to try it external. Let me know how it does if you do.

Hover and Active in IE CSS Specification

Pseudo-Classes Fix for Internet Explorer

Despite the fact that it has been almost six years since CSS 2 specification became a W3C recommendation, Internet Explorer, the dominating browser that is being forced
onto unsuspecting public my Microsoft Corporation, still fails to implement pseudo-classes, such as :hover and :active, for all but anchor elements.

Majority of web developers redeem this problem by polluting thier HTML with endless onmouseover and onmouseout handlers. Very few realize that the time Microsoft could not find to create a compliant browser was wasted on development of proprietory features, which often are the way to work around those limitations. In this particular case, while :hover and :active pseudo-classes do not behave as we expect them to on all but
one element, there are Internet Explorer Behaviors to add the desired functionality with little extra work. The scripting of the behavior goes into a separate .htc file which compliant
browsers would not ever see:

IEFixes.htc
<PUBLIC:ATTACH EVENT="onmouseover" ONEVENT="DoHover()" />
<PUBLIC:ATTACH EVENT="onmouseout"  ONEVENT="RestoreHover()" />
<PUBLIC:ATTACH EVENT="onmousedown" ONEVENT="DoActive()" />
<PUBLIC:ATTACH EVENT="onmouseup"   ONEVENT="RestoreActive()" />

function DoHover()
  { element.className += ' hover';
}

function DoActive()
{ element.className += ‘ active’;
}

function RestoreHover()
{ element.className = element.className.replace(/bhoverb/,”);
}

function RestoreActive()
{ element.className = element.className.replace(/bactiveb/,”);
}

The behavior is attached to the desired elements using CSS declaration:

button, tr, td
  { behavior: url('IEFixes.htc');
  }

The .hover and .active classes to be used by IE are
declared along with the :hover and :active pseudo-classes:

button:active, button.active
  { /*Active styles here */}
button:hover, button.hover
  { /*Hover styles here */}

Examples that use hover and active styling

Button

Table
Column 1 Column 2 Column 3
Cell 1:1 Cell 1:2 Cell 1:3
Cell 2:1 Cell 2:2 Cell 2:3
Cell 3:1 Cell 3:2 Cell 3:3

How to get the Client's Default printer name

Code snippet to obtain the default printer’s name using .NET

// You are explicitly linking to GetDefaultPrinter because linking
// implicitly on Windows 95/98 or NT4 results in a runtime error.
// This block specifies which text version you explicitly link to.
#ifdef UNICODE
  #define GETDEFAULTPRINTER "GetDefaultPrinterW"
#else
  #define GETDEFAULTPRINTER "GetDefaultPrinterA"
#endif

// Size of internal buffer used to hold "printername,drivername,portname"
// string. You may have to increase this for huge strings.
#define MAXBUFFERSIZE 250

/*----------------------------------------------------------------*/
/* DPGetDefaultPrinter                                            */
/*                                                                */
/* Parameters:                                                    */
/*   pPrinterName: Buffer alloc'd by caller to hold printer name. */
/*   pdwBufferSize: On input, ptr to size of pPrinterName.        */
/*          On output, min required size of pPrinterName.         */
/*                                                                */
/* NOTE: You must include enough space for the NULL terminator!   */
/*                                                                */
/* Returns: TRUE for success, FALSE for failure.                  */
/*----------------------------------------------------------------*/
BOOL DPGetDefaultPrinter(LPTSTR pPrinterName, LPDWORD pdwBufferSize)
{
  BOOL bFlag;
  OSVERSIONINFO osv;
  TCHAR cBuffer[MAXBUFFERSIZE];
  PRINTER_INFO_2 *ppi2 = NULL;
  DWORD dwNeeded = 0;
  DWORD dwReturned = 0;
  HMODULE hWinSpool = NULL;
  PROC fnGetDefaultPrinter = NULL;
  
  // What version of Windows are you running?
  osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osv);
  
  // If Windows 95 or 98, use EnumPrinters.
  if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  {
    // The first EnumPrinters() tells you how big our buffer must
    // be to hold ALL of PRINTER_INFO_2. Note that this will
    // typically return FALSE. This only means that the buffer (the 4th
    // parameter) was not filled in. You do not want it filled in here.
    SetLastError(0);
    bFlag = EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 2, NULL, 0, &dwNeeded, &dwReturned);
    {
      if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (dwNeeded == 0))
        return FALSE;
    }
    
    // Allocate enough space for PRINTER_INFO_2.
    ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
    if (!ppi2)
      return FALSE;
    
    // The second EnumPrinters() will fill in all the current information.
    bFlag = EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded, &dwReturned);
    if (!bFlag)
    {
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // If specified buffer is too small, set required size and fail.
    if ((DWORD)lstrlen(ppi2->pPrinterName) >= *pdwBufferSize)
    {
      *pdwBufferSize = (DWORD)lstrlen(ppi2->pPrinterName) + 1;
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // Copy printer name into passed-in buffer.
    lstrcpy(pPrinterName, ppi2->pPrinterName);
    
    // Set buffer size parameter to minimum required buffer size.
    *pdwBufferSize = (DWORD)lstrlen(ppi2->pPrinterName) + 1;
  }
  
  // If Windows NT, use the GetDefaultPrinter API for Windows 2000,
  // or GetProfileString for version 4.0 and earlier.
  else if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT)
  {
    if (osv.dwMajorVersion >= 5) // Windows 2000 or later (use explicit call)
    {
      hWinSpool = LoadLibrary("winspool.drv");
      if (!hWinSpool)
        return FALSE;
      fnGetDefaultPrinter = GetProcAddress(hWinSpool, GETDEFAULTPRINTER);
      if (!fnGetDefaultPrinter)
      {
        FreeLibrary(hWinSpool);
        return FALSE;
      }

      bFlag = fnGetDefaultPrinter(pPrinterName, pdwBufferSize);
        FreeLibrary(hWinSpool);
      if (!bFlag)
        return FALSE;
    }
    
    else // NT4.0 or earlier
    {
      // Retrieve the default string from Win.ini (the registry).
      // String will be in form "printername,drivername,portname".
      if (GetProfileString("windows", "device", ",,,", cBuffer, MAXBUFFERSIZE) <= 0)
        return FALSE;
      
      // Printer name precedes first "," character.
      strtok(cBuffer, ",");
      
      // If specified buffer is too small, set required size and fail.
      if ((DWORD)lstrlen(cBuffer) >= *pdwBufferSize)
      {
        *pdwBufferSize = (DWORD)lstrlen(cBuffer) + 1;
        return FALSE;
      }
      
      // Copy printer name into passed-in buffer.
      lstrcpy(pPrinterName, cBuffer);
      
      // Set buffer size parameter to minimum required buffer size.
      *pdwBufferSize = (DWORD)lstrlen(cBuffer) + 1;
    }
  }
  
  // Clean up.
  if (ppi2)
    GlobalFree(ppi2);
  
  return TRUE;
}
#undef MAXBUFFERSIZE
#undef GETDEFAULTPRINTER


// You are explicitly linking to SetDefaultPrinter because implicitly
// linking on Windows 95/98 or NT4 results in a runtime error.
// This block specifies which text version you explicitly link to.
#ifdef UNICODE
  #define SETDEFAULTPRINTER "SetDefaultPrinterW"
#else
  #define SETDEFAULTPRINTER "SetDefaultPrinterA"
#endif

/*-----------------------------------------------------------------*/
/* DPSetDefaultPrinter                                             */
/*                                                                 */
/* Parameters:                                                     */
/*   pPrinterName: Valid name of existing printer to make default. */
/*                                                                 */
/* Returns: TRUE for success, FALSE for failure.                   */
/*-----------------------------------------------------------------*/
BOOL DPSetDefaultPrinter(LPTSTR pPrinterName)

{
  BOOL bFlag;
  OSVERSIONINFO osv;
  DWORD dwNeeded = 0;
  HANDLE hPrinter = NULL;
  PRINTER_INFO_2 *ppi2 = NULL;
  LPTSTR pBuffer = NULL;
  LONG lResult;
  HMODULE hWinSpool = NULL;
  PROC fnSetDefaultPrinter = NULL;
  
  // What version of Windows are you running?
  osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osv);
  
  if (!pPrinterName)
    return FALSE;
  
  // If Windows 95 or 98, use SetPrinter.
  if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  {
    // Open this printer so you can get information about it.
    bFlag = OpenPrinter(pPrinterName, &hPrinter, NULL);
    if (!bFlag || !hPrinter)
      return FALSE;
    
    // The first GetPrinter() tells you how big our buffer must
    // be to hold ALL of PRINTER_INFO_2. Note that this will
    // typically return FALSE. This only means that the buffer (the 3rd
    // parameter) was not filled in. You do not want it filled in here.
    SetLastError(0);
    bFlag = GetPrinter(hPrinter, 2, 0, 0, &dwNeeded);
    if (!bFlag)
    {
      if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (dwNeeded == 0))
      {
        ClosePrinter(hPrinter);
        return FALSE;
      }
    }
    
    // Allocate enough space for PRINTER_INFO_2.
    ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
    if (!ppi2)
    {
      ClosePrinter(hPrinter);
      return FALSE;
    }
    
    // The second GetPrinter() will fill in all the current information
    // so that all you have to do is modify what you are interested in.
    bFlag = GetPrinter(hPrinter, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded);
    if (!bFlag)
    {
      ClosePrinter(hPrinter);
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // Set default printer attribute for this printer.
    ppi2->Attributes |= PRINTER_ATTRIBUTE_DEFAULT;
    bFlag = SetPrinter(hPrinter, 2, (LPBYTE)ppi2, 0);
    if (!bFlag)
    {
      ClosePrinter(hPrinter);
      GlobalFree(ppi2);
      return FALSE;
    }
    
    // Tell all open programs that this change occurred.
    // Allow each program 1 second to handle this message.
    lResult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0L,
      (LPARAM)(LPCTSTR)"windows", SMTO_NORMAL, 1000, NULL);
  }
  
  // If Windows NT, use the SetDefaultPrinter API for Windows 2000,
  // or WriteProfileString for version 4.0 and earlier.
  else if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT)
  {
    if (osv.dwMajorVersion >= 5) // Windows 2000 or later (use explicit call)
    {
      hWinSpool = LoadLibrary("winspool.drv");
      if (!hWinSpool)
        return FALSE;
      fnSetDefaultPrinter = GetProcAddress(hWinSpool, SETDEFAULTPRINTER);
      if (!fnSetDefaultPrinter)
      {
        FreeLibrary(hWinSpool);
        return FALSE;
      }

      bFlag = fnSetDefaultPrinter(pPrinterName);
      FreeLibrary(hWinSpool);
      if (!bFlag)
        return FALSE;
    }

    else // NT4.0 or earlier
    {
      // Open this printer so you can get information about it.
      bFlag = OpenPrinter(pPrinterName, &hPrinter, NULL);
      if (!bFlag || !hPrinter)
        return FALSE;
      
      // The first GetPrinter() tells you how big our buffer must
      // be to hold ALL of PRINTER_INFO_2. Note that this will
      // typically return FALSE. This only means that the buffer (the 3rd
      // parameter) was not filled in. You do not want it filled in here.
      SetLastError(0);
      bFlag = GetPrinter(hPrinter, 2, 0, 0, &dwNeeded);
      if (!bFlag)
      {
        if ((GetLastError() != ERROR_INSUFFICIENT_BUFFER) || (dwNeeded == 0))
        {
          ClosePrinter(hPrinter);
          return FALSE;
        }
      }
      
      // Allocate enough space for PRINTER_INFO_2.
      ppi2 = (PRINTER_INFO_2 *)GlobalAlloc(GPTR, dwNeeded);
      if (!ppi2)
      {
        ClosePrinter(hPrinter);
        return FALSE;
      }
      
      // The second GetPrinter() fills in all the current<BR/>
      // information.
      bFlag = GetPrinter(hPrinter, 2, (LPBYTE)ppi2, dwNeeded, &dwNeeded);
      if ((!bFlag) || (!ppi2->pDriverName) || (!ppi2->pPortName))
      {
        ClosePrinter(hPrinter);
        GlobalFree(ppi2);
        return FALSE;
      }
      
      // Allocate buffer big enough for concatenated string.
      // String will be in form "printername,drivername,portname".
      pBuffer = (LPTSTR)GlobalAlloc(GPTR,
        lstrlen(pPrinterName) +
        lstrlen(ppi2->pDriverName) +
        lstrlen(ppi2->pPortName) + 3);
      if (!pBuffer)
      {
        ClosePrinter(hPrinter);
        GlobalFree(ppi2);
        return FALSE;
      }
      
      // Build string in form "printername,drivername,portname".
      lstrcpy(pBuffer, pPrinterName);  lstrcat(pBuffer, ",");
      lstrcat(pBuffer, ppi2->pDriverName);  lstrcat(pBuffer, ",");
      lstrcat(pBuffer, ppi2->pPortName);
      
      // Set the default printer in Win.ini and registry.
      bFlag = WriteProfileString("windows", "device", pBuffer);
      if (!bFlag)
      {
        ClosePrinter(hPrinter);
        GlobalFree(ppi2);
        GlobalFree(pBuffer);
        return FALSE;
      }
    }
    
    // Tell all open programs that this change occurred.
    // Allow each app 1 second to handle this message.
    lResult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0L, 0L,
      SMTO_NORMAL, 1000, NULL);
  }
  
  // Clean up.
  if (hPrinter)
    ClosePrinter(hPrinter);
  if (ppi2)
    GlobalFree(ppi2);
  if (pBuffer)
    GlobalFree(pBuffer);
  
  return TRUE;
}
#undef SETDEFAULTPRINTER

Scroll to bottom of a div

Allow user to scroll and maintain position with “Scroll To Bottom of the  Div” example

Well I am getting tired of being emailed the same question about my entry Scroll To Bottom of a Div. So I sat down in a few minutes I  came up with this. My first attempt used onscroll, but it Opera appears to not  supporet onscroll on a div. So I had to twidle my thumbs and realized I just had  to use the last know position as a reference. Duh…

So how do I keep the scroll position of a div if the user scrolls it and also  allow for it to stick to the bottom?

Put this code in your head:

  var chatscroll = new Object();

  chatscroll.Pane = function(scrollContainerId){
    this.bottomThreshold = 20;
    this.scrollContainerId = scrollContainerId;
    this._lastScrollPosition = 100000000;
  }

  chatscroll.Pane.prototype.activeScroll = function(){

    var _ref = this;
    var scrollDiv = document.getElementById(this.scrollContainerId);
    var currentHeight = 0;

    var _getElementHeight = function(){
      var intHt = 0;
      if(scrollDiv.style.pixelHeight)intHt = scrollDiv.style.pixelHeight;
      else intHt = scrollDiv.offsetHeight;
      return parseInt(intHt);
    }

    var _hasUserScrolled = function(){
      if(_ref._lastScrollPosition == scrollDiv.scrollTop || _ref._lastScrollPosition == null){
        return false;
      }
      return true;
    }

    var _scrollIfInZone = function(){
      if( !_hasUserScrolled ||
          (currentHeight - scrollDiv.scrollTop - _getElementHeight() <= _ref.bottomThreshold)){
          scrollDiv.scrollTop = currentHeight;
          _ref._isUserActive = false;
      }
    }

    if (scrollDiv.scrollHeight > 0)currentHeight = scrollDiv.scrollHeight;
    else if(scrollDiv.offsetHeight > 0)currentHeight = scrollDiv.offsetHeight;

    _scrollIfInZone();

    _ref = null;
    scrollDiv = null;

  }

Create a new instance with the name of the div;

var divScroll = new chatscroll.Pane('divExample');

When ever you add something to the div call the method activeScroll

divScroll.activeScroll();

And the magic will occur.

I tested this on Win XP with IE6, Firefox 1.5, Netscape 8.04, Mozilla 1.7.12,  and Opera 8.5.1 with no issues. My MAC testers came through: Safari 2.0.4,  Camino 1.0.2int, Firefox 1.5.0.5, and Opera 9.0.1 are good. Minor issue with  Opera 8.52 and touchpad. I don’t think that is a show stopper.

Eric Pascarello Coauthor of Ajax In Action Moderator of HTML/JavaScript at www.JavaRanch.com Author of: JavaScript: Your Visual Blueprint for building Dynamic  Web Pages