Archiv für Oktober 2009

Auswahl einer geeigneten Open Source Lizenz

Donnerstag, 15. Oktober 2009

Wie ich bereits geschrieben habe, bin ich in den Vorbereitungen, um kleines Projekt als Open Source auf CodePlex, dem Open Source Portal von Microsoft, zu veröffentlichen. Dabei ist auch die Wahl der Lizenz ein Thema. Die CodePlex Verwaltung macht es einem hier sehr einfach, es sind schon diverse Lizenztexte vorbereitet, aus welchen man nur einen auswählen kann.

Auswahl der Lizenzen bei CodePlex

Auswahl der Lizenzen bei CodePlex

Aber welchen? Und in welchen Punkten unterscheiden sich die einzelnen Lizenzen? Eine kurze Recherche brachte etwas Licht in dieses mir bislang unbekannte Gebiet.
Die folgende Matrix zeigt die verschiedenen Lizenzen und die Punkte, in denen sie sich unterscheiden:

Open Source Lizenz Matrix

Open Source Lizenz Matrix

Allen Lizenzen gemeinsam ist, dass die Lizenz immer mitverbreitet werden muss, und dass von einer Software abgeleitete Arbeiten verkauft werden dürfen. Die einzelnen Lizenzen besitzen noch folgende Punkte, welche sie von anderen unterschieden.

Apache License 2.0

Die Apache Lizenz stellt sicher, dass die Software frei verfübar ist, und dass der Name der Apache Foundation nicht für das eigene Produkt verwendet werden kann. Weiter müssen Änderungen an der Software als Quelltexte veröffentlicht werden.

New BSD License

Die New BSD License ist wie die MIT License nicht allzu restriktiv, ausser dass die Namen der Kontributoren nicht für abgeleitete Software verwendet werden dürfen.

GNU General Public License (GPL)

Die GPL ist in einem Punkt restriktiv, was auch bereits zu vielen Diskussionen in der Open Source Szene geführt hat. Jede Software, welche eine GPL lizenzierte Komponente verwendet, muss ebenfalls unter der GPL vertrieben werden.

GNU Library General Public License (LGPL)

Die LGPL ist eine Version der GPL, welche sich besonders für Software Komponenten eignet. Eine Software, die eine LGPL Komponente verwendet, kann eine andere Lizenz verwenden.

MIT License

Die MIT License ist die einfachste und am wengsten restriktive Open Source Lizenz. Sie verfügt nur über zwei Teile. Der erste Teil gestattet es jedem, die MIT lizenzierte Software frei zu nutzen, zu vertreiben oder gar verkaufen (immer aber mit Quellcode). Der zweite Lizenzteil stellt schliesst eine Garantie der Software aus.

Mozilla Public License

Die MPL wird vorwiegend von der Mozilla Corporation für ihre Software verwendet.

Microsoft Public License (Ms-PL)

Die Microsoft Public License ist ähnlich wenig restriktiv wie die BSD oder MIT Lizenzen. Einzige Ausnahmen sind das Verbot der Verwendung des Namens, Logos oder von geschützten Marken (Trademark) des Lizenzgebers, sowie die freie Anwendung von Patenten, welche innerhalb der Software des Lizenzgeber verwendet werden.

Microsoft Reciprocal License (Ms-RL)

Diese Lizenz ist der Ms-Pl sehr ähnlich. Der einzige Unterschied besteht darin, dass wenn irgendwelche Dateien einer Ms-RL lizenzierten Software in eigenen Lösungen verwendet werden, diese Dateien in Quellform und mit einer Kopie der Ms-RL angeboten werden müssen.


Warnung:
Für die obigen Informationen kann ich keinerlei Garantie übernehmen, die Angaben basieren auf meiner persönlichen Interpretation der Lizenztexte. Lesen Sie die entsprechenden Lizenzen gut durch, bevor Sie sich für eine entscheiden. Für juristische Beratung in diesen Belangen verweise ich Sie gerne an meinen geschätzten Partner affolter.net.

Security Trimming mit ASP.NET MVC

Mittwoch, 14. Oktober 2009

Einer der grossen Pluspunkte des ASP.NET MVC Frameworks ist, dass viel von der bestehenden ASP.NET Infrastruktur weiterverwendet werden kann. In einigen wenigen Fällen lassen sich auch die WebForm Server Controls sinnvoll einsetzen. Dies ist möglich, solange die Standard WebFormViewEngine verwendet wird.

Ein sehr praktisches Feature der klassischen Webforms ist das Security Trimming, also das Anzeigen von rollenbasierten Inhalten. Je nach Rolle, welche der eingeloggte Benutzer zugewiesen hat, werden ihm nur für diese Rolle passende Inhalte angezeigt. Das ganze Membership System der Webforms wird vom MVC Framework unterstützt. Auch das LoginView Control lässt sich im MVC Framework verwenden, da es keinen ViewState benötigt:

<asp:LoginView ID="LoginView1" runat="server">
  <AnonymousTemplate>I can be seen when the user is Anonymous.</AnonymousTemplate>
  <LoggedInTemplate>I can only be seen when a user without group membership is logged in.</LoggedInTemplate>
  <RoleGroups>
    <asp:RoleGroup Roles="Admin,User">
      <ContentTemplate>Only users with the "Admin" and "User" role can see this.</ContentTemplate>
    </asp:RoleGroup>
    <asp:RoleGroup Roles="Admin">
      <ContentTemplate>Only users with the "Admin" role can see this.</ContentTemplate>
    </asp:RoleGroup>
    <asp:RoleGroup Roles="User">
      <ContentTemplate>Only users with the "User" role can see this.</ContentTemplate>
    </asp:RoleGroup>
  </RoleGroups>
</asp:LoginView>

Zu beachten ist, dass die RoleGroups Templates der Reihe nach ausgewertet werden. Nur das erste zu einer Benutzerrolle passende Template wird angezeigt, die anderen nicht. Wenn sich also im Beispielprojekt ein Benutzer mit einer “User” Rolle einloggt, wird er nur das erste RoleGroup Template sehen.
Das LoggedInTemplate wird nur dann angezeigt, wenn kein RoleGroup Template zur Benutzerrolle passt.

Ich habe ein Beispielprojekt zusammengestellt, welches das LoginView Control im Einsatz zeigt. Das Projekt verwendet eine SQLite Datenbank und die SQLite Membership Provider von Roger Martin.

Download des Beispielprojekts: SecurityTrimming.zip

Visual Studio Projekt Files für Release Builds erweitern

Dienstag, 13. Oktober 2009

Ich bin momentan in den Vorbereitungen um eine kleine Library auf CodePlex zu publizieren. Ich will auf der Projektseite nebst dem Source Code, den man ja einfach via Subversion Client hochladen kann, auch binäre Releases zum Download anbieten.

So ein Release von meinem kleinen Projekt besteht aber immerhin aus einigen Files. Nebst dem eigentlichen Assembly wären da noch Bilddateien, Stylesheets, JavaScripts und natürlich auch die notwendigen externen Libraries, die benötigt werden. Nun wäre es doch sehr mühsam, für jeden Release alle diese Dateien immer wieder einheitlich und genau gleich zusammenzukopieren. Zum Glück lässt sich dies relativ einfach mit einer kleinen Erweiterung des Visual Studio Projektfiles erreichen.

Die Visual Studio Projektdatei ist sogar schon dafür vorbereitet. Am Ende jeder .csproj Datei (wahrscheinlich in ähnlicher Form auch bei .vbproj Files) findet man folgenden Kommentar:

<!-- 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">
 </Target>
 -->

Microsoft hat also zwei definierte Targets vorgesehen, welche für eigene Erweiterungen genutzt werden können. Wenn nun der Build beendet wird, wird das Target “AfterBuild” ausgeführt. Hier kann man also durch eigene Tasks wiederkehrende Aufgaben automatisieren lassen. Das Zusammenkopieren meiner Dateien in einen Release Ordner und Anlegen einer Zip Datei wird so zum Kinderspiel:

<PropertyGroup>
  <MSBuildCommunityTasksPath>$(MSBuildProjectDirectory)\..\..\lib\MSBuild.Community.Tasks</MSBuildCommunityTasksPath>
  <ReleasePath>$(MSBuildProjectDirectory)\..\..\Release</ReleasePath>
</PropertyGroup>

<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />

<Target Name="AfterBuild" DependsOnTargets="ClearRelease" Condition="'$(Configuration)' == 'Release'">
  <!-- Create an item collection of assemblies and other artifacts produced by the build -->
  <ItemGroup>
    <EtcDll Include="$(MSBuildProjectDirectory)\bin\Release\etcetera.Mvc.dll" />
    <MvcContrib Include="$(MSBuildProjectDirectory)\bin\Release\mvccontrib.dll" />
    <Css Include="$(MSBuildProjectDirectory)\UI\css\**\*.*" />
    <Images Include="$(MSBuildProjectDirectory)\UI\images\**\*.*" />
    <Scripts Include="$(MSBuildProjectDirectory)\UI\scripts\**\*.*" />
    <ZipFiles Include="$(MSBuildProjectDirectory)\Release\**\*.*" />
  </ItemGroup>

  <Copy SourceFiles="@(EtcDll)" DestinationFolder="$(ReleasePath)" />
  <Copy SourceFiles="@(MvcContrib)" DestinationFolder="$(ReleasePath)\Dependencies" />
  <Copy SourceFiles="@(Css)" DestinationFolder="$(ReleasePath)\css" />
  <Copy SourceFiles="@(Images)" DestinationFolder="$(ReleasePath)\images\etcetera" />
  <Copy SourceFiles="@(Scripts)" DestinationFolder="$(ReleasePath)\scripts" />

  <!-- Create a release zip file using the MSBuild Community tasks -->
  <ItemGroup>
    <ZipFiles Include="$(ReleasePath)\**\*.*" />
  </ItemGroup>
  <Zip Files="@(ZipFiles)" ZipFileName="$(ReleasePath)\etcetera.Mvc.zip" WorkingDirectory="$(ReleasePath)" />
  <Message Text="Copied all etcetera.Mvc files to folder $(MSBuildProjectDirectory)\Release" Importance="high" />
</Target>

<Target Name="ClearRelease">
  <ItemGroup>
    <OldReleaseFiles Include="$(ReleasePath)\**\*.*" />
  </ItemGroup>
  <Message Text="Clearing files in folder $(ReleasePath)" Importance="high" />
  <Delete Files="@(OldReleaseFiles)" />
</Target>

Das Zusammenstellen des Releases soll aber nur stattfinden, wenn auch die “Release” Konfiguration ausgewählt ist. Dafür kann das Target eine Condition auswerten. Zuvor sollen aber die Files des letzten Builds gelöscht werden, deshalb wird das “AfterBuild” Target vom “ClearRelease” Target abhängig gemacht. Somit wird dieses zuerst ausgeführt.

Zusätzlich zu den von Microsoft bereitgestellten Tasks (wie Copy, Delete etc.) lassen sich auch eigene oder Tasks von Dritten verwenden. Mein Beispiel verwendet den Zip Task der äusserst praktischen MSBuild Community Tasks.

Wenn ein Text im Output Fenster von Visual Studio ausgegeben werden soll, muss das “Importance” Attribut des Message Tasks auf “high” gesetzt werden.

Wenn die Projekt Datei derart angepasst wird, kann es sein, dass Visual Studio beim Laden des Projekts warnt, dass die Projektdatei erweitert wurde.

Die WCF mit einem eigenen Nachrichten Logger erweitern

Mittwoch, 7. Oktober 2009

Die WCF bietet einigen Diagnose Komfort an, um Problemen bei der Übertragung von Nachrichten auf die Spur zu kommen. Dies insbesondere wenn nicht nur WCF Dienste oder Clients beteiligt sind.
Das Message Logging kann benutzt werden, um den Inhalt von oder an Clients und Services aufzuzeichnen. Das Logging kann konfiguriert werden, um Nachrichten auf dem Service Level, dem Transport Level, oder fehlerhaft Nachrichten auszuzeichnen.
Das Logging ist per Default ausgeschaltet und kann so über die WCF Konfiguration eingeschaltet werden:

&lt;system.serviceModel&gt;
...
    &lt;diagnostics&gt;
        &lt;messageLogging logEntireMessage=&quot;true&quot;
                        logMalformedMessages=&quot;true&quot;
                        logMessagesAtServiceLevel=&quot;true&quot;
                        logMessagesAtTransportLevel=&quot;false&quot;
                        maxMessagesToLog=&quot;1000&quot;&gt;
            &lt;filters&gt;
                &lt;clear/&gt;
                &lt;add&gt;/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='Action'][text()='http://tempuri.org/Operation']&lt;/add&gt;
            &lt;/filters&gt;
        &lt;/messageLogging&gt;
    &lt;/diagnostics&gt;
&lt;/system.serviceModel&gt;

&lt;system.diagnostics&gt;
    &lt;sources&gt;
        &lt;source name=&quot;System.ServiceModel.MessageLogging&quot;&gt;
            &lt;listeners&gt;
                &lt;add name=&quot;messages&quot;
                     type=&quot;System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089&quot;
                     initializeData=&quot;messages.svclog&quot;/&gt;
            &lt;/listeners&gt;
        &lt;/source&gt;
    &lt;/sources&gt;
    &lt;trace autoflush=&quot;true&quot;/&gt;
&lt;/system.diagnostics&gt;

Dieses Beispiel zeichnet alle Nachrichten auf, welche über die Service Methode “Operation” ein- oder ausgehen. Diese Einschränkung kann durch den Filter als XPath Ausdruck definiert werden.
Das Log wird in einer Datei gespeichert. Diese kann dann recht komfortabel mit dem Service Trace Viewer des SDKs ausgewertet werden.

Wenn nun aber die eigentliche Applikation selber ein Log führt, kann es bei der Fehlersuche recht mühsam sein, die beiden Logs zeitlich miteinander zu korrelieren. Viel praktischer wäre es doch, die WCF Nachrichten und das Applikationslog in einem einzigen Log zu haben.
Ich verwende meistens das Logging Framework log4net.
Die umfangreichen Erweiterungsmöglichkeiten der WCF machen es nun recht einfach, die WCF Nachrichten auch in das bereits vorhandene und konfigurierte log4net Log zu schreiben. Ob das Log in einer Datei oder einer Datenbank geführt wird, ist dabei völlig dem Logging Framework übrlassen.

Die sogenannten “Custom Behaviors” ermöglichen es, Code innerhalb der WCF Runtime und der Message Pipeline ausführen zu lassen. Diese Behaviors können via Code oder Konfiguration hinzugefügt werden. Ein solches Behavior ist also ideal für einen Logging Mechanismus.

Um ein Custom Behavior zu implementieren, sind drei Schritte notwendig:

  1. Erstelle ein Klasse, welche ein Inspector, Selector, Formatter oder Invoker Interface implementiert. Diese Klasse definiert dann meistens auch das eigentliche Verhalten des Behaviors. Um nun alle eingehenden Nachrichten an einen Service zu loggen, kann das Interface IDispatchBehaviour implementiert werden und in dessen Methode AfterReceiveRequest die Nachricht geloggt werden. Wenn auch die vom Service oder Client gesendeten Antworten mitgeschnitten werden sollen, kann zusätzlich das Interface IClientMessageInspector implementiert werden, und die ausgehende Nachricht in dessen AfterReceiveReply Methode geloggt werden.
    public class MessageInspector : IClientMessageInspector, IDispatchMessageInspector
    {
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            Log.InfoFormat(GetType(), &quot;MessageInspector::BeforeSendRequest(): Sending the following request{0}{1}&quot;, Environment.NewLine, request);
            return null;
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            Log.InfoFormat(GetType(), &quot;MessageInspector::AfterReceiveReply(): Got the following reply{0}{1}&quot;, Environment.NewLine, reply);
        }
    
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            Log.InfoFormat(GetType(), &quot;MessageInterceptor::AfterReceiveRequest(): Got the follwing request{0}{1}&quot;, Environment.NewLine, request);
            return null;
        }
    
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            Log.InfoFormat(GetType(), &quot;MessageInterceptor::BeforeSendReply(): Sending the following reply{0}{1}&quot;, Environment.NewLine, reply);
        }
    }
    
  2. Erstelle ein weitere Klasse, welche eines der Behavior Interfaces IServiceBehavior, IEndpointBehavior, IOperationBehavior oder IContractBehavior implementiert. Die in Schritt 1 erstellt Klasse wird nun der Liste der Behaviors hinzugefügt.
    public class MessageInspectingBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public override Type BehaviorType
        {
            get { return typeof(MessageInspectingBehavior); }
        }
    
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            // Ignore
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new MessageInspector());
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageInspector());
        }
    
        public void Validate(ServiceEndpoint endpoint)
        {
            // Ignore
        }
    
        protected override object CreateBehavior()
        {
            return new MessageInspectingBehavior();
        }
    }
    
  3. Der Client oder Service kann nun konfiguriert werden, dieses neue Behavior zu verwenden:
    &lt;system.serviceModel&gt;
        &lt;services&gt;
            &lt;service name=&quot;WcfDemoService.DemoService&quot; behaviorConfiguration=&quot;WcfDemoService.DemoServiceBehavior&quot;&gt;
            ...
                &lt;endpoint address =&quot;&quot;
                          binding=&quot;basicHttpBinding&quot;
                          contract=&quot;WcfDemoService.IDemoService&quot;
                          behaviorConfiguration=&quot;inspectorBehavior&quot; /&gt;
            &lt;/service&gt;
        &lt;/services&gt;
        ...
        &lt;extensions&gt;
            &lt;behaviorExtensions&gt;
                &lt;add name=&quot;messageInspector&quot;
                     type=&quot;WcfLogging.MessageInspectingBehavior, WcfLogging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bb5b60893581ebcd&quot;/&gt;
            &lt;/behaviorExtensions&gt;
        &lt;/extensions&gt;
        &lt;behaviors&gt;
            &lt;endpointBehaviors&gt;
                &lt;behavior name=&quot;interceptorBehavior&quot;&gt;
                    &lt;clientInterceptor /&gt;
                &lt;/behavior&gt;
            &lt;/endpointBehaviors&gt;
        &lt;/behaviors&gt;
    &lt;/system.serviceModel&gt;

Wichtig: Das Assembly, welches das Behavior enthält, muss unbedingt signiert werden!

Das vom Service erzeugte Log enthält nun die über den Service verarbeiteten Nachrichten, sowie Logeinträge der Applikation selber:

2009-10-07 11:51:05,100 [6  ] INFO  - MessageInterceptor::AfterReceiveRequest(): Got the follwing request
&lt;s:Envelope xmlns:s=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
  &lt;s:Header&gt;
    &lt;To s:mustUnderstand=&quot;1&quot; xmlns=&quot;http://schemas.microsoft.com/ws/2005/05/addressing/none&quot;&gt;http://localhost:8731/Design_Time_Addresses/WcfDemoService/DemoService/&lt;/To&gt;
    &lt;Action s:mustUnderstand=&quot;1&quot; xmlns=&quot;http://schemas.microsoft.com/ws/2005/05/addressing/none&quot;&gt;http://tempuri.org/IDemoService/GetData&lt;/Action&gt;
  &lt;/s:Header&gt;
  &lt;s:Body&gt;
    &lt;GetData xmlns=&quot;http://tempuri.org/&quot;&gt;
      &lt;value&gt;42&lt;/value&gt;
    &lt;/GetData&gt;
  &lt;/s:Body&gt;
&lt;/s:Envelope&gt;
2009-10-07 11:51:05,115 [6  ] DEBUG - DemoService::GetData(): Received 42
2009-10-07 11:51:05,116 [6  ] INFO  - MessageInterceptor::BeforeSendReply(): Sending the following reply
&lt;s:Envelope xmlns:s=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
  &lt;s:Header&gt;
    &lt;Action s:mustUnderstand=&quot;1&quot; xmlns=&quot;http://schemas.microsoft.com/ws/2005/05/addressing/none&quot;&gt;http://tempuri.org/IDemoService/GetDataResponse&lt;/Action&gt;
  &lt;/s:Header&gt;
  &lt;s:Body&gt;
    &lt;GetDataResponse xmlns=&quot;http://tempuri.org/&quot;&gt;
      &lt;GetDataResult&gt;You entered: 42&lt;/GetDataResult&gt;
    &lt;/GetDataResponse&gt;
  &lt;/s:Body&gt;
&lt;/s:Envelope&gt;

Ein Beispielprojekt, welches so ein Behavior enthält, kann hier runtergeladen werden: WcfLogging.zip

Die RetryActivity erweitern

Montag, 5. Oktober 2009

In mein WF Activity Toolkit gehört unter anderem die RetryActivity von Matt Milner. Wie bereits beschrieben kann mit dieser Custom Activity die Ausführung einer Sequenz wiederholt werden, falls innerhalb der Sequenz ein Fehler auftritt. Dabei können die Anzahl der erneuten Versuche und ein Intervall zwischen den Versuchen angegeben werden. Nun habe ich diese Activity noch ein wenig erweitert, um das Intervall zwischen den Versuchen etwas flexibler konfigurieren zu können.

Erweiterte RetryActivity

Erweiterte RetryActivity

Dabei werden zwischen zwei Intervall Modi unterschieden:

  • Linear (IntervalExponential = false)
    Die Zeitabstände zwischen den einzelnen Intervallen sind gleichmässig, zB. immer 1 Minute.
  • Exponentiell (IntervalExponential = true)
    Die Zeitabstände zwischen den einzelnen Intervallen wird immer verdoppelt bis alle Versuche ausgeschöpft sind. Also wird zB. mit einer Sekunde begonnen, und das nächste Intervall dauert zwei Sekunden. Nach 10 Intervall Schritten wird bereits 512 Sekunden gewartet, also etwa achteinhalb Minuten.

Dies kann nützlich sein, wenn zB. ein Aufruf an ein externes System fehlerhaft sein kann, man aber bei vielen parallelen Workflows das externe System nicht durch zahlreiche gleichzeitige Aufrufe belasten will.

Hier ist der der Source Code der erweiterten RetryActivity: RetryActivityEx.zip

3 Nützliche Custom WF Activities

Donnerstag, 1. Oktober 2009

Nachdem ein grösseres Projekt nun beinahe abgeschlossen ist, komme ich auch wieder dazu, ein paar Erfahrungen der letzten Monate zu beschreiben. Die Basis Library der WF 3 enthält ja einige Activities, welche benutzt werden können, um den Ablauf zu modellieren. In einem typischen Projekt kommen dann sofort eigene Custom Activities dazu, welche sich um die Prozess relevanten Punkte kümmern wie zB. den Zugriff auf eine externe DB.

Folgende 3 Custom Activities haben sich als unentbehrlich erwiesen und gehören fest in mein WF Toolkit:

CallWorkflow

Die InvokeWorkflowActivity der WF kann zwar auch andere Workflows ausführen, sie macht dies jedoch asynchron. Das bedeutet, sofort nach dem Aufruf kehrt die Ausführung in den aufrufenden  Workflow zurück. Die CallWorkflowActivity mit dazugehörigem Runtime Service des WF Spezialisten Jon Flanders bietet die synchrone Variante an. Die Ausführung im aufrufenden Workflow wartet solange, bis die aufgerufene Workflow Instanz beendet ist.

Retry

Die RetryActivity von Matt Milner kann immer dann verwendet werden, wenn der Aufruf einer Activity fehlschlagen kann (werfen einer Exception), und man dann das ganze noch einmal ausführen lassen will. Es kann eine Anzahl der Wiederholungsversuche und ein Intervall zwischen den Versuchen angegeben werden.

RetryActivity

RetryActivity

PersistencePoint

Die letzte Activity ist eine wirklich simple, aber sehr nützliche. Sie kann im Workflow eingesetzt werden, um einen Speicherpunkt im WF Store zu erzwingen auch wenn die WF dies an dieser Stelle nicht von sich aus tun würde. Wenn im Ablauf ein wichtiger Punkt erreicht wurde, kann dies in die Persitenz DB gespeichert werden, und falls der WF unterbrochen wird, fährt die Ausführung beim letzten Speicherpunkt weiter.

Jede Activity, welche mit dem Attribut [PersistOnClose] ausgezeichnet wird, erreicht dieses Verhalten. Die PersistencePointActivity wird somit trivial:

[PersistOnClose]
public partial class PersistencePointActivity : Activity
{
    public PersistencePointActivity()
    {
        InitializeComponent();
    }
}