General Ramblings comments edit

Hypothetical situation:

A coworker approaches you and asks you to help them out with something. It’s a simple task like copying a file or changing a setting. This isn’t the first time they’ve asked you to help them do this, and they say they can’t do it themselves because of something they’ve known about for a while but haven’t gotten around to addressing - their machine is malfunctioning, their account should have permissions on something but it doesn’t, that sort of thing.

Do you help?

Honestly, in situations like this, I’m torn.

The first time or two, sure, I’ll help out, but after a couple of “please help with this quick little task” requests, I balk. It’s not that I don’t want to be unhelpful or *gasp* not a team player, but that I’d rather treat the cause instead of the symptom. if the reason the person can’t take care of things is something known that they need addressed, I’d much rather help them through the process of getting the root issue resolved so they can take care of the small things on their own. Even if the issue is education (“You know how to do this but I don’t, so can you just do it for me?”) I’d rather train the person than just keep fielding little help requests.

Of course, there are always exceptions (e.g., when there’s a huge deadline that solving the root issue would jeopardize, etc.) but by-and-large, I’d rather things just be fixed.

vs comments edit

Dear Visual Studio Extension Developers:

(This includes folks who create products that have “value-add” features that install into Visual Studio, extensions done through classic or new mechanisms, and/or anyone who has something that otherwise “bolts on” to VS to enhance the development experience.)

I have a lot of different products, add-ins, and extensions installed in Visual Studio. Most of them behave reasonably, but I’ve had some recent bad luck with a couple that have caused… frustration. This is to ask you to help me.

Help me enjoy your product. Help me not be frustrated. Help me be that guy who blogs about how double-plus-awesome you are. In order to do that, I have some ideas for you:

  • Don’t require administrative privileges to run. I don’t develop as an administrator. You shouldn’t either. Even if you do, for whatever reason, your QA process should test the extension as a non-admin. I don’t want to get all energized to use the stuff just to fire up VS and get an inexplicable exception message that I have to trace back to your extension.
  • Remember the state in which I left your extension. If you have some sort of window or menu bar addition, make sure if I close the window, move it, dock it, or otherwise use the standard window/menu customization options that I won’t totally lose that customization when I close VS and restart it later.
  • Test your upgrade path. If I install your extension, change some settings, and then later upgrade, I don’t want to lose my settings. Also, if your product is offered as a standalone installer and through the VS Extension Gallery, make sure the two mechanisms understand each other so I don’t manually install the latest version just to be prompted to “upgrade” by the VS Extension Gallery.
  • Don’t write files to my source tree. In my ideal world, I don’t want you writing anything to my source tree because in most cases are those files are going to be per-user settings (right?) which I don’t want to accidentally check into my repository. I also don’t want to have to chase everyone down that’s using your extension and make sure they don’t check them into their branches/clones/etc.
  • If you absolutely must write files to the source tree, use a file extension I’m already ignoring. Most source code control projects for .NET have *.suo and *.user ignored. Feel free to use those extensions so for your per-solution or per-project files so it’s seamless.
  • Don’t add a top-level menu to Visual Studio. You don’t need to show up at the same level as File, Edit, View, etc. I have no less than 19 of these top-level menus right now, counting the stock items. If I size the VS window less than 1200 pixels wide, the menu starts wrapping. That’s ridiculous, especially when some of these just open up settings menus. There’s a “Tools” menu. Use it. There is a standard VS options dialog. Hook into it. Putting yourself right at the top is like trying to install a desktop shortcut every time. (I figured out how to use the Start menu since Windows 95 came out. I don’t need a desktop icon.) Note: The exception to this is if your plugin actually does have a huge ton of things that are menu-driven. Chances are, though, your plugin doesn’t fall into this territory. You might think it does, but it probably doesn’t.
  • Have options to enable/disable sets of functionality. If your plugin does more than one logical “thing” (e.g., it does syntax highlighting and enhances the Solution Explorer) you need to offer me options to enable or disable the individual features. You may have five things that your plugin does but only four of them are things I’m interested in, while the last one is just annoying. Let me disable that annoying one. Don’t force me to choose between uninstalling your plugin or putting up with annoying behavior.

DevExpressis actually a good example of what to do in pretty much every case here. The CodeRush/Refactor set of VS extensions are fantastic. They do add a top-level menu to VS, but since there is a ton of functionality (tool windows, etc.) you’d want to get through menus and it’d be cumbersome nested under the Tools menu, it’s justified. They cover every other item I listed here like a champ and are, in fact, double-plus-awesome.

Anyway, thanks, VS Extension Developers, for hearing me out.

Sincerely, Me

I’m sensitive to smells.

I think I get it from my mom, who is also sensitive to smells, though she really likesstrong perfumes and scents whereas I really can’t stand anything of volume. I can’t really walk down the soap aisle at the store and I’m not a big fan of scented laundry soap or fabric softener. In most cases, I’d rather things just not have a smell.

There are exceptions, of course. I like the smell of chocolate. I like the smell of coffee. I also like the smell of pipe tobacco that you get when you walk past the smoke shop, but I don’t want to be in the middle of a bunch of pipe smokers.

Anyway, this is all coming to mind because the entire first floor at work smells like god damn hot ham sandwich and it’s making me sort of ill.

My cubicle neighbors are probably irritated with me and my smell issue since it basically means if you cook last night’s stank-ass fish dinner and bring it back to your desk, you’ll probably be getting a polite knock on the wall of your cube with my smiling face ready to have a polite discussion about maybe please could you keep the cooked food in the break area thanks.

I think smelly food falls into that same respect-for-your-neighbors arena as nail clipping. If you need to clip your nails, go ahead and take that into the appropriate place (restroom) so you’re not bugging other people with it.

I recently moved my cube not too far from the original location so I could get a nice view of the outside world rather than being stuck in a dark windowless tank all day. It’s a nice location, reasonably quiet, with a view of the parking lot which is really not so bad if I do say so myself. Unfortunately, what I did find is that the air vent over my head is, through some dark magic, connected to the vent over the break area, so whenever someone’s got something luscious a-cookin’, it ends up blowing out the vent in the ceiling right into my face.

I’m not entirely sure what to do about that. I have done some experimentation to see if this really is the case, and, yes, it does appear to be so. If it starts stinkin’, I can jog over to the break area and, sure enough, there’s the microwave going.

While I am figuring out how to somehow alleviate the issue at the source, I have “rigged up” a sort of counter-stink defense involving a small desk fan and “Clean Linen” scented air freshener. Step 1: Turn on fan. Step 2: Spray into the fan. Step 3: Sigh in relief.

The fan-and-spray anti-stink
setup.

Thank goodness for laptops, right? “Ack! They released the mustard gas! Grab your laptop and hide out in a conference room until it dissipates!”

Work’s not the only place with some dicey smells. Babies bring with them a whole new set of unpleasant aromas, most of which involve poop or vomit, and sometimes both. I am partial to neither, and that makes for a challenge. I’m not sure who figured out what baby formula is supposed to smell like, but that guy needs to be fired. Baby formula, even fresh in the bottle, smells like ass. Come on, you couldn’t stick some vanilla scent in there or something? Oh, and the Auntie Anne’s pretzel stand at the mall always smells so sickeningly sweet it catches my breath.

OK, the ham sandwich appears to be dying down. I can probably take this mask off and get back to work.

net, gists, aspnet comments edit

We have a custom VirtualPathProvider that serves some static files (*.js, *.css) from embedded resources in assemblies. It is similar in function to the WebResource.axd that ships with ASP.NET, but instead of having some crazy URL, you just access the file directly and the VPP finds it in embedded resources and serves it just like it was on the disk. It makes for a nice deployment experience and easy upgrade.

The problem I’ve run into a bunch, particularly with routing showing up, is that even with a wildcard map to ASP.NET, my static files end up with a 404 error code because routing is catching them, sending the requests to the MVC handler, and no route is found. Fail.

So, as a note to myself (and anyone else who’s doing something similar), here’s what I’ve found you need to do to get your VPP serving up static files.

First, you need to get the desired static file types mapped to ASP.NET. In an integrated pipeline, that means adding the StaticFileHandler in your web.config (or doing some other machinations, based on your setup, but the web.config method makes it easy and controlled from the web app rather than the IIS console). A snippet of web.config looks like this:

<?xml version="1.0"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="AspNetStaticFileHandler-GIF" path="*.gif" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
      <add name="AspNetStaticFileHandler-JPG" path="*.jpg" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
      <add name="AspNetStaticFileHandler-CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
      <add name="AspNetStaticFileHandler-JS" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
    </handlers>
  </system.webServer>
</configuration>

Obviously you’ll have a whole bunch of other stuff in your web.config, but this is the relevant bit here. Make sure the static file handlers are the last handler entries in your web.config.

UPDATE/IMPORTANT: In the original post for this article I set a wildcard mapping to AspNetStaticFileHandler. That actually messes other things up. For example, it starts serving web form .aspx files as text files directly. Not good. Instead, map the static file handler directly ONLY to the static file types you plan on serving.

Now the problem is that ASP.NET routing is going to pick up every incoming request for those file types and you’ll end up with a 404 when the request doesn’t match any route. This is the problem that is so hard to debug - your VirtualPathProvider.FileExists method will be properly called to determine whether the file can be served up… but then you get a 404 without ever getting your VirtualPathProvider.GetFile method to try and serve the thing up. WTF?! The answer is to ignore routes to the static files.

In Global.asax, in your RegisterRoutes method, set it up so static file extensions get ignored. This is based on Phil Haack’s blog entry about ignoring requests for a certain file extension:

routes.IgnoreRoute("{*staticfile}", new { staticfile = @".*\.(css|js|gif|jpg)(/.*)?" });

Now when you make a request for your static file, it will properly be served up by your VirtualPathProvider and won’t have to be in the filesystem.

net, vs, testing, gists comments edit

With the ability to transform your web.config file when deploying your web site came, at least for me, a question: How do I test my web site’s behavior without deploying a whole copy of my web site?

I figured out a reasonable, if slightly kludgy, solution and I figured I’d share. The general idea is to have a project in Visual Studio that…

  • Acts as the point of entry for debugging the packaged version of the web site.
  • Automatically updates IIS Express configuration to point to the packaged web site.

What it allows you to do is hit F5 and IIS Express will start up pointed to the packaged version of the web site rather than the one in your source tree. It’ll have the transformed web.config (and any other build-time changes) so you’ll be debugging what would normally be deployed.

First, create an empty class library project in your solution. You won’t actually put code in here; it’s a marker that you can use as the Debug startup project. I called mine DebugPlaceholder.

Next, add a Project Reference in your debug placeholder project to all of the web sites you want to have set up automatically in IIS Express.

Now it’s time to manually edit the debug placeholder project a bit. Open the debug placeholder .csproj in a text editor.

Scroll down until you find the list of project references. Inside each ProjectReference node

  • Add a node called IISExpressUrl. Inside that node put the URL that IIS Express will host the site on.
  • Add a node called IISExpressBindings. This is another way of writing the URL, but in IIS binding format.

A sample modified ProjectReference node looks like this:

<ProjectReference Include="..\MyWebApplication\MyWebApplication.csproj">
  <Project>{8F2D1C2C-E12D-4880-B731-66F5051A6EF1}</Project>
  <Name>ChannelWebApplication</Name>
  <IISExpressUrl>http://localhost:22446</IISExpressUrl>
  <IISExpressBindings>http/*:22446:localhost</IISExpressBindings>
</ProjectReference>

Again, the URL and Bindings listed up there need to match (note the port in each matches) and they need to be unique for each project. (IIS Express can’t host multiple sites at the same listening destination.) The path to the project, the project GUID, and the project Name will, of course, be your own values that were put there when you added the project reference.

IMPORTANT: The endpoint you list in the project references can’t be the same as the one you have set up in the Web settings of your web application. The problem is that you can’t stop VS from launching IIS Express (or the Visual Studio dev server, or whatever) when you start debugging, so if you have your web application, say, configured to listen to port 22446 and you have your debug placeholder set to configure the deployed project to 22446, then you’ll get a failure. I’m not sure this is really a limitation since you probably shouldn’t have anything in your web app that’s glued to the specific port anyway.

What you just did was add some metadata to each project reference that you can use later. We’ll use it in the AfterBuild target.

Scroll down to almost the bottom of the debug placeholder .csproj and uncomment the AfterBuild target.

Inside the AfterBuild target, put these three lines:

<MSBuild Projects="%(ProjectReference.FullPath)" Targets="Package" Properties="Configuration=$(Configuration);Platform=$(Platform)" />
<Exec Command="&quot;$(MSBuildProgramFiles32)\IIS Express\appcmd.exe&quot; delete site %(ProjectReference.IISExpressUrl)" ContinueOnError="true" />
<Exec Command="&quot;$(MSBuildProgramFiles32)\IIS Express\appcmd.exe&quot; add site /name:&quot;%(ProjectReference.Name)&quot; /bindings:%(ProjectReference.IISExpressBindings) /physicalPath:&quot;%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp&quot;" />

What those do:

  • Run the “Package” target on the web application projects that you’ve referenced.
  • Deletes and then re-adds the IIS Express configuration that points to the referenced projects. (That way if you’ve got multiple copies of the source checked out, you’ll be sure to always be pointed to the one you’re working on.)

The key thing you’ll note is in that last line - we’re referring IIS Express to the obj folder for each web project where the packaging target stages files.

The last thing you need to do is choose one of the project references as the site you want to start up when debugging. That’s a limitation of this solution - you only get to choose one site to start. You’ll have to start and/or attach to the others manually. (On the other hand, if your solution only has one web site then it’s no big deal.)

Scroll up to the top of the debug placeholder .csproj file and add the following three properties to the very top PropertyGroup (the one without a Condition on it):

<StartAction>Program</StartAction>
<StartProgram>$(MSBuildProgramFiles32)\IIS Express\iisexpress.exe</StartProgram>
<StartArguments>/site:MyWebApplication</StartArguments>

This makes it so you’re checking in the information about what to start up when you debug rather than storing it in an external .csproj.user file. You want to do this so it’s easy for everyone using the source to debug. Note that last property, StartArguments, contains the name of one of your project references. See how the Name property on the project reference matches the name of the site starting up?

Now just set the debug placeholder as your startup project and fire it up. The solution will build, your web application will run through a package process, and IIS Express will start up pointed to the deployed version of the app. Visual Studio will attach to it, and then it’s up to you to start up your browser and do your testing.

Below is an example DebugPlaceholder.csproj with the edits highlighted so you can see what a finished project looks like. Standard disclaimer applies: No warranty, no support, you’re on your own. Works on My Machine! Have fun!

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>8.0.30703</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{594FFDF6-6911-47DA-AE93-29CBCE757C19}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>DebugPlaceholder</RootNamespace>
    <AssemblyName>DebugPlaceholder</AssemblyName>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <StartAction>Program</StartAction>
    <StartProgram>$(MSBuildProgramFiles32)\IIS Express\iisexpress.exe</StartProgram>
    <StartArguments>/site:MyWebApplication</StartArguments>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\MyWebApplication\MyWebApplication.csproj">
      <Project>{8F2D1C2C-E12D-4880-B731-66F5051A6EF1}</Project>
      <Name>MyWebApplication</Name>
      <IISExpressUrl>http://localhost:22446</IISExpressUrl>
      <IISExpressBindings>http/*:22446:localhost</IISExpressBindings>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>  -->
  <Target Name="AfterBuild">
    <MSBuild Projects="%(ProjectReference.FullPath)" Targets="Package" Properties="Configuration=$(Configuration);Platform=$(Platform)" />
    <Exec Command="&quot;$(MSBuildProgramFiles32)\IIS Express\appcmd.exe&quot; delete site %(ProjectReference.IISExpressUrl)" ContinueOnError="true" />
    <Exec Command="&quot;$(MSBuildProgramFiles32)\IIS Express\appcmd.exe&quot; add site /name:&quot;%(ProjectReference.Name)&quot; /bindings:%(ProjectReference.IISExpressBindings) /physicalPath:&quot;%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp&quot;" />
  </Target>
</Project>