Implementing ITypedList for Virtual Properties

ITypedList allows you to create “views” of objects for databinding purposes without actually having to modify the object(s) underlying the view. You can prevent public properties from show up by using attributes. However, attributes won’t allow you to present methods as properties or crosstab an array of values. Prior to developing this technique, I would implement a method that returned a DataTable that I used for databinding. This works OK for read-only displays but you still end up duplicating data in memory at least for a small time. Once you allow updating, you must keep both the view (datatable) and the model (object collection) in memory and synchronized. At this point, implementing ITypedList becomes a very elegant, effecient solution. (see a later post for more on this topic).

In the .net 2.0 world, you should be using a generic collection class as the base for your underlying data. So the first question is, since List<X> is a strongly typed list, will databinding still recognize the ITypedList implementation? Fortunately, the answer is yes. The databinding system in .net 2.0 windows forms will display class defined as

public class PersonCollection : List<Person>, ITypedList {...}

using the ITypedList implementation and instead of the default view consisting of all of the Person public properties.

On the surface, implementing ITypedList looks easy — you only have to write two methods

PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors);

string GetListName(PropertyDescriptor[] listAccessors);

In fact, you only have to write one method — GetItemProperties. GetListName isn’t used by the .net 2.0 DataGridView. Wow, just one method. How hard can that be? Unfortunately, the documentation is less than stellar and the steps required are less than obvious. First, you can’t just create an empty PropertyDescriptorCollection and start adding items to it. No, that would make too much sense. Instead, you have to create a PropertyDescription array and pass that to the PropertyDescriptorCollection constructor.

Now that you’ve figured out how to create a PropertyDescriptorCollection, you must find a way to create a PropertyDescriptor. PropertyDescriptor is an abstract class. If you search the documentation, you won’t find any obviously useful concrete subclasses, either. Since PropertyDescriptor is not sealed, you can subclass it. When you subclass it, you find that there is really only one useful base constructor which takes a property name and an attribute array as a parameter. However, you still have 8 members that you must implement. I was hoping for a constructor that would take some of these as parameters but you really do have to implement them all yourself.

In the paragraphs that follow, I will step you through implementing a List<Person> that implements ITypedList. I will define an interface for buildng a Person view. I will implement that interface and pass an instance of that implmentation in to the PersonCollection class using the Strategy design pattern. Finally, in order to create the “virtual” properties, I will also implement a concrete subclass of PropertyDescriptor.

Below is the code for a Person class. There’s nothing special about it but we need something to work with to demonstrate ITypedList.

public class Person {
 protected string _firstName;
 protected string _lastName;
 protected string _midName;
 protected DateTime _dob;
 
 public Person(string firstName, string lastName, string midName, DateTime dob) {
  _firstName = firstName;
  _lastName = lastName;
  _midName = midName;
  _dob = dob;
 }
 
 public string FirstName {
  get { return _firstName; }
 }

 public string LastName {
  get { return _lastName; }
 }

 public string MiddleName {
  get { return _midName; }
 }

 public DateTime DateOfBirth {
  get { return _dob; }
 }
}

Here’s the complete PersonCollection class definition — all it does is implement ITypedList by deferring all of the work to another class (note that using System.ComponentModel is assumed in all of the following classes):

public class PersonCollection : List<Person>, ITypedList {
 protected IPersonViewBuilder _viewBuilder;

 public PersonCollection(IPersonViewBuilder viewBuilder) {
  _viewBuilder = viewBuilder;
 }

 #region ITypedList Members

 protected PropertyDescriptorCollection _props;

 public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
  if (_props == null) {
   _props = _viewBuilder.GetView();
  }
  return _props;
 }

 public string GetListName(PropertyDescriptor[] listAccessors) {
  return ""; // was used by 1.1 datagrid
 }

 #endregion
}

I am using the Strategy pattern to allow specification of the view builder. If you needed to provide multiple, simultanous views of the same underlying collection, you would need to use a different design. I have defined an interface to facilitate the Strategy pattern.

public interface IPersonViewBuilder {
 PropertyDescriptorCollection GetView();
}

The next two classes are where are the real work happens with respect to creating the view. In this case we know how many properties we are exposing up front. In other cases, the number of “virtual” properties exposed could be dynamic and the code below demonstrates that by using a List<> instead of a predefined array. Yes, I know that Age isn’t accurate but I kept it simple for demonstration purposes.

public class PersonFullNameAgeView : IPersonViewBuilder {
 public PropertyDescriptorCollection GetView() {
  List<PropertyDescriptor> props = new List<PropertyDescriptor>();
  PersonMethodDelegate del = delegate(Person p) 
   { return p.FirstName + " " + p.MiddleName + " " + p.LastName; };
  props.Add(new PersonMethodDescriptor("FullName", del, typeof(string)));
  del = delegate(Person p) { return DateTime.Today.Year - p.DateOfBirth.Year; };
  props.Add(new PersonMethodDescriptor("Age", del, typeof(int)));
  PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
  props.CopyTo(propArray);
  return new PropertyDescriptorCollection(propArray);
 }
}

public delegate object PersonMethodDelegate(Person person);

public class PersonMethodDescriptor : PropertyDescriptor {
 protected PersonMethodDelegate _method;
 protected Type _methodReturnType;

 public PersonMethodDescriptor(string name, PersonMethodDelegate method,
  Type methodReturnType)
  : base(name, null) {
  _method = method;
  _methodReturnType = methodReturnType;
 }

 public override object GetValue(object component) {
  Person p = (Person)component;
  return _method(p);
 }

 public override Type ComponentType {
  get { return typeof(Person); }
 }

 public override Type PropertyType {
  get { return _methodReturnType; }
 }

 public override bool CanResetValue(object component) {
  return false;
 }
 
 public override void ResetValue(object component) { }
 
 public override bool IsReadOnly {
  get { return true; }
 }

 public override void SetValue(object component, object value) { }

 public override bool ShouldSerializeValue(object component) {
  return false;
 }
}

Finally, we have the actual code to create the collection, set the view, and bind it to a datagridview.

 PersonCollection pc = new PersonCollection(new PersonFullNameAgeView());
 pc.Add(new Person("John", "Opincar, Jr", "Thomas", new DateTime(1968, 8, 12)));
 pc.Add(new Person("Abraham", "Lincoln", "X", new DateTime(1825, 1, 1)));
 pc.Add(new Person("John", "Smith", "David", new DateTime(1985, 2, 15)));
 this.dataGridView1.DataSource = pc;

Here’s a screenshot of what is displayed in the grid:

itypedlistdemo.jpg

Some final thoughts. There are many different ways to implement subclasses of PropertyDescriptor. I have demonstrated one and it only provides readonly virtual properties based on methods. You can provide read-write virtual properties that are based on properties or methods. You could write a very specific subclass that takes the component and an index to provide read-write access to an indexed property. You could write very generic subclass that takes a member name, component type, getter and setter delegates that would serve all your needs (I ended up doing this myself) at the cost of being a little harder to follow and probably less effecient.

Bear in mind that this sample code is for demonstration purposes only. Obviously, you could have just implemented FullName and Age as public properties on Person. From a purist standpoint, I would argue that making changes to the Person class solely for display purposes not necessarily a good thing. However, if you have compound or nested data that you wish to display as a single row in a crosstab, this technique is invaluable.

Advertisements

Oracle (PL/SQL) Equivalents for MS SQL Server (T-SQL) Constructs

For the last several months, I have been working primarily with Oracle 8i and 10g databases.  Over the last 10 years, 95% of my work has been with MS SQL server.  The other 5% was with Oracle, but it was limited to simple SELECT statements or calling stored procedures that other people wrote.  You don’t realize how much basic knowledge you take for granted until you get outside of your comfort zone and have to stop and figure out how to do every little thing.  Here’s a list of the top 10 things I wish I had known about Oracle – PL/SQL coming from an MS SQL Server – T-SQL background.

  1. Bind variables versus PL/SQL variables instead of just variables.  Oversimplifying, use “:” in place of “@” when you want a parameterized query.  This is the most awkward and annoying aspect of Oracle compared to MS SQL Server, IMO.  The bottom line is that you can’t very easily copy code from a stored procedure and run it in Toad or SQL Developer.  Nor can you directly assign values to bind variables — you get prompted for them.
  2. You can’t do a SELECT without an INTO inside of a PL/SQL block.  This is really puzzling.  Especially since you can’t have anything but bind variables outside of a PL/SQL block and you can’t programmatically assign values to bind variables.  All of this adds up to being forced to be prompted for bind variable values every time you run a parameterized query in the SQL Worksheet or in Toad.
  3. There’s no built-in Query Analyzer/SSMS Query tool.  There’s an extremely crippled but free version of Toad.  This what I use if I have to work with a pre-10g instance.  There’s Oracle SQL Developer which is what I use if I am working with a 10g instance.  They both are lacking compared to Query Analyzer/SSMS Query window.  You will press ctrl-E and ctrl-R repeatedly with no effect so many times it will make you sick.
  4. DATEADD doesn’t exist.  You have to add fractional days to dates instead.  DATEADD(hour, 1, D) would be D + 1/24.
  5. INT can’t be used for columns.  You use NUMBER instead.  This can be a real pain in ADO.NET because you have to explicitly convert all those NUMBERs to ints unless you want to treat them as decimals.
  6. No IDENTITY columns.  Instead of typing that one word you have to create a completely separate object called a SEQUENCE and you also have to write a trigger.
  7. You can’t have multiple databases on an Oracle instance.  There are just multiple schemas which are really owners.  Public synonyms are used to make objects from different schemas accessible without explicit schema specification. 
  8. The default date format does not include the time part.  You have to “alter session set NLS_DATE_FORMAT=’MM/DD/YYYY HH24:MI’;” to see the time part.
  9. You concatenate strings with the oh-so-intuitive || instead of the obscure + operation.  Alternatively you can use CONCAT.
  10. You can’t PRINT.  There’s a print-like statement, but it only works in SQL*PLUS.  You don’t ever really want to use SQL*PLUS unless you are a massochistic, command-line-loving freak from the past that still uses VI or EMACS.  Actually, there’s a web-based version of SQL*PLUS that’s usable but I like to stay in the SQL Worksheet.

Those are the top 10 — the things I really struggled to understand coming from the MS SQL side of things.  Here are some more that I found useful:

  • SP_TABLES = SELECT * FROM ALL_TABLES
  • sp_help = DESCRIBE
  • You can’t just SELECT without a FROM clause.  You always have to include FROM DUAL.  I still don’t know what “DUAL” is.
  • GETDATE() = SYSDATE
  • SUSER_SNAME() = USER
  • Getting just the date part of a date value: CONVERT(datetime, CONVERT(varchar(10), GETDATE(), 101)) = trunc(SYSDATE)
  • Just use VARCHAR2 and don’t ask about VARCHAR.
  • You can’t return values from procedures using the RETURN statement.  Use an OUT param instead.
  • In version 8i and earlier, there’s a “rule-based” optimizer.  The rule-based optimizer can suck badly and you may need to force the join order on some of your queries.  You do that with “SELECT /*+ ordered */” (yes, it really is in the comment).
  • The Oracle compiler doesn’t do a good job of telling what’s wrong with your statement.  It can give obscure messages for missing commas and semi-colons.  Don’t forget that you must terminate each statement with a semi-colon.
  • There’s no built-in support for UUIDs.
  • Oracle does not distingish between an empty string and null (which really sucks)

Here are a couple of non-obvious things I learned about Oracle and .NET 2.0:

  • You can’t send multiple SQL statements in a batch using ADO.NET like you can with MSSQL Server
  • You can’t use the ODP.NET with the Enterprise Library
  • You can’t use non-tnsnames.ora connection strings with System.Data.Oracle but you can with ODP

Custom ConfigurationSections in .NET 2.0 .config Files

Recently, I was needing to store information about 5 instances of an application server that I was accessing from my program.  As I was entering the third set of values in <appSettings> section I decided to go a better route.  What I wanted seemed simple: a section that worked exactly like the <connectionStrings> section, but with my own attributes.  After several hours with the documentation and some on-line research, I got what I wanted.  I was hoping it would only take 20-30 minutes.  In order to provide you with the experience I hoped to have, I have written this post.

First, let show you what I wanted my section to look like:

<acmeConfiguration>
 <add name="ERCOT"
  connectionName="AcmeER" fileDir="\\server\path" baseUrl="http://server:9999" />
  <add name="PM"
   connectionName="AcmePM" fileDir="\\server2\path" baseUrl="http://serverxx:9901" />
</acmeConfiguration>

You will need to subclass three classes from System.Configuration namespace: ConfigurationSection, ConfigurationElementCollection, and ConfigurationElement which correspond to the <acmeConfiguration>, the set of elements within <acmeConfiguration>, and the <add> elements respectively.  Understanding the above is the key to simplifying this exercise.  Here’s the code that implements the section above:

public class AcmeConfigSection : ConfigurationSection {
[ConfigurationProperty("", IsRequired=true, IsDefaultCollection=true)]
  public AcmeInstanceCollection Instances {
   get { return (AcmeInstanceCollection) this[""]; }
   set { this[""] = value; }
  }
}

public class AcmeInstanceCollection : ConfigurationElementCollection {
  protected override ConfigurationElement CreateNewElement() {
   return new AcmeInstanceElement();
  }
  protected override object GetElementKey(ConfigurationElement element) {
   return ((AcmeInstanceElement) element).Name;
  }
}
public class AcmeInstanceElement : ConfigurationElement {
  [ConfigurationProperty("name", IsKey=true, IsRequired=true)]
  public string Name {
   get { return (string) base["name"]; }
   set { base["name"] = value; }
}

  [ConfigurationProperty("connectionName", IsRequired=true)]
  public string ConnectionName {
   get { return (string)base["connectionName"]; }
   set { base["connectionName"] = value; }
  }

  [ConfigurationProperty("fileDir", IsRequired = true)]
  public string FileDir {
   get { return (string)base["fileDir"]; }
   set { base["fileDir"] = value; }
  }

  [ConfigurationProperty("baseUrl", IsRequired = true)]
  public string BaseUrl {
   get { return (string)base["baseUrl"]; }
   set { base["baseUrl"] = value; }
  }
 }

Finally, for convenience, I created a class to provide static access to this collection similar to System.Configuration.ConfigurationManager.ConnectionStrings:

public class AcmeConfig {
  protected static Dictionary<string, AcmeInstanceElement> _instances;
  static AcmeConfig() {
   _instances = new Dictionary<string,AcmeInstanceElement>();
   AcmeConfigSection sec = (AcmeConfigSection) System.Configuration.ConfigurationManager.GetSection("AcmeConfiguration");
   foreach ( AcmeInstanceElement i in sec.Instances ) {
    _instances.Add(i.Name, i);
   }
  }
  public static AcmeInstanceElement Instances(string instanceName) {
   return _instances[instanceName];
  }

  private AcmeConfig() {
  }

}

That’s it.  I wish MS would put a sample like mine in the docs because I think this is what 99% of developers are looking for.

Generics, Invalid Cast Exceptions, and Non-Generic Base Classes

Its hard even for me to believe that I’ve been coding for 25 years now.  I started writing code when I was 15 and I’ve been doing it pretty much non-stop ever since.  I took an upper division survey of programming languages class back in my college days at UT where I learned two things:  One, I never wanted to write Cobol again, and, two, ADA was really cool because it had generics.

I was really excited when c# 2.0 was announced and I heard that it would include generics.  Over the last couple of years I’ve had the opportunity to use generics in c# in the real world and I’ve generally been pleased with the results.  Most of what I’ve done has been simple, collection-related coding that made straight forward use of System.Collections.Generic classes.  I have also developed some relatively simple classes that either derived from built-in generic types or actually were generic themselves.

Given my quarter-century of development experience combined with my familiarity and practical use of generics in production-deployed code, I was surprised to find myself spinning my wheels for over a day on what seemed like a relatively simple task at the outset (How many times have I thought that :)).  So without further ado, let me share some valuable nuggets that I gathered over a couple of long days and late nights that didn’t produce what I expected.

I had an existing class, Series, that stored data for a single calendar day.  This is a very useful class in the power scheduling business domain that I work in because it provides:

  • varying the period of the data from 1 to 60 minutes
  • merging and splitting functionality to change the period of the data
  • mathematical operations with overloaded operators
  • daylight savings time transition support
  • loading and saving to and from datasets

If you’ve never worked with time-series data in a 24×7 sub-hourly scheduling environment, then you may not appreciate the complexities involved, particularly with respect to the daylight savings time transition days.  It’s incredible for me to think how much time we spend coding for those 2 hours out of 8,760 hours  in a year.  After years of experience, I’ve concluded that using GMT behind the scenes, and then computing the GMT start and and end times for a given calendar day is definitely the cleanest way to handle this.  One day has 23 hours, another has 25, and the rest have 24 but time remains continuous in GMT.  The previous hour is always one hour less and the next hour is always one hour more.  That sounds obvious, but its only true if you always stick with GMT.

I digress.  The Series class is great.  In fact, I also had a SeriesCollection class.  Series has a DateOf field and a Description field so stuffing a bunch of Series in a collection was very useful for displaying different types of data across time and even for displaying cross-tabbed data to the user in a grid.

There were several sub-classes but they weren’t sub-classes that added any real functionality.  They simply allowed you to store a different set fields/keys/tags with the series of numerical data for that day.  So when I needed to add yet another pair of Series, SeriesCollection sub-classes I decided enough was enough, time to make these generic.

My seemingly simple idea was to convert Series and SeriesCollection to Series<I> and SeriesCollection<I>, where I would be an interface that provided the “fields” that described the assocated Series.  I could really be any class you wanted as long as it implemented the ISeriesDescriptor interface.  Here’s a simplified, high-level overview of the interface and classes:

public interface ISeriesDescriptor {
 void GetColDefs(DataColumn[] cols);
 void GetValues(object[] vals);
 int Count { get; }
}

public class Series<I> where I : ISeriesDescriptor {
 double this[int] { get {...} set {...} }
 void AddInPlace(Series<I> other) {...}
 static DataSeries operator +(DataSeries d1, double val) {...}
 public static DataSeries operator +(DataSeries d1, double val) {...}
}

public class SeriesCollection<I> : IList<Series<I>>, ITypedList  where I : ISeriesDescriptor {...}

Those of you that have also been lured down this seemingly inviting path may recognize the dreaded nested <<>>.  I am leaving out several other classes that help provide the ITypedList implementation and some cool “virtual property” functionality for databinding.  The key here is that I blindly started replacing every occurence of Series with Series<I> and every occurence of what used to be “string Description” with “I Description.”  The <I> started to ripple outwards in a seemingly never-ending spiral.  Soon, I had 8 classes that where based on <I> in varying fashions. 

I (myself) was also using reflection in my SeriesCollection<I> class to add new instances of Series<I> to the collection.  So when I finally finished several hours of making changes, trying to compile, making more changes, ad infinitum, it was with great pleasure that I finally ran my newly compiling, super-generic code.  I then began modifying the code that would use this wonderful new construct.  I created subclasses of Series<I> and SeriesCollection<I> that didn’t add anything other than specific load from database methods.  Really all they did was invoke different methods on a data-access object and then populate the collection using a reflected constructor on Series<I>.  As I was doing this I thought shouldn’t I be using a factory method on a passed in object here?  Naw, the reflection keeps it more “generic.”

public class StockDescriptor : ISeriesDescriptor {...}
public class StockSeries : Series<StockDescriptor> {...}
public class StockSeriesCollection : SeriesCollection<StockDescripor> {...}

That was quick.  I run the code.  I am binding a StockSeriesCollection to a DataGridview.  Wow, that implementation of ITypedList is working beautifully.  I want to add some validation code so I need to take grd.Rows[e.RowIndex].DataBound and cast it to a StockSeries.  At run-time I get an invalid cast exception.  What?  You’re telling me I can’t cast a Series<StockDescriptor> to a StockSeries when StockSeries : Series<StockDescriptor>?  When I write this now it seems obvious that you can’t cast down the inheritance tree, only up. 

But I was so caught up in the generics aspect of it that I dug myself in even deeper.  I read the c# spec sections on generics and the type casting rules.  I try to solve the problem by adding a second type variable.  That takes a long time and yields exactly the same result. At 4am, I am really frustrated and dejected.  I go to bed.  The next morning I finally realize my simple mistake with the cast.  Then I started to look at what I’d done to my Series operators.  Before I changed to the generics, I could do:

VolumeSeries vs = new VolumeSeries();
// load it up
PriceSeries ps = new PriceSeries();
// load it up
PriceSeries result = (PriceSeries) ps * vs;

That wouldn’t be possible with my generic implementation because I hadn’t created a non-generic base or interface.  Something I hadn’t even thought about up front.  Unfortunately, this same limitation would cripple my SeriesCollection<I> class.  I could no longer put all of the different sub-classes of Series into a single SeriesCollection.  The bad news wasn’t over.  I also realized that my implementation of ITypedList which brought the ISeriesDescriptor properties “up one level” for data binding and turned the interval numeric values normally accessed via indexors into “virtual properties” for cross-tabbed display using System.Component.PropertyDescriptors wouldn’t handle a mixed collection anyway.

I hate that feeling that you just wasted a day or two of your life on what initially looked so simple and promising and turned out so ugly and inflexible.  Unfortunately, I didn’t have the time to properly refactor any of it.  I finally solved my immediate problem by replacing StockSeries with Series<StockDescriptor>.  I completed the UI component that depended on the Series and SeriesCollection. I could have just used the original set of classes and added just one more pair of subclasses an probably finished in 1/4th the time or even less.

The only silver lining of the whole affair is that I gained some valuable insight into non-trivial generic class design: generic type equivalence can be tricky and you will more than likely want a non-generic base or interface so that you can mingle different generic derivations in one collection or define operators on the base class.  I am still not sure whether using generics to achieve composition the way I tried is a good idea.  I also realized that my ITypedList implementation would need to be much more flexible to handle binding a mixed collection.  I relearned for the nth time the hard way that you really should stop and think hard about what your doing when you start changing the second or third class you weren’t expecting to touch.

—————————————–

P.S. (5/12/2007)

The classes described above have been working pretty well with some small refinements. Just because you have the <T> around, doesn’t mean you are always dealing directly with a C<T>. You may still need to reflect the actual type of the instance you are working on or one of it’s properties so that you can return properly typed object from operators.

For example:

public class SeriesCollection : List<Series<I>>, ITypedList {

public Series<I> GetTotal() {
    PropertyInfo pi = this.GetType().GetProperty(”Item”, new Type[] {typeof(int)});
    ConstructorInfo ci = pi.PropertyType.GetConstructor(new Type[] {typeof(DateTime), typeof(int)});
    DateTime dateOf = DateTime.Today;
    object obj = ci.Invoke(new object[] {dateOf, this._displayPeriod});
    Series<I> ret = (Series<I>) obj;
    foreach ( Series<I> ds in this ) {
       …total things up
    }
    return ret;
}
}

Visual Studio 2005 Help So SLOW!

I’m sure I’m the just the one millionth person to say this, but I dread pressing F1 that first time in Visual Studio 2005.  It takes forever even when you turn off the internet related stuff.  Why did they do this?  VS 2003’s help was lightning fast.  What were they thinking?  I know with intellisense, you really don’t need to go to the help that often.  But when you do, it shouldn’t be coffee break time.

PS. Thanks to Rich A we have a solution!

SQL Server only Accessible Locally

You can ping the server.  You can sign in locally using the computer name via both windows authentication and sql server authentication.  Surprisingly, you can’t even sign in locally using the computer’s own IP address or localhost.  Stop and restart sql server.  View the application log and filter for MSSQLSERVER.  Review the log entries and verify that the server is not just listening on shared memory.  If it is, there’s your problem.  Got the binn directory whereever sql server is installed and run SVRNETCN.exe, the Server Network Configuration Utility.  Enabled TCP/IP.

Windows HTTP Subversion Install

PS (04/04/2009): Just use VisualSVN Server — it takes all the pain out of installation

——————————————————–

I wrote this several months ago after spending more time than seemed reasonable getting subversion running.  Enjoy…

 Install Apache 2.0 (not 2.2): http://httpd.apache.org/download.cgi
Select public on port 80 option.  If you already have a web server running, you will get an error.  Ignore.
After install completes, Select Program|Apache|Configure|Edit..config
Change Listen 80 to Listen 8080 (or some other port), save, and restart the service using the apache feather icon in the tray
If nothing shows up in tray, run this: “C:\Program Files\Apache Group\Apache2\bin\apache” -k install -n “Apache2” and then you will see it in the tray.
Surf to http://localhost:8081/ to verify apache install

Install Subersion: http://subversion.tigris.org/files/documents/15/31465/svn-1.3.1-setup.exe

Install Tortoise: http://tortoisesvn.tigris.org/

Edit httpd.conf again and add this at the end:
<Location /svn>
  DAV svn
  SVNPath C:\SubversionRepo
</Location>
where /svn is the virtual (web) path you will use to access the repository and C:\SubversionRepo is the local filesystem path to the repository.  Restart the Apache service.

Verify that you can access the repository via http by opening windows explorer, right clicking, selecting TortoiseSVN|Repo-brower.  Type in http://localhost:8080/svn.  Right-click the top level and select Create Folder.  Enter TestApp and press OK.  If the folder creates, everything is working OK.

Now, the final step is to add digest authentication to Apache.  Open httpd.conf and uncomment this line:
LoadModule auth_digest_module modules/mod_auth_digest.so
Next, add the following lines (subbing in your own “domain” EG AcmeSoftware) to the <Directory “C:/Program Files/Apache Group/Apache2/htdocs”> secion:
  AuthType Digest
  AuthName “domain”
  AuthDigestFile “C:\Program Files\Apache Group\Apache2\passwd\passwords”
  Require valid-user

Now, you must use the command line to add users and passwords.  First, create the following directory: C:\Program Files\Apache Group\Apache2\passwd.  When adding the first user, run the following:

“C:\Program Files\Apache Group\Apache2\bin\htdigest” -c “C:\Program Files\Apache Group\Apache2\passwd\passwords” domain username

For subsequent users, remove the -c or you will recreate the file and lose the first user already stored in the file.  Now, restart Apache, browse to the repository using TortoiseSVN from Windows Explorer.  You should now be prompted for a user id and password.  Check “save authentication” to avoid being prompted for every access to the repository.  Even after you check this the first time, you will get prompted one more time on your next access to the repository.

You can do everything you need to do to manage the repository via TortoiseSVN.  Don’t get distracted by other things like WebSVN, Cygwin, Ankh (which is slow and buggy) or anything else.  Subversion + TortoiseSVN = complete solution.

If you do not have a static IP and you want to access your repository from remote locations, visit DynDns.com.  If you are behind a NAT router, you can probably configure your router to forward all incoming requests on the port you specified in the steps above to a specific private address.  Note that if you are using DHCP to assign IP private addresses to your computers, you will need to reserve a specific private address for the machine hosting SVN.