Publishing web project on disk during build

During a build you can ask MsBuild to deploy on build using the switch /p:DeployOnBuild=true as I described in previous posts. This is mainly used to deploy the site on IIS thanks to WebDeploy, but you can also use WebDeploy to deploy on a disk path. The problem is that the path is stored in publication settings file, but what about changing that path during a Build?

The answer is simple, you can use the /p:publishUrl=xxx to override what is specified inside the publication file and choose a different directory for deploy. Es.

msbuild WebApplication1.csproj /p:Deploy
OnBuild=true /p:PublishProfile=Profile1 /p:publishUrl=c:\temp\waptest

Thanks to this simple trick you can instruct MsBuild to store deployed site in any folder of the build server.

Gian Maria.

Detect Client-side reconnection with SignalR

Signalr is really good on keeping alive the connection between server and the client and make sure that the client automatically reconnect if there are connection issue. To verify this you can write a simple test with a simple hub that each second broadcasts to all clients current server timestamp with a simple timer.

 private static Timer testTimer = null;
 static MyHub()
 {
     testTimer = new Timer();
     testTimer.Interval = 1000;
     testTimer.Elapsed += (sender, e) =>
     {
          var context = GlobalHost.ConnectionManager.GetHubContext();
          context .Clients.All.setTime(DateTime.Now.ToString());

Now you can simply reference the hub on a page, register for the setTime method and watch the page dynamically update each second.

function SignalrTestViewModel(option) {

    var self = this;
  
    //signalr configuration
    self.myHub = $.connection.myHub;

    self.serverTime = ko.observable('no date from server');

    self.myHub.client.setTime = function (time) {
        self.serverTime(time);
    };

This is a simple KnockoutJs view model, you can now bind a simple span to the property serverTime and watch everything works.

image

Figure 1: Web page automatically updated from the server

The interesting part is that you can now kill the w3wp.exe process from the task manager (if you are using IIS) or whatever hosting server you are using, and you can verify that almost immediately w3wp.exe process is bring to life again and the timer continues to count. This happens because when the client detect that the server is dead, it tries automatically to reconnect, then IIS creates another worker process and everything starts working again.

The only drawback is that the server had lost every volatile information it has collected during its life. In my situation each clients initialize some javascript code calling certain method on the hub Es. RegisterViewRoom, and I keep such information in static variables inside the hub. This works, except that if the server process goes down for whatever reason (scheduled IIS worker process recycle) these information are lost. I do not want to bother with storing data on the server, my typical situation is no more than 5 clients at a time and I want the simplest thing that could possibly work.

The simplest solution to this problem is letting the client javascript code detect when a re-connection occurs, whenever there is a reconnect, the client can call registration function again. Registration call is idempotent so there is no problem if the reconnection happens because of connectivity problem and not for a restart of the server. To detect in signalr a re-connection you can use this piece of code.

 $.connection.hub.stateChanged(function (change)
    {
        self.signalrState(change.newState);

        if (change.newState === $.signalR.connectionState.reconnecting)
        {
                self.messageHub.server.registerViewRoom(self.conversationId());
        }
  });

This simple code is used to detect when the state of the connection changed, I store this information inside a KnockoutJS View Model variable, to be informed of the actual status, then I simply detect if the new state is reconnecting and I simply call initialization function on the server to re-register information for this client connection.

Signalr is really one of the most powerful and interesting Javascript library I worked with in the past years :).

Gian Maria

Error publishing Click-once moving from .NET 3.5 to 4.5:

I’ve a customer where we set up a TFS Build that automatically compile, obfuscates assembly and finally publish with click-once on an internal server. As a part of the process, a tool is used to move the published packages from the internal server to public server, to make it available to final customers. This tool uses mage.exe to change some properties of the package and then repack to publish to final server.

When the solution moved from .NET 3.5 to .NET 4.5, published application failed to install with this error:

ERROR SUMMARY
Below is a summary of the errors, details of these errors are listed later in the log.
* Activation of http://www.mycompany.net/test/uploadtest/uploadtest.application resulted in exception. Following failure messages were detected:
+ Application manifest has either a different computed hash than the one specified or no hash specified at all.
+ File, UploadTest.exe.manifest, has a different computed hash than specified in manifest.

We first thought that the culprit was the obfuscation process or something related to the build process, because publishing directly from Visual Studio generates a correct installer. Then we were able to replicate the error with a simple application with a single form so we were sure that something wrong happened during the build+publish process. Finally we determined that the problem generates when the published url for the package is changed during the build to point to final location (test server or production server).

Actually the process of changing publishing location was done with a direct call to mage.exe command line utility, and after some investigation we found that the encryption mechanism of Click-once was changed to SHA256RSA in .NET 4.5. Unfortunately mage.exe, does not automatically detect .NET version used by the application to apply the correct hash and uses SHA1 by default. If you want to use mage.exe to change some properties of a click-once application based on .NET 4.5 or greater version you must use -a command line option to choose sha256RSA algorithm to calculate manifest hash. The correct command line must contain -a sha256RSA option to generate a correct package.

In my opinion we have a couple of problem here that Microsoft could address.

1) The error message you got when you try to install published application, should states that the hash is computed with an incorrect algorithm, allowing you to better diagnose the error. Telling you that the hash is different from the one specified in the manifest is something misleading because it lead to incorrect assumption that something modifies the files after the hash is generated.

2) Mage.exe should automatically find version of Framework used by the package, and should give you a warning. If checking framework version is not simple, it would be better to always display a warning that tells the user “Warning: starting with .NET 4.5 you should use the option -a sha256RSA to resign manifest because we changed the algorithm used”.

This is a story that is true even for your application. If you change something so radical from one version to another of your application, always display clear and informative warning to the user, to avoid problems.

Gian Maria.

Sorting a WPF ListView in Grid Mode

There is an article on MSDN that demonstrates how to enable sorting column for a ListView used with Grid layout. The solution presented there works perfectly, but I do not want to put code behind my windows, because I work with MVVM approach. The solution is wrapping everything in a behavior, so I took the code from original MSDN example and I wrapped inside a behavior.

public class ListViewGridSortableBehavior : Behavior
    {
        protected override void OnAttached()
        {
            if (AssociatedObject != null)
            {
                AssociatedObject.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridHeaderClickEventHandler));
            }
            base.OnAttached();
        }

        GridViewColumnHeader _lastHeaderClicked = null;
        ListSortDirection _lastDirection = ListSortDirection.Ascending;

        void GridHeaderClickEventHandler(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked =
                  e.OriginalSource as GridViewColumnHeader;
            ListSortDirection direction;

            if (headerClicked != null)
            {
                if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
                {
                    if (headerClicked != _lastHeaderClicked)
                    {
                        direction = ListSortDirection.Ascending;
                    }
                    else
                    {
                        if (_lastDirection == ListSortDirection.Ascending)
                        {
                            direction = ListSortDirection.Descending;
                        }
                        else
                        {
                            direction = ListSortDirection.Ascending;
                        }
                    }

                    if (_lastHeaderClicked != null) 
                    {
                        SetSortDownVisibility(_lastHeaderClicked, Visibility.Collapsed);
                        SetSortUpVisibility(_lastHeaderClicked, Visibility.Collapsed);
                    }

                    //string header = headerClicked.Column.Header as string;
                    String sortString = GetSortHeaderString(headerClicked);
                    if (String.IsNullOrEmpty(sortString)) return;

                    Sort(sortString, direction);

                    if (direction == ListSortDirection.Ascending)
                    {
                        SetSortDownVisibility(headerClicked, Visibility.Collapsed);
                        SetSortUpVisibility(headerClicked, Visibility.Visible);
                    }
                    else
                    {
                        SetSortDownVisibility(headerClicked, Visibility.Visible);
                        SetSortUpVisibility(headerClicked, Visibility.Collapsed);
                    }

                    // Remove arrow from previously sorted header 
                    if (_lastHeaderClicked != null && _lastHeaderClicked != headerClicked)
                    {
                        _lastHeaderClicked.Column.HeaderTemplate = null;
                    }


                    _lastHeaderClicked = headerClicked;
                    _lastDirection = direction;
                }
            }

        }
        private void Sort(string sortBy, ListSortDirection direction)
        {
            ICollectionView dataView =
              CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource);

            dataView.SortDescriptions.Clear();
            SortDescription sd = new SortDescription(sortBy, direction);
            dataView.SortDescriptions.Add(sd);
            dataView.Refresh();
        }

        public static readonly DependencyProperty SortHeaderStringProperty =
           DependencyProperty.RegisterAttached
           (
               "SortHeaderString",
               typeof(String),
               typeof(GridViewColumnHeader),
               new UIPropertyMetadata(String.Empty)
           );

        public static String GetSortHeaderString(DependencyObject obj)
        {
            return (String)obj.GetValue(SortHeaderStringProperty);
        }

        public static void SetSortHeaderString(DependencyObject obj, String value)
        {
            obj.SetValue(SortHeaderStringProperty, value);
        }

        public static readonly DependencyProperty SortDownVisibilityProperty =
          DependencyProperty.RegisterAttached
          (
              "SortDownVisibility",
              typeof(Visibility),
              typeof(GridViewColumnHeader),
              new UIPropertyMetadata(Visibility.Collapsed)
          );

        public static Visibility GetSortDownVisibility(DependencyObject obj)
        {
            return (Visibility)obj.GetValue(SortDownVisibilityProperty);
        }

        public static void SetSortDownVisibility(DependencyObject obj, Visibility value)
        {
            obj.SetValue(SortDownVisibilityProperty, value);
        }

        public static readonly DependencyProperty SortUpVisibilityProperty =
         DependencyProperty.RegisterAttached
         (
             "SortUpVisibility",
             typeof(Visibility),
             typeof(GridViewColumnHeader),
             new UIPropertyMetadata(Visibility.Collapsed)
         );

        public static Visibility GetSortUpVisibility(DependencyObject obj)
        {
            return (Visibility)obj.GetValue(SortUpVisibilityProperty);
        }

        public static void SetSortUpVisibility(DependencyObject obj, Visibility value)
        {
            obj.SetValue(SortUpVisibilityProperty, value);
        }
    }

The code is really simple, I just add and handler to the ClickEvent of the GridViewColumnHeader for the ListView and then used the code from MSDN example to do the sorting. I’ve added also a couple of Dependency Properties called SortDownVisibility and SortUpVisibility that determines the visibility of graphical indicator of the current sorting. I also added a property called SortHeaderString that will contains the name of the property that should be used to sort. Thanks to this simple code I can simply enable sorting in XAML.

<ListView ItemsSource="{Binding SearchesResult}" HorizontalContentAlignment="Stretch" >
    <i:Interaction.Behaviors>
        <Behaviours:ListViewGridSortableBehavior />
    </i:Interaction.Behaviors>

Now I need to enable sorting for all sortable columns, using my SortHeaderString property.

<GridViewColumn  DisplayMemberBinding="{Binding Dto.Author}" >
    <GridViewColumn.Header>
        <GridViewColumnHeader Behaviours:ListViewGridSortableBehavior.SortHeaderString="Dto.Author">
            <StackPanel Orientation="Horizontal">
                <Label Content="?" Visibility="{Binding Path=SortDownVisibility, RelativeSource={RelativeSource AncestorType={x:Type GridViewColumnHeader}}}"></Label>
                <Label Content="?" Visibility="{Binding Path=SortUpVisibility, RelativeSource={RelativeSource AncestorType={x:Type GridViewColumnHeader}}}"></Label>
                <Label Content="Author"></Label>
            </StackPanel>
        </GridViewColumnHeader>
    </GridViewColumn.Header>
</GridViewColumn>

Thanks to my Dependency Properties, it is really simple to enable sorting for a column because I need only to populate the SortHeaderString property of the Header. In previous example, the column is bound to Dto.Author property, the header shows the string Author, and I’ve also added a couple of Arrows to show the actual sorting. No resource is needed because I’ve used a couple of Unicode chars to represent up and down arrows.

Here is the result

image

Figure 1: Clickable and sortable header in ListView with Grid layout

This simple approach is perfectly compatible with MVVM and permits you to choose the exact layout of the header while maintaining the option to sort with clickable headers.

Gian Maria.

Change appearance of a control if the windows is maximized in WPF

Yesterday I was presented with an interesting question:

How can I design a user control that change appearance when the windows containing it is maximized?

Thanks to WPF binding and Triggers the solution is simple, suppose you want to create a border control that change thickness when the windows that contains this User Control is maximized, here is the code.

 <Border BorderBrush="Black"
                HorizontalAlignment="Left" 
                Height="100" 
                Margin="124,54,0,0" 
                VerticalAlignment="Top"
                Width="100">
            <Border.Style>
                <Style TargetType="Border">
                    <Setter Property="BorderThickness" Value="1" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding 
                                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, 
                                Path=WindowState}" 
                              Value="Maximized">
                            <Setter Property="BorderThickness" Value="10" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
        </Border>

The solution is using a simple style that determines the value for the BorderThickness property of the Border Control. The key part is that I did not set an explicit value for BorderThickness in declaration of the control, but only in the style. This is necessary because value set at control level has higher priority than the style. The real power of WPF binding permits you to bind to RelativeSource with FindAncestor where Type is a Window. That specific binding is really useful in a UserControl, because you cannot know any property name of containers, but it is easy to find the first ancestor of type Window. Then you can simply create the trigger with this specific binding and change the property BorderThickness of the Border to 10 when the windows containing the user control gets maximized.

Gian Maria.