May 3, 2009
@ 06:36 PM

It looks like I'll be speaking at this year's Southwest Fox 2009. I'll be presenting two different topics:

Full Text Search using Lucene.NET and
Refactoring Legacy Code.

I'm looking forward to catching Craig Boyd's FLLs and the Visual FoxPro API session (assuming the schedule allows it). I had debated whether to interface to Lucene.NET from an FLL, but ultimately decided that it didn't quite fit with the way I imagined it being used. Craig has a number of different FLL's over on his site, so if you've ever wondered how to put together an FLL this should be good.

Christof Wollenhaupt's session, Using .NET in FoxPro Applications should also be a nice companion to my Lucene session. I hadn't planned on covering too much of the interop story in my session, I'll be focusing more on actual use. This should give you a bit more background if you're interested.

Refactoring is a big topic, so you might want to check out Alan Stevens's Break It Down: Dealing With Legacy Code pre-con session. Then check out my session as a refresher and (hopefully) come away with some other ideas on how to manage legacy code. If you can't make the pre-con, hopefully you'll still find my session valuable. I plan on showing real code and some of the ways I have handled refactoring, things to look out for, and various refactoring techniques.

I look forward to seeing you at Southwest Fox.

 

Links

http://www.swfox.net/sessions.aspx#Full_Text_Search_using_Lucene.NET
http://www.swfox.net/sessions.aspx#Refactoring_Legacy_Code
http://www.swfox.net/sessions.aspx#FLLs_and_the_Visual_FoxPro_API
http://www.swfox.net/sessions.aspx#Using_.NET_in_FoxPro_Applications
http://www.swfox.net/sessions.aspx#Break_It_Down:_Dealing_With_Legacy_Code
http://www.swfox.net/register.aspx


 
Categories: Conference

April 21, 2009
@ 06:38 PM

Check out my article in this month's UT Magazine (April 2009) about interfaces in .NET. I try to explain the how's and why's of them, especially when coming from a loosely-typed language (such as VFP). Hopefully you'll find it useful.


 
Categories: .NET

February 21, 2009
@ 05:36 PM

I just posted a new release of my calendar controls that includes a bunch of new functionality, some more documentation (courtesy of HTML Help Builder from West Wind), along with a few more samples of how this is all supposed to work.

A Bit of History

This all initially started with a small calendar. I initially planned on using Microsoft's ActiveX calendar but realized I needed to be able to select multiple days. Their control really didn't quite work the way I needed it to, so I created my own. I created three different sizes (normal, smaller, and smallest) just because it was easy to do (they're all the same control) and I thought I might need it. Like any project the "hey, wouldn't it be cool if..." features started to grow. In some cases, I needed this functionality for an application anyway, so I went ahead and improved them.

Calendar

The first thing I ended up needing was a drop-down calendar. I already had the calendar portion, so all I needed to do was show and hide it when you clicked on a button next to a text box. So that's basically what I did. It worked well enough, but if the control happened to be at the bottom of a form it was clipped - the initial control was based on a container. I moved this into a form and adjusted the code a bit so it would be able to bleed off the form.

I needed to be able to bind to a date/time field but only display the date portion for data coming from SQL Server, so I named it "rcsDatetimePicker". In hindsight, not a great name. It gives you the impression it lets you view/edit both the date and time portion when in fact it doesn't. At this point I've got a bunch of code which depends on it so it's not being renamed (sorry).

DatePicker

A common use-case for dropdown calendars is to pick a date range. I took an idea from a previous job where you could link two date controls to keep the start date and the end dates from overlapping (ex. the start date can be after the end date). So this was added.

I worked on another project which needed a time control (hours/minutes) so I created one. It seemed like a natural fit to this calendar library so it's been added in. The next logical idea is, "hey, I need a date/time dropdown calendar". The better name for this was already taken, so this was was named rcsCtrDateTimePicker. Yeah, not great, but I didn't have any other ideas.

DateTimePicker

Finally, I just worked on an application which needed an Outlook-style calendar (month view). It seemed like I already had most of the work done from my other calendar, so (I optimistically though) it wouldn't be a big deal to re-purpose it. It actually required a number of changes so it could be nicely resized, display events, etc.

That's basically where the library is today: 3 different sized "static" calendars, a drop-down date w/calendar, a drop-down date/time w/calendar, a time control, plus a large resizable calendar that can have events displayed on it.

LargeCalendar

LargeCalendarMoreEvents

I've tried to provide a few more examples of how these controls can be used along with a help file. It's not as comprehensive as I'd like, but it's a decent start.

Misc. Notes

One thing that's not really covered in the help yet is related to performance of the large calendar when you have a lot of events on it. I ran into this during testing - as you add more events, the calendar navigation became slower and slower. I attempted to optimize this a bit but ultimately ended up modifying my application code to only populate events for the current month plus one month prior and one month following. The calendar shows days from the previous and following months, so if I didn't populate them they'd be missing from the calendar.

You can hook in populating events via the RefreshEvents() event. It fires anytime the calendar needs to be refreshed. I'd suggest just using BINDEVENT to call a form-level method for this event:

BINDEVENT(This.ctrCalendar, "RefreshEvents", This, "RefreshEvents") 

You can determine the current month by looking at the iCurrentYear/iCurrentMonth properties.

Getting Started

I'd suggest playing around with the samples and digging through the help file to get started. The examples cover most of the basic functionality (try clicking on everything and hovering over things, resizing, etc.) and the help file explains some of the class structure.

Let me know what you think.

Links

http://www.west-wind.com/wwHelp/
http://www.rcs-solutions.com/downloads.aspx


 
Categories: VFP

February 14, 2009
@ 12:39 PM

I just uploaded an update for the collapsing comments plug-in for CodeRush/Refactor! I noticed after updating to the latest version (3.2.3) that it suddenly stopped working. After a bunch of digging, and going back and forth with Rory Becker on twitter. He suggested I take a look at manually loading the plug-in via the new Plug-in Manager (DevExpress > Options > Core > Plug-in Manager). I noticed that my plug-in (which is written as a refactoring) had it's Load Type set as "Demand", but most of the other ones were set as "Idle". After a bit of digging I figured out how to change this in my code - it's an attribute in AssemblyInfo. Making this change and recompiling seemed to fix the issue.

 

Links:

http://www.rcs-solutions.com/downloads.aspx
http://twitter.com/rorybecker


 
Categories: CodeRush

February 7, 2009
@ 03:22 PM

I just finished up a report which generates a list of customers whose birthdays fall within a specified date range. I wrote a simple query which did essentially:


DECLARE @startDate datetime
DECLARE @endDate datetime
SET @startDate = '2009-2-15'
SET @endDate = '2009-3-14'

SELECT c.iid,
        c.FirstName,
        c.LastName,
        c.BirthDate,
        c.Address1,
        c.Address2,
        c.City,
        c.State,
        c.ZipCode
   FROM Customers c
  WHERE c.BirthDate BETWEEN @startDate AND @endDate


Of course, that didn't work. That is, unless our customers happened to actually just been born (as of when I wrote this they wouldn't have been even born yet). Hmm....My first thought was maybe joining this to a date table to get the month and day split apart but that idea falls apart pretty quickly so I dismissed it. I'll come back to this idea in a second.

The other simple way to do this is to convert the date to a day of the year, so January 1st is 1, Jan. 2nd is 2, December 31st is 365. SQL Server includes a nice DATEPART() function to make this easy - you can specify that you want the day of the year with it.

So my query was rewritten as:

SELECT c.iid,
        c.FirstName,
        c.LastName,
        c.BirthDate,
        c.Address1,
        c.Address2,
        c.City,
        c.State,
        c.ZipCode
   FROM Customers c
  WHERE DATEPART(dayofyear, c.BirthDate)
BETWEEN DATEPART(dayofyear, @startDate) AND DATEPART(dayofyear, @endDate)


While this one works it's a bit slow. That's not so surprising since it has to use DATEPART on the rows to generate the day of the year in the WHERE clause. My actual code filters the customers a bit more but it was still a fair number of records. Ultimately, since this a summary/reporting table that gets populated and updated nightly I just added another (integer) column to store the precalculated day of the year number.

Surprisingly, this version isn't that much faster - maybe 15% or so. Apparently DATEPART is pretty quick.

It wasn't until after making these changes that I realized I could have added a day of the year column to my date table, done a join then used this column in my WHERE clause. That actually would have been easier if it had occurred to me sooner. I'm guessing performance is probably equivalent, especially since adding the column directly in the table didn't have a huge impact on the speed of the query.


 
Categories: SQL

I've been using CR/R! for a while and one of the simplest changes I've made that makes using it a bit easier is redefining the "Refactor" key - that's the key which causes the refactoring options to pop-up. I've switched mine to just use ` from its default of Ctrl+` - one keypress (it's not like I use the dumb key for much of anything else). There are some many options in CR/R! it's not always easy to find this.

To set it:

  • DevExpress > Options
  • From the side menu select "IDE", then "Shortcuts"
  • In the list you should see "Refactor!", underneath that is the key bound to Refactor.
  • Click on it and on the right hand side change they key binding to whatever you'd like.
  • Hit Apply.

RefactorKey

That all that's needed.


 
Categories: CodeRush

Extension methods were added as a new compiler feature in .NET 3.5. More specifically, that means you can use VS 2008 to use an extension method and then use VS's multi-targeting to run it under .NET 2.0. They're basically a means of tacking on methods onto existing classes or interfaces w/o actually needed to subclass or modify an interface. It's used extensively by (and added because of) LINQ. The methods aren't really part of the class, but the way you use them (and the way they appear in intellisense) make them feel like they're now part of the class. They're essentially static methods scoped to a specific interface or class.

I've been playing around with them a bit and ran into a case where I thought they'd be kind of a cool fit. I've needed to be able to convert a datatable into a comma-delimited file (CSV) so it can easily be opening in something like Excel, or pretty much anything that understands CSV files. I could create a separate class to do this, but it seems like this should be part of the DataTable class. To write an extension method you basically create a static method in a static class and prefix the first parameter with "this". Yep, that's about it.

I wanted it to basically work like this:

DataTable table = myBizObj.DataSet.Tables["SomeTable"];
string csv = table.ToCSV();

Creating the CSV is pretty straightforward - I loop through the column headers to generate the first header row, then I loop through each row in the table, then each item in the ItemArray of the row. I specifically decided to use quotes as delimiters around everything to keep it simple - the rules as to when you can/should include quotes for a CSV are pretty complicated. The only thing I do is escape out embedded quotes in the data by doubling them, ex. " becomes "". As soon as I had it working, I decided to create a few more overloads to let me control whether a header row was required, and the actual delimiter used (ex. instead of comma you could change it to a | pipe for example). Their is some example code in the XML help at the top of the class. In addition, I'm actually using this for a web page so it might be helpful to see what that code looks like:

string results = act.DataSet.Tables[tableName].ToCSV();

string mimeType = RCSSolutions.Web.Utility.DetermineMimeType("csv");

Response.ContentType = mimeType;

Response.AddHeader("Content-Length", results.Length.ToString());

Response.AddHeader("Content-disposition",

                   string.Format("attachment;filename={0}", "DelimitedList.CSV"));

Response.Write(results);

Response.End();

I'm calling out to another helper class which returns the mime type - in this case, all it does is return "application/csv". The above code basically pops open a dialog box with the file name filled in the browser on the client side.

using System;

using System.Collections.Generic;

using System.Data;

using System.Linq;

using System.Text;

 

namespace RCSSolutions.Utility

{

    /// <summary>

    /// <para>Various extension methods.</para>

    /// </summary>

    /// Sample of using ToCSV

    /// <example>

    /// DataTable table = dv.Table;

    /// // Assumes table is a DataTable

    /// string result = table.ToCSV(true);

    /// System.IO.File.WriteAllText(@"C:\sample.csv", result);

    /// System.Diagnostics.Process proc = new System.Diagnostics.Process();

    /// proc.StartInfo.FileName = @"C:\sample.csv";

    /// proc.StartInfo.UseShellExecute = true;

    /// proc.Start();

    /// </example>

    public static class Extensions

    {       

        /// <summary>

        /// Converts the passed in data table to a CSV-style string.      

        /// </summary>

        /// <param name="table">Table to convert</param>

        /// <returns>Resulting CSV-style string</returns>

        public static string ToCSV(this DataTable table)

        {

            return ToCSV(table, ",", true);

        }

 

        /// <summary>

        /// Converts the passed in data table to a CSV-style string.

        /// </summary>

        /// <param name="table">Table to convert</param>

        /// <param name="includeHeader">true - include headers<br/>

        /// false - do not include header column</param>

        /// <returns>Resulting CSV-style string</returns>

        public static string ToCSV(this DataTable table, bool includeHeader)

        {

            return ToCSV(table, ",", includeHeader);

        }

 

        /// <summary>

        /// Converts the passed in data table to a CSV-style string.

        /// </summary>

        /// <param name="table">Table to convert</param>

        /// <param name="delimiter">Delimiter used to separate fields</param>

        /// <param name="includeHeader">true - include headers<br/>

        /// false - do not include header column</param>

        /// <returns>Resulting CSV-style string</returns>

        public static string ToCSV(this DataTable table, string delimiter, bool includeHeader)

        {

            StringBuilder result = new StringBuilder();

 

            if (includeHeader)

            {

                foreach (DataColumn column in table.Columns)

                {

                    result.Append(column.ColumnName);

                    result.Append(delimiter);

                }

 

                result.Remove(--result.Length, 0);

                result.Append(Environment.NewLine);

            }

 

            foreach (DataRow row in table.Rows)

            {

                foreach (object item in row.ItemArray)

                {

                    if (item is System.DBNull)

                        result.Append(delimiter);

                    else

                    {

                        string itemAsString = item.ToString();

                        // Double up all embedded double quotes

                        itemAsString = itemAsString.Replace("\"", "\"\"");

 

                        // To keep things simple, always delimit with double-quotes

                        // so we don't have to determine in which cases they're necessary

                        // and which cases they're not.

                        itemAsString = "\"" + itemAsString + "\"";

 

                        result.Append(itemAsString + delimiter);

                    }

                }

 

                result.Remove(--result.Length, 0);

                result.Append(Environment.NewLine);

            }

 

            return result.ToString();

        }

    }

}


 
Categories: .NET | C#

December 23, 2008
@ 10:12 PM

I was trolling Digg tonight and ran across this article about finding rare songs on YouTube. I hadn't really thought about it but I had found some great Pink Floyd videos on there a while back. I've had a song (not Pink Floyd) knocking around in my brain for a while that I was never able to identify; all I could remember was "suckerpunch". So I typed "suckerpunch song" into the search on YouTube and bang, there it was. Actually, after hearing this again (besides realizing it was from around 1993) was how much this sounded like Nine Inch Nails, March of the Pigs (released around the same time). OK, it doesn't quite match up as well as it did in my head (or as well as this), but whatever. It's got the same sort of feel.

That got me thinking about when I saw Pink Floyd at the Pontiac Silverdome; I was trying to figure out what year I went. This, of course, got me thinking about the first concert I ever saw: Iron Maiden at Joe Louis Arena. That led me back to YouTube. And Guitar Hero.

The internets is cool. And a huge waste of time. But mostly cool.

Links:

http://digg.com
http://news.cnet.com/8301-13772_3-10125016-52.html?part=rss
http://www.youtube.com
http://www.youtube.com/watch?v=M_bvT-DGcWw
http://www.youtube.com/watch?v=plBna98XZNQ
http://www.youtube.com/watch?v=U2LwEQFK3qc
http://www.youtube.com/watch?v=7xNQZHuAQJ8
http://ourworld.compuserve.com/homepages/PFArchives/tourdate.htm
http://www.maidenfans.com/imc/index.php?url=tour06_sit/dates06_sit&link=tours&lang=deu
http://www.youtube.com/watch?v=AUOpUqni0_g
http://www.youtube.com/watch?v=e39D8VBQ4sw

Bonus Track (just because this is a great song):

http://www.youtube.com/watch?v=k5JkHBC5lDs


 
Categories: Other

I recently added a maintenance form to our website which allows a user to add and delete entries to a list of banner ads stored in a simple XML file. Previously we've just been maintaining them manually by editing the XML directly and copying the associated banner images into a folder on the website. When you delete an ad, I decided to NOT delete the associated image since they may be reused (and I wanted to avoid forcing the user to upload the image again). However, we'd still like to periodically clean out this folder and "archive" the images so they're not cluttering up the selection screen.

I basically needed some code which would get me a list of files in the banner images folder which were not referenced in my banner XML file. It can be kind of clunky to iterate through XML but they've made it much easier with the introduction of LINQ. I fired up LINQPad (which, BTW, is an AWESOME free tool for testing out LINQ code) and tried out a few ideas. As a side note, it looks like Intellisense is now available if you purchase a copy of LINQPad.

http://www.linqpad.net/

I started with querying the filesystem to get a list of files:

  

DirectoryInfo info = new DirectoryInfo(@"X:\inetpub\wwwroot\images\banner");

FileInfo[] files = info.GetFiles();

 

files.Dump();

 

.Dump() is an extension method available in LINQPad which dumps out the results of the query (we haven't actually used LINQ yet to do anything).

Here's what it looks like:

FileInfoLinq

You might notice that Directory contains a DirectyInfo element. If you click on the down arrow it will expand out these values as well.

So I now had a list of files, I wanted to then get a list of images referenced in my banner file. Here's the format of the XML file:

<News>   
   <NewsItem>
       <Title>Supertooth3-banner.gif</Title>
       <Image>/images/banner/Supertooth3-banner.gif</Image>
       <Height>183</Height>
       <Link>/PortalView.aspx?navto=/Supertooth3-banner.gif</Link>
       <Date>July, 19th, 2003</Date>
       <Target></Target>
   </NewsItem>   

  

 

I haven't really been using Title for anything besides the name of the image file, so I was able to take a bit of a shortcut here and use it for my comparison. To pull out the list of images used in the XML, I wrote this query:

XDocument doc = System.Xml.Linq.XDocument.Load(@"X:\inetpub\wwwroot\adv.xml");

doc.Dump();

 

var news = from item in doc.Descendants("Title")

           orderby item.Value

           select item.Value;

news.Dump();

 

Which produces this:

XmlTitlesLinq

I noticed that the banner images folder contained a bunch of other files that I really didn't want to consider for filtering, so I needed to narrow my query to files that had specific extensions. C# doesn't really have a direct equivalent for INLIST, but we can do this a slightly different way for the same effect.

First I define an array of valid extensions, then (inside the where clause of the LINQ query) I check to see if this list of file types contains the filetype of the file I'm currently evaluating. It's a bit backwards, but it's simple and it works.

string[] fileTypes = { ".jpg", ".gif", ".png" };

var imgFiles = from file in files

               where (fileTypes.Contains(file.Extension))

               orderby file.Name

               select file.Name;

 

imgFiles.Dump();

 

Now I've got a list of files from my XML and a list of files from the banner images folder. I want to get a list of files from the banner images folder that aren't in the XML list. I do this via a final query:

var extra = from singleFile in imgFiles

            where !(news.Contains(singleFile))

            select singleFile;

extra.Dump();   

This returns my extra files. Now I can just use this list to move my images into an archive folder periodically.

If I put it all together, I end up with this:

 

XDocument doc = System.Xml.Linq.XDocument.Load(@"X:\inetpub\wwwroot\adv.xml");

DirectoryInfo info = new DirectoryInfo(@"X:\inetpub\wwwroot\images\banner");

FileInfo[] files = info.GetFiles();

doc.Dump();

files.Dump();

 

var news = from item in doc.Descendants("Title")

           orderby item.Value

           select item.Value;

news.Dump();

string[] fileTypes = { ".jpg", ".gif", ".png" };

var imgFiles = from file in files

               where (fileTypes.Contains(file.Extension))

               orderby file.Name

               select file.Name;

 

imgFiles.Dump();

 

var extra = from singleFile in imgFiles

            where !(news.Contains(singleFile))

            select singleFile;

extra.Dump();   

Links:

http://www.linqpad.net/


 
Categories: .NET | C# | LINQ

November 26, 2008
@ 08:50 PM

I thought this was an interesting question that has come up a couple of times in the last few days over on the Universal Thread. In VFP you can create a subclass of a form, customize it, then configure VFP to use this subclass as the "template" for any new forms you create (by going into Tools > Options > Forms and setting the base class).

The question was, how do I do this in .NET?

It's pretty easy - create your form and add any controls, methods, properties, etc. that you want. Then go to File > Export Template. An Export Template Wizard will open. Change the selection to an "Item template" and click Next.

Export Template Wizard

 

On the next screen put a checkmark next to the item(s) you want included in the template. If you have other dependencies (like external classes) you can also select them here. Then click Next again.

Export Template Wizard Select Item to Export

Now you can select any external references you want to include. For example, if you created a project which includes a set of base controls that you used on your form, you may want to select them here (so when you use this template these references will automatically get added to your project). Click Next.

Export Template Wizard Select Item References

Finally you can select an icon that is associate with this template, enter the template name, description, and it's output location. I'd suggest leaving the "Automatically import the template into Visual Studio" option checked. If you don't like the output location, unfortunately you can't change it here. You have to change it via Tools > Options > Projects and Solutions. Finally click Finish.

Export Template Wizard Select Template Options

At this point, when you do an "Add New Item" you should be able to scroll to the bottom of the templates and see "My Templates" - the new template you created should exist there.

Although I showed this for a WinForms application, this can actually be used with any class (and for ASP.NET applications). For example, I have a base business object class I use. Every time I create a new one I just select a template based on a subclass of it to create the new one.


 
Categories: .NET | Visual Studio

I received this error on a page which had previously been working. That morning I had updated our site, so I was expecting some issues, but this error doesn't really tell you much. We have a maintenance screen which displays a list of records in a grid. When you click on an edit link the grid converts into an "in place" editor (a UI metaphor I thought I'd like when I initially put the form together but which, as it turns out, I really hate).

A user had reported that when they clicked on a button which saves this record that it wasn't closing and updating the list. We are using Telerik web controls for most of the site so I figured it had to do with them since I updated a number of pages, including our master page, to start using the RAD AJAX version of the controls instead of their previous version. It seems this error was being caused because I had added an instance of the RadAjaxManager control to the master page, and I already had an instance of a AjaxManager (older version) on the content page itself. Both managers were conflicting with one another, which caused this error.

All I needed to change was the "RadAjaxManager" markup code to a "RadAjaxManagerProxy" and that resolved this particular error.


 
Categories: ASP.NET

November 8, 2008
@ 10:19 AM

I needed to sign back up for online access to one of my bank accounts. I used to have this all automated through Quicken but at some point it broke and I never seemed to get around to fixing it. I couldn't remember the account password so I requested it to be changed. They had a cool step where they had me select which phone number I had registered with the account would be called. As soon as I hit "Next" they (well, their automated system) was calling. They wanted me to confirm the fact that I was expecting the call and to enter a code which was shown on the screen. As soon as I did that it allowed me to continue on to change my password.

That was pretty cool - a pretty painless way of ensuring I'm really who I say I am. Imagine how disappointed I was when I started typing in my new password:

Bank Password

Really? No special characters and I'm limited to 12 characters for access to my bank account? (sigh). My LinkedIn account has a more secure password than this. Lame.


 
Categories: Soapbox

I'm working on an application which is going to use WCF heavily for communications between a client side application and a server-based service application. One aspect of the application that I've been ignoring during testing was WCF authentication. During testing the application has been running on my local network. I had enabled connection based authentication (ex.SSL) and it is using TCP as it's binding since I do a lot of callback messaging. In the back of my mind I realized that when it's deployed the client will be on a different network, communicating over the internet, so there may be some issues with the current security model.

A week ago I finally got around to looking at it and it completely breaks when the client isn't local, since it was using Windows authentication to do it's authentication. Microsoft has a nice manual which walks through the various ways of configuring security in WCF, which you should choose (and why) under various scenarios. Scenarios, Patterns, and Implementation Guidance for Web Services Enhancements (WSE) 3.0 from their patterns & practices team. Based on a number of factors, it suggested I use SSL along with message based security. I'm not going to be hosting this in IIS, so HTTPS wasn't an option for the SSL connection. I also wasn't using Windows authentication and didn't want to deal with some of the issues of the other credential types (IssuedToken, Digest, etc.). So I decided on UserName.

I now had two things to fix - first, I needed to implement my own UserName authentication scheme (again, I like making my life hard so I wasn't using the default authentication provider in ASP.NET that used SQL Server). This turns out to be pretty simple by inheriting from the UserNamePasswordValidator class in the System.IndentityMode.Selectors namespace and overriding the Validate method:

using System;
using System.Collections.Generic;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Text;

namespace MySampleApp.Server

{

    /// <summary>

    /// This class is responsible for validating the username/password

    /// used by the connecting WCF client.

    /// </summary>

    /// <remarks>

    /// <para>

    /// The server is configured to use this class in the Service Behavior

    /// serviceCredentials section of the config file.

    /// CustomUserNamePasswordValidatorType is set to SampleValidator

    /// UseNamePasswordValidationMode is set to Custom

    /// </para>

    /// </remarks>

    /// <developer>Paul Mrozowski</developer>

    /// <created>10/13/2008</created>

    public class SampleValidator : UserNamePasswordValidator

    {

        public override void Validate(string userName, string password)

        {

            // TODO: Finish, this is just here to test out the idea.

            if (userName != "user" || password != "pass")

                throw new SecurityTokenException("Unknown user.");

        }

    }

}

 

If the passed in username/password is invalid, throw an exception. Easy. I added this new class to my server project, then modified the app.config file to let WCF know to use this for authentication.

<bindings>
      <nettcpbinding>
          <binding name="StandardServerBinding" maxbuffersize="8192000" 
              maxreceivedmessagesize="8192000" listenbacklog="5000" maxconnections="1000"> 
              <readerquotas maxdepth="24" maxstringcontentlength="8192000" maxarraylength="8192000" 
               maxbytesperread="8192000" maxnametablecharcount="8192000" />
              <reliablesession inactivitytimeout="01:00:00" />
              <security mode="Message">                  
                  <message clientcredentialtype="UserName" />
              </security>
          </binding>
      </nettcpbinding>
  </bindings>
        <behaviors>
            <servicebehaviors>
                <behavior name="RegisterBehavior">					
                    <servicedebug includeexceptiondetailinfaults="true" />
                    <servicemetadata />
                    <servicethrottling maxconcurrentcalls="48" maxconcurrentsessions="5000" 
                      maxconcurrentinstances="5000" />
                    <servicecredentials>
                        <usernameauthentication usernamepasswordvalidationmode="Custom" 
          customusernamepasswordvalidatortype="MySampleApp.Server.SampleValidator,MySampleApp.Server" 
                          cachelogontokens="true" cachedlogontokenlifetime="01:00:00" />
                    </servicecredentials>					
                </behavior>
                <behavior name="ThrottlingBehavior">
                    <servicethrottling maxconcurrentcalls="48" maxconcurrentsessions="5000" 
                        maxconcurrentinstances="5000" />
                </behavior>
            </servicebehaviors>
        </behaviors>		
        <services>
            <service name="MySampleApp.ServerProcess" behaviorconfiguration="RegisterBehavior">
                <endpoint name="PrimaryEndpoint" contract="MySampleApp.Server.Contracts.IServer" 
                   binding="netTcpBinding" address="net.tcp://localhost" 
                   bindingconfiguration="StandardServerBinding" />
                <endpoint name="MEXEndpoint" contract="IMetadataExchange" 
                   binding="mexHttpBinding" address="http://localhost/MEX/" bindingconfiguration="" />
            </service>
        </services>

You add a <serviceCreditials\userNameAuthentication section to the ServiceBehavior and specify a Custom userNamePasswodValidationMode. I tell it to use the full name of the class, the second part of that after the comma tells it which assembly it's located in. In the netTcpBinding you can see that I've enabled security on the message, and told it to use the UserName authentication type.

        <bindings>
            <netTcpBinding>
                <binding name="NewBinding0" maxBufferSize="8192000" maxReceivedMessageSize="8192000">
                    <readerQuotas maxDepth="24" maxStringContentLength="8192000"
                        maxArrayLength="8192000" maxBytesPerRead="8192000" maxNameTableCharCount="8192000" />
                    <reliableSession inactivityTimeout="01:00:00" />
                    <security mode="Message">
                        <transport clientCredentialType="None" protectionLevel="None" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </netTcpBinding>

I've set the security mode to Message and clientCredentialType to UserName as well. Now I needed to somehow get my client application to pass the user name and password. I had used Visual Studio and let it build my client proxy, so I thought I'd be able to set this immediately after I created the proxy, ex:

InstanceContext context = new InstanceContext(this);

 

this.m_client = new ServerClient(context);

this.m_client.ClientCredentials.UserName.UserName = "user";

this.m_client.ClientCredentials.UserName.Password = "pass";

Unfortunately, that doesn't work - I was getting "Object is read only" when I attempted to do this. From some searching I was able to find out that this can occur when the connection has already been opened, but in my case I hadn't done that yet. I spent a bunch of time trying to figure out why it wouldn't work and finally ended up putting the code inside of the proxy class VS had generated by editing the constructor and setting things up there.

public ServerClient(System.ServiceModel.InstanceContext callbackInstance) :

        base(callbackInstance)

{       

    base.ClientCredentials.UserName.UserName = "user";

    base.ClientCredentials.UserName.Password = "pass";      

}

At this point, I thought I was all set to go. I fired things up and it immediately faulted the connection. I opened up the WCF Service Configuration Editor (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\svcconfigeditor.exe) and enabled tracing on both the server and client and re-ran things so I could capture a trace. Once that was done I opened up the WCF Svc Trace Log Viewer (which is part of the Windows SDK, mine is located at: C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\SvcTraceViewer.exe). After looking through the log, it appeared to be failing with a "The security protocol cannot secure the outgoing message" error.

Again, I did a bunch of searching, reading, and testing and finally realized that it wanted to encrypt the connection but couldn't. I mentioned earlier that I decided to use SSL for the connection, but when I was working on this I didn't realize that it didn't magically decide how to do that. It looked like I could use an X509 certificate to enable encryption, but I wasn't at all thrilled with the prospect of trying to get that cert. install on client machines. From the couple of times I've seen it needed, it never seems to run particularly smoothly (when it works, it just works, but when it fails, you have no idea why it's failing). So I essentially wanted to be able to load my cert. at runtime instead of loading it into the Windows cert. store. I could buy a real cert., but for this app. it just wasn't necessary. VS includes some tools for self-signing certificates so I looked into that instead.

If you open a Visual Studio command prompt (hiding in the Visual Studio Tools subdirectory of Visual Studio on the Start menu), it sets up the pathing so you can use the various command line tools. The first thing I needed to do was create a key for the server:

makecert -r -pe -n "CN=RCS Solutions, Inc." -b 01/01/2008 -e 12/31/2099 -sky exchange 
Where:
Server.cer -sv Server.pvk Server.cer - the certificate (public key)
Server.pvk - the private key

When it runs, it prompts for a password used to encrypt the private key. You can leave it blank, but it's suggested you fill it in (so I did).

Next I decided to merge the public and private key into a single file with a PFX extension. It requires you to re-enter the password you used to encrypt the private key (or pass in in the command line, which is what I did).

pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx -pi mycertpassword

At this point I added all three files to my project. I also needed to get my proxy to use this cert. Since it's not a real certificate (meaning there isn't a chain of trust established between my cert at some known/trusted provider) I had to disable certificate validation. Yeah, pretty secure stuff ;-)

           ServiceHost host = null;

           try

           {

               // Specifically not calling Dispose() on host since it also calls Close and if we've

               // disposed after calling Close that will cause errors.

               host = new ServiceHost(typeof(ServerProcess));

 

               string dir = System.IO.Directory.GetCurrentDirectory();

 

               X509Certificate2 cert = new X509Certificate2(dir + "\\Server.pfx", @"mycertpassword");

               host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;                               

               host.Credentials.ServiceCertificate.Certificate = cert;

 

               host.Open();

 

I repeated the above sequence for my client, creating it's own public/private key. I added the same code to configure the cert, except I put it in the pre-generated proxy constructor.

public ServerClient(System.ServiceModel.InstanceContext callbackInstance) :

        base(callbackInstance)

{       

    base.ClientCredentials.UserName.UserName = "1";

    base.ClientCredentials.UserName.Password = "2";

 

    // TODO: Fill in with correct credentials

 

    string dir = System.IO.Directory.GetCurrentDirectory();

    X509Certificate2 cert = new X509Certificate2(dir + "\\Client.pfx", @"mycertpassword");

    base.ClientCredentials.ClientCertificate.Certificate = cert;       

    base.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;         

 

}

 

I thought I was good to go, so I started everything back up. It failed with the same error as before. Yet more searching revealed that I needed to include a copy of the server's certificate inside of the client's app.config (I have no idea why, it seems like it should happen automatically as part of it's public key exchange). The problem was getting it in the format it required. I tried a few of the various ways that were suggested, but didn't have much luck with them. I finally regenerated the client side proxy using the svcutil command line app. and pointing it to a MEX (Metadata EXchange) endpoint and it magically created the certificate value I needed. I cut and pasted that code into my production proxy.

       <client>
            <endpoint address="net.tcp://localhost/" binding="netTcpBinding"
                bindingConfiguration="PrimaryEndpoint" contract="Server" name="PrimaryEndpoint">
                <identity>
			<certificate encodedValue="AwAAAA(long value removed)" />                    
                </identity>
            </endpoint>

When I restarted everything it was finally able to open a connection. I still need to figure out how to get my WCF service and IIS to share the same port, but I'm making some progress at least.


 
Categories: WCF

October 28, 2008
@ 07:38 PM

In case you missed it, it looks like Microsoft is making videos of the PDC sessions available on the Microsoft PDC site available within 24 hours of them occurring, which is pretty cool. It's not particularly easy to figure out where the heck the videos are hiding. They are being posted on the Channel 9 site but it's the same thing - good luck finding them all (at least it wasn't clear to me on how to find them).

However, if you go to the agenda timeline page you can click on a session. At the bottom there is a "View Session Details" link - click on that to view the session. They have one camera view on the speaker, the other on the slides.

Check it out.

Links:

http://www.microsoftpdc.com
http://channel9.msdn.com
http://sessions.microsoftpdc.com/public/timeline.aspx


 
Categories: Conference

October 28, 2008
@ 07:30 PM

I posted some info last night about how to move the TempDB database in SQL Server. That morning I had made the changes and just needed to restart SQL Server for the changes to take effect. This morning I restarted the SQL Server service and after it restarted, tried to log into our website (since it's the main interface to the data in SQL). I received a network connection error from ASP.NET which wasn't surprising, so I reset IIS (IISRESET from a DOS prompt). After it restarted, which only took 10 seconds or so, I tried hitting the site again. And nothing. The page sat there attempting to connect and finally timed out.

Uh oh, not good.

I still had Management Studio open so I tried a few test queries, which seemed to work OK. I opened up the Activity Monitor and didn't see any connections from ASP.NET. OK, so SQL Server is probably denying the connection or blocking for some reason. I took in a look in the new tempdb location and SQL had recreated the mdf/ldf files like I expected, so what's up?

I opened up the Event Viewer and saw a number of entries about the various databases starting up, which was good. Hey, what's that "Failure Audit" entry?

sqleventlog

"Login failed for user 'NT AUTHORITY\NETWORK SERVICE'. [CLIENT: 192.168.1.8]".

Hmmm...192.168.1.8 is the web server. What user does SQL Server run under? I opened up Services, found "SQL Server" and double clicked on it. Then I went to the "Log on" tab. It runs under NT AUTHORITY\NetworkService. Could it be??? I opened Explorer and right-clicked on the folder where tempdb was located and selected "Sharing and Security" then went to the Security tab. Sure enough, "NETWORK SERVICE" wasn't listed. I went ahead and added it to the folder level then test the site again.

Success!

What was really strange about this was that most of our SQL databases are (and have been) located in this same folder. I'm guessing the permissions for those was set on the file level, which is why they've been working OK. The other weird thing was that it was even able to create the tempdb files at all.


 
Categories: SQL

I run across this from time to time and can never remember the specifics, so I figured I'd document it here:

To move a SQL database from one drive to another drive (or just move it to a new folder):

Inside of Microsoft SQL Management Studio execute this script (of course, replace "NameOfDatabase" with the real database name):

  USE master
  GO
  sp_detach_db 'NameOfDatabase'
  GO

Copy the database (usually NameOfDatabase.MDF and NameOfDatabase_log.LDF, assuming they haven't been renamed) to the new location.

  NOTE: The paths are relative to the server SQL Server is running on.

  USE master
  GO
  sp_attach_db 'NameOfDatabase', 'E:\SQLServerDataFolder\NameOfDatabase.mdf', 'E:\SQLServerDataFolder\NameOfDatabase_log.ldf'
  GO

To move the TempDB system database to a new location:

USE master
  GO
  ALTER DATABASE tempdb modify file (name = tempdev, filename = 'E:\SQLServerDataFolder\tempdb.mdf')
  GO
  ALTER DATABASE tempdb modify file (name = templog, filename = 'E:\SQLServerDataFolder\templog.mdf')

Restart SQL Server for the changes to take effect. One it's restarted you can delete the tempdb.mdf and templog.mdf from the old location.

Some instructions for shrinking the tempdb database:

http://support.microsoft.com/kb/307487


 
Categories: SQL