Using AOP to run xUnit BDD Extensions in a TFS 2010 build

Friday, March 25, 2011 8:33:13 PM (W. Europe Standard Time, UTC+01:00)

For the past 2 years, I’ve been using Björn Rochels xUnit BDD extensions to write the unit tests for my applications. The xUnit BDD extensions are a great framework that makes it easy to write well structured unit tests. They even include an automocking framework, so that mocks are created automatically for all the dependencies that need to be injected into the system under test via constructor injection. For some strange reason, I cannot find any good introductory articles about the bdd extensions in English, but you can take a look at one of the samples to get an idea how they work.

This post was not meant to be an introduction to the xunit bdd extensions, but I’ll quickly summarize how they work:

A unit test usually has three parts: arrange, act and assert. In the arrange part, you do everything you need to to to setup the test scenario. In the act part, you call the method that you want to test. And finally, in the arrange part, you verify that the Sut (system under test) actually did what it was supposed to do.

The xUnit BDD extensions make it easy to follow that structure: in your specification, you override the method EstablishContext for the arrange part of the test. For the act part of the test, you override the Because-method, and for every observation that you want to assert, you create a method that has the Observation attribute (actually, you could simply use xUnits FactAttribute, Observation is just an alias for Fact). You don’t have to write any code to create the Sut. The bdd extensions do that automatically for you, even if your Sut needs parameters for its constructor. If you want to create the Sut yourself for some reason, you can do so by overriding the CreateSut method.

While writing tests is really easy and fun with the xUnit BDD extensions, integrating them into a TFS build is not. As the name implies, xUnit BDD extensions are based on the xUnit framework. It’s possible to run xUnit tests in TFS, but running xUnit tests in TFS usually requires modifications to the projects MSBuild files or the TFS build process template. None of the integration approaches I found supported additional functionality like code coverage or unit test impact anlysis, and getting the test results into TFS also requires additional work.

When you use MSTest, you get all these integration features for free and don’t have to modify any build files. However, MSTest does not make it as easy as the BDD extensions to write the tests.

What I wanted was to find a way to combine the best of both worlds. I wanted to use xUnit BDD Extensions, and I wanted them to automatically be integrated into my TFS builds.

When I started to rewrite NLocalize, I began to use PostSharp for the first time. PostSharp is a framework for aspect oriented programming. Now I don’t want to explain AOP in this post, as it’s already getting quite long and I did not even really begin to write about what I wanted to write about in the first place Winking smile. So if you don’t know AOP, take a look a the samples on the PostSharp web site.

So how can PostSharp help with my unit testing problem? The basic idea is, that I take a xunit bdd specification and wrap it in an MSTest test, so that it can be run by the MSTest test runner.

How would that look like? Let’s look at one of the specifications from NLocalize:

[Concern(typeof(WizardController))]    
    public class When_the_current_step_is_valid_and_the_next_command_is_executed : InstanceContextSpecification<WizardController>
    {
        private IWizardStep[] steps;
        protected override void EstablishContext()
        {
            base.EstablishContext();
            steps = new IWizardStep[]
                {
                    An<IWizardStep>(),
                    An<IWizardStep>(),
                    An<IWizardStep>(),
                };
            steps[0].WhenToldTo(s => s.IsValid).Return(true);
            steps[0].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
            steps[1].WhenToldTo(s => s.IsValid).Return(true);
            steps[1].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
            steps[2].WhenToldTo(s => s.IsValid).Return(true);
            steps[2].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
        }
        protected override void PrepareSut()
        {
            base.PrepareSut();
            Sut.Steps = steps;
        }
        protected override void Because()
        {
            Sut.NextCommand.Execute(null);
        }
        [Observation]
        private void Should_enable_the_previous_command()
        {
            Sut.PreviousCommand.CanExecute(null).ShouldBeTrue();
        }
        [Observation]
        private void Should_move_to_the_next_step()
        {
            Sut.CurrentStep.ShouldBeEqualTo(steps[1]);
        }
    }

That’s one of the specifications for a wizard control. It verifies that the wizard actually moves to the next step when the user clicks next, and that the “previous”-button is enabled.

To make this test runnable by MSTest, we have to rewrite every observation as MSTest test. We somehow have to get MSTest to use xUnit BDD extensions to create the Sut. For each test, we need to make sure that EstablishContext is executed. We need to mark each observation with the TestMethod-attribute. We also need to mark the specification with the TestClass-attribute.

The test should still be executable by the xUnit test runner (I’m using ReSharpers test runner to run my test locally, as it is way more usable than the Visual Studio test runner). So any modifications made to enable the test to be run by MSTest should not have any side effects when I execute the test with a xUnit test runner.

I solved my problem with a PostSharp aspect. The aspect makes several changes to a xUnit specification when it is applied to the specification class:

  • It searches for all methods that have the ObservationAttribute, and adds a TestMethodAttribute to each of those methods.
  • It adds a TestClassAttribute to the specification class.
  • It injects a new method into the specification class that has the TestInitializeAttribute. This method is used to call the InitializeContext method of the xunit BDD specification (this method calls EstablishContext and CreateSut).
  • It injects a new method into the specification class that has the ClassCleanupAttribute. This method is used to call the CleanupSpecification method of the xunit BDD specification.
  • It initializes the xUnit runner configuration.

Now all I have to do to execute my specifications with MSTest is that I have to add the MSTestRunnable attribute to my specifications. I also have to make sure that my observations are public (that is not required by the xUnit BDD extensions, but MSTest test methods must be public).

For MSTest, my tests now look like any other MSTest test. That means that my tests are supported automatically by TFS and just work. I don’t have to modify my build files or my TFS build process templates. Code Coverage and Test Impact Analysis also work just like with other MSTest tests.

Here is the code for my aspect:

namespace Neovelop.TestAspects
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using PostSharp.Aspects;
    using PostSharp.Aspects.Advices;
    using PostSharp.Reflection;
    using Xunit;
    /// <summary>
    /// The MSTestRunnable-Attribute can be used to to modify a xUnit BDD Extension specification so that it can be called by the MSTest test runner.
    /// </summary>
    [Serializable]
    public sealed class MSTestRunnableAttribute : InstanceLevelAspect, IAspectProvider, IContextSpecification
    {
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented",
            Justification = "Reviewed. Suppression is OK here.")]
        public void InitializeContext()
        {
        }
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented",
            Justification = "Reviewed. Suppression is OK here.")]
        public void CleanupSpecification()
        {
        }
        /// <summary>
        /// This method adds TestMethodAttributes to all xunit BDD observations, and a TestClassAttribute to the specification class.
        /// </summary>
        /// <param name="targetElement">
        /// The target element.
        /// </param>
        /// <returns>
        /// Returns a list of aspects that should be provided to the targetElement.
        /// </returns>
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type)targetElement;
            Debug.WriteLine(targetType.Name);
            var testMethod = new ObjectConstruction(typeof(TestMethodAttribute).GetConstructor(Type.EmptyTypes));
            var introduceTestMethodAspect = new CustomAttributeIntroductionAspect(testMethod);
            foreach (MethodInfo mi in GetObservations(targetType))
            {
                yield return new AspectInstance(mi, introduceTestMethodAspect);
            }
            var testClass = new ObjectConstruction(typeof(TestClassAttribute).GetConstructor(Type.EmptyTypes));
            var introduceTestClassAspect = new CustomAttributeIntroductionAspect(testClass);
            yield return new AspectInstance(targetElement, introduceTestClassAspect);        
        }
        /// <summary>
        /// This method calls the InitializeContext method of the xUnit BDD specification during the initialization of the MSTest-test.
        /// </summary>
        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Ignore, Visibility = Visibility.Public)]
        [CopyCustomAttributes(typeof(TestInitializeAttribute))]
        [TestInitialize]
        public void Initialize()
        {
            ((IContextSpecification)this.Instance).InitializeContext();
        }
        /// <summary>
        /// This method calls the CleanupSpecification method of the xUnit BDD specification during the ClassCleanup of the MSTest-test.
        /// </summary>
        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail, Visibility = Visibility.Public)]
        [CopyCustomAttributes(typeof(TestCleanupAttribute))]
        [ClassCleanup]
        public void MSTestCleanup()
        {
            ((IContextSpecification)this.Instance).CleanupSpecification();
        }
        /// <summary>
        /// This method ensures that the xUnit configuration is initialized.
        /// </summary>
        /// <param name="type">
        /// The type that is initialized.
        /// </param>
        public override void RuntimeInitialize(Type type)
        {
            base.RuntimeInitialize(type);
            var config =
                typeof(RunnerConfiguration).GetField(
                    "Instance", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic).GetValue(null) as
                IRunnerConfiguration;
            Xunit.RunnerConfigurationAttribute.DefaultConfiguration.Configure(config);
        }
        /// <summary>
        /// This method returns all xUnit observation methods in the given target type.
        /// </summary>
        /// <param name="targetType">
        /// The target type.
        /// </param>
        /// <returns>
        /// Returns all xUnit observation methods in the given target type.
        /// </returns>
        private static IEnumerable<MethodInfo> GetObservations(Type targetType)
        {
            return targetType.GetMethods().Where(m => m.GetCustomAttributes(true).OfType<ObservationAttribute>().Any());
        }        
    }
}

Feel free to use this code in your own projects, but if you do, you use it at your own risk, I make no guarantees that this code is bug free.

It won’t behave in exactly the same way as when the test is executed by xUnit, but so far it worked for all of my tests. I even discovered a subtile bug in my code because a test passed when executed by xUnit but failed when it was executed by MSTest.

Kick it on dotnet-kicks.de

xUnit BDD Extensions

Wednesday, October 22, 2008 6:24:03 PM (W. Europe Daylight Time, UTC+02:00)

Auf dem .NET Open Space hat Björn Rochel seine xUnit BDD Extensions vorgestellt und wir haben uns in einer Session auch länger darüber unterhalten.

Heute habe ich seine Extensions das erste Mal ausprobiert und einen neuen Unit Test nach BDD Art geschrieben.

Dieser spezielle Test war besonders kompliziert, da ich hier Code in einem Windows Service testen wollte und dafür die OnStart Methode aufrufen musste, die protected ist. Außerdem startet der Code der getestet wird mehrere neue Threads, und der Test sollte prüfen, ob diese Threads auch wirklich gestartet wurden.

Der Test sieht dann folgendermaßen aus:

public class 
When_the_Service_Starts_And_There_Are_three_Tasks : 
TaskRunnerServiceSpecification
   {
       #region Arrange

       protected override void EstablishContext()
       {
           base.EstablishContext();

           newThreads = new List<Thread>();

           // add some tasks
           tasks.Add(new TestTask(this));
           tasks.Add(new TestTask(this));
           tasks.Add(new TestTask(this));
       }

       internal IList<Thread> newThreads;



       #endregion

       #region Act

       protected override void Because()
       {
           // because OnStart was called...
           taskRunnerService.InvokeNonPublicMethod(
              "OnStart", new object[] {
                 new string[0]});

           // ... wait a second to give the other 
           // threads a chance to execute
           Thread.Sleep(1000);
       }

       #endregion

       #region Assert

       [Observation]
       public void should_create_three_new_threads()
       {
           newThreads.Distinct().Count().
           ShouldBeEqualTo(3);
       }

       [Observation]
       public void should_execute_all_tasks()
       {
           tasks[0].wasExecuted.ShouldBeTrue();
           tasks[1].wasExecuted.ShouldBeTrue();
           tasks[2].wasExecuted.ShouldBeTrue();
       }


       #endregion

       #region Cleanup


       protected override void AfterEachObservation()
       {
           base.AfterEachObservation();

           foreach (var thread in newThreads)
           {
               if (thread.IsAlive)
                   thread.Abort();
           }
       }

       #endregion
   }

Im Prinzip verbirgt sich dahinter ein “ganz normaler” Unit Test mit xUnit und Rhino Mocks (wobei ich in diesem speziellen Test zufällig gerade keine Mocks nutze), allerdings besser strukturiert und mit lesbareren Methodennamen (z.B. ShouldBeTrue statt Assert.True).

Durch die Bennenung der Tests und Methoden nach BDD-Art ist die Ausgabe im ReSharper Testrunner richtig gut lesbar:

image

In dem Beispielcode oben fehlt noch eine Hilfsklasse für diesen Test, TestTask. Diese setzt nur ihre “wasExecuted”-Eigenschaft auf true, falls die Execute-Methode aufgerufen wurde, und fügt ihren Thread zur newThreads-Liste hinzu.

Wir werden das in der Praxis weiter ausprobieren (ein anderer Kollege hat sich vorhin auch schon an BDD versucht), und diesen Ansatz wenn er sich bewährt für alle unsere Unit Tests übernehmen.

Kick it on dotnet-kicks.de

Lambda-Ausdrücke in C# 3.0

Monday, April 28, 2008 10:43:51 PM (W. Europe Daylight Time, UTC+02:00)

Ganz weit oben in der Liste meiner “Lieblingsfeatures” von C# stehen Lambda-Ausdrücke. Vereinfacht gesagt sind Lambda-Ausdrücke die Weiterentwicklung der Anonymen Methoden aus C# 2.0. Anonyme Methoden erlauben es, Code direkt an einen Delegate zuzuweisen. Beispiel:

In C# 1.0/1.1 muss man einem Delegate eine vorhandene Methode zuweisen:

   1: public void BeispielMethode()
   2: {
   3:     button1.Click += new EventHandler(button1_click);
   4: }
   5:  
   6: private void button1_Click(object sender, EventArgs e)
   7: {
   8:     MessageBox.Show("Button wurde geklickt!");
   9: }

In C# 2.0 ist es nicht mehr erforderlich, dem Delegate eine benannte Methode zuzuweisen. Stattdessen kann man ihm direkt eine anonyme Methode zuweisen, der Code verkürzt sich auf die folgenden Zeilen:

   1: public void BeispielMethode()
   2: {
   3:     button1.Click += delegate(object sender, EventArgs e)
   4:     {
   5:         MessageBox.Show("Button wurde geklickt!");
   6:     };
   7: }

Mit den Lambda-Ausdrücken aus C# 3.0 wird der Code erneut deutlich kürzer:

   1: button1.Click += 
   2:     (sender, e) => MessageBox.Show("Button wurde geklickt!");

Dem Click-Ereignis wird hier ein Lambda Ausdruck zugewiesen. Dieser besteht aus zwei Teilen: einer Parameterliste (links vom =>) und einem Ausdruck, auf den diese Parameter abgebildet werden (rechts vom =>).

Der Compiler erkennt hier automatisch, dass sender vom Typ object und e vom Typ EventArgs ist.

Da ich super müde bin und gleich einschlafe (habe den ganzen Tag einen 70-536 Prüfungsvorbereitungskurs unterrichtet) soll das erst mal genug sein zu dem Thema, wer mehr über Lambdas wissen möchte finden einen guten Artikel bei Scott Guthrie:

New "Orcas" Language Feature: Lambda Expressions

Technorati Tags: ,
Kick it on dotnet-kicks.de

CardSpace login for dasBlog - download

Tuesday, March 27, 2007 3:24:40 PM (W. Europe Daylight Time, UTC+02:00)

(this post is in English, because I think it might be useful for non-German readers as well)

A few days ago I wrote that I added CardSpace support to my blog. I've now cleaned up and commented my source code and you  can download it now.

CardSpace support for dasBlog - download

Use this file at your own risk. I do not guarantee that it is bug free or that it is the best and easiest way to add CardSpace to your blog. All I can say is that it works for me :).

You need .NET 3.0 on your server. You also need a valid SSL certificate (or otherwise you must create your own certificate with tools like selfssl form theIIS 6.0 resource kit and install it on every client from which you want to use the CardSpace login). For my blog, I use a SSL certificate that I bought from http://www.namecheap.com/, which costs less than 15$ / year.

To install CardSpace support for dasBlog, just copy CardSpaceLogin.aspx, CardSpaceLogin.aspx.cs, images\informationcard.gif and app_code\TokenProcessor.cs to your dasBlog directory. 

I'm not the author of TokenProcessor.cs, this file is part of the CardSpace samples on http://cardspace.netfx3.com.

In CardSpace v1, this only works via HTTPS. You need a valid SSL certificate for your web site. The user account under which you run dasBlog
must have access to the private key of your SSL certficate. The CardSpace samples on http://cardspace.netfx3.com contain a tool which can find the name and location of the file in which your SSL certificate is stored (findprivatekey.exe). Find that file and grant your dasBlog ASP .NET account read permissions.

To associate an existing user account with an information card, login by using username and password first. Then go to the CardSpace login page and select a card. This card will then be associated with your user account.

If you use this for your blog, please let me know by e-mail or by writing a comment for this blog entry.

Technorati tags: ,
Kick it on dotnet-kicks.de

Windows CardSpace Login in dasBlog integrieren

Wednesday, March 14, 2007 12:00:58 AM (W. Europe Standard Time, UTC+01:00)

In diesem Beitrag werde ich zeigen, wie sich Windows CardSpace in dasBlog integrieren lässt, ohne dass dafür Änderungen am Quellcode von dasBlog notwendig sind. Ziel ist, dass sich der Blog-Administrator mit Windows CardSpace anmelden kann. CardSpace soll in diesem Beispiel nicht für Besucher des Blogs genutzt werden (z.B. für die Kommentarfunktion), sondern nur für den Administrator.

Dafür sind einige Voraussetzungen erforderlich:

1) WebSpace mit .NET 3.0 Unterstützung

Zum Entschlüsseln des CardSpace-Tokens verwende ich die TokenProcessor Hilfsklasse von netfx3.com, die im Moment noch .NET 3.0 Klassen verwendet. Es soll aber bald eine .NET 1.1 Version dieser Klasse geben, so dass mein Code mit dieser Klasse (und noch 2-3 weiteren kleinen Änderungen - ich verwende generische Typen) ebenfalls auf .NET 1.1 laufen würde.

2) Ein SSL Zertifikat

CardSpace funktioniert nur mit einem gültigen und fehlerfreien SSL Zertifikat. Selbstausgestellte Zertifikate gehen auch, wenn sie im Client als vertrauenswürdige Zertifikate gespeichert werden, was in diesem Beispiel (Login nur für den Admin) noch ok wäre. Sollen sich beliebige Besucher der Seite über CardSpace anmelden, ist das keine akzeptable Alternative mehr.

Zum Glück gibt es aber mindestens einen sehr günstigen Anbieter, der Zertifikate für unter 15€ pro Jahr verkauft: http://www.namecheap.com/learn/other-services/cheap-ssl-certificate-rapidssl.asp

Dort habe ich auch ein Zertifikat für OutOfCoffeeException.de für 2 Jahre gekauft.

3) Der Prozess, unter dem dasBlog läuft, muss Zugriff auf den private Key des SSL Zertifikats haben, damit er die Security-Tokens entschlüsseln kann. Einige der Samples auf netfx3 (z.B. dieses) enthalten das Tool findprivatekey.exe, das die Datei, indem ein Zertifikat gespeicher ist, anzeigen kann. Auf diese Datei müssen Leserechte für den Prozess, unter dem dasBlog läuft, gesetzt werden.

Somit wären also die Vorbedingungen geklärt.

Mir war es wichtig, dass mein CardSpace-Login ohne Änderungen am dasBlog Quellcode selbst funktioniert, damit ich später problemlos neuere Versionen installieren kann, ohne meine Änderungen zu überschreiben. Ich habe deshalb eine neue Seite CardSpaceLogin.aspx erstellt, die die CardSpace Anmeldung übernimmt. Außerdem habe ich ein Makro geschrieben, dass einen Link "Anmeldung mit Windows CardSpace" erzeugt, der auf diese Seite verlinkt und das für CardSpace benötigte Object-Tag in die Hauptseite meines Blogs einbaut (wenn man in den Seitenquelltext schaut findet man es ganz am Ende der Seite). Sofern ich mein Blog über https aufgerufen habe kann ich mich so direkt vom Blog aus anmelden.

Die CardSpaceLogin.aspx Seite selbst besteht nur aus einem Button zum Anmelden mit CardSpace und einem Object-Tag:

(ja, der Code ist zu breit für mein Blog, im Moment habe ich aber keine Idee wie ich das schöner formattieren könnte)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CardSpaceLogin.aspx.cs" Inherits="_Default"
    ValidateRequest="false" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Windows CardSpace Login</title>
</head>
<body>
    <form id="CardSpaceForm" runat="server">
        <div style="text-align: center; padding: 128px;">
            <div style="text-align: center; padding: 32px; background-color: #a0ffa0;">
                <img src="/images/informationcard.gif" alt="Windows CardSpace" /><br /><br />
                <asp:Button runat="server" Text="Anmeldung mit Windows CardSpace" /><br />
                <asp:Literal ID="LoginStatus" runat="server"></asp:Literal>
                <object type="application/x-informationcard" name="xmlToken">
                    <param name="tokenType" value="urn:oasis:names:tc:SAML:1.0:assertion" />
                    <param name="requiredClaims" value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" />
                </object>
            </div>
        </div>
    </form>
</body>
</html>

 Dieser Object-Tag wird von Browsern, die CardSpace unterstützten, erkannt. In dem Moment wo der Button geklickt wird, wird das Formular "submitted", also die Formularinhalte an den Server übertragen. Da der Object-Tag im Formular steht, erkennt der Browser, dass er an dieser Stelle ein CardSpace-Securitytoken übermitteln soll. Also wird dem Benutzer ein Dialog (der Identityselector) angezeigt, in dem er eine Karte für die Anemdlung an der Seite auswählen kann. Das (verschlüsselte und signierte) SecurityToken wird anschließend an den Server übertragen und kann dort ausgewertet werden.

Etwas vereinfacht wird nun der folgende Code ausgeführt:

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SiteConfig siteConfig = SiteConfig.GetSiteConfig();

        string stoken = Request["xmltoken"] as string;
        if (string.IsNullOrEmpty(stoken))
        {
            return;
        }

        Microsoft.IdentityModel.TokenProcessor.Token token = 
            new Microsoft.IdentityModel.TokenProcessor.Token(stoken);       

        List<CardSpaceToken> tokens = CardSpaceToken.GetTokens();

        // compare the token with all tokens in cardspaceSecurity.config
        foreach (CardSpaceToken cst in tokens)
        {
            // found token in cardSpacesecurity.config, now find
            // the user with the user name that was specified there
            if (token.UniqueID == cst.UniqueID)
            {
                SiteSecurityConfig securityConfig = SiteSecurity.GetSecurity();
                foreach (User user in securityConfig.Users)
                {
                    // found user with this CardSpace token. log the user in.
                    if (user.Name == cst.UserName)
                    {
                        Login(user.Name, user.Password);
                        Response.Redirect(Utils.GetBaseUrl(), true);
                        return;
                    }
                }
            }
        }        
    }

}

Mit der anfangs erwähnten Hilfsklasse TokenProcessor wird das Token entschlüsselt. Über die Klasse könnte ich sämtliche Inhalte des Tokens abfragen, mich interessiert hier aber nur die UniqueID. Diese ID ist für jeden Benutzer eindeutig, und ersetzt quasi Benutzername + Kennwort. Ich muss diese ID nur noch mit den IDs vergleichen, die ich bereits für meine Blog-Benutzer gespeichert habe. Finde ich eine passende gespeicherte ID, dann melde ich den Benutzer an. Falls ich keine ID finde, aber bereits ein Benutzer angemeldet ist, dann ordne ich die ID der verwendeten Karte diesem Benutzer zu (dieser Teil ist im Codeausschnitt oben schon nicht mehr zu sehen).

Das war's auch schon. Um CardSpace in meinem Blog zu verwenden musste ich keine einzige vorhandene Datei ändern, und nur 3 neue Dateien hinzufügen (CardSpaceLogin.aspx, CardSpaceLogin.aspx.cs und TokenProcessor.cs). Zusätzlich habe ich dann noch ein Makro geschrieben, um den Anmeldelink in mein Blog zu integrieren, aber das ist im prinzip schon zusätzliche Sonderausstattung :).

Ich werde in den nächsten Tagen den Code noch etwas aufräumen und anschließend veröffentlichen.

Ich bin nicht der erste, der CardSpace in dasBlog integriert. Eine andere Variante gibt es hier: Early SVN patch of DasBlog 1.9.6264 with information card support available. Für meine Variante ist jedoch keine Modifikation am original Quellcode erforderlich.

Kick it on dotnet-kicks.de
Page 1 of 2 in the CodeBeispiele category Next Page