Thomas Freudenberg

Confessions of a caffeine addict
NuGet package for 7Zip4Powershell

nugetA few days ago I mentioned 7-Zip for Powershell. I’ve now created a NuGet package and published it at NuGet.org.

It took me a while to figure it out, but finally it’s a “tools only” package, i.e. it adds no references to your project.

To use the new commands just add the package to your solution and import it in your Powershell script:

$SolutionDir = split-path -parent $PSCommandPath
Import-Module (Join-Path $SolutionDir "packages\7Zip4Powershell.1.0\tools\7Zip4Powershell.dll")
Fun with RavenDB and ASP.NET MVC: part I

I’m working on a small pet project with ASP.NET MVC, where hierarchical structured documents are stored in RavenDB. These documents can be retrieved by their unique URL, which is also stored in the document. Because there are different kinds of document class, they all derive from the common interface IRoutable. This interface defines a property Path, by which the document can be accessed.

public interface IRoutable {
    string Id { get; set; }
    string Path { get; set; }
}

public class Document : IRoutable {
    public string Id { get; set; }
    public string Path { get; set; }
}

using (var session = _store.OpenSession()) {
    session.Store(new Document { Path = "a" });
    session.Store(new Document { Path = "a/b" });
    session.Store(new Document { Path = "a/b/c" });
    session.Store(new Document { Path = "a/d" });
    session.Store(new Document { Path = "a/d/e" });
    session.Store(new Document { Path = "a/f" });
    session.SaveChanges();
}

Additionally there’s the requirement, that the incoming URL may consist of more parts than the document’s path, carrying some additional information about the request. Here are some examples of possible requests, and which document should match:

Request Found document
a/x a
a/b/c/y/z a/b/c

So, given the path, how can you find the correct document?

The solution to this consists of three parts:

  1. Identify documents in the database which can be accessed via their path
  2. Index those documents
  3. Find the document which matches best a given URL

Marking routable documents

Because there’s not a single class for pages stored in the database, I mark all documents implementing IRoutable by adding a flag “IsRoutable” to the document’s metadata. This is done by implementing IDocumentStoreListener, so the code is called by RavenDB whenever a document is stored:

public class DocumentStoreListener : IDocumentStoreListener {
    public const string IS_ROUTABLE = "IsRoutable";

    public bool BeforeStore(string key, object entityInstance, RavenJObject metadata, RavenJObject original) {
        var document = entityInstance as IRoutable;
        if (document == null) {
            return false;
        }
        if (metadata.ContainsKey(IS_ROUTABLE) && metadata.Value<bool>(IS_ROUTABLE)) {
            return false;
        }
        metadata.Add(IS_ROUTABLE, true);
        return true;
    }

    public void AfterStore(string key, object entityInstance, RavenJObject metadata) {
    }
}

Indexing routable documents

The next step is to create an index for all documents with the proper flag in their metadata:

public class IRoutable_ByPath : AbstractIndexCreationTask {
    public override IndexDefinition CreateIndexDefinition() {
        return new IndexDefinition {
            Map = @"from doc in docs where doc[""@metadata""][""" + DocumentStoreListener.IS_ROUTABLE + @"""].ToString() == ""True"" select new { doc.Path }"
        };
    }
}

Searching for documents

Ok, so much for the preparation. The interesting part starts when a request comes in. Here RavenDB’s boosting feature is quite handy. The more parts of the path match, the higher score the document will get. E.g. if the requested path is a/b/c/d/e, following RavenDB search will be queried:

search term boost
a/b/c/d/e 5
a/b/c/d 4
a/b/c 3
a/b 2
a 1

The code to create such a query looks like this:

public IRoutable GetRoutable(string path) {
    var query = _documentSession
        .Query<IRoutable, IRoutable_ByPath>();

    if (!String.IsNullOrEmpty(path)) {
        var pathParts = path.Split('/');
        for (var i = 1; i <= pathParts.Length; ++i) {
            var shortenedPath = String.Join("/", pathParts, startIndex: 0, count: i);
            query = query.Search(doc => doc.Path, shortenedPath, boost: i, options: SearchOptions.Or);
        }
    } else {
        query = query.Where(doc => doc.Path == String.Empty);
    }

    var document = query.Take(1).FirstOrDefault();
    return document;
}

This method will finally return the document with the longest matching path.

Based on the incoming request we have found a matching document. What we can do with the remaining part of the URL I’ll leave for the next installment.

I published the complete code with unit tests in this Github repository.

7-Zip for Powershell

powershell_logoAt work we deal with different big databases, and by big I mean between 3.5 and 8 GB. Those databases are zipped with 7-Zip and stored on a server. Depending on the scenario we unzip one of these databases to a local folder and attach it to the SQL Server. To simplify these steps we have a couple of Powershell scripts, among other things invoking the command line version of 7-Zip.

However, extracting an archive of several gigabytes over the network might take some time, and we’d like to see the progress in the Powershell console. Powershell provides a standard way to report progress by calling Cmdlet.WriteProgress, which then will be displayed by the current host (e.g. command line) appropriately.

Therefore with some support of a co-worker I’ve written two Cmdlets for extracting and compressing archives. The syntax is simple as this:

Expand-7Zip [-ArchiveFileName] <string> [-TargetPath] <string>  [<CommonParameters>]

Compress-7Zip [-ArchiveFileName] <string> [-Path] <string> [[-Filter] <string>] [-Format <OutArchiveFormat>
{SevenZip | Zip | GZip | BZip2 | Tar | XZ}] [-CompressionLevel <CompressionLevel> {None | Fast | Low | Normal |
High | Ultra}] [-CompressionMethod <CompressionMethod> {Copy | Deflate | Deflate64 | BZip2 | Lzma | Lzma2 | Ppmd |
Default}]  [<CommonParameters>]

It works with both x86 and x64 and uses SevenZipSharp as a wrapper around 7zip’s API.

7zip4powershell

You can download the cmdlets with the required libraries here or grap the sources at Github.

Don’t copy my referenced assemblies

There are cases where you don’t want referenced assemblies to be copied to your output folder.

E.g. you write a plugin for an existing application, which provides a library declaring its contracts. Since the application will load that contract assembly itself, there’s no need for a second copy of the assembly in your plugin folder.

“But you can configure Visual Studio not to copy a reference to the output folder,” you may say. Well, that’s right. In the properties of a referenced assembly you can simply set “Copy Local” to “False”.

But…

If you’re an avid user of NuGet like I am, every time you update a package, the old references are removed from your project and the new ones will be added, and “Copy Local” will be “True” again. Do you think you will always remember to change that property back to “False” whenever you update a package? I don’t know about you, but I won’t.

Therefore, here’s a little trick I use in such cases. I have a little MSBuild file which suppresses any referenced assembly to be copied to the output folder:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- make all references non-private, so they won't be copied to the output folder -->
  <Target Name="ClearReferenceCopyLocalPaths" AfterTargets="ResolveAssemblyReferences">
    <ItemGroup>
      <ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)" />
    </ItemGroup>
  </Target>

</Project>

Save that snippet to a file i.e. PreBuild.targets, and include it in your project file like this:

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
  <Import Project="PreBuild.targets" />

That’s it. From now on the output folder will only contain your assembly (along with other build artifacts such as the pdb file, of course)

Creating Dynamic Windows 7 Taskbar Overlay Icons, the MVVM Way

metrotwitSince Windows 7 the icon of an application can get an overlay bitmap. You can use that to indicate some state of the application, or–like MetroTwit–to show the number of unread items.

Overlay Icon in WPF

In WPF, the API is pretty simple:

<Window.TaskbarItemInfo>
    <TaskbarItemInfo 
        Overlay="{StaticResource ResourceKey=MyOverlayImage}" />
</Window.TaskbarItemInfo>

However, in one of my projects I have to display dynamic text in the overlay, similar to MetroTwit, but above example only shows a static resource.

While searching in the internet I found Pete Brown’s article Creating Dynamic Windows 7 Taskbar Overlay Icons. He uses a WPF DataTemplate to define the content of the overlay, and in his code-behind he takes that template, renders it to a bitmap and assigns it to the TaskbarItemInfo’s Overlay property. See his article for the detailed steps.

Though I think Pete’s solution pretty clever, it lacks the separation of logic and presentation. In my application I don’t want to create images in the code-behind, code-aside, whatever. It follows the MVVM pattern, so the creation of the overlay image shouldn’t be the concern of my viewmodel.

Solution

Extending TaskbarItemInfo doesn’t work because it is sealed. Therefore I took the same route as in my previous post, attaching dependency properties:

public class TaskbarItemOverlay  {
    public static readonly DependencyProperty ContentProperty =
        DependencyProperty.RegisterAttached(
            "Content", 
            typeof(object), 
            typeof(TaskbarItemOverlay), 
            new PropertyMetadata(OnPropertyChanged));

    public static readonly DependencyProperty TemplateProperty =
        DependencyProperty.RegisterAttached(
        "Template", 
        typeof(DataTemplate), 
        typeof(TaskbarItemOverlay), 
        new PropertyMetadata(OnPropertyChanged));


    public static object GetContent(DependencyObject dependencyObject) {
        return dependencyObject.GetValue(ContentProperty);
    }

    public static void SetContent(DependencyObject dependencyObject, object content) {
        dependencyObject.SetValue(ContentProperty, content);
    }

    public static DataTemplate GetTemplate(DependencyObject dependencyObject) {
        return (DataTemplate)dependencyObject.GetValue(TemplateProperty);
    }

    public static void SetTemplate(DependencyObject dependencyObject, DataTemplate template) {
        dependencyObject.SetValue(TemplateProperty, template);
    }

    private static void OnPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
        var taskbarItemInfo = (TaskbarItemInfo) dependencyObject;
        var content = GetContent(taskbarItemInfo);
        var template = GetTemplate(taskbarItemInfo);

        if (template == null || content == null) {
            taskbarItemInfo.Overlay = null;
            return;
        }

        const int ICON_WIDTH = 16;
        const int ICON_HEIGHT = 16;

        var bmp =
            new RenderTargetBitmap(ICON_WIDTH, ICON_HEIGHT, 96, 96, PixelFormats.Default);
        var root = new ContentControl {
            ContentTemplate = template, 
            Content = content
        };
        root.Arrange(new Rect(0, 0, ICON_WIDTH, ICON_HEIGHT));
        bmp.Render(root);

        taskbarItemInfo.Overlay = bmp;
    }
}

The first lines a boilerplate code to define the attached properties. There are two of them, Content and Template. The former defines, well, the content we’re going to bind to our model. The latter defines the template used to render the content.

The actual work is done in the method OnPropertyChanged. It takes the template together with the content, renders it, and assigns the resulting bitmap to the Overlay property of the TaskbarItemInfo element.

Usage

I have created a small application to demonstrate the use of the attached properties. The XAML of the window is this:

<Window 
    x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:WpfApplication1" 
    Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="OverlayIcon">
            <Grid Width="16" Height="16">
                <Ellipse 
                    Fill="Red" 
                    Stroke="White" 
                    StrokeThickness=".5" />
                <TextBlock 
                    Text="{Binding}" 
                    TextAlignment="Center" 
                    Foreground="White" 
                    FontWeight="Bold" 
                    Height="16" 
                    VerticalAlignment="Center" 
                    FontSize="10"/>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Window.TaskbarItemInfo>
        <TaskbarItemInfo 
            src:TaskbarItemOverlay.Content="{Binding Count}" 
            src:TaskbarItemOverlay.Template="{StaticResource OverlayIcon}" />
    </Window.TaskbarItemInfo>
    <Viewbox>
        <TextBlock Text="{Binding Count}" />
    </Viewbox>
</Window>

TaskbarItemOverlayIn the window’s resources we define the template for the overlay. Notice that the Text is bound! Later you can see the TaskbarItemInfo with the attached properties in action: Content binds to the Count property of my viewmodel, and Template references the DataTemplate defined in the resources.

The code-behind is straight forward. I won’t repeat it here, but you can see it at GitHub. Basically it increments the Count property of the viewmodel every seconds in a background thread. You can see the result in the image to the left.

The source code is attached, but also available at GitHub.

Binding WebBrowser content in WPF

When you’re using a WebBrowser control in your WPF application, you may have noticed that you can’t bind the control’s content. WebBrowser has no property to set its content but a method named NavigateToString. So when you’re following a strict MVVM approach you’re lost because you don’t want any code-behind for your views.

But then there are attached properties. As their name implies they allow you to attach new properties to existing dependency objects. In your XAML code you apply such a attached property to your element and can access it as any other property of the object.

Ok, first here’s the code of an attached property to set a WebBrowser’s content:

public class WebBrowserHelper {
    public static readonly DependencyProperty BodyProperty =
        DependencyProperty.RegisterAttached("Body", typeof (string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));

    public static string GetBody(DependencyObject dependencyObject) {
        return (string) dependencyObject.GetValue(BodyProperty);
    }

    public static void SetBody(DependencyObject dependencyObject, string body) {
        dependencyObject.SetValue(BodyProperty, body);
    }

    private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var webBrowser = (WebBrowser) d;
        webBrowser.NavigateToString((string)e.NewValue);
    }
}

The static BodyProperty defines the type of the attached property: its name is Body, the type is string, and whenever it is changed the method OnBodyChanged should be called.

The accessors for a attached property must be conventionally named SetXxx and GetXxx. They are called whenever you set or get the property’s value.

Last but not least OnBodyChanged is called when the value of the property has changed. The first parameter is the object the property is attached to, so we can cast it to WebBrowser and call its NavigateToString method.

The actual usage of the new Body property is pretty simple:

<WebBrowser
    src:WebBrowserHelper.Body="{Binding MyHtml}"
    />

given that the ViewModel has a property named MyHtml providing the desired content for the control.

A complete sample application is available on GitHub.

Reactivating this site

germany.hamburgYes, this blog is still alive, though the last post is about two and a half years old.

It has happened a lot since then, but it can be condensed in two points: a) I moved again, this time to Bernau am Chiemsee at the “other end” of Germany, and b) I got married. And the latter caused the former :-)

Anyway, after 30 month of silence I think I should reactivate this blog. In the past I hesitated because most of the time I thought my topics are too trivial to write about. But that’s because whenever I had a particular programming error, I thought about it and tried to solve it. But at the point of the solution I’m already so familiar with the problem that I think it’s not interesting enough anymore to blog about it.

But on the other hand, other might have had the same problem, so why not help them and publish a possible solution? At least Google will find it.

Additionally blogging will help me to sharpen my rusty English skills ;)

Anyway, in my profession and in my spare time I deal with WPF, IoC containers, ASP.NET MVC, NoSQL databases (RavenDB in particular) among others, so you know what to expect in the future.

Sharing Extension for Graffiti

Two weeks ago Telligent published the second beta of their upcoming new product Graffiti. It is a simple lightweight content management system. And by simple I don't mean lame. Far from it! It is simple in the sense of easy deployment, management, and publishing.

Additionally it's easy to extend. Keyvan, who I got to know and appreciate while working on the Community Server Modules, already wrote several extensions for Graffiti. To wrap up all his addons and provide a simple installation experience he started the Graffiti Extras project on CodePlex. And he was kind enough to accept me as a contributor.

So here it is, my first extension for Graffiti. In fact, I was inspired by Danny Douglass' Social Bookmarks extension for BlogEngine.NET. It enables you to link your posts to some of the most popular social bookmarking sites. The image to the right depicts an exemplary post with the extension rendered below.

Implementation 

The Sharing extension is implemented as a so called chalk. Think of chalks as of macros. How to write your own chalk is well-documented, so I won't describe how I implemented the Sharing extension. If you want to have a look at the sources, go to the GraffitiExtras project on CodePlex and either download them or browse them online.

Installation 

To install the Sharing chalk (and all other extensions provided in Graffiti Extras) download the attached ZIP File. This archive contains two root folders: in the bin folder you can find the GraffitiExtras.dll which you must drop into the bin folder of your Graffiti installation. The second folder, sharing-images, contains two flavors of icons in different sizes (16x16, 24x24, 32x32, and 48x48) for several social bookmarking sites (original icons are provided by FastIcon). Either copy that folder entirely or only the desired flavor/sizes somewhere to the Graffiti web folder.

Usage

To add the sharing extension to your posts, you just have to add a single line to your theme file:

$sharing.Write($post, "<image folder>")

Replace image folder with the path to the desired images. E.g. if you have copied the entire sharing-images folder to the root of your web application, and you want to see the round images with a size of 16x16, you would add following line:

$sharing.Write($post, "/sharing-images/circle/16x16/")

By default the different images are separated by a non-breaking space (&nbsp;) but you can change that with the optional third parameter:

$sharing.Write($post, "<image folder>", " | ")

 

.NET-Forum.de launched

.NET ForumA couple of days ago Jan Welker launched a new German .NET related community site, .NET-Forum.de. I didn't expect the developsphere to require just another site, but there are already 51 users registered, even though the site wasn't advertised anywhere except the dotnet-snippets.de newsletter.

Jan set up Community Server 2007.1 to drive the site (Did you know that .NET related non-profit communities may receive a free license?) I try to support Jan whenever he experiences issues with CS or has a configuration question. So for me it's an appreciated opportunity to get to learn CS's forum capabilities (Till now I only used the blogging part.)

I wish Jan success, and maybe this post will lead some more people to his site.

String.IsNullOrEmpty as Extension Method

Most you will probably know about Extension Method introduced with C# 3.0. If not, I strongly recommend to read ScottGu's explanation.

Anyway, a couple of days ago Brad Wilson posted an experiment:

What I wasn't sure was whether or not you could call these extension methods when you have a null instance of the object, since they're instance methods. The C++ guy in me said "sure, that should be legal", and the C# guy in me said "it's probably illegal, and that's too bad". Amazingly, the C++ guy in me won!

This code executes perfectly:

using System;

public static class MyExtensions {
    public static bool IsNull(this object @object) {
        return @object == null;
    }
}

public class MainClass {
    public static void Main() {
        object obj1 = new object();
        Console.WriteLine(obj1.IsNull());

        object obj2 = null;
        Console.WriteLine(obj2.IsNull());
    }
}

When you run it, it prints out "False" and "True". Excellent!

When I read that I immediatley thought of another application. I guess all my readers are familiar with String.IsNullOrEmpty which was introduced with .NET 2.0. So I asked myself if you can make IsNullOrEmpty a parameterless extension method:

using System;

public static class MyExtensions
{
    public static bool IsNullOrEmpty(this String s)
    {
        return String.IsNullOrEmpty(s);
    }
}

public class MainClass
{
    public static void Main()
    {
        string s1 = "Hello world";
        Console.WriteLine(s1.IsNullOrEmpty());

        string s2 = null;
        Console.WriteLine(s2.IsNullOrEmpty());
    }
}

Again, it prints out False and True. And in my opinion this syntactic sugar looks much more elegant than the ordinary String.IsNullOrEmpty(s2).

If only C# would support extension properties...

Goodbye Aachen, Hello Hamburg

Cycos After more than 8 years I´ll leave my current employer Cycos by the end of this year. Starting 1/1/08 I'll work as a Senior Software Developer at Proximity (international website.)

Cycos is my first employer, I started here right after university in 1999. Originally I didn't plan to stay that long here but 5 years at maximum. However, because the work was so diversified and the working atmosphere so prolific, I stayed some years longer.Proximity E.g. I was delegated to Munich and San Jose (CA) for several month each, and they paid me the attendances and expenses for both PDC´03 and ´05 in LA. In other words, I can´t accuse myself of being inflexible or inmobil. And it was always fun working at Cycos. I´ll be contended if the working atmosphere at my new job is half as prolific as I got to know it in the past.

However, after 8 years it´s time for a change. Most of my time at Cycos I wrote proprietary software, mainly clients for computer telephony integration. In contrast, at Proximity I will leverage Microsoft products such as MOSS and BizTalk, i.e. areas I don´t have a clue about where I can gain experience. But don´t get me wrong, I´m really looking forward for the new job.

And I´m not only changing the employer, but the town as well. I´ll move to Hamburg, which is 500 km away from Würselen/Aachen (for my American friends: that´s about 310 miles). Therefore the search of a new domicile has my highest priority at the moment. I have 18 paid leave days left, and I want to complete my relocation this year. So this blog will stay quite silent the next few weeks. But stay tuned, I´ll keep you informed.

XTOPIA 2007

Now Germany has its own Mix conference, though here Microsoft calls it Xtopia. It will cover Silverlight, WPF, the Expression suite, and much more. It will take place in Berlin from 10th to 12th of October (if you include the post-conference on Friday).

I'll go to Berlin tomorrow already, and stay until Saturday. My Hotel is the Quality Hotel & Suites Berlin City East. If you want to join me for a beer or two, drop me a line or call me at +49 (173) 285 21 81. Lars, I'm expecting your call.

All Firefox Extensions Gone after upgrading to 2.0.7

FirefoxToday I upgraded Firefox to 2.0.7, which fixes a flaw in the QuickTime plugin. However, after the upgrade Firefox didn't load any of the extensions I have installed

Fortunately I found this thread in the support forum. Simply delete extensions.ini, extensions.cache, and extensions.rdf from your profile folder. On its next start Firefox will scan for installed extansions and regenerate these files.

I hope this post will be indexed properly so you can find this information faster than me. 

Shot myself in the foot

If you have tried to leave a comment on my site in the last two days, you may have noticed that they weren’t accepted. Here is why:

Because the rate of incoming spam decreased dramatically after I implemented the Honeypot CAPTCHA on this site, I wondered if I could really give that solution the credit. Therefore I entered a comment myself to check it. And what happened? Nothing! Instead, that small red asterisk appeared next to the comment field, indicating that nothing was entered. WTF? I did enter some text! Garbage, I admit, but at least a few characters. Then I remembered that I switched the identifiers of the regular comment text box and the honeypot box. Then I had a sneaking suspicion, and I quickly checked post.aspx. My apprehension proved true: I have forgotten to point the RequiredFieldValidor to the other identifier! So as long as you didn't fill in the hidden honeypot, you couldn't post your comment. How stupid is that? The ironic side of the story is, that only comment spammers were able to cross that barrier, just to get marked as spam instantly afterwards.

Anyway, it’s fixed by now, and your comments are welcome (again)!

Honeypot Captcha for Community Server

A few days ago Phil Haack wrote about Honeypot Captcha:

At the same time, spam bots tend to ignore CSS. For example, if you use CSS to hide a form field (especially via CSS in a separate file), they have a really hard time knowing that the field is not supposed to be visible.

To exploit this, you can create a honeypot form field that should be left blank and then use CSS to hide it from human users, but not bots. When the form is submitted, you check to make sure the value of that form field is blank.

A great idea, which didn’t occur to me before. And Phil wasn’t even the first one with that idea.

I added a “regular” captcha control to my site some time ago, but removed it again after a couple of days. Why burden the innocent commentor? He’s not the problem. The comment spammers are.

So I took the idea of the honeypot and leveraged Community Server’s spam filtering capabilities.

First, I replaced the original Community Server’s WeblogPostCommentForm with my own version by simply copying it to my own assembly. (If you build CS from the SDK, you can also just edit the existing one.)

  1. Added the private member
    private TextBox HoneyPot;
  2. Added the property HoneyPotTextBoxId, which references the actual TextBox in the form:
    public string HoneyPotTextBoxId
    {
        get { return ((string)ViewState["HoneyPotTextBoxId"]) ?? string.Empty; }
        set { ViewState["HoneyPotTextBoxId"] = value; }
    }
  3. Added following lines to the method AttachChildControls:
    HoneyPot = WeblogControlUtility.Instance().FindControl(this, HoneyPotTextBoxId) as TextBox;
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("\n<script type=\"text/javascript\">");
    sb.AppendLine("document.getElementById(\"" + HoneyPot.ClientID + "\").style.display = \"none\";");
    sb.AppendLine("</script>");
    CSControlUtility.Instance().RegisterStartupScript(HoneyPot, typeof (TextBox), "honeypot", sb.ToString(), false);
    You see that the honeypot field is hidden dynamically via JavaScript. No CSS is giving a hint to the spammer that the control will not be visible. Instead, as soon as the page is loaded, the textbox will be hidden programmatically.
  4. Added following lines in Submit_Click right before WeblogPosts.Add is called:
    if (!string.IsNullOrEmpty(HoneyPot.Text)) { 
        EventLogs.Info("Spammer entered \"" + HoneyPot.Text + "\" in the honey pot", "Spam Rules", 0); 
        c.SetExtendedAttribute("GotchaInMyHoneyPot", "trapped"); 
    }
    So whenever the honeypot textbox is filled in, the comment gets an extended attribute named GotchaInMyHoneyPot.

Now this new form must be used on the actual page. There’s only one page (per theme), which allows visitors to leave comments, which is post.aspx. Unfortunately, this must be done for every theme.

So open post.aspx and replace the original WeblogPostCommentForm with our new one, add the HoneyPotTextBoxId attribute to the form, and add the textbox. Don’t forget to use the same id for the textbox (this example uses tbBody, which shouldn’t ring a bell for the spammer.) Here’s the modified form declaration from the PaperClip theme:

<tfr:HoneyPotWeblogPostCommentForm runat="server" ValidationGroup="CreateCommentForm" 
    MessageTextBoxId="tbComment" 
    NameTextBoxId="tbName" 
    RememberCheckboxId="chkRemember" 
    SubjectTextBoxId="tbTitle" 
    SubmitButtonId="btnSubmit" 
    UrlTextBoxId="tbUrl" 
    ControlIdsToHideFromRegisteredUsers="RememberWrapper" 
    HoneyPotTextBoxId="tbBody" 
    > 
    <SuccessActions> 
        <CSControl:GoToModifiedUrlAction runat="server" QueryStringModification="CommentPosted=true" TargetLocationModification="commentmessage" /> 
    </SuccessActions> 
    <FormTemplate> 
        <fieldset id="commentform"> 
        <legend><CSControl:ResourceControl runat="server" ResourceName="Weblog_CommentForm_WhatDoYouThink" id="rc_think"/></legend> 
            <p /> 
            <div><CSControl:FormLabel runat="server" ResourceName="Title" LabelForId="tbTitle" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required"/>)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbTitle" ValidationGroup="CreateCommentForm" /></div> 
            <div><asp:TextBox id="tbTitle" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <p /> 
            <div id="NameTitle" runat="server"><CSControl:FormLabel LabelForId="tbName" runat="server" ResourceName="Weblog_CommentForm_Name" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required" />)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbName" ValidationGroup="CreateCommentForm" /></div> 
            <div id="NameDesc" runat="server"><asp:TextBox id="tbName" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <p /> 
            <div><CSControl:FormLabel runat="server" LabelForId="tbUrl" ResourceName="Weblog_CommentForm_YourUrl" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Optional" /></em>)</div> 
            <div><asp:TextBox id="tbUrl" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /></div> 
            <%-- the honeybot textbox --%> 
            <asp:TextBox id="tbBody" runat="server" CssClass="smallbox" ValidationGroup="CreateCommentForm" /> 
            <p /> 
            <div><CSControl:FormLabel LabelForId="tbComment" runat="server" ResourceName="Weblog_CommentForm_Comments" /> <em>(<CSControl:ResourceControl runat="server" ResourceName="Required" />)</em><asp:RequiredFieldValidator runat="server" ErrorMessage="*" ControlToValidate="tbComment" ValidationGroup="CreateCommentForm" /></div> 
            <div><asp:TextBox id="tbComment" runat="server" Rows="5" Columns="25" TextMode="MultiLine" ValidationGroup="CreateCommentForm" /></div> 
            <asp:PlaceHolder runat="server" id="RememberWrapper"> 
                <p /> 
                <div><asp:CheckBox id="chkRemember" runat="server" Text="Remember Me?" ValidationGroup="CreateCommentForm"></asp:CheckBox></div> 
            </asp:PlaceHolder> 
            <p /> 
            <asp:Button id="btnSubmit" runat="server" Text="Submit" ValidationGroup="CreateCommentForm"></asp:Button> 
        </fieldset> 
    </FormTemplate> 
</tfr:HoneyPotWeblogPostCommentForm>

But that’s only the first half. Now whenever the honeypot field is filled with some text the comment will have an extended attribute GotchaInMyHoneyPot.

The second step is to give the comment “spam points”, which is done by a CS spam rule. Keyvan Nayyeri published a complete tutorial how to write your own spam rules, so I won’t get into details. Here’s the complete code for the new rule:

public class HoneyPotRule : BlogSpamRule { 
    private static readonly Guid _ruleId = new Guid("57E216D4-D100-468d-BB37-1B7A0A103CEF"); 
    private const int _defaultPoints = 10; 

    public override ArrayList GetAvailableSettings() { 
        ArrayList list = new ArrayList(); 
        list.Add(new RuleSetting(_ruleId, "points", "Points", _defaultPoints.ToString())); 
        return list; 
    } 
    
    public override int CalculateSpamScore(WeblogPost weblogPost, CSPostEventArgs e) { 
        if (weblogPost.BlogPostType == BlogPostType.Comment) { 
            if (!String.IsNullOrEmpty(weblogPost.GetExtendedAttribute("GotchaInMyHoneyPot"))) { 
                EventLogs.Info("A spammer fell for the honey pot", "Spam Rules", 0); 
                return Globals.SafeInt(GetSettingValue("points"), _defaultPoints); 
            } 
        } 

        return base.CalculateSpamScore(weblogPost, e); 
    } 

    public override string Name { 
        get { return "HoneyPot"; } 
    } 

    public override string Description { 
        get { return "HoneyPot description"; } 
    } 

    public override Guid RuleID { 
        get { return _ruleId; } 
    } 
}

I’m running this solution for a couple of days now on my site, and it works pretty well. It’s amazing how many spam bots fill each and every textbox they can find. But I admit that a lot of steps are involved in my solution, there’s lot of programming required. However, I decided against publishing an all-inclusive package, because I’d like to add this solution to next release of either the CSMVP CSModules or Community Server Stuff. So either follow my instructions above, wait for an official release, or leverage Phil’s honeypot control which is part of the Subkismet project (not released yet too, but you can already get the sources.)

nrw07 Slides

In my nrw07 talk I promised that I would publish my slides. Originally they´re German, so I translated them first (no, it did not take me two weeks to translate them Stick out tongue). Here they are:

CustomizingCommunityServer - en.pptx

Deflowered twice on one day at nrw07

nrw07 speaker Last Friday the nrw07 took place in Wuppertal, the biggest community conference in North Rhine-Westphalia. About 100 attendees including the 22 speakers! The Diebels brewery contributes a couple of beer crates, and Subway served lots of sandwiches for lunch.

And I was deflowered twice on that day.

me speakingFirst, it was my very first talk! I gave a Community Server presentation a couple of month ago at our local .NET UserGroup, and the other guys suggested that I should repeat that at the nrw07. Unfortunately, I didn´t object enough.

When I sat in the hotel lobby with some other speakers the night before nrw07, I started getting nervous, but they managed to calm me down (or was it the beers we had?). The next day was fine, my talk was one of the first. It ran pretty well (as far as I am concerned). Of course we experienced the usual technical difficulties such as a projector, whose picture was twice as large as the screen and not resizable. Although I got the second largest room in the facility, there were only 7 attendees. Seems to me as Community Server is not of interest for everyone. The good thing about that is that those folks already knew CS, so I didn´t have to start at square one, but instead dive right into the technical stuff. The time flew, I ran over about 10 minutes and had still enough material for another hour. Nevertheless my talk can not have been that bad because the audience asked the right questions afterwards. I hopethink that´s a good sign.

all speakersMy second premiere happened later that night, when Craig Murphy interviewed me for a podcast (not aired yet). At that point in time I already had a couple of beers, so it went quite smooth. It´s self-evident that we talked much about Community Server. Because I mentioned Twitter in my talk, Craig took me up on that, and somehow we drifted into social networking, a topic I´m quite interested in. Even when the podcast was over, we continued the discussion.

To sum it up, it was a great event, where I met many smart people. Many thanks to the orga team, Stephan Oetzel and Daniel Fisher!

And now that I lost my virginity, I´m looking forward to give a talk again next year (assumed they let me on stage ever again)

Here are some other speakers and attendees blogging about nrw07:

I met some more nice guys, who didn´t blog about this event. Nevertheless I´d like to send them my regards because we had such a good time and talks: Andreas Hoffmann, Christian Schütz, Mischa Hüschen, Pascal Kremmers, Constantin Klein, Marcel Gnoth, and Marcel Franke. Sorry if I forgot one, there were so many faces new to me. When you see me next time, just stop by and treat me to a beer...

ReSharper 3.0 Released

JetBrains released ReSharper 3.0

ReSharper 3.0 is finally out and it’s better than ever. C#, VB.NET, XML, XAML or ASP.NET - we got it all for you right here!

You simply owe it to yourself to test-drive this baby and learn a whole new way to code in Visual Studio!

Get the dirty low-down on ReSharper 3.0 at New Features or use one of the links below:

Grab a free 30-day evaluation of ReSharper at http://www.jetbrains.com/resharper/download/index.html
Twitter Publisher for CruiseControl.NET

Some weeks ago I posted a CC.NET task which pushes build results to a blog using the MetaWeblogAPI. This might be a feasable solution for projects which sources aren’t updated that often. Otherwise that blog would be really cluttered, and you won’t be able to keep track of all the build results.

Several month ago a new social networking site started called Twitter. It offers a kind of micro-blogging service, allowing its users to send text-only stati, up to 140 characters long. Whenever you update your status, it is delivered instantly to other users who have put you to their “friends” list. Though you can receive the updates of your friends via an RSS feed, it is more common to either use Twitter’s website or a desktop client such as TeleTwitter. Additionally, Twitter offers a RESTful API.

Therefore it was pretty obvious to write a CC.NET task which announces new build results on Twitter. The project manager creates a special Twitter account and configures CC.NET to post build results as updates for that user. The developers then just have to add that user to their friend list, and will get the announcements in the Twitter front-end of their choice.

The attached ZIP file contains both the sources and the compiled assembly, which you have to dump into CC.NET’s server directory. The configuration of the task is pretty easy, just specify the user and the password of the Twitter account.

<publishers>
    ...
    <twitter>
        <user>username</user>
        <password>password</password>
    </twitter>
    ...

CommunityServer MVPs CSModules Pack for CS2007 released

Yesterday a new version of the CSMVPs CSModules package was released. Except the new LinkManager (which enables you to add customizable attributes to links in your posts automatically) it´s mainly a technical refresh targeting CommunityServer 2007 (SP1).

Read the full announcement is available on the CS MVP site.

More Posts Next page »