Thursday, 1 May 2008

CruiseControl.Net

I'm working with CruiseControl.net this week. First time I've worked with the product and I'm finding a couple of issues (that are all my mistakes) but that aren't particularly well documented. So this is as much for my benefit as anything else.

Firstly, last night after editing the config file I couldn't get the build to run. Now, there are several config files. in c:\Program Files\CruiseControl.NET\server, the main one is the ccnet.config file, this is the default location for this file, that location can be changed, to do that you'd edit the ccservice.exe.config file.

The ccservice.exe.config file is re-read when the CCNet service is (re)started, as is the ccnet.config file. When you edit the ccnet.config file it appears that the service does re-read the file, however, if the file is invalid the service ignores it, and doesn't appear to log this fact anywhere. Re-starting the service will force a re-read of the config file and then log an error if the file is invalid.

I was changing a CCNet config file to do multiple checkouts, after a bit of trial and error the source control section of the file ended up looking like this

<sourcecontrol type="multi">
	<sourceControls  autoGetSource="true" applyLabel="true">
		<vsts autoGetSource="true" >
			<server>http://server:8080</server>
			<project>$/FOO/SharedUILibraries</project>
			<username>user</username>
			<password>user</password>
			<domain>nt</domain>
			<workingDirectory>
                               C:\...\FOO\SharedUILibraries
                        </workingDirectory>
			<cleanCopy>true</cleanCopy>
			<workspace>WS_MINE</workspace>
			<deleteWorkspace>true</deleteWorkspace>
		</vsts>
		<vsts autoGetSource="true" >
			<server>http://server:8080</server>
			<project>$/FOO/SharedEnterpriseLibrary</project>
			<username>user</username>
			<password>user</password>
			<domain>nt</domain>
                        <workingDirectory>
                            C:\...\SharedEnterpriseLibrary
                        </workingDirectory>
			<cleanCopy>true</cleanCopy>
			<workspace>WS_MINE</workspace>
			<deleteWorkspace>true</deleteWorkspace>
		</vsts>
		<vsts autoGetSource="true" >
			<server>http://server:8080</server>
			<project>$/FOO/WD</project>
			<username>user</username>
			<password>user</password>
			<domain>nt</domain>
			<workingDirectory>
                            C:\...\FOO\WD
                        </workingDirectory>
			<cleanCopy>true</cleanCopy>
			<workspace>WS_MINE</workspace>
			<deleteWorkspace>true</deleteWorkspace>
		</vsts>
	</sourceControls>
</sourcecontrol>
And this worked, however there was one gotcha.

Before getting this to a working stage I was getting a status in the CCNet dashboard of CheckingModifications, whatever I did I'd get this status, even forcing a build wouldn't change it. It turned out the reason for this was that I had put the wrong name in the project element. This meant that CCNet was trying to look for bits that didn't exist, and sat there spinning its wheels. Once corrected everything worked fine.

So if you get the CheckingModifications status, check that your source control settings are correct and that you can do a checkout

Posted by kevin at 7:09 AM in Net

Wednesday, 26 March 2008

LINQ Nasties

This was raised on an internal mailing list by Brock Allen, and as he isn't blogging much (not that I can comment) I thought I'd raise it here.

Look at this query:

NorthwindDataContext db = new NorthwindDataContext();
db.Log = Console.Out;

var supp = (from s in db.Suppliers
           select s).FirstOrDefault();

var prods = from p in supp.Products
           where p.UnitsInStock > 10
           select p;

foreach (var p in prods)
{
   Console.WriteLine(p.ProductName);
}

Looks pretty harmless, get a supplier, then get the products fromt that supplier where the UnitsInStock > 10.

Problem is if yuou run this, you see the following in SQLProfiler

exec sp_executesql N'SELECT TOP (1) [t0].[SupplierID],
[t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle],
[t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode],
[t0].[Country], [t0].[Phone], [t0].[Fax], [t0].[HomePage]
FROM [dbo].[Suppliers] AS [t0]
WHERE [t0].[SupplierID] = @p0',N'@p0 int',@p0=1

exec sp_executesql N'SELECT [t0].[ProductID], [t0].[ProductName],
[t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit],
[t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder],
[t0].[ReorderLevel], [t0].[Discontinued]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[SupplierID] = @p0',N'@p0 int',@p0=1

The first statement is executed when you call FirstOrDefault(), the second when you execute the foreach. Notice anything about the statements? They are almost exactly the same, except the second statement loses the TOP(1) part. This means that the where clause is happening on the client! Not a big deal you may think, unless of course the select returns 1000s of rows.

Fixing this is easy

var prods = from p in db.Products
           join s in db.Suppliers
           on p.SupplierID equals s.SupplierID
           where s.SupplierID == 2
           && p.UnitsInStock > 10
           select p;

which gives:

exec sp_executesql N'SELECT [t0].[ProductID], [t0].[ProductName],
[t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit],
[t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder],
[t0].[ReorderLevel], [t0].[Discontinued]
FROM [dbo].[Products] AS [t0]
INNER JOIN [dbo].[Suppliers] AS [t1] ON [t0].[SupplierID] = ([t1].[SupplierID])
WHERE ([t1].[SupplierID] = @p0) AND ([t0].[UnitsInStock] > @p1)',N'@p0
int,@p1 int',@p0=2,@p1=10

Which is probably what you want.

I like LINQ, especially LINQ to XML, but this reminds of the dark days of EJB Entity Beans. You really have to profile the generated code to understand exactly what LINQ is giving you, don't be seduced by the ease of use.

Update

Ian Griffith pointed this out:

"One subtlety with your LINQ Nasties post is that you kind of make it look like the solution is to use one query instead of two. In fact, the key is understanding which operations will evaluate the query and which won't."

And he's dead right, Ian's blogged about this here. Even if you don't read Ian's full post, read the "Know Your Tools" section at the end.

Posted by kevin at 2:28 PM in Net

Thursday, 20 March 2008

ASP.Net Page_XXX events

In ASP.Net the engine looks for various methods to call to handle events such as
public void Page_Load(object sender, EventArgs e) {}
I had always assumed that the framework searched for these methods based on their full signature, turns out this isn't the case. The ASP.Net MVC framework uses a
public void Page_Load() {}
method, and I was trolling through the code trying to find where this is called from, when I ended up inside the Page ProcessRequest method, i.e. the default ASP.net processing. Which meant there was no special processing for this version of Page_Load.

I quickly create a bog standard web app and added a no parameter Page_Load to it, and sure enough it fires!

Note that if you have the parameterised and no parameter Page_Load only the parameterised one is called.

Posted by kevin at 10:03 AM in Net

Good Things With MVC

I've just started playing with ASP.NET MVC (or Microsoft's Homage to Ruby on Rails as Tim Ewald labelled it!), it's good to see a community growing up around this.

Posted by kevin at 6:02 AM in Net

Upgrading SQL Server

As a developer I constantly use Visual Studio and SQL Server, and it turns out thatn when you install these, the install order is important. If you install SQL Server before VS200x then you get all the SQL tools such as the Management Studio and the Profiller. However, if you install VS200x and then install SQLServer the tools do not get installed. This has annoyed and frustrated me for years, then I discovered it wasn't just me, other people were having the same problem.

If only I'd read the warnings!

This week when I was going through the process again, having already installed VS200x I paid attention to what the SQL Server installation was telling me. When you install SQLServer it goes through a systems check to make sure you are able to install the software, things such as how much memory you have, whether you have IIS etc. One of the warnings I got was an "Edition Change Check (Warning)". Normally I ignore this as it's only a warning. But this time I took notice

The warning says

To change an existing instance of Microsoft SQL Server 2005 to a different edition of SQL Server 2005, you must run SQL Server 2005 Setup from the command prompt and include the SKUUPGRADE=1 parameter.
and this time I did take notice. I fired up a command prompt, flipped to the Servers directory and ran
Setup SKUUPGRADE=1
and joy of joys all the tools appear as part of the install.

Of course, the annoying thing is, why doesn't the installer do this. It's detected the problem, one little 'Do you want to upgrade?' checkbox wouldn't have hurt anybody!

Posted by kevin at 5:11 AM in Net

Tuesday, 5 June 2007

How the CI Example From MS Works

Continuous Integration is not built into the current version on TFS, however MS ship an un-official CI solution. I've never had reason to dig into how this worked until the last day or so and thought I'd post what I found.

When the CI example receives a check-in event it queues a new build. It does this by calling QueueUserWorkItem which means it uses the .Net thread pool to do the work. The web service calls QUWI for each unique build type that's been scheduled. So if I have two build types called typeA and typeB, if I schedule typeA and typeB they get separate thread pool threads, one for each type. If while typeA is building and another notification comes in then the build type doesn't get re-scheduled. What happens is that the thread running the typeA build looks at a flag to see if another build has been scheduled for this type. If a build is pending then it simply loops around and runs the next build in the queue. So there's only one build type per thread. If a given build type is already executing and another notification comes in then the build runs again.

This means that each check-in does not kick off a separate build. A build will run after 1 or more check-ins and after the previous build has completed.

Posted by kevin at 4:34 PM in Net

Let's Crush This Myth Now or VSTS does do TDD!

I've just read a post part of which made my blood boil (and I'm not going to link to it but just say that he's not "my very good friend"). The post was comparing VSTS to NUnit and said "Moreover, it's solution inside VSTS is not able to be used in a way that supports TDD as we know it, with its emphasis on generating tests from code." This is complete bullshit. There is nothing inherent in VSTS that stops it supporting TDD. VSTS unit testing is incredibly like NUnit. As an NUnit user (and I am) you can easily move to using VSTS, it's simply a matter of adapting to different attribute names for setting up the tests and different method names for the asserts. In fact it could be argued that with the ability to generate method stubs from within the code VSTS is more able to support TDD than NUnit.

For example I can do this

public void MyTest()
{
   User u = new User();
   u.CallANewMethod();
}

where

CallANewMethod
doesn't exist.

At this point, click on

CallANewMethod
and select 'generate metod stub' and bingo, test driven development. I've written the test and generated stub from the test.

Posted by kevin at 4:14 PM in Net

Thursday, 24 May 2007

VSTS Guidance

For the last month or so I've been working with J.D. Meier and his Patterns and Practices group at Microsoft. The guys have just released a beta copy of the Team Foundation Server guide which I've made a (small) contribution to. You can see the guide here.

Read!
Enjoy!
Comment!

Posted by kevin at 7:59 AM in Net

Wednesday, 23 August 2006

ASP.Net Web Project Details

I've been asked this on a course several times so I thought I'd mention it here. By default ASP.Net 2.0 web projects don't have a project file, but project specific information has to be kept somewhere. That somewhere is in C:\documents and settings\[user name]\Local Settings\Application Data\Microsoft\WebsiteCache

Thanks to Simon Horrell and Brock for getting to the bottom of this.

Posted by kevin at 9:09 AM in Net

Wednesday, 9 August 2006

TestResults "In" Directory

When unit testing in VSTS I always puzzled as to how the "In" TestResults sub-directory was used, the "Out" directory is obvious. Turns out that it is used for two things. Firstly the code coverage results are stored there. Secondly it is used as the location of files used as part of the test that aren't copied to the output directory. Files can be put into the "In" directory by calling TestContext.AddResultFile("filename"); You could use this to, for example, store files that were created and/or written to as part of the testing process.

Thanks to Michael Koltachev for pointing this out on the VSTS forums.

Posted by kevin at 10:09 AM in Net

Sunday, 6 August 2006

Access is denied. in VSTS Team Build

A wise man once said "A man who never made a mistake, never made anything", well I've just made a fundamental error and learned a lot from it.

I've been using Team Build to build projects and recently kept getting "Access is denied." on files in either a project's obj directory or in the bin directory. This didn't happen for all the projects within a solution, just one or two. Checking the security within the build server showed that all the directories for all the projects had the same security settings. It took me a while to figure out what was happening!

Some time during the lifetime of the project Visual Studio had crashed . I had some projects that were in the solution but had not been added to source control correctly. To fix this I went through and added the folders containing the projects to source control, then checked everything in. This was the mistake. When I added the folder it also meant I added the binary files, i.e. the files in the obj and bin folders. During a build the buildserver checks out the files defined in the workspace mappings, including the binaries that I had now added. These files are checked out as read-only. Of course, when the project was built, as the binaries were read-only they could not over-written so the build was failing.

Deleting the binaries from source control fixed the problem. And the moral of the story is, don't put anything into source control that wil be re-built as part of the standard project build!

Posted by kevin at 11:10 AM in Net

Monday, 24 July 2006

Reports in VSTS and case sensitvity

I've been meaning to blog about this for a while. You can create new reports for use in your VSTS project. The mechanism is easy, you have to create a new Business Intelligence Project; choose Report Server Wizard; and create a report. You then want to deploy the report, to do that, in the project propertiews you specify the target report folder and the target server URL. This is where it gets weird.

The TargetReportFolder is the name of the TFS project you want the report deployed to. This value is Case Sensitive!!! But the bizarre thing is, if you enter a case in-sensitive name the report will deploy OK. It will also run OK from report services. It's only when you go to Team Explorer and try and get a list of reports that you will notice an error. At that point you will get the report folder (in Team Explorer) with a little red star and no idea as to the problem.

You need to delete the report in Report Services, make sure the TargetReportFolder name is the correct case and re-deploy.

Posted by kevin at 6:00 PM in Net

Thursday, 29 June 2006

log4Net and ASP.Net Medium Trust

I'm writing an an ASP 2 application and I wanted to add loggin functionallity via log4Net. In ASP.Net you configure log4Net by adding a new config section to the web.config file, log4Net can then read this configuration via a call to XmlConfigurator.Configure() placed in the Application_Start event.

This all works if the ASP.Net application is run in full trust, however I want my app to work in medium trust (otherwise Dominick will send the boys around) and this proved more difficult. The problem is that log4Net uses the new Configuration APIs and these demand the System.Configuration.ConfigurationPermission which is medium trust fails.

After a few false starts I found the answer in Stefan Schackow's excellent book Professional ASP.Net 2.0 Security, Membership and Role Management. In ASP.Net 2.0 there is a new attribute you can add to the section element in web.config, the attribute is requirePermission, setting this to false stops the configuration APIs demanding permissions. My log4net section now looks like this

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"  requirePermission="false"/>
  </configSections>
and log4net now works in medium trust.

Posted by kevin at 10:47 PM in Net

Tuesday, 18 April 2006

Browse WebApp Here

It's all Dominick's fault. He pointed me at this blog entry. After I'd downloaded this I realised it wasn't what I thought, very cool, but not what I was expecting.

I was expecting this to be a 'Run Cassini and browse to the default page' web extension and as it wasn't I decided to write my own.

The code is available here. The zip comes with a .reg file that you need to edit to include the fully qualified path the the executable, the readme.txt shows and example. After it is installed you should be able to right click on a folder and see this .
Select 'Browse Web Application Here' and Cassini should start and a browser page should be opened connecting to the web site. You will need Visual Studio 2005 installed (Cassini is shipped with VS 2005).

Enjoy, and as always feedback is welcome.

Posted by kevin at 2:32 PM in Net

Sunday, 16 April 2006

Visual Studio Custom Checkin Policy Template

I've been spending some time writing custom checkin policies for VSTS, so many in fact that I decided to write a Visual Studio template. This weas very straightforward except for the fact that I when you edit the .vstemplate file you also need to remember to edit the .csproj file (if any file names are affected).

The template is here. Copy this file to your "Visual Studio user project templates location" (you can find this under Tools..Options..Projects and Solutions") and you should be good to go.

Any feedback gratefully received.

Posted by kevin at 5:35 PM in Net

Saturday, 15 April 2006

Using Config Files to Manage DataDrivenTesting in VSTS

I've posted about Data Driven Testing before, unfortunately that solution (setting the DataDirectory attribute) no longer works. It looks like the between Beta3 (when the entry was written) and the release the way the test code was parsed has changed. Setting the DataDirectory now fails to work. A colleague of mine (Simon Horrell) came up with a better solution which I thought I'd document more fully here.

When using the DataSource attribute in the testing code you can, rather than entering all the details for the attribute, instead refer to an App.config file. This file would then contain the connection string and the other details needed by the data source.

This makes our code look like this

[TestMethod]
[DataSource("dataDrivenTestingDS")]
public void TestFirePhotonTorpedoe()
{
    // code here
}
This refers to an entry in an app.config file called "dataDrivenTestingDS". This entry is part of a custom configuration section parsed by the testing framework.

To get this code to work you need to add an App.config file to your testing project. This file will need to have three configuration sections. The first is for the connection string that would appear in the DataSource attribute of the test. You also need to add a configuration section handler that is part of the unit testing framework. This custom handler parsers a section called DataSources that contains the information the DataSource attribute needs.

The config file looks like this

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microsoft.visualstudio.testtools" 
        type="Microsoft.VisualStudio.TestTools.UnitTesting.TestConfigurationSection, 
Microsoft.VisualStudio.QualityTools.UnitTestFramework, 
Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  </configSections>
  <connectionStrings>
    <add name="dataDrivenTesting" 
connectionString="server=.\SQLExpress;AttachDBFilename=|DataDirectory|\StarShipTest.mdf;Integrated 
Security=True" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <microsoft.visualstudio.testtools>
    <dataSources>
      <add name="dataDrivenTestingDS" connectionString="dataDrivenTesting"
        dataTableName="FireTorpedoesTest" dataAccessMethod="Sequential"/>
    </dataSources>
  </microsoft.visualstudio.testtools>
</configuration>
This doesn't quite get you all the way home as the data base still has to becopied to the 'out' directory wasting space, and hardcoding the fully qualified directory name is not an option (the config file should be under source control). This means we need to amend the App.config file to use an external per user file containing the user's settings, something like this.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microsoft.visualstudio.testtools" 
        type="Microsoft.VisualStudio.TestTools.UnitTesting.TestConfigurationSection, 
Microsoft.VisualStudio.QualityTools.UnitTestFramework, 
Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  </configSections>
  <connectionStrings configSource="mysettings.config">
  </connectionStrings>
  <microsoft.visualstudio.testtools>
    <dataSources>
      <add name="dataDrivenTestingDS" connectionString="dataDrivenTesting"
        dataTableName="FireTorpedoesTest" dataAccessMethod="Sequential"/>
    </dataSources>
  </microsoft.visualstudio.testtools>
</configuration>
and the external configuration file contains the following
<connectionStrings>
  <add name="dataDrivenTesting" connectionString="server=.\SQLExpress;AttachDBFilename=c:\datadirectory\StarShipTest.mdf;Integrated 
Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
You need to remember to deploy the per user config file (mysettings.config above) to the 'out' directory (the App.config is deployed and renamed automatically by Visual Studio), I typically do this in the testrunconfig configuration file.

Your developers can check the App.confg into source control and keep the mysettings.config only on their local machine.

Posted by kevin at 7:10 PM in Net

Tuesday, 7 March 2006

VMWare, TFS and Cloned Images

I'm having a real wierd problem trying to install TFS into a VMWare image and I wonder if anybody has seen the same thing and maybe has a solution. I have a VMWare image that is a Domain Controller and I create another image onto which I install TFS. If I create a clean VMWare image for the TFS the install works and I can create a new team project, however if I use a cloned image then the install succeeds but I cannot create a new team project.

What I tried is this. Create a 'base' Windows 2003 Enterprise image. Clone this and create a DC (this works). Clone the base image again and install TFS. That seems to work. However in the cloned TFS image I cannot create a new team project; each time I try I fail trying to connect to Sharepoint. The error I see is TF30177, I've hunted this down on various forums but none of the fixes works for me. If I do the exact same install on a clean (non-cloned) image then everything is fine.

For each cloned image I run the NewSid tool from SysInternals make sure the machine is in the domain and all the accounts are correct.

I've hit the same problems in Beta3 refresh and in the RC. Anybody with any ideas feel free to contact me.

Updated March 8th 19:00

I had two replies to this from colleagues. One from Dominick Baier who pointed me at this post thinking I had the same issue. I tried this, rebooting after each step, but no luck. The cloned image still failed.

Then I had an email from Mark Smith. Mark had had a similar issue and he pointed me here. Mark thought I had the same worker process problem that he had. But comparing the "linked clone" VM I had with the full clone the IIS setups looked exactly the same. However, something else in Mark's post caught ny eye: he said

"You must install IIS after installing Active Directory"

Now, the base VNWare image I was cloning had IIS installed. I would then clone and add the cloned image to the domain. Seeing the above comment I decided to first un-install IIS, then add the machine to the domain, re-install IIS and then do a full TFS install.

And it worked. I now have a linked clone with a full TFS install. Thanks to Dominick and Mark for suggesting things that pointed me in the right direction.

Posted by kevin at 1:10 PM in Net

Sunday, 5 March 2006

Adding non-SQL Datasources to Web Test

A question I've been asked frequently is can I use something other than SQL Server as a datasource in, for example, a web test.

Sean Lumley's post answers this question.

Posted by kevin at 3:09 PM in Net

Friday, 3 March 2006

VSTS Keyword Expansion

Just to answer a question from DevWeek, VSTS Source Control does not currently support keyword expansion

Posted by kevin at 8:01 AM in Net

Thursday, 2 March 2006

VSTS References

For those folks that attended mine and Simon's VSTS talks at DevWeek and those who have been on the VSTS training classes I've put the VSTS references here

For those who have no idea what the reference list is, it's a collection of VSTS sites and references that I've found extremely useful when learning VSTS.

Posted by kevin at 3:21 PM in Net

Unit Testing Web Applications in VSTS

I just realized I haven't blogged anything since the end of January. This is because I taught our first VSTS class in Boston at the end of January, and the buildup to that class was so intense that I decided to take it easy for a week or so, then other things start to pile up and less important things like blogging got left behind. Anyway, enough of my whining!

One of the chapters I taught in January was on unit testing web applications and web services. This is different to the web testing framework that comes as part of VSTS. In web testing you are performing functional testing of web applications. In VSTS you can also run in-web server tests on the code that makes up a web application. I will write another entry explaining how do to this later but here I wanted to point out an issue I found when unit testing web applications.

The lab we had written was ASP code had some security stuff going on, in particular it was using forms authentication and had an authorization entry that looked like this

    <authorization>
      <deny users="?"/>
    </authorization>
and then another section to allow access to the CreateUser.aspx page that looked like
  <location path="CreateUser.aspx">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>
This is a fairly normal setup for ASP, deny unathenticated access to the site but allow all users access to specific pages (in this case we want a user to be able to create an account for themselves).

This is pretty innocuous code until it comes to unit testing in VSTS. I have a 'proof of concept' unit test that simply does this:

[TestMethod()]
[HostType("ASP.NET")]
[AspNetDevelopmentServerHost(@"C:\path to web site", "/StocksWebSite")]
[UrlToTest("http://localhost/StocksWebSite")]
public void WebServerTest()
{
    HttpContext context = HttpContext.Current;

    Assert.IsNotNull(context);
}
If you run this test using the web.config I showed above you will get the following error: The web site could not be configured correctly; getting ASP.NET process information failed. Requesting 'http://localhost:10672/StocksWebSite/VSEnterpriseHelper.axd' returned an error: The information returned is invalid.. Notice the reference to VSEnterpriseHelper.axd. So what is going on here?

It turns out that VSEnterpriseHelper.axd is the URL used by VSTS to trigger the runtime to load our tests into the web server (another entry on this in the future). Therefore that URL needs to be available to run. With the web.config we have all URLs (except CreateUser.aspx) are blocked to un-authenticated users, which means that VSEnterpriseHelper.axd cannot be accessed. There is one simple fix for this, allow un-authenticated access to the URL. You can add the following to your web.config and the testing code will work:

<location path="VSEnterpriseHelper.axd">
  <system.web>
    <authorization>
      <allow users="*"/>
    </authorization>
  </system.web>
</location>

Hope that helps the next person who hits this problem,

Posted by kevin at 9:46 AM in Net

Thursday, 12 January 2006

Subscribing to VSTS Events

VSTS has an eventing model that allows it to notify 'us' when something interesting has happened. The 'us' here is interesting, VSTS can send an email when an event fires or it could call a web service.

To subscribe to an event you run the BISSubscribe tool. This tool lives in the "C:\Program Files\Microsoft Visual Studio 2005 Team Foundation Server\TF Setup" directory on the TFS Server and you run it specifying (among other things) the event to subscribe to and the endpoint to send the event notification to (email or SOAP). Events can also be filtered using a language similar to SQL. However, unsubscribing from an event is not possible with this tool.

TFS maintains three tables (so far as I can tell) that track events, these tables are all in the TfsIntegration database. The first table is the tbl_subscription table. When you subscribe to an event TFS creates an entry in this table. Next comes the tbl_client_event table. This is where the events themselves get posted. So if you've subscribed to a checkin event, details of that event get posted here, those details include an event id (an auto increment field in the table) and the XML 'description' of the event. Finally there is the tbl_notification table. This table links the subwcription and the client_event tables, and specifies which events TFS has actually tried to deliver and includes the status of the delivery and the retry_count.

There are not many tools to help track down events within TFS so hopefully this information helps somebody.

Posted by kevin at 10:27 AM in Net

Thursday, 15 December 2005

Almost, but not Entirely, Unlike Version Control

Version control in VSTS is almost, but not entirely, unlike any other version control system I've ever used. I'm a long time user of other version control systems, CVS for several years and more recently (last couple of years) Subversion. I wasn't a huge fan of CVS, being mainly a Java developer I need support for directories as Java packages use an on-disk directory structure and CVS's support for directories sucked. However, I do like Subversion, I have several repositories that I administer, mostly source code but with some other work related bits in. Both Subversion and CVS are desgined to work in disconnected. That means that as a developer I can check out code from the repository, work on it when I need to, and check it back in when I'm done. The primary advantage of this is that I can work away from the network, on a plane for example, or on a customer site where I typically don't have network access.

In both CVS and Subversion the server takes no account of the clients code, that is no locks are held in the server regarding the client's checked out files. CVS does use locks internally to manage it's file system, but these are like database locks, they are there to stop data corruption. Subversion has introduced locking in 1.2 to support reserved checkouts, but this is the exception rather than the rule. In CVS/Subversion once the user has checked out files she can do with them what she wants, including deleting the files from disk if necessary. The CVS/Subversion servers are blisfully unaware that the checked out files even exist. Both CVS and Subversion maintain hidden directories on the users local disk, and it uses the data in these directories to decide what to do when the user access the repository. So if the user has edited a file on disk then the file can be compared against data held in the .svn directory before the file is uploaded to the server. This mechanism is known as copy-modify-merge

The new version control system is also a copy-modify-merge system, sort-of. VSTS eseentially maintains no data about files that are in version control on the local disk apart from a couple of things. There is data about the workspaces that a given user has, and each project file (csproj etc.) holds information about the source control mechanism in use and each solution file knows which projects are in version control and which server they are associated with. Given this paucity of information how does the version control system "know" that you have changed files? CVS/Subversion "know" because they can check against the data they store on the local disk, VSTS can't do this! VSTS tracks all version control changes through the database.

When you check a project into VSTS the files get marked locally as read-only. This is a warning to you that the files are in source control. If you open a file in Visual Studio and edit it, this sends a request to the server. The server then checks that it is OK to check out the file (another user may have locked it), then in the database marks the file as having 'Pending Changes' and sends the latest copy of the file to the client. The client application then removes the read-only flag. If the user tries to check the file back into source control the client sends a request to the server that then checks to see if the file has changed, if so, the check in happens, if not, the user gets a message saying that nothing needs to be checked in, and the file gets put back to read-only again.

This is a complete pain in the neck, the primary issue being that it is difficult to work off-line.

If you are disconnected from your the TFS and open a Visual Studio project that is under source code control Visual Studio tries to communicate with the source control server. When it finds it cannot do this you will get a dialog telling you that "The solution appears to be under source control but its binding information cannot be found. ..." then another dialog asking whether you want to temporarily work uncontrolled or to remove the project's source control bindings. If you choose the latter then the source control information is removed from the proj file and, so far as Visual Studio is concerned, the project is not under source control. If you choose the first option (temporarily work uncontrolled), then the bindings remain but the files are left as read-only. As a user you can, of-course, make the files read-write and then edit the files. The problem now is, if you edit the file the server doesn't know! This means that you are going to have to track your own changes; then remember to check out the files you have edited; then check those files back in.

Posted by kevin at 11:43 PM in Net

Workspaces, Workspaces Everywhere

The first thing to understand about VSTS is the idea of a 'workspace'. The workspace is a client side 'copy' of the data held in source control. Workspaces provide a mapping of source control folders to local directories. Information about workspaces is held in two places, on the client and on the server. On the client the information about the workspace is held in C:\Documents and Settings\[user]\Local Settings\Application Data\Microsoft\Team Foundation\1.0\Cache\VersionControl.config. The VersionControl.config file maps the name of the workspace to the local directory on disk. It does not hold the mapping between the source control folders and the local directories; that information is held on the server in several tables (notably tbl_Workspace and tbl_workingfolder) in the TfsVersionControl database. No other information is held on the client about the files kept in version control (actually, each project has a .vspscc file associated with it, but this doesn't seem to hold much realm information).

Each user may have multiple workspaces on multiple machines, with each workspace holding different data. This is where things can get tricky. There is no connection between a workspace and a Visual Studio project. What this means is this. Suppose that you have a Visual Studio project open, that project may well be part of a workspace. You could also be using the Source Control Explorer in Visual Studio to be looking at the source code rtepository. The Source Control Explorer also lets you select a differnet workspace (see the image), however if you change workspaces in the Source Control Explorer you get no warning or signal that the workspace you are now browsing does not match the workspace of the project.

Remember that workspaces map Source Control Folders to local directories. This means that if you select a file from within the Source Control Explorer and open it, that file will open within Visual Studio, this may not be the same file as is in your Visual Studio project. You can edit this file and save it, but if you then build your project you will find your changes have not been added.

And the moral is: Be careful with your workspaces, they can lead to lots of misunderstanding!

Using Source Control Explorer

Posted by kevin at 11:42 PM in Net

Monday, 28 November 2005

Removing TFS Zombie Projects

Today I was testing the Team Foundation Server part of VSTS. I was adding and removing projects to get the screenshots for the course I'm in the process of authoring. At some point I added a project then deleted it using TFSDeleteProject (TFSDeleteProject ships with Visual Studio 2005 Team Explorer, it's in the c:\program files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\ directory).

Having deleted the project I tried to re-add it. The wizard started to run the add task and failed towards the end telling me that it could not rollback the addition. This is an omimous message as it means you have a "zombie" project. You can't remove the project from TFS and you can't list the project in the team explorer.

Taking my courage in my hands I looked at the SQLServer 2005 TFS tables (there are dozens of them), for a clue as to what is going on. If found a couple of things. In the TfsIntegration databse there is a table called tbl_projects and this has a column called 'state'. For projects that had been correctly installed the value of this was 'WellFormed' for my zombie project it was 'New' (IIRC, I've sinced chenged the value and my memory isn't what it used to be). Changing this value to 'WellFormed' for my zombie project lets me add the project to the Team Explorer but I still couldn't delete it using TFSDeleteProject.

My next thought was to run the SQL Server profiler to see what TFSDeleteProject was sending to the database to delete the project. It appears that TFSDeleteProject first checks that this user has permissions to delete the project. It then calls this "exec ObjectExists @Path=N'/Foobar',@AuthType=1" on SQL Server's Report Server, i.e. does the Report Server have an entry for the project. It turned out in my case that Report Server did not have an entry. It looks like the Add Project Wizard failed before adding the reports to the Report Server.

Opening Report Server in SQL Server Management Studio and adding a new Folder called 'FooBar' (the name of the project) fixed the problem. Now, running TFSDeleteProject deleted the project. I had to run TFSDelete /force to make sure that TFSDelete continues even if it finds errors. I no longer have a zombie project.

I've also seen zombie projects when TFS seems not to be correctly installed, not sure if this has the same root cause but hopefully this post will save people some wasted time.

* Edited * Thanks to James Manning for pointing out that the that name of the exe used to delete projects is TFSDeleteProject and not TFSDelete which I had used originally

Posted by kevin at 1:57 PM in Net