home · blog · groups · about us · contact us
DevelopmentNow Blog
 Friday, September 01, 2006
 
 

It's been a while since I posted anything in the management category, so it's time to get caught up. In a past life I was a Software Development Director, so I wanted to write about some things I figured out so that other programmers-turned-sudden-managers have some tips. The below is about goals, and is written for the employee (i.e. the person writing and achieving the goals), but it's equally useful for managers -- just adapt the perspective so that everytime you see the word "You" mentally replace it with "My Employee".

Goals as Performance Review Items

I was thinking the other day about employee goals. Some companies have managers sit down with their employees each year and come up with goals to achieve for the upcoming year. An employee's performance review is then often tied to whether they complete their goals or not. So there's an incentive to be shouting "Gooooaaaaaallllll" at the end of the year.

However, it's hard for many people to come up with their own goals in the workplace. We all know it has to be work-related, and in some professions it's easier than others (e.g. sell 10% more widgets than last year). In fact when I researched this in the past, a lot of the example "good goals" were in terms of manufacturing or sales.

But as a programmer (or other IT worker), in a workplace where priorities can change and projects come and go and get completed in months or weeks, what do you do? What are some goals you can choose and have a chance at pulling them off?

Be Smart About Your Goals

First of all, let's review some basics on goal picking. There's an acronym for good goals: S.M.A.R.T. (or as a friend liked to say, "TARMS"). I've seen many versions of what the different letters mean, but in genenral SMART goals are

  • Specific
    • What specifically will you do? Answer the Who, What, Where, When, Why behind it.
    • Bad: "Do a better job of estimating effort" 
    • Good: "Have my actual effort come within 30% of my effort estimates, on average, for my web development projects in the next six months."
    • Other meanings for "S":
      • Stretching: it should be a challenge for you
      • Significant: it should be important
      • Systematic: it should be something you can work on or chip away at
      • Synergistic: working towards the goal should not be at cross-purposes to getting your job done -- you don't want feel like you're stealing time away from your "real" projects to work on your goals. It would be ideal if working towards your goal is incorporated into or part of doing your job. This is particularly important for employees who are already overloaded at work and don't have a lot of free time outside of work.
  • Measurable
    • How will you and your manager know if the goal has been achieved? There should be some criteria for knowing that it's been completed. Anyone who deals with project management should be familiar with this theme.
    • Bad: "Learn C#"
    • Good: "Complete a C# class this fall and get a Brainbench C# certification by the end of the year."
    • Other meanings for "M":
      • Meaningful
      • Motivating
      • Memorable
  • Acceptable
    • The goal should ideally be set by you, or at least acceptable to you. You should have a desire and willingness to complete it. It should be something you're interested in and that will have real value for you when it's completed (other than just a performance bonus based on goal achievement). For example, will you learn a new skill or technology? Will you feel a sense of pride and accomplishment?
    • Bad: "Devote an extra day a week to code maintenance on our legacy systems" (unless you like legacy maintenance, in which case I have some jobs for you....)
    • Good: "Research new technology and development techniques and deliver a proposal on upgrading some of our legacy systems."
    • Other meanings for "A":
      • Attainable: this is a common meaning for "A". It closely overlaps with Realistic, below. 
      • Action plan: you should put together a plan on how you will accomplish your goal
      • Agreed-upon: similar to Acceptable, it should be something that the manager and employee both agree is a good goal.
      • Accountability: you should be accountable to completing your goal, and failing at it should require an explanation from you as to why. Not achieving your goal should be seen in a similar light as not completing any other project.
  • Realistic
    • You should be able to actually attain this goal. It should be a bit of a challenge to accomplish, but not so much that it's a hardship. It should require a level of commitment from you.
    • Bad: "Run 1 mile a month" (too easy) or "Work out twice a day" (too much)
    • Good: "Register, train for, and participate in the marathon this year, including finding a training regimen and/or running club appropriate for beginners"
    • Other meanings for "R":
      • Relevant: it should be relevant to work or your career. It probably shouldn't be "Take a wine-tasting class this fall." FYI, I feel a fitness- or health-oriented goal is OK as long as you have other goals that are more IT-focused. IMO a healthy, fit employee is beneficial to the company because they're less sick, have more energy and less stress, can save the company money on health insurance and/or workers compensation, and can inspire fitness in others. Plus they're better able to help lug those 21" CRTs to the new office. ;)
  • Time frame
    • There should be a timeframe or deadline for completion of the goal. This is part of being Specific, but having an end point helps encourage employees to get it done.
    • Time frame also means you should know when you're going to be able to work on your goal. Do you have available time to complete it? When will you work on it? How much time will it require to complete? There are a lot of people who are overloaded at work, working 50+ hours a week, and have full and equally-demanding family lives outside of work. Those people will need to sacrifice something to gain the time they need, or choose goals that don't require as much time, or get their boss to free up their work schedule a bit so they can work on their goals.
    • Bad: "Take a C# class sometime" or "Write a 1000-page book on project management next month"
    • Good: "Take a C# class in the evening next quarter" or "Write an article on project management next month and submit it to at least 3 web sites and magazines."
    • Other meanings for "T":
      • Tangible: a goal that can be measured on paper or in an otherwise physical manner is tangible. But IMO that's too closely tied to Measurable

That's all for today, but next time I'll follow up with more suggestions, guidelines, and ideas for goal-setting in an IT world. I'll write more for the manager's perspective.

 

 

September 1, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 
 

I saw an article today by Stephen Walther about custom validators with ASP.NET. Or I guess it's not so much an article as an excerpt from a book. Anyhow, the article discusses making your own validator controls, and he shows some code (in VB, though, not C#) about how to make a reusable AJAX validator control. It's similar to the CustomValidator, except that it does its validate via an AJAX call. Thay way, if you need to do some expense or fancy server-side validation (e.g. do a database lookup), your user doesn't have to deal with a postback.

I won't make use of it today, but possibly in the future.

September 1, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Wednesday, August 30, 2006
 
 

I recently needed to strip out non-alphanumeric characters in SQL Server. I initially thought I might be able to use a managed stored procedure and C# regular expressions to do so, but I thought the performance would be bad (e.g. you'd have to cursor through a table, extract a field value, use RegEx on it, go to the next row, etc.). So I came up with the below function using T-SQL's quasi-regular expressions in PATINDEX:

/*******************************************************************
dbo.fnStripNonAlphaNumeric

Removes all non-alphanumeric characters (including spaces) from
@input, e.g.

select dbo.fnStripNonAlphaNumeric('Help, I "think" I''m falling!')

returns

HelpIthinkImfalling

*******************************************************************/

CREATE FUNCTION dbo.fnStripNonAlphaNumeric
(
    @input varchar(500)
)
RETURNS varchar(500)
AS
BEGIN
    
    DECLARE @i int
    DECLARE @result varchar(500)
    SET @result = @input
    SET @i = patindex('%[^a-zA-Z0-9]%', @result)
    WHILE @i > 0
    BEGIN
        SET @result = STUFF(@result, @i, 1, '')
        SET @i = patindex('%[^a-zA-Z0-9]%', @result)
    END

    RETURN @result

END

Then in use it's something like

SELECT dbo.fnStripNonAlphaNumeric(FieldWithAlphaNumerics) as AlphaCleanValue
FROM MyTable

FWIW, to strip non-alphanumeric in C# you can use the one-liner (assuming you have a initial string called "input")

System.Text.RegularExpressions.Regex.Replace(input, @"[\s\W]*", "")

:)

 

 

 

August 30, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Monday, August 21, 2006
 
 

I went into this in my post about SQL Server remote connections, but basically, for each SQL Server instance running on your database server, you can enable remote access, control which IPs and ports SQL Server listens on, and which IPs are allows to access which port.

Use Windows Security when possible

Connecting via a Windows account is more secure than connecting with a SQL Server login and password. In the past those values were sent in plaintext (eek), although now SQL Server 2005 offers lockouts, password complexity enforcements, and password expiration for SQL Server logins, so they're not as unsecure as they used to be. Also if you're using the SQL Native Client to connect, your login packet is encrypted, so the password isn't in plaintext anymore.

If you need to use SQL Server logins, don't use the sa account -- log in with a different account with limited permissions. And use strong passwords. If it's available over the internet, you should use an SSL certificate to encrypt connections, too (see Encrypting Connections to SQL Server in Books Online).

Allowing Remote Access

SQL Server doesn't allow remote access by default. So if you want other machines to access SQL Server, open up SQL Server Surface Area Configuration (in Programs->Microsoft SQL Server 2005->Configuration Tools) and click Surface Area Configuration for Services and Connections. On the left side, navigate to the Remote Connections node under your SQL Server instance's Database Engine node, and ensure Local and Remote connections is selected with Using TCP/IP only. Then click OK.

CropperCapture[23].gif

 

Configuring IPs

You do so via opening up SQL Server Configuration Manager, opening the Network Configuration node and clicking the Protocols section. You'll see TCP/IP on the right.

CropperCapture[14].gif

 

Double click TCP/IP. You'll see a dialog like below with some settings on the Protocol tab:

CropperCapture[15].gif

By default, Listen All is set to Yes, which means SQL Server is listening on every IP the server has. If the server has a public IP, or if you don't want it listening on certain IPs, set Listen All to No. Now switch to the IP Addresses tab.

CropperCapture[17].gif

You'll see entries for all the machine's bound IPs -- in the above example 192.168.0.10 is the internal IP, 77.89.121.42 is a public IP, and 127.0.0.1 is the local loopback IP (note those aren't my real IPs, they're just for show).  Active means the IP address is a working IP network-wise -- changing the value via the dropdown doesn't do anything. If Listen All is set to No, you can set specific ports for various IPs, or tell SQL Server to not listen on them by setting Enabled to No. Notice how I've used the port 2000 and disabled access on the public IP.

FYI, Dynamic Ports is a setting where SQL Server finds an available port at runtime. I don't recommend Dynamic Ports because a) you have to run the SQL Server Browser service (a security risk) in order to inform clients which port is being used, and b) it's hard to protect SQL Server with a firewall because you never know which port is going to be used. So you're stuck opening up a bunch of ports, which is bad.

Anyhow, if Listen All was set to Yes on the Protocol tab, then you can't apply settings for individual IPs -- you instead apply settings for all IPs in the IPAll section at the bottom.

CropperCapture[18].gif

 

Configuring the Firewall

Now you can create a firewall rule allowing certain IPs access to your machine over certain ports. If you're using Windows Firewall, you can do so by first opening Windows Firewall from the Control Panel and clicking the Exceptions tab. You'll see a list of current exceptions.

CropperCapture[19].gif

Click Add Port. Give your rule a name (e.g. "SQL Server Rule") and specify the port you set in SQL Configuration Manager.

CropperCapture[20].gif

Now we want to specify who can access our server over this port. By default, everyone can (even folks over the internet), which IMO isn't super secure. So click Change Scope. You can choose My Network to allow all computers on your network to access your server, but if you want a more narrow range (good if you have a large network but only a few machine should be able to access your machine), you can enter the specific IPs and/or masks in the Custom List section.

CropperCapture[22].gif

Now click OK, and click OK again, and your new firewall rule is applied.

Finishing Up

Now restart your SQL Server service, and you're ready to connect. You can also feel better that you've protected your SQL Server instance.

 

 

August 21, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 
 

By default, remote connections are disabled for all versions of SQL Server 2005, including SQL Server 2005 Express. If you try to connect from a different machine, you'll get an error message like this:

An error has occurred while establishing a connection to the server when connecting to SQL server 2005, this failure may be caused by the fact that under default settings SQL server does not allow remote connection.

To resolve it, you need to enable remote connections in the SQL Server Surface Area Configuration Tool (under Start->Programs->Microsoft SQL Server 2005->Configuration Tools) and then restart the SQL Server Express service. See this full walkthrough is on MSDN on how to do so.

If you then get errors like "Error Locating Service/Instance Specified" you have two options: using the SQL Server Browser, or using TCP/IP & specifying the port when you connect.

SQL Server Browser

The SQL Server Browser service tells interested parties the SQL Server instances available on the machine. It's a mild security risk (especially if you expose it over the internet), but it lets you easily connect to SQL Server instances, especially if your database server has multiple instances running. You'll need to ensure that the SQL Server Browser Service is running on the database server, and that the database server's firewall isn't blocking the SQL Server Express service or the SQL Server Browser service. See this MSDN article on how to do so.

TCP/IP Ports

This method is more secure but a little more work. You don't have to run the SQL Server Browser service, and you get to pick (and manage traffic on) the IP and port that each SQL Server instance listens on. Worth it for a public server, IMO. If you're using TCP/IP and don't want to run SQL Server Browser for security reasons, or if you have multiple SQL Server instances on your server, you'll need to check the TCP/IP ports that your SQL Server Database Engines are listening on, and ensure each running instance has its own port.

Open up SQL Server Configuration Manager (under Start->Programs->Microsoft SQL Server 2005->Configuration Tools), expand SQL Server Network Configuration, click Protocols for SQLEXPRESS, double-click TCP/IP. Note whether "Listen All" is Yes or No. Click the IP Addresses tab. If "Listen All" was Yes, then you can set the ports in the "IPAll" section below. If "Listen All" was set to No, then ensure that there's a port specified for the listed IP addresses, or disable (set Enabled to "No") any public IPs you don't want to expose SQL Server on.

Then open up Windows Firewall on the database server (or whatever your firewall program is) and allow TCP traffic in for the port you specified. Be sure to indicate who you want to access this port, too (e.g. the internet, your local network, a specific subnet, or just the local machine).

Then you should be able to connect to the server using "<ip address>,<port>" (e.g. "192.168.0.10,8000") from SQL Server Management Studio.

 

August 21, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Thursday, August 17, 2006
 
 

I wanted to share a quick & easy way to validate uploaded images in ASP.NET. One of my projects has a feature allowing users to upload a logo. But I wanted to restrict them to JPG & GIF images, and ensure that the image was within a certain height. So....asp:CustomValidator to the rescue!

Here's the code for the web page with the INPUT file tag and the CustomValidator:


<INPUT type="file" id="txtCorporateLogo" name="txtCorporateLogo" runat="server">

<asp:CustomValidator ID="valLogo" Runat=server CssClass="validator"
ErrorMessage="Logos can only be GIF or JPG images under 100 pixels high"
OnServerValidate="ValidateLogo" ClientValidationFunction="checkLogo" ControlToValidate="txtCorporateLogo"
><br>Logos can only be GIF or JPG images under 100 pixels high</asp:CustomValidator>


Here's the client-side javascript function that the CustomValidator calls to ensure that we don't post to the server if the image isn't a JPG or GIF. The function sets IsValid to true if there's no file specified, or if it ends with jpg, jpeg, or gif:


<script language="javascript">

		function checkLogo(sender, args)
		{
			var filename = document.getElementById('txtCorporateLogo').value.toLowerCase();
if (filename.length < 1) { args.IsValid = true; } else if (filename.indexOf('.jpg') == -1 && filename.indexOf('.jpeg') == -1 && filename.indexOf('.gif') == -1) { args.IsValid = false; } else { args.IsValid = true; } } </script>

And here's the server-side method that confirms the image is 100 pixels high or shorter:


        protected void ValidateLogo(object sender, ServerValidateEventArgs args)
        {
            HttpPostedFile imageFile = this.txtCorporateLogo.PostedFile;

            if (imageFile.FileName == String.Empty)
            {
                args.IsValid = true;
            }
            else if (!imageFile.FileName.ToLower().EndsWith("jpg") && !imageFile.FileName.ToLower().EndsWith("gif"))
            {
                args.IsValid = false;
            }
            else
            {
                System.Drawing.Bitmap bitmap = new Bitmap(imageFile.InputStream);

                if (bitmap.Height > 100)
                    args.IsValid = false;
                else
                    args.IsValid = true;

                bitmap.Dispose();

                imageFile.InputStream.Position = 0;    // reset the position in the stream
            }
        }



That's it. Just remember to check Page.IsValid in your submit method before doing anything with the image.

And I'm starting to get annoyed with FTB's code import. Look at that mess above! :)

August 17, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Saturday, August 12, 2006
 
 

SQL Server has a decent full text search engine (IMO), but if you have HTML data in your database, it can be tricky searching on it. For example, if users search on the word "strong" you don't want to bring back data like "<strong>this text is emphasized</strong>". Also, there were early problems with SQL 2000's word breaker, in that it didn't treat > or < as a word delimiter (this problem has since been resolved).

Since SQL 2005 is around I thought I'd throw out a few ways I've noticed to store & search on HTML data.

Just Store HTML data in a varchar or text column

First of all, you can go the simple route & store it in a varchar(max) or text column, and create a full text index on that column. 

The upsides

  • It's a simple approach
  • FTS Change Tracking will track the values for varchar columns. If you're using a text column, changes are tracked unless made via WRITETEXT and UPDATETEXT. That's not much of an issue with SQL 2005, though, since WRITETEXT and UPDATETEXT are now deprecated.
  • It's easy to update values in varchar or text columns
  • You'll get matches on all the words

The downside

  • You'll also get matches on words inside comments and HTML tags (e.g. "font", "arial", "body")

So this might be a place to start for an 80/20 HTML search engine approach, and you could maybe treat words like "font" "td" etc as noise words so they're ignored in searches. Not a perfect solution, though, especially if your users like to search on the word "title."

Store HTML data in an XML Column

Now that SQL 2005 has an "XML" data type column, you can store your HTML data in that instead and search on it.

Upsides:

  • Search results won't include tagnames, attribute names, or words within comments
  • Change tracking will track XML column changes

Downsides:

  • Could be hard to update values in the column, I don't know how easy it is to programmatically interact with XML column data types
  • Your HTML needs to be well-formed. "< font > hey there </ font>" will return an error. "<font> hey there </font>" won't.
  • Full Text Search won't match on tag and attribute names (good), but will match on attribute values (bad). For example, if your data is "<font face="Arial">hi there</font>", searching on "font" won't return a match, but searching on "Arial" will.

Here's a complete script to try out in your own database (SQL Server 2005 only). It creates a new database called "ftstest" and a table called "Test".

create database ftstest
go
use ftstest
go
sp_fulltext_database 'enable'
go
Create fulltext catalog FTSCatalog as default
go
CREATE TABLE Test (
ID int not null identity constraint PK_Test primary key,
Title varchar(1000),
Description XML)
go

insert into Test (Title, Description) values
('some stuff goes here', '<font face="Arial">test1</font>')
go
insert into Test (Title, Description) values
('some second row', '<font> test2 </font>')
go
insert into Test (Title, Description) values
('some stuff here', '<font> test3 foobar </font>')
go
insert into Test (Title, Description) values
('some stuff here', '<font>boogie</font>')
go

CREATE FULLTEXT INDEX ON Test(Title, Description) KEY INDEX PK_Test
GO

-- these queries return data
select * from Test where FREETEXT(*,'stuff')
select * from Test where FREETEXT(*,'test1')
select * from Test where FREETEXT(*,'test2')
select * from Test where FREETEXT(*,'boogie')
select * from Test where FREETEXT(*,'Arial')

-- these don't
select * from Test where FREETEXT(*,'face')
select * from Test where FREETEXT(*,'font')

Store the HTML in an Image Column

This is the old standby. SQL Server can automatically ignore HTML markup in search results if you store your HTML data in a column of the image data type. You also need a second column whose value (e.g. 'htm') indicates the type of data.

Upsides:

  • All HTML markup is ignored for searches (except for a questionable feature where if you have spaces around your tags like this "< strong >" the tagname will be included in the search results).
  • You can actually use this feature to store & perform FTS searches on other types of documents, like PPT, PDF, DOC, etc. So it's good if you're doing a document management system & need to search on not only HTML documents but other kinds, too.

Downsides:

  • You have to deal with updating Image data types, which can be a huge PITA. I really wish SQL supported this for varchar or text columns.

Here's a sample script, in this case the DescriptionContentType column contains the value 'htm', telling SQL Server FTS that the Description column contains HTML data & that the indexer should use the HTML iFilter:

CREATE TABLE Test (
ID int not null identity constraint PK_Test primary key,
Title varchar(1000),
Description image,
DescriptionContentType char(3) default 'htm'
)
go

CREATE FULLTEXT INDEX ON Test(Title, Description TYPE COLUMN DescriptionContenttype)
KEY INDEX PK_Test
go

INSERT INTO Test (Title, Description) VALUES ('hi','<strong>test</strong>')
go

SELECT * FROM Test WHERE CONTAINS(*,'hi')    -- returns results
SELECT * FROM Test WHERE CONTAINS(*,'test')    -- returns results
SELECT * FROM Test WHERE CONTAINS(*,'strong')    -- no results

Use a Separate Keywords Column

This is a complex but common approach. Basically, you store your HTML in a varchar or text column, then strip out the HML markup & store the resulting text in a separate keyword column. You then perform searches on the keyword column. 

Upsides:

  • You get to avoid working with Image columns
  • HTML markup is avoided in search results
  • Change tracking will handle the keyword column (provided it's varchar or text w/o using WRITETEXT)

Downsides

  • You need to find or write a function to strip HTML from your column (easy enough with RegEx)
  • Extra storage space is consumed since your storing a lot of the data twice
  • You have to maintain two columns for HTML data: the HTML column, and the keyword column. Thus more work, more risk of bugs, & possibly more confusion. Plus all code that interacts with that table may need to be aware of & correctly use the columns correctly.

Here's a SQL blurb illustrating the concept.

CREATE TABLE Test (
ID int not null identity constraint PK_Test primary key,
Title varchar(1000),
Description varchar(max),
DescriptionKeywords varchar(max)
)
go

CREATE FULLTEXT INDEX ON Test(Title, DescriptionKeywords)
KEY INDEX PK_Test
go

INSERT INTO Test (Title, Description, DescriptionKeywords)
VALUES ('hi','<strong>test</strong>', fnStripHtmlFromText('<strong>test</strong>'))
go

SELECT * FROM Test WHERE CONTAINS(*,'hi')    -- returns results
SELECT * FROM Test WHERE CONTAINS(*,'test')    -- returns results
SELECT * FROM Test WHERE CONTAINS(*,'strong')    -- no results

Notice the fnStripHtmlFromText function -- that's the function you'd need to write to strip HTML from incoming data. For better protection, you could restrict access to the table to store procedures only, and only expose the Description column, like this:

CREATE PROCEDURE spInsertTest (
    @Title varchar(1000),
    @Description varchar(max)
)
AS
BEGIN
    INSERT INTO Test (Title, Description, DescriptionKeywords)
    VALUES (@Title,@Description, fnStripHtmlFromText(@Description))

    RETURN @@IDENTITY
END

Alternately, if you needed to use raw SQL instead of stored procedures, you could use INSERT and UPDATE triggers to maintain the DescriptionKeywords column, and your SQL could just interact with the Description column. Sorta like this:

CREATE TRIGGER dbo.tuTest
ON dbo.Test
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

-- update keyword column with keywords from html column
    UPDATE Test SET
        DescriptionKeywords = fnStripHtmlFromText(i.Description)
        FROM Test t INNER JOIN inserted i ON t.ID = i.ID
END

Use a Separate Image Column

Like the separate keyword column solution, above, except that instead of parsing out the keywords, you just store a second copy of your HTML data in an Image column along with a content type column. The upside is you don't need to write an HTML keyword parser, but the downside is your keywords are in an image column (which may be a non issue since you shouldn't interact with it directly). Here's a sample script

CREATE TABLE Test (
ID int not null identity constraint PK_Test primary key,
Title varchar(1000),
Description varchar(max),
FTSDescription image,
FTSDescriptionContenttype char(3) default 'htm'
)
go

CREATE FULLTEXT INDEX ON Test(Title, FTSDescription TYPE COLUMN FTSDescriptionContenttype)
KEY INDEX PK_Test
go

Conclusion

Well that was a long post. The solution depends on what your goals are, but I'd recommend architecting your application in such a way that if you start out with the first, simplest solution, you can enhance your system later to a more sophisticated implementation without breaking everything. That means you should ideally be interacting with your system either via store procedures or objects. Then if you need to change the underlying database schema in order to handle a better search feature, you can do it in your DAL and/or procedures.

Since I'm doing this for an existing project (building a light CMS), I'm personally leaning towards a separate keyword or separate image column approach. I don't want to directly interact with Image columns at all if I can help it. :)

August 12, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Friday, August 11, 2006
 
 

If this were an ordinary post I'd show you a bunch of code illustrating how to send multipart MIME emails using .NET. But yesterday I ran across DotNetOpenMail, an open-source mail component for .NET. And I don't believe in reinventing the wheel too much.

As a reminder, multipart MIME emails allow you to embed multiple content with different MIME types (e.g. HTML and TEXT) into a single email. That way, recipients with HTML-capable email clients will see the HTML version of your email, while older email programs will display the text version.

In .NET 1.1 (which is what I was developing in yesterday), multipart MIME emails aren't really supported, although if System.Net.Mail uses CDO.Message behind the scenes, you'll automatically get a multipart MIME email generated.

So anyhow, I happily found this open-source component & it appears to work fine for my purposes. And so I thought I'd pass along the tip.

August 11, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Wednesday, August 09, 2006
 
 

Had a few issues running a 1.1 site on Windows 2003. Things I did to resolve the issues:

  • Made sure v1.1 was selected in the ASP.NET tab in IIS Manager for that site. That fixed the issue with ASP.NET not sending the aspnet_client files to the browser.
  • Made sure the \aspnet_client\system_web\1_1_4322 files were in the wwwroot directory for that site. Also copied the latest versions of the js files from C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\ASP.NETClientFiles into the \aspnet_client\system_web\1_1_4322 wwwroot folder. That resolved the issue where no postbacks were occurring due to an old bug w/ client side validation, discussed on Thomas Freudenberg's blog.
  • Was getting a weird error "CS0016: Could not write to output file 'c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\xxxxx'. The directory name is invalid." Turns out the TEMP & TMP environment values were set to a user-specific account. KB825791 gives the fix .. basically changing the environment values and ensuring that the ASPNET and NETWORK SERVICE accounts have full rights to the temp directory.

Now it works. :)

August 9, 2006    Bookmark to Digg or other social bookmarking
#    Disclaimer  |  Comments [0]



 Monday, August 07, 2006
 
 

So I was having a little trouble getting full text search to work with the GUI in SQL Server Express with Advanced Services (formerly SQL Server 2005 Express SP1), so I had to do things manually. It was probably a permissions or setup issue with SQL Server Expres or the tools. In addition to setting up FTS, I wanted a search query to weight columns differently in the search rankings -- something that SQL Server FTS doesn't really support.

Setting up Full Text Search

First I had to download and install SQL Server Express with Advanced Services. It's big, but comes with the goodies I wanted.

Then I connected to my SQL Server Express database using SQL Server Management Studio so I could type in some queries. If your SQL Server Express database is in your Visual Studio Project's App_Data folder, you may be out of luck -- I wasn't able to get full text search to work on those, although maybe adjusting permissions would do it.

Once connected to the database, I created a full text catalog

CREATE FULLTEXT CATALOG MyFTCatalog

Next I needed to get the name of a unique index for my table. You can only create full-text indexes on tables with a single-key unique index (e.g. an autonumber primary key index). Remember that your unique index doesn't have to be on the columns that you want to perform full text searches on.

I had a table called Listing a primary key of IdListing and three varchar fields I wanted to search on: Address, Realtor, and Notes. My table already had a unique index called PK_Listing_IdListing, so it was time to create a full-text index on the three columns I wanted to be able to search on:

CREATE FULLTEXT INDEX ON Listing (Address, Realtor, Notes)
KEY INDEX PK_Listing_IdListing
ON MyFTCatalog
WITH CHANGE_TRACKING AUTO

What the above query did is create a full-text index on those three Listing table columns and store it in the full-text catalog named MyFTCatalog. I indicated PK_Listing_IdListing as the index to help uniquely identify rows on the Listing table, and I told the Full Text Search engine to automatically update the full-text catalog if values in the table change.

Lastly I did a quick check to confirm the catalog existed and wasn't still building

SELECT FULLTEXTCATALOGPROPERTY('MyFTCatalog', 'Populatestatus')

And we're set up. Now it was time to query. And man is it hot in here. I guess overclocking your PC makes for a sweaty summer. Anyhow...moving on.

Performing Weighted Queries

There are plenty of pages about performing full-text queries in SQL Server. Here's a place to start.

So my first query looked like this

SELECT IdListing, Address, Realtor, Notes
FROM Listing
WHERE FREETEXT(*,'some keywords')

The * tells FTS to perform the search on all columns in the full-text index. But the query wasn't going to work for me, since it doesn't give more weight to one column over the other. Plus, in order to sort results by ranking, I needed to use the *TABLE full-text queries. I'm partial to FREETEXTTABLE because it already does all the stemming/etc for me.

Then I did a UNION query like this

SELECT TOP 100 Rank, Address, Realtor, Notes
FROM
(
    SELECT f.Rank, l.Address, l.Realtor, l.Notes
    FROM listing l INNER JOIN
    FREETEXTTABLE(listing, Address, 'some keywords') as f
    ON l.idListing = f.[KEY]
    UNION
    SELECT f.Rank, l.Address, l.Realtor, l.Notes
    FROM listing l INNER JOIN
    FREETEXTTABLE(listing, Realtor, 'some keywords') as f
    ON l.idListing = f.[KEY]
    UNION
    SELECT f.Rank, l.Address, l.Realtor, l.Notes
    FROM listing l INNER JOIN
    FREETEXTTABLE(listing, Notes, 'some keywords') as f
    ON l.idListing = f.[KEY]
) as myTable
ORDER BY Rank DESC

which I quickly rewrote to

SELECT TOP 100 f.Rank, l.Address, l.Realtor, l.Notes
FROM Listing l INNER JOIN
(
SELECT Rank, [KEY] from FREETEXTTABLE(listing, Address, 'some keywords')
UNION
select Rank, [KEY] from FREETEXTTABLE(listing, Realtor, 'some keywords')
UNION
select Rank, [KEY] from FREETEXTTABLE(listing, Notes, 'some keywords')
) as f
ON l.IdListing = f.[KEY]
ORDER BY f.Rank DESC

and then added some weights to the rankings, like so.

SELECT TOP 100 f.WeightedRank, l.Address, l.Realtor, l.Notes
FROM listing l INNER JOIN
(
        SELECT Rank * 5.0 as WeightedRank, [KEY] from FREETEXTTABLE(listing, Address, 'some keywords')
        UNION
        select Rank * 3.0 as WeightedRank, [KEY] from FREETEXTTABLE(listing, Realtor, 'some keywords')
        UNION
        select Rank * 1.0 as WeightedRank, [KEY] from FREETEXTTABLE(listing, Notes, 'some keywords')
) as f
ON l.idListing = f.[KEY]
ORDER BY f.WeightedRank