<2017 October>
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

On this page...

Search

Links

Member of...


ASP Insiders

MVP Visual Developer ASP/ASP.NET

Enter CodeZone

Blog Categories

Microsoft

Blogroll

Deutsche Resourcen

Management

Sign In
 

#  Wednesday, 27 July 2005

This post is again motivated by last week's Community Bootcamp on ASP.NET 2.0, the CBC05. I presented "Under the Covers - Exploring Internals, Page Lifecycle and the Compilation Mode" from TechEd, using the samples that Simon Calvert provided me with (special thanks fly out to Simon, Ben Miller and Rich Ersek @MS for providing us with material - I know I can be a royal pain in the posterior... sometimes at least).

The talk included a demo of a database-backed virtual path provider (files don't come from the file system but a database, dynamically). Somehow we started talking about how cool it would be if you could test your Web sites without checking them out from source control in the first place - by simply writing a virtual path provider that goes to the repository on demand. I wrote that idea down.

Actually, I didn't think I'd get around to doing that. But yesterday I decided to pester one of my devs on the #develop project, namely Daniel Grunwald. He has implemented the Subversion addin for our 2.0 version, so he had experience with NSvn, the managed API for talking to Subversion. I sent a stripped down version of the vpath provider to him, and asked him to replace database code with NSvn code where appropriate.

It didn't take long, and I had a command-line verified version back, and all I had to do was make sure that it works with ASP.NET 2.0. There were a few problems I ran into (like Subversion is case-sensitive and I didn't want that for the Web scenario). Some of the issues arose simply because client and Web developers have different backgrounds. Talk about path separators. Or directories where you have to drop assemblies.

Now, let's stop talking, let's take a look at the provider in action:

SvnVPathProvider.wmv (3.76 MB)

Want to get your hands on that DemoSiteSvn directory with the current rendition of the SubversionVirtualPathProvider? No problem, just a couple of notes up front on what you should be aware of:

  • Only file names are currently treated specially for casing. Ie directories still do react in a case sensitive way.
  • The file name cracking code needs to be reviewed. Currently, this is a quick hack.
  • appSettings need to be placed in a separate .config file. Reason is that web.config cannot be obtained via a VirtualPathProvider, and thus this file has to be checked out separately. And I don't want to get in the way of automating this by requiring entries in web.config.
  • Package it as an assembly, so only the \bin folder needs to be copied to get up and running.
  • The VirtualPathProvider requires (at the very least) anonymous access to the repository. Passing security tokens is not implemented.

With those notes out of the way, thanks fly out to the ASP.NET team for providing me with the sample of their virtual path provider in the first place. It has been a tremendous help to get this thing off the ground. And maybe in turn this sample will help others to get started:

SvnVppDemo.zip (972.37 KB)

Installation note: the two DLLs in the system32 folder need to be dropped in the respective folder of your system. Do not place them into \bin. Unless you want to get into trouble, that is.

What is left to say? Oh, the source code, of course! I thought you might be interested in reading it online instead of having to download an almost 1MB-size file first. Here it is (App_Code\SubversionVirtualPathProvider.cs):

using System;
using System.IO;
using System.Collections;
using System.Globalization;
using System.Configuration;
using System.Text;
using System.Web;
using System.Web.Util;
using System.Web.Hosting;
using System.Web.Caching;
using NSvn.Core;
using NSvn.Common;

namespace ICSharpCode.Web.Providers
{
public class SubversionVirtualPathProvider : VirtualPathProvider
{
#region class HashCodeCombiner
internal sealed class HashCodeCombiner
        {
            // Start with a seed
            private long _combinedHash = 5381;
            
            internal void AddLong(long l)
            {
                _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ l;
            }
            
            internal string CombinedHashString
            {
                get
                {
                    return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
                }
            }
}
#endregion

#region class SubversionVirtualFile
internal class SubversionVirtualFile : VirtualFile
    {
        string fullPath;
        DirectoryEntry entry;
            
        public SubversionVirtualFile(string virtualPath, string fullPath, DirectoryEntry entry)
                : base(virtualPath)
        {
            this.fullPath = fullPath;
            this.entry = entry;
        }
            
        public override bool IsDirectory {
            get {
                return entry.NodeKind == NodeKind.Directory;
            }
        }
            
        public override Stream concat()
        {
            Client client = new Client();
            MemoryStream ms = new MemoryStream();
            client.Cat(ms, fullPath, Revision.Head);

            // .Cat closes the stream, so we have to copy it
            MemoryStream ms2 = new MemoryStream(ms.GetBuffer());
            ms2.Position = 0;
            return ms2;
        }
}
#endregion

public static void AppInitialize()
    {
        SubversionVirtualPathProvider provider = new SubversionVirtualPathProvider();
        HostingEnvironment.RegisterVirtualPathProvider(provider);
    }
        
    string GetSvnFullpath(string virtualPath)
    {
        if (bool.Parse(ConfigurationManager.AppSettings["svnvppStripVdir"]))
        {
            // this will break root Webs, StripVdir should be the default however
            int pos = virtualPath.IndexOf('/', 1);
            virtualPath = virtualPath.Substring(pos, virtualPath.Length - pos);
        }

        return ConfigurationManager.AppSettings["svnvppRepositoryUrl"]
            + virtualPath;
        }

    string GetSvnFullpath(string virtualPath, string fileName)
    {
        return FixupSvnFullpath(GetSvnFullpath(virtualPath), fileName);
    }

    // Subversion is case sensitive, this we switch the filename here
    string FixupSvnFullpath(string svnPath, string fileName)
    {
        int pos = svnPath.LastIndexOf('/');
        string parentDirectory = svnPath.Substring(0, pos + 1);
        return parentDirectory + fileName;
    }
        
    DirectoryEntry GetEntry(string virtualPath)
    {
        Client svnClient = new Client();
        string fullPath = GetSvnFullpath(virtualPath);
        int pos = fullPath.LastIndexOf('/');
        string parentDirectory = fullPath.Substring(0, pos);
        string entryName = fullPath.Substring(pos + 1);

        try
        {
            DirectoryEntry[] entries = svnClient.List(parentDirectory, Revision.Head, false);
            foreach (DirectoryEntry entry in entries) 
            {   
                if (0 == String.Compare(entry.Path, entryName, true))
                        return entry;
            }

            return null;
        }
        catch (SvnClientException ex)
        {
            if (ex.ErrorCode == 160013) // parent directory not found
                return null;

            throw;
        }
    }
        
        public override bool FileExists(string virtualPath)
        {
            DirectoryEntry e = GetEntry(virtualPath);
            if (e != null)
                return e.NodeKind == NodeKind.File;

            return Previous.FileExists(virtualPath);
        }
        
        public override bool DirectoryExists(string virtualDir)
        {
            DirectoryEntry e = GetEntry(virtualDir);
            if (e != null)
                return e.NodeKind == NodeKind.Directory;
            return Previous.FileExists(virtualDir);
        }
        
        // Obtain the file. This will only be called if the hash that we return is
        // different than that the runtime holds on to as a cached indicator.
        public override VirtualFile GetFile(string virtualPath)
        {
            DirectoryEntry e = GetEntry(virtualPath);
            if (e != null)
                return new SubversionVirtualFile(virtualPath, GetSvnFullpath(virtualPath, e.Path), e);
            // Default to the previous implementation
            return Previous.GetFile(virtualPath);
        }
        
        /// ///////////////////////////////////////////////////////////////
        /// Return a hash value indicating a key to test this file and dependencies have not been
        /// modified
        public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
        {
            HashCodeCombiner hashCodeCombiner = new HashCodeCombiner();
            
            ArrayList unrecognizedDependencies = new ArrayList();
            
            foreach (string virtualDependency in virtualPathDependencies)
            {
                DirectoryEntry e = GetEntry(virtualDependency);
                if (e != null) {
                    hashCodeCombiner.AddLong(e.Size);
                    hashCodeCombiner.AddLong(e.CreatedRevision);
                } else {
                    unrecognizedDependencies.Add(unrecognizedDependencies);
                }
            }
            
            string result = hashCodeCombiner.CombinedHashString;
            
            if (unrecognizedDependencies.Count > 0)
            {
                result += Previous.GetFileHash(virtualPath, unrecognizedDependencies);
            }
            
            return result;
        }
        
        /// ///////////////////////////////////////////////////////////////
        /// The cache dependency is a specialized object that means that the runtime
        /// can perform file monitoring and change notifications directly
        public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            // This VPP does not create CacheDependencies
            DirectoryEntry e = GetEntry(virtualPath);
            if (e != null)
                return null;
            
            return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }
    }
}

 

Categories: 2 Ohhhh | ASP.NET | Community | Subversion

 



© Copyright 2017 Christoph Wille

newtelligence dasBlog 2.3.9074.18820
Subscribe to this weblog's RSS feed with SharpReader, Radio Userland, NewsGator or any other aggregator listening on port 5335 by clicking this button.   RSS 2.0|Atom 1.0  Send mail to the author(s)

 
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.