The official Fatica Labs Blog! RSS 2.0
# Saturday, 17 December 2011

I would like to present here a little argument verification library that does not require you to type any string for specifying the name of the parameter you are checking. This lets the library faster to use, not intrusive in the actual method code, and refactor friendly. As a bonus you can use it by just embedding a single file. We can see below an example, just to get immediately to the point:

As we can see, there is no magic string at all. All the argument name are guessed thanks to the metadata contained in the linq Expression we use. For example the method at line 14 if called with a null value will report:

Value cannot be null.
Parameter name: arg1

The same happens to the more complex check we do at line 46, when we write:

Contract.Expect(() => array).Meet(a => a.Length > 0 && a.First() == 0);

We have a complex predicate do meet, described by a lambda, standing that the input array should have first element zero, and non zero length. Notice that the name of the parameter is array, but we need to use another name for the argument of the lambda ( in this case I used ‘a’ ), the library is smart enough to understand that ‘a’ actually refers to array, and the error message will report it correctly if the condition does not meet. Just to clarify, the message in case of failure would be:

Precondition not verified:((array.First() == 0) AndAlso (ArrayLength(array) > 1))
Parameter name: array

Well it is not supposed to be a message to an end real user, it is a programmer friendly message, but such validation error are supposed to be reported to a developer ( an end user should not see method validation errors at all, should he ? )

Well Meet is a cutting edge function we can use for complex validations. Out of the box, for simpler cases we have some functions too, as we can see on the IContract interface definition:

An interesting portion of the codebase proposed is the one renaming the parameter on the lambda expression, to achieve the reported message reflect the correct offending parameter. It is not so easy because plain string replacement would not work:we can have a parameter named ‘a’, seen in any place in the expression string representation and a plain replacement would resolve in a big mess, furthermore Expressions are immutable. So I found help on StackOverflow, and a reply to this question solved the problem, let see the “Renamer” at work ( Thanks to Phil ):

Basically is a reusable class that take the new name of the parameter and returns a copy of the input expression with the (single) argument changed.

To improve the library or just use it, please follow/check out the project on Bitbucket, suggestions and comments are always welcome.

Saturday, 17 December 2011 13:24:25 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
CodeProject | CSharp | Linq | Recipes

# Friday, 12 August 2011

Sometimes happens that we need to roll over all the combinations of elements in multiple arrays. This operation is called Cartesian Product and is defined as:

Definition (Cartesian product): Let A1, ..., An be n sets.

Then the set of all ordered n-tuples <x1, ..., xn> , where xi Ai for all i, 1 i n ,

is called the Cartesian product of A1, ..., An, and is denoted by A1 ... An .

Achieving this is possible by some linq tricks, or by an helper class I propose in this post. But just for clarify let’s start from the example:

We have three array as below:

 { "JUICY", "SWEET" }
{ "GREEN", "YELLOW" }
{ "APPLE", "BANANA", "MANGO" }

we want to obtain all the possible tuples, ie:

 

image

This can be achieved by the following code ( helper class ):

public class CartesianProduct<T>
    {
        int[] lengths;
        T[][] arrays;
        public CartesianProduct(params  T[][] arrays)
        {
            lengths = arrays.Select(k => k.Length).ToArray();
            if (lengths.Any(l => l == 0))
                throw new ArgumentException("Zero lenght array unhandled.");
            this.arrays = arrays;
        }
        public IEnumerable<T[]> Get()
        {
            int[] walk = new int[arrays.Length];
            int x = 0;
            yield return walk.Select(k => arrays[x++][k]).ToArray();
            while (Next(walk))
            {
                x = 0;
                yield return walk.Select(k => arrays[x++][k]).ToArray();
            }

        }
        private bool Next(int[] walk)
        {
            int whoIncrement = 0;
            while (whoIncrement < walk.Length)
            {
                if (walk[whoIncrement] < lengths[whoIncrement] - 1)
                {
                    walk[whoIncrement]++;
                    return true;
                }
                else
                {
                    walk[whoIncrement] = 0;
                    whoIncrement++;
                }
            }
            return false;
        }
    }

 

And, just for completeness, the example application:

 

static void Main(string[] args)
        {
            var cross = new CartesianProduct<string>(
               new string[] { "JUICY", "SWEET" }
               , new string[] { "GREEN", "YELLOW" }
               , new string[] { "APPLE", "BANANA", "MANGO" }
               );

            Console.WriteLine("=========================================================");
            foreach (var item in cross.Get())
            {
                Console.WriteLine("{0}\t{1}\t{2}", item[0], item[1], item[2]);
            }
            Console.WriteLine("=========================================================");
        }
Really simple and clear to use in comparison with other linq based solution, even when arrays are unknown at design time.
Friday, 12 August 2011 11:46:46 (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
Programmin | Recipes

# Monday, 02 May 2011

Sometimes we want to use some shortcut key for special commands in our application, but the KeyBinding objects works with just routed commands, and so it is not directly usable with Caliburn Micro ( or with many other MVVM strategies ).
In similar situation CM rely on System.Windows.Interactivity.dll, for example when we need to map an event on a Caliburn action we can write:

 

 <Button x:Name="Save" >
           
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <cl:ActionMessage MethodName="Save" ></cl:ActionMessage>
                </i:EventTrigger>
            </i:Interaction.Triggers>
 </Button>

 

With the code above, we explicitly link the "Click" event from the button to the action calling the metod Save. We can act in a similar way by creating a custom trigger, lets see how.
First look at what we can do it in XAML:

 

<i:Interaction.Triggers>
        <local:InputBindingTrigger>
            <local:InputBindingTrigger.InputBinding>
                <KeyBinding Modifiers="Ctrl" Key="S"/>
            </local:InputBindingTrigger.InputBinding>
            <cl:ActionMessage MethodName="Save"/>
        </local:InputBindingTrigger>
    </i:Interaction.Triggers>

 

We need to declare a class, deriving from TriggerBase in System.Windows.Interactivity in order to fire the action(s) in place of executing a routed command when the user press the proper key gesture. Here the code:

class InputBindingTrigger:TriggerBase<FrameworkElement>,ICommand
    {
        public InputBindingTrigger()
        {

        }
        public InputBinding InputBinding
        {
            get { return (InputBinding)GetValue(InputBindingProperty); }
            set { SetValue(InputBindingProperty, value); }
        }
        public static readonly DependencyProperty InputBindingProperty =
            DependencyProperty.Register("InputBinding", typeof(InputBinding)
            , typeof(InputBindingTrigger)
            , new UIPropertyMetadata(null));
        protected override void OnAttached()
        {
            if (InputBinding != null)
            {
                InputBinding.Command = this;
                AssociatedObject.InputBindings.Add(InputBinding);
            }
            base.OnAttached();
        }

        #region ICommand Members
        public bool CanExecute(object parameter)
        {
            // action is anyway blocked by Caliburn at the invoke level
            return true;
        }
        public event EventHandler CanExecuteChanged = delegate { };

        public void Execute(object parameter)
        {
            InvokeActions(parameter);
        }

        #endregion
    }

 

Very easily we add the InputBinding to the bindings list on the object, and we attach the trigger as a command handler. In the execute function we fire the InvokeActions and it done. Please not the command on the KeyBinding is not required in the markup, since there is virtually any routed command.

Monday, 02 May 2011 14:16:17 (GMT Daylight Time, UTC+01:00)  #    Comments [8] - Trackback
Caliburn | Recipes | WPF

# Sunday, 24 April 2011

If you are used to works with Winform, you probably feel being less productive in WPF, but this can be overtaken by approaching the design the correct way, and with proper tools productivity would be the same as Winform, and probably better. MVVM seems to be the right pattern to cleanup the scaring and messy stuff polluting the XAML and the code behind on the view. There is some framework to help to do this, and I choose Caliburn  for this sample application, but I think the idea is the important part, so let’s try and compare. By the way, it worked for me trying to write my own MVVM framework, this increase the learning curve gradient when we go for another more proven and stable solution. If you want follow this suggestion you probably would look at this very famous video about MVVM, edited by the author of Caliburn, Rob Eisenberg. Rob is a man that look at the mess in the XAML and said: Ok let’s create something to clean up this, instead of start up again with a WinForm app Smile. So let’s talk about the app, should be simple but doing something interesting, and must take 1/2 hour to write. I decided to write a Flickr picture browser so there is as well some async call to web services to show.

Let’s prepare our toolbelt for start we need to:

So start the application and put the required dependencies in the Lib folder of the solution, so we have something like this at the end:

image

All this reference are dependencies of our app, and with this we are ready to start to write the application itself. We start to “bootstrapping” the application. We have a main window for our application, and this will be our application main View, and we need to stick to it a ViewModel. Caliburn can do this automatically in the boostrap phase, lets see.

First create a class deriving from Botstrapper<T> where T is our ViewModel. As below:

class Boot:Bootstrapper<MainWindowModel>
{

This class can be empty to start, we come back later, now we modify the App.Xaml markup, by removing the StartupUri and adding a bootstrapper instance as a resource, so an instance will born:

<Application x:Class="FlickAndFlock.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:FlickAndFlock">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:Boot x:Key="Boot"></local:Boot>
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
            <Style TargetType="Window">
                <Setter Property="TextOptions.TextFormattingMode" Value="Display"/>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

If we launch the application at this point, the main window appear anyway, why ?

image

Because Caliburn infer the main window to start from the view model class name. The default convention is that the view has the same type name as the model minus “Model”, so if our model is MainWindowModel, the view to launch is MainWindow. So what happened in the boot was:

ViewModelBinder-INFO:Binding FlickAndFlock.MainWindow and FlickAndFlock.MainWindowModel.
Action-INFO:Setting DC of FlickAndFlock.MainWindow to FlickAndFlock.MainWindowModel.
Action-INFO:Attaching message handler FlickAndFlock.MainWindowModel to FlickAndFlock.MainWindow.

As I can see in my trace output in Visual Studio after having set up the log in the boot class static constructor:

static Boot()
{
     LogManager.GetLog = t => GetLogger(t);
}

We overwritten the function GetLog of LogManager with our one, returning an instance of a class implementing ILog that just dump the messages on the trace. Having a log enabled is a good idea when we have a convention over configuration based framework. Sometimes we don’t understand why convention are not applied as we expect, and usually we can guess where the problem is by looking at the log. Another point here is how we change the log recovering strategy: by setting a function to a property. This way of customization is cross used in the entire Caliburn framework. So as you probably guessed, if we want to change the way we look for the view from the model name, the strategy is exactly the same:

ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>…

 

In the example application we used the default one, but is good to know that if does not feet our convention, we can change.

So now we have the main view,and the main ViewModel, but what exactly we can put inside the ViewModel ? Well we can think the ViewModel as a model oriented to the presentation, focalized on the view, not on the business logic. Is the ViewModel to that is notified about the UI events, ie the “Actions” the user want to do.

We proceed now to add some user control on the XAML to allow the user to search by tag the Flicker photo database, obtaining something like:

image
<StackPanel Grid.Column="0">
            <TextBlock Margin="20">Tag:</TextBlock>
            <TextBox Margin="20" x:Name="Tags"></TextBox>
            <Button Margin="30,20,30,20" IsDefault="True" x:Name="Search">Search Flickr</Button>
            <StackPanel Orientation="Horizontal">
                <Button Margin="10,10,10,10" Width="80"  x:Name="PrevPage">Previous</Button>
                <Button Margin="10,10,10,10" Width="80" x:Name="NextPage">Next</Button>
            </StackPanel>
        </StackPanel>

The XAML is quite simple, there is nothing related to events in it, but, as you can see from the picture, previous and next are disabled, and search is not. This is another Caliburn magic, let see what’s in the log:

ViewModelBinder-INFO:Action Convention Applied: Action Search on element Search.
ViewModelBinder-INFO:Action Convention Applied: Action NextPage on element NextPage.
ViewModelBinder-INFO:Action Convention Applied: Action PrevPage on element PrevPage.

ViewModelBinder-INFO:Binding Convention Applied: Element Tags.

ActionMessage-INFO:Action: Search availability update.
ActionMessage-INFO:Action: Search availability update.
ActionMessage-INFO:Action: PrevPage availability update.
ActionMessage-INFO:Action: PrevPage availability update.
ActionMessage-INFO:Action: NextPage availability update.
ActionMessage-INFO:Action: NextPage availability update.

As we can see inside the log, the framework found some methods in our ViewModel called as the x:Name on the UIelement, so it automatically wired it. Even the command availability is automatically wired to property called Can+action name. Similar sort was reserved to the TextBox, automatically bound to the property Tags.

Let’s have a look now on how the command are actually implemented. Here the search function:

 

       public IEnumerable<IResult> Search()
       {
           currentPage = 1;
           return PageSearch();
       }
       public IEnumerable<IResult> PageSearch()
       {
           Thumbs.Clear();
           yield return new Wait(true);
           yield return new Search(Tags, currentPage);
           yield return new Wait(false);
           NotifyOfPropertyChange(() => this.CanNextPage);
           NotifyOfPropertyChange(() => this.CanPrevPage);
       }

That’s could sound a little strange. The routine is returning an IEnumebrable<IResult>, what is this ? Well this is a strategy to make easy and readable code when we need to spawn asynchronous process, wait for the result, then go on with something else, and so on. This can result in a lot of messy code and complicated lambdas. By returning ( or better, Yielding ) an IResult we return an object that is capable to do something and signal when done. As a consequence, functions cooperate together and all returns in the “master” function, that is called CoRoutine. Not really a new idea since comes from Knuth, but in fact C# has not a native way of express such a context. Result are obtained in C# by an orchestration part ( implemented in Caliburn ) and leveraging the yield statement to leave and re-enter the main routine. I think it is interesting to know that someone creates the same strategy before the yield statement were created ( NET 2.0 ), have a look here if you are courious. It is important to know that implementing the acgtions as CoRoutines is not mandatory, but is strongly encouraged when you have to deal with sync-async calls wired together.

Before ending, let’s have a look on how we wire an IoC to that strategy. This is done by customizing the bootstrapper, here an example:

           protected override object GetInstance(Type service, string key)
           {
               return kernel.Get(service);
           }

 

Caliburn call the function above when he need to instantiate an object, or when we try to instantiate something using the IoC static class. So that function is the extension point to bind the container. In the simple application I bound NInject, but any container can probably fit the purpose. Note that, even if you use the static IoC, without customizing the bootstrapper, natively Caliburn does not perform any IoC, but just plainly creates the requested type.

So the application is finished:

image

And we manage to avoid writing any code inside the view. And it is not all, Caliburn Micro has a lot of feature for us, even remaining a thin framework.

Sunday, 24 April 2011 00:03:42 (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
Programmin | Recipes | WPF

# Monday, 14 March 2011

This is the list of the posts related to Workflow4 Re-Hosting

Post 1 First drop, showing we can host the designer view in pure markup.
Post 2 Adding the activities bar into the game.
Post 3 Removing the blue gear, and adding the proper activity image.
Post 4 Showing the property editor grid.
Post 5 Default WorkflowView command routing.

 

Sample Project has a place on Codeplex.

Monday, 14 March 2011 20:16:50 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

There is an entire suite of command defined by the designer view:

      • DesignerView.CopyAsImageCommand;
      • DesignerView.CopyCommand;
      • DesignerView.CreateArgumentCommand;
      • DesignerView.CreateVariableCommand;
      • DesignerView.CreateWorkflowElementCommand;
      • DesignerView.CutCommand;
      • DesignerView.CycleThroughDesignerCommand;
      • DesignerView.DeleteBreakpointCommand;
      • DesignerView.DisableBreakpointCommand;
      • DesignerView.EnableBreakpointCommand;
      • DesignerView.ExpandAllCommand;
      • DesignerView.ExpandCommand;
      • DesignerView.ExpandInPlaceCommand;
      • DesignerView.FitToScreenCommand;
      • DesignerView.GoToParentCommand;
      • DesignerView.InsertBreakpointCommand;
      • DesignerView.PasteCommand;
      • DesignerView.RedoCommand;
      • DesignerView.ResetZoomCommand;
      • DesignerView.SaveAsImageCommand;
      • DesignerView.SelectAllCommand;
      • DesignerView.ToggleArgumentDesignerCommand;
      • DesignerView.ToggleImportsDesignerCommand;
      • DesignerView.ToggleMiniMapCommand;
      • DesignerView.ToggleSelectionCommand;
      • DesignerView.ToggleVariableDesignerCommand;
      • DesignerView.UndoCommand;
      • DesignerView.ZoomInCommand;
      • DesignerView.ZoomOutCommand;

But since the view is internal in the designer it is not automatic binding commands form the UI to the underlying target. So our hosting solution propose an attached property, to route the command to the view in a way suitable in pure markup too. The property we create is called DesignerCommandTarget, and is used as below:

   1:  <Button Style="{StaticResource toolButton}" ToolTip="Zoom In"   Command="wfview:DesignerView.ZoomInCommand"
   2:   host:HostingHelper.DesignerCommandTarget="{StaticResource wfHost}" 
   3:  >
   4:                      <Image Source="Assets/zoom_in.png"></Image>
   5:  </Button>
   6:                 

 

So we basically bind the command to the button by using Command, as usual, but we set the DesignerCommandTarget to our hosting instance, that is defined in the resource. In order to have the commands exposed by the designer in XAML, we added the wfview namespace reference as underlined below:

   1:  <Window x:Class="WF4Host.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:toolbox="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.Presentation"
   5:          xmlns:host="clr-namespace:WF4Host"
   6:          xmlns:custom="clr-namespace:MyActivities;assembly=MyActivities"
   7:          xmlns:wf="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   8:          xmlns:wfview="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
   9:          xmlns:activities="clr-namespace:System.Activities.Statements;assembly=System.Activities"
  10:          Title="MainWindow" Height="650" Width="825"
  11:          Icon="Assets/App.ico"
  12:          TextOptions.TextFormattingMode="Display"
  13:         
  14:          >

Command routing is very simple, and it is shown below:

 

   1:   protected static void OnCommandTargetChanged(DependencyObject depo, DependencyPropertyChangedEventArgs depa)
   2:          {
   3:              var src = depo as ICommandSource;
   4:              if (null != src && depa.NewValue != null )
   5:              {
   6:                  var ctargetdescr = DependencyPropertyDescriptor.FromName("CommandTarget"
   7:                                                , depo.GetType()
   8:                                                , depo.GetType()
   9:                                                ,true);
  10:                  ctargetdescr.SetValue(depo
  11:  , (depa.NewValue as HostingHelper).Designer.Context.Services.GetService<DesignerView>());
  12:              }
  13:          }

Every time we attach the property we check if the target is a CommandSource, so basically the strategy works for any object implementing this interface. If this pre condition is satisfied, we simply bind the WorkflowDesigner view as a CommandTarget for that source. In this way commands from our UI behave as they are defined inside the workflow, and we don’t need to write any code behind to custom wire them.

Our example application now looks like this:

 

image

Code for this post is hosted here.
Monday, 14 March 2011 11:13:09 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

# Monday, 07 March 2011

Showing the property grid is achieved using  the same strategy we used to show the main editor, since it is a View internally exposed by the WorkflowDesigner. So we can decide where to put the property grid in XAML only in order to be blend friendly. Let’s have a look at the XAML:

   1:              <toolbox:ToolboxControl Grid.Column="0" Grid.Row="1" host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}"/>
   2:              <GridSplitter ResizeDirection="Columns" Grid.RowSpan="3" Grid.Column="1" Grid.Row="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
   3:              <host:MainViewPresenter HostingHelper="{StaticResource wfHost}" Grid.RowSpan="3" Grid.Column="2" Grid.Row="1"/>
   4:              <GridSplitter ResizeDirection="Rows" Grid.Column="0" Grid.Row="2" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
   5:              <host:PropertyViewPresenter HostingHelper="{StaticResource wfHost}" Grid.Column="0" Grid.Row="3"/>

Here we have all the graphical widget composing the designer as XAML tags. At line 5 we actually present the property editor. As a noise there is also some splitter we add in order to have a better usable interface. Code for the presenter is quite the same as the one we use to present the main view. So similar that could be refactor in some base class, but it is not done at the moment.

What we have right now is this:

image

A little problem appear to be the cursor over the splitter. For some weird reason the WF controls seems to stole the mouse cursor management and there is no way out to change it.

A poor man workaround is to make the splitter turn to bold when the mouse hover on it:

       <Style TargetType="GridSplitter">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="Background" Value="Black"/>
                 </Trigger>
 
            </Style.Triggers>
        </Style>

 

As usual the code so far can be found here.

Monday, 07 March 2011 20:41:30 (GMT Standard Time, UTC+00:00)  #    Comments [2] - Trackback
Programmin | Recipes | ReHosting | WF4

# Sunday, 06 March 2011

When we host the activities palette, we notice a blue gear without meaning on each of our activity. This should be easy to solve, but it is not so easy for the built in activities. So what we have till now is:

image

We want to have something better, even for the standard activities. In WF4 is not said that the ToolboxBitmapAttribute, the one used to bind the image to show in the palette is attached to all activities, and we can say it is not for sure for the built in activities. Fortunately the WF4 architecture provides a way to attach attributes to objects runtime, this is achieved via the AttributeTableBuilder class, that provide a way to register and apply attributes runtime to objects via another helper class: MetadataStore. So if we have somewhere these icons, we can attach the proper attribute runtime, and all is done, but where to find it ?  Do we have proper licensing to distribute it ? A reply to this question could be found here, and in particular in the Brannon King’s comment. Brannon show how to grab the icons from the redistributable System.Activities.Presentation dll, and more, it post the whole code to provide the attributes creation and binding, and graphics retrieval too. So we grab this great work and we add it in our helper class ToolboxItemSource, so we can ensure designer has the proper graphic information for both built in and custom activities we add as a source. Let’s have a look at the code, that is just a little modified:

   1:                  var builder = new AttributeTableBuilder();
   2:                  foreach (var item in query)
   3:                  {
   4:                      AddIconAttributes(item, builder);
   5:                  }
   6:                  MetadataStore.AddAttributeTable(builder.CreateTable());

Well this portion is in the member function AddTools, already present in our helper, we basically creates an AttributeTableBuilder, then we add the resources attributes, and then we realize, at line 6, the association. Let’s have a look at the Brannon’s code, that we embed in the AddIconAttributes:

   1:        protected static  bool AddIconAttributes(Type type, AttributeTableBuilder builder)
   2:          {
   3:              var secondary = false;
   4:              var tbaType = typeof(ToolboxBitmapAttribute);
   5:              var imageType = typeof(System.Drawing.Image);
   6:              var constructor = tbaType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { imageType, imageType }, null);
   7:              string resourceKey = type.IsGenericType ? type.GetGenericTypeDefinition().Name : type.Name;
   8:              int index = resourceKey.IndexOf('`');
   9:              if (index > 0)
  10:              {
  11:                  resourceKey = resourceKey.Remove(index);
  12:              }
  13:              if (resourceKey == "Flowchart")
  14:              {
  15:                  resourceKey = "FlowChart"; // it appears that themes/icons.xaml has a typo here
  16:              }
  17:              resourceKey += "Icon";
  18:              Bitmap small, large;
  19:              object resource = resources[resourceKey];
  20:              if (!(resource is DrawingBrush))
  21:              {
  22:                  resource = resources["GenericLeafActivityIcon"];
  23:                  secondary = true;
  24:              }
  25:              var dv = new DrawingVisual();
  26:              using (var context = dv.RenderOpen())
  27:              {
  28:                  context.DrawRectangle(((DrawingBrush)resource), null, new Rect(0, 0, 32, 32));
  29:                  context.DrawRectangle(((DrawingBrush)resource), null, new Rect(32, 32, 16, 16));
  30:              }
  31:              var rtb = new RenderTargetBitmap(32, 32, 96, 96, PixelFormats.Pbgra32);
  32:              rtb.Render(dv);
  33:              using (var outStream = new MemoryStream())
  34:              {
  35:                  BitmapEncoder enc = new PngBitmapEncoder();
  36:                  enc.Frames.Add(BitmapFrame.Create(rtb));
  37:                  enc.Save(outStream);
  38:                  outStream.Position = 0;
  39:                  large = new Bitmap(outStream);
  40:              }
  41:              rtb = new RenderTargetBitmap(16, 16, 96, 96, PixelFormats.Pbgra32);
  42:              dv.Offset = new Vector(-32, -32);
  43:              rtb.Render(dv);
  44:              using (var outStream = new MemoryStream())
  45:              {
  46:                  BitmapEncoder enc = new PngBitmapEncoder();
  47:                  enc.Frames.Add(BitmapFrame.Create(rtb));
  48:                  enc.Save(outStream);
  49:                  outStream.Position = 0;
  50:                  small = new Bitmap(outStream);
  51:              }
  52:   
  53:              var tba = constructor.Invoke(new object[] { small, large }) as ToolboxBitmapAttribute;
  54:              builder.AddCustomAttributes(type, tba);
  55:              return secondary;
  56:          }

 

I’ve just stored the resources needed in a local static dictionary, by this call:

 

   1:  static ResourceDictionary resources = new ResourceDictionary
   2:   { Source = new Uri("pack://application:,,,/System.Activities.Presentation;component/themes/icons.xaml") };

 

Basically Brannon’s code use the assumption the icon is the name of the activity suffixed by “Icon”, except for a typo on FlowChart solved at lines 13-16. Then he create a bitmap and it save it on a memory stream. That stream is then use as an argument for ToolboxBitmapAttribute. So we put all together and we obtain this new presentation:

image

So we got rid of the blue gear. We will need the property editor next, always a XAML only strategy to present it has to be provided, probably with the same strategy we used for the main editor itself.

Have a look at the code here.

Sunday, 06 March 2011 22:10:10 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

# Saturday, 05 March 2011

In the previous post we had a look on how to host WF4 designer in pure markup, we continue now to host, always in markup, the toolbar for dropping activities into the designer surface. The ToolboxControl is fortunately a standard WPF control, so it is not a problem to use it in markup, but it is a little difficult to add a list of categorized activities, because we have to add as a content of the control a list of category, each with a list of category. We want to use some sort of activity list source instead. So let’s have a look at the markup that add a ToolBoxControl to our designer:

   1:   <host:MainViewPresenter HostingHelper="{StaticResource wfHost}"  Grid.Column="1" Grid.Row="1"/>
   2:   <toolbox:ToolboxControl Grid.Column="0" Grid.Row="1" host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}" >

So the line 2 is the markup tag for adding the ToolboxControl, but the interesting part is the:

host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}"

CategorySource is an attached property of type ToolboxItemSource, an helper class we use to make easier binding to a list of activities. In this particular case we have defined an instance on the resources, called GeneralTools, as below:

   1:   <host:ToolboxItemSource x:Key="GeneralTools">
   2:              <host:ToolboxSource TargetCategory="General" AllSiblingsOf="{x:Type activities:Delay}"  />
   3:              <host:ToolboxSource TargetCategory="Custom" AllSiblingsOf="{x:Type custom:Activity1}"  />
   4:   </host:ToolboxItemSource>

As we can see, we can add to the ItemSource a list of source of type ToolboxSource, an helper class too. That class has the only pourpose of collecting the target category name, and a type of one activity to pick an assembly and contextually add all activities contained in. Most of the job is done by ToolboxItemSource, when the property is actually attached, so the code below:

   1:          protected static void OnCategorySourceChanged(DependencyObject depobj,DependencyPropertyChangedEventArgs dpcea)
   2:          {
   3:              if (null != dpcea.NewValue && dpcea.NewValue is ToolboxItemSource)
   4:              {
   5:                  var tbsrc = dpcea.NewValue as ToolboxItemSource;
   6:                  foreach (var source in tbsrc.Sources)
   7:                  {
   8:                      AddTools(depobj as ToolboxControl, source);
   9:                  }
  10:              }
  11:          }
  12:   
  13:          private static void AddTools(ToolboxControl toolboxControl, ToolboxSource source)
  14:          {
  15:              if (null != source.AllSiblingsOf)
  16:              {
  17:                  var cat = toolboxControl.Categories.Where(q => q.CategoryName.Equals(source.TargetCategory)).FirstOrDefault();
  18:                  if (null == cat)
  19:                  {
  20:                      cat = new ToolboxCategory(source.TargetCategory);
  21:                      toolboxControl.Categories.Add(cat);
  22:                  }
  23:                  var query = from type in source.AllSiblingsOf.Assembly.GetTypes()
  24:                              where type.IsPublic &&
  25:                              !type.IsNested &&
  26:                              !type.IsAbstract &&
  27:                              !type.ContainsGenericParameters &&
  28:                              (typeof(Activity).IsAssignableFrom(type) ||
  29:                              typeof(IActivityTemplateFactory).IsAssignableFrom(type))
  30:                              orderby type.Name
  31:                              select new ToolboxItemWrapper(type);
  32:                  foreach (var item in query)
  33:                      cat.Add(item);
  34:              }

35: }

 

Very easy, when we attach the item source, we pump on the control all the required activities, and it’s done. So this is another step on a XAML only re-hosting, let’s see the result:

image

Well you will probably note the very fancy gear icon that is the “mean nothing” icon that we usually have when we forgot something, and actually we forgot something, but differently from the old workflow is not so easy: a look here could help, and we probably work on that direction in further steps.

All are welcome to grab or cooperate on the source code for this sample here.

Saturday, 05 March 2011 22:37:05 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

This post is based on this great post from The Problem Solver, but here we try to use less code behind and more markup language, to let the “host” decide where to put the designer components in his application. Basically there is three main chunk involved in hosting the WF:

  1. The main designer surface
  2. The property view
  3. The toolbox.

Components 1) and 2) are exposed as property of the WorkflowDesigner class, that itself is not a UIElement, but a simple class derived from object. The two property of interest are View and PropertyInspectorView, and both are UIElements. The problem is: how to let’s the host application writer to decide where to place these in XAML ? Lets start creating a class that act as an helper, and provide inside it some initialization code:

namespace WF4Host
{
    public class HostingHelper:DependencyObject
    {
        public WorkflowDesigner Designer { get { return workflowDesigner;} }
        WorkflowDesigner workflowDesigner;
        public HostingHelper()
        {
            workflowDesigner = new WorkflowDesigner();
            new DesignerMetadata().Register();
            //Creates an empty workflow
            workflowDesigner.Load(new Sequence());
        }
    }
    
}

basically we creates an instance of the WorkflowDesigner itself, load a first Sequence activity, and start the designer runtime. In xaml we can declare such an object as a StaticResource:

   1:  <Window x:Class="WF4Host.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:host="clr-namespace:WF4Host"
   5:          xmlns:wf="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   6:          Title="MainWindow" Height="650" Width="825"
   7:          >
   8:      <Window.Resources>
   9:          <host:HostingHelper x:Key="wfHost"/>
  10:          <LinearGradientBrush x:Key="backBrush" StartPoint="0,0" EndPoint="0,1" >
  11:              <GradientStop Offset="0.1" Color="DarkSlateBlue"></GradientStop>
  12:              <GradientStop Offset="0.2" Color="DarkSlateGray"/>
  13:              <GradientStop Offset="0.9" Color="BlanchedAlmond"/>
  14:          </LinearGradientBrush>
  15:      </Window.Resources>

 

Easy: at line 4 we add the namespace for our project. At line 9 we declare an instance of our HostingHelper and we name it wfHost. Remaining resources are just some beautiful ( ehm ) arts.

Then we need to present the graphical components. Lets declare in our code a control deriving from grid:

   1:  namespace WF4Host
   2:  {
   3:      public class MainViewPresenter : Grid
   4:      {
   5:          public HostingHelper HostingHelper
   6:          {
   7:              get { return (HostingHelper)GetValue(HostingHelperProperty); }
   8:              set { SetValue(HostingHelperProperty, value); }
   9:          }
  10:   
  11:          public static readonly DependencyProperty HostingHelperProperty =
  12:              DependencyProperty.Register("HostingHelper", typeof(HostingHelper)
  13:              , typeof(MainViewPresenter)
  14:              , new UIPropertyMetadata(null, new PropertyChangedCallback(OnHostingHelperChanged)));
  15:   
  16:          protected static void OnHostingHelperChanged(DependencyObject dobj
  17:              , DependencyPropertyChangedEventArgs dpcea)
  18:          {
  19:              if (dobj is MainViewPresenter)
  20:              {
  21:                  var mvp = dobj as MainViewPresenter;
  22:                  mvp.Children.Clear();
  23:                  if (dpcea.NewValue != null)
  24:                  {
  25:                      var host = dpcea.NewValue as HostingHelper;
  26:                      host.Designer.View.SetValue(Panel.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
  27:                      host.Designer.View.SetValue(Panel.VerticalAlignmentProperty, VerticalAlignment.Stretch);
  28:                      mvp.Children.Add(host.Designer.View);
  29:                  }
  30:              }
  31:          }
  32:      }
  33:  }

 

So this class is the MainView presenter. We call the Main View the one in which the WF is actually drawn. This control has a dependency property called HostingHelper, so we can bind it to the current HostingHelper that orchestrates the Workflow design. Let see how to do it in xaml:

   1:  <Grid Name="grid">
   2:              <Grid.RowDefinitions>
   3:                  <RowDefinition Height="32"/>
   4:                  <RowDefinition Height="*"/>
   5:              </Grid.RowDefinitions>
   6:                  <Grid.ColumnDefinitions>
   7:              <ColumnDefinition Width="100"></ColumnDefinition>
   8:              <ColumnDefinition></ColumnDefinition>
   9:              <ColumnDefinition Width="100"></ColumnDefinition>
  10:          </Grid.ColumnDefinitions>
  11:               
  12:              <host:MainViewPresenter
  13:               HostingHelper="{StaticResource wfHost}"  Grid.Column="1" Grid.Row="1"/>
  14:              
  15:          </Grid>

So at line 12 we declare our presenter, and we bind his hosting helper property to the hosting helper instance wfHost.

This is the current result:

image

So we achieved less code behind and more “blendability” by creating a little tool library. We should improve it with others components to complete the designer environment. I decided to publish this example as a project on codeplex, so you can join to use and improve it.

Saturday, 05 March 2011 10:49:00 (GMT Standard Time, UTC+00:00)  #    Comments [2] - Trackback
Programmin | Recipes | ReHosting | WF4

# Tuesday, 28 September 2010

Per una piccola applicazione che sto scrivendo, un generatore di etichette molto colorate, con grafici e altre informazioni sopra, ho pensato di utilizzare XAML con uno user control WPF, disegnato in una immagine ( JPEG o PNG ). Con la classe XamlReader si legge lo script XAML, con RenderTargetBitmap si ottiene un rendering in memoria, e tramite PngBitmapEncoder si salva l’immagine, per esempio in PNG. Ho quindi costruito un “server di etichette”, basato su WebServer C#, di cui ho parlato qui. Il problema è che, all’interno di un servizio, o comunque in qualsiasi thread non di interfaccia utente, il rendering provoca questo simpatico errore:

“Cannot create instance of 'UserControl' defined in assembly 'PresentationFramework,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
The calling thread must be STA, because many UI components require this.  Error at Line 1 Position 2”

XamlRender vuole che il thread usato per il rendering sia un thread STA, cosa abbastanza normale per la UI, che deve gestire le interazioni con l’utente in modo sequenziale ( message based ). Il problema può essere risolto facendo partire un nuovo thread in modalità STA, e aspettandolo in modo sincrono. Il codice è questo:

   1:   EventWaitHandle xamlLoaded = new EventWaitHandle(false,EventResetMode.ManualReset);
   2:         
   3:          private void CreateVisualInStream(IRequest request,MemoryStream ms)
   4:          {
   5:              string fileName = GetFileName(request.Uri);
   6:              Thread t = new Thread(q => StaLoadAndRenderXaml(fileName, ms));
   7:              t.SetApartmentState( ApartmentState.STA);
   8:              xamlLoaded.Reset();
   9:              t.Start();
  10:              xamlLoaded.WaitOne(TimeSpan.FromSeconds(30));
  11:          }
  12:          private void StaLoadAndRenderXaml(string fileName, MemoryStream ms)
  13:          {
  14:              if (File.Exists(fileName))
  15:              {
  16:                  using (var s = new FileStream(fileName, FileMode.Open))
  17:                  {
  18:                      logger.Warn("Loading control:" + fileName);
  19:                      var control = XamlReader.Load(s) as Control;
  20:                      int w = Convert.ToInt32(control.GetValue(Control.WidthProperty));
  21:                      int h = Convert.ToInt32(control.GetValue(Control.HeightProperty));
  22:                      control.DataContext = new DummyDataContext();
  23:                      control.Measure(new System.Windows.Size((int)control.Width, (int)control.Height));
  24:                      control.Arrange(new System.Windows.Rect(new System.Windows.Size((int)control.Width, (int)control.Height)));
  25:                      control.UpdateLayout();
  26:                      logger.Warn("Rendering control:" + fileName);
  27:                      var renderedXaml = RenderLabel(control, w, h);
  28:                      PngBitmapEncoder png = new PngBitmapEncoder();
  29:   
  30:                      png.Frames.Add(BitmapFrame.Create(renderedXaml));
  31:                      png.Save(ms);
  32:                      logger.Info("Saved rendered control. Size=" + ms.Length);
  33:                      xamlLoaded.Set();
  34:                  }
  35:              }
  36:              else
  37:              {
  38:                  xamlLoaded.Set();
  39:                  throw new InternalServerException("Failed to process file '" + fileName + "'.");
  40:              }
  41:   
  42:          }

 

 

Alla linea 1 è creato loggetto di sincronizzazione che ci serve per capire che il thread ha finito. Alla linea 7 specifichiamo che il thread creato deve essere di tipo STA, e dopo averlo lanciato lo aspettiamo alla linea 10. Notare il timeout che ci consente di evitare deadlock in ogni situazione. Alla linea 27 il codice di RenderLabel provvede a fare il rendering vero e proprio:

   1:   private RenderTargetBitmap RenderLabel(Visual v, int width, int height)
   2:   {
   3:              var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
   4:              bmp.Render(v);
   5:              return bmp;
   6:   }
Tuesday, 28 September 2010 09:59:37 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin | Recipes

# Wednesday, 16 June 2010

Probabilmente vi sarà capitato di dovere eseguire qualche azione all’interno del vostro software dipendentemente da trigger innescati da data e/o ora. Per esempio un certo processo non si scatena del week-end, oppure un altro processo deve partire solo a mezzanotte etc etc. Quando queste cose non siano attuabili esternamente con il task scheduler di sistema, sarebbe bello poter avere una libreria pronto uso in .NET. Ebbene questa libreria c’è, ed è il parser di espressioni Cron che trovate qui. Lungi dall’essere un concetto moderno, si tratta di un modo di esprimere range temporali tramite una stringa che arriva da Unix. Oggi chiamiamo una cosa simile “Domain Specific Language”, ma tant’è il concetto è il medesimo. Lungi anche dall’ essere originale il concetto in .NET, la libreria che propongo è una estrapolazione di codice dalla più completa e complessa libreria Quartz.NET, a sua volta un porting della libreria Java Quartz. Se non volete introdurre troppa complessità al vostro deploy aggiungendo una nuova libreria, potete “grabbare” le classi dalla dll di Cron.NET ( sono tre file… ) e compilarli con il vostro progetto, ed avrete a disposizione tutta la potenza delle espressioni Cron ;).

Già, ma come funzionano le espressioni Cron ? Facciamo un paio di esempi:

"* * 10-11 L * ?" = questo trigger è valido l’ultimo del mese dalle 10-11

"* * * ? * MON-FRI" = questo trigger è valido tutti i giorni, a tutte le ore, eccetto il week end.

Ok, questo da l’idea delle potezialità della cosa, ma ovviamente serivirebbe una più estesa…

Documentazione

Una espressione Cron è composta da 6 campi obbligatori, più uno opzionale. Il significato dei campi è schematizzato nella tabella qui sotto:

Campo   Valori permessi   Caratteri speciali ammessi
Secondi   0-59   , - /
Minuti   0-59   , - /
Ore   0-23   , - /
Giorno del mese   1-31   , - ? / L W C
Mese   1-12 or JAN-DEC   , - /
Giorno della settimana   1-7 or SUN-SAT   , - ? / L #
Anno ( Opzionale )   vuoto, 1970-2099   , - /

 

Il carattere jolly ‘*’ può sempre essere usato con il significato di “tutti i valori”, per esempio se metto ‘*’ nel campo minuti significa tutti i minuti.

Il carattere ‘?’ può essere usato solo come Giorno del mese o giorno della settimana. Significa “nessun valore in particolare” e serve quando devo fissare l’altro. Se per esempio fisso il giorno della settimana metterò ‘?’ nel giorno del mese, e viceversa.

Il carattere ‘-‘ lo utilizzo quando voglio specificare un range: per esempio 10-12 nel campo ora significa dalle 10 a tutte le 12.

Uso invece la ‘,’ quando voglio specificare singolarmente più valori possibili: JAN,MAR significa il mese di Gennaio e di Marzo, ma non Febbraio ( e, ovviamente, non tutti gli altri ).

Per specificare dei periodi utilizzo ‘/’: per esempio se metto 0/15 nel campo secondi, significa “ogni 15 secondi”: sarà attivo il trigger quindi ai secondi 15,30,45 etc etc.

Il carattere speciale ‘L’ significa ultimo. Nel campo mese significa semplicemente l’ultimo giorno del mese (30/31/28 o 29 ) in modo concorde con il mese / anno bisestile. Nel campo settimana significa semplicemente 7 ( cioè Sabato ), ma se preceduto da un numero significa “l’ultimo xxxx del mese”. Per esempio se scrivo 6L significa l’ultimo Venerdì del mese.

Il carettere ‘W’ può essere specificato solo nel campo giorno del mese, preceduto da un solo carattere, e significa “il giorno lavorativo più vicino a”. Per esempio se scrivo 1W significa il giorno lavorativo più vicino al primo del mese. Se scrivo LW significa l’ultimo giorno lavorativo del mese.

Per ultimo il carattere ‘#’, utilizzabile solo nel giorno della settimana, indica l’ennesimo xxxx del mese. Per esempio, se mi interessa il terzo venerdì del mese, scriverò 6#3.

Per utilizzare una cron expression basta creare un istanza dell’ogegtto CronExpression, passando la stringa sul costruttore. Ecco un esempio:

   1:          [Test]
   2:          public void LastOfTheMonth()
   3:          {
   4:              var exp = new CronExpression("* * * L * ?");
   5:              Assert.IsTrue(exp.IsSatisfiedBy(new DateTime(2000, 1, 31)));
   6:              Assert.IsTrue(exp.IsSatisfiedBy(new DateTime(2000, 2, 29)));
   7:              Assert.IsFalse(exp.IsSatisfiedBy(new DateTime(2000, 2, 28)));
   8:          }
Wednesday, 16 June 2010 16:52:39 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Cron | Programmin | Recipes

# Thursday, 20 May 2010

Può succedere di dover specificare il nome di un assembly completo, magari in un file di configurazione dell’applicativo. Sfortunatamente non si può tagliare ed incollare la stringa dalla GAC, e bisogna copiare ed incollare i vari pezzi dalla finestra property. Ecco quindi un piccolo tool che mostra tutto il contenuto della GAC e ci permette di copiare l’assembly full name. Eccolo all’opera:

gac

Si accettano complimenti per la meravigliosa interfaccia utente ;).

Nondimeno importante segnalare lo snapshot di codice che consente con facilità di maneggiare la GAC run-time.

Thursday, 20 May 2010 11:48:35 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin | Recipes

# Thursday, 06 May 2010

Può essere interessante, quando si sviluppano e si utilizzano in modalità test servizi di windows, avere una modalità di funzionamento “Console”, che ci permetta di lanciare il nostro servizio come una console application per vedere “cosa fa” senza stare lì ad aprire ogni secondo il file di log. Ovviamente dobbiamo prevedere un sistema di logging che scriva anche sulla console  (se per loggare usate log4net, allora basta mettere un ConsoleAppender, o meglio ancora, un ColoredConsoleAppender). Per farlo basta creare la solita WindowsServiceApplication, avendo però cura di impostare il progetto come console:

consoleapp

Il main dovrà essere modificato in questoi modo

   1:          static void Main(string[] args)
   2:          {
   3:              if (!Environment.UserInteractive)
   4:              {
   5:                  log.Info("Starting as a service...");
   6:                  ServiceBase[] ServicesToRun;
   7:                  ServicesToRun = new ServiceBase[] 
   8:                  { 
   9:                      new Service1() 
  10:                  };
  11:                  ServiceBase.Run(ServicesToRun);
  12:              }
  13:              else
  14:              {
  15:                  log.Info("Starting as a console...");
  16:                  var s = new Service1(log);
  17:                  s.GetType().GetMethod(   "OnStart"
  18:                                          ,BindingFlags.Instance|BindingFlags.NonPublic
  19:                                          ).Invoke(s,new object[]{args});
  20:                  Console.WriteLine("Press enter to exit the console mode");
  21:                  Console.ReadLine();
  22:                  s.GetType().GetMethod(      "OnStop"
  23:                                              , BindingFlags.Instance | BindingFlags.NonPublic
  24:                                              ).Invoke(s, null);
  25:              }
  26:          }
 
 
 

Alla linea 3 il codice che ci permette di capire se siamo stati lanciati da uno user “vero” o dal sistema,e quindi scegliamo se partire nel modo tradizionale oppure in modalità console. Se a questo codice aggiungiamo anche la possibilità di interpretare la command line e di autoinstallarsi, abbiamo un buon segmento di codice da riusarsi per ogni WindowService.

Thursday, 06 May 2010 08:23:11 (GMT Daylight Time, UTC+01:00)  #    Comments [3] - Trackback
Recipes

# Monday, 03 May 2010

Quando andiamo in deploy con un servizio C# abbiamo l’opzione di utilizzare installutil.exe per “installare” il servizio, ovvero far si che compaia nel Service Control Manager. Tuttavia è possibile fare la stessa cosa programmaticamente, e farlo semplifica la vita a chi installera il vostro servizio. L’idea è che il main del servizio risponda a dei comandi sulla cmd line, per esempio “—install” o “—uninstall” in modo  che l’unico strumento che serve in fase di deploy sia il servizio stesso.

Ecco dunque la ricetta: si deve intendere “ricetta” come un segmento di codice troppo piccolo da essere messo in una libreria, ma troppo importante per essere dimenticato :-)

La ricetta in oggetto è quindi composta di due funzioni:

Install()

   1:          private static void Install()
   2:          {
   3:              ServiceProcessInstaller ProcesServiceInstaller = new ServiceProcessInstaller();
   4:              ProcesServiceInstaller.Account = ServiceAccount.LocalSystem;
   5:             
   6:              ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
   7:              InstallContext Context = new System.Configuration.Install.InstallContext();
   8:              String path = String.Format("/assemblypath={0}", Assembly.GetExecutingAssembly().Location);
   9:              String[] cmdline = { path };
  10:   
  11:              Context = new System.Configuration.Install.InstallContext("", cmdline);
  12:              ServiceInstallerObj.Context = Context;
  13:              ServiceInstallerObj.DisplayName = "Nome da visualizzare in SCM";
  14:              ServiceInstallerObj.Description = "Descrizione del servizio";
  15:              ServiceInstallerObj.ServiceName = "NomeDelServizio";
  16:              ServiceInstallerObj.StartType = ServiceStartMode.Automatic;
  17:              ServiceInstallerObj.Parent = ProcesServiceInstaller;
  18:   
  19:              System.Collections.Specialized.ListDictionary state = new System.Collections.Specialized.ListDictionary();
  20:             
  21:              ServiceInstallerObj.Install(state);
  22:          }

Uninstall()

   1:          private static void Uninstall()
   2:          {
   3:              ServiceProcessInstaller ProcesServiceInstaller = new ServiceProcessInstaller();
   4:              ProcesServiceInstaller.Account = ServiceAccount.LocalSystem;
   5:   
   6:              ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
   7:              InstallContext Context = new System.Configuration.Install.InstallContext();
   8:              String path = String.Format("/assemblypath={0}", Assembly.GetExecutingAssembly().Location);
   9:              String[] cmdline = { path };
  10:   
  11:              Context = new System.Configuration.Install.InstallContext("", cmdline);
  12:              ServiceInstallerObj.Context = Context;
  13:              ServiceInstallerObj.ServiceName = "ServiceName";
  14:              ServiceInstallerObj.Uninstall(null);
  15:   
  16:          }
 

Importante: Per entrambe le funzioni, ServiceName deve essere uguale alla proprietà ServiceName del servizio che si vuole installare.

Monday, 03 May 2010 10:46:55 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Recipes

My Stack Overflow
Contacts

Send mail to the author(s) E-mail

Tags
profile for Felice Pollano at Stack Overflow, Q&A for professional and enthusiast programmers
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2017
Felice Pollano
Sign In
Statistics
Total Posts: 157
This Year: 0
This Month: 0
This Week: 0
Comments: 124
This blog visits
All Content © 2017, Felice Pollano
DasBlog theme 'Business' created by Christoph De Baene (delarou) and modified by Felice Pollano