Friday, November 6, 2020

Binding problem caused by SetProperty

 A popular implementation of the PropertyChanged event looks like this

public event PropertyChangedEventHandler PropertyChanged;
public void SetProperty<T>(ref T storageT value, [CallerMemberNamestring name = "")
{
    if (!Object.Equals(storagevalue))
    {
        storage = value;
        if (PropertyChanged != null)
            PropertyChanged(thisnew PropertyChangedEventArgs(name));
    }
}
 
If the value has changed, store it and notify the framework. You see it in many places on the Internet and I have copied and used it many times. But it causes a problem if you bind a Combobox.SelectedValue to an integer. Allow me to elucidate.

Start a new Visual Studio C#, WPF application. I used .Net Core. Call it "BindingToDataTable" (slightly wrong because it's binding to an integer that is the problem).

The XAML defines three combo boxes that are bound to the same data table but in different ways and a button that repopulates the data table.

<Window x:Class="BindingToDataTable.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingToDataTable"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="RepopulateCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource RepopulateCommand}" Executed="RepopulateCommand_Executed"/>
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <ComboBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Names}" SelectedValuePath="ID" SelectedValue="{Binding SelectedNameID}" DisplayMemberPath="Name"/>
        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding SelectedNameID}"/>
        <ComboBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Names}" SelectedValuePath="ID" SelectedValue="{Binding SelectedNameIDString}" DisplayMemberPath="Name"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding SelectedNameIDString}"/>
        <ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding Names}" SelectedItem="{Binding SelectedName}" DisplayMemberPath="Name"/>
        <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding SelectedName[ID]}"/>
        <Button Grid.Row="3" Grid.Column="0" Content="Repopulate" Command="{StaticResource RepopulateCommand}" Height="20" Width="200"/>
    </Grid>
</Window>

The code behind contains code to populate the data table and some properties.

using System;
using System.ComponentModel;
using System.Data;
using System.Runtime.CompilerServices;
using System.Windows;
namespace BindingToDataTable
{
    public partial class MainWindow : WindowINotifyPropertyChanged
    {
        private DataTable _Names = null;
        public DataTable Names
        {
            get { return _Names; }
            set { SetProperty(ref _Names, value); }
        }
        private int _SelectedNameID;
        public int SelectedNameID
        {
            get { return _SelectedNameID; }
            set { SetProperty(ref _SelectedNameID, value); }
        }
        private String _SelectedNameIDString;
        public String SelectedNameIDString
        {
            get { return _SelectedNameIDString; }
            set { SetProperty(ref _SelectedNameIDString, value); }
        }
        private DataRowView _SelectedName;
        public DataRowView SelectedName
        {
            get { return _SelectedName; }
            set { SetProperty(ref _SelectedName, value); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void SetProperty<T>(ref T storageT value, [CallerMemberNamestring name = "")
        {
            if (!Object.Equals(storagevalue))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(thisnew PropertyChangedEventArgs(name));
            }
        }
        public MainWindow()
        {
            InitializeComponent();
            InitializeNames();
        }
        private void InitializeNames()
        {
            Names = CreateNamesTable();
            SelectedNameID = (int)Names.Rows[0]["ID"];
            SelectedNameIDString = Names.Rows[0]["ID"].ToString();
            SelectedName = Names.DefaultView[0];
        }
        private DataTable CreateNamesTable()
        {
            DataTable n = new DataTable();
            n.Columns.Add("ID"Type.GetType("System.Int32"));
            n.Columns.Add("Name"Type.GetType("System.String"));
            n.Rows.Add(0, "Anne");
            n.Rows.Add(1, "Bob");
            n.Rows.Add(2, "Stephan");
            return n;
        }
        private void RepopulateCommand_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            InitializeNames();
        }
    }
}

When you first run the program everything looks fine. Don't play with the combo boxes yet.


Now click the [Repopulate] button. Oh, dear!

You can see the first combo box did not set it's selected item correctly and has a binding error as indicated by the red border. The other two combo boxes repopulated correctly. Let's try an experiment. Start the program again, select a different name in the first combo box, and click [Repopulate] again. It worked correctly. That's a clue.



The first combo box binds SelectedValue to an integer.
The second combo box binds SelectedValue to a string
The third combo box binds SelectedItem to a DataRowView.

Here's what is happening - when you repopulate the Item Source, the SelectedValue is changed in curious ways. Strings and objects become null but integers are unchanged possibly because they can't hold a null value. When we populate SelectedNameID we set it to zero, SelectedNameString is set to "0", and SelectedName is set to a DataRowView. In each case, SetProperty is called. But the value of SelectedNameID didn't change so PropertyChanged is not called. That's the problem.

There are several ways to fix this problem.
  • Always call PropertyChanged from SetProperty. Could be side-effects.
    public event PropertyChangedEventHandler PropertyChanged;
    public void SetProperty<T>(ref T storageT value, [CallerMemberNamestring name = "")
    {
        storage = value;
        if (PropertyChanged != null)
            PropertyChanged(thisnew PropertyChangedEventArgs(name));
    }
    
  • Bind SelectedValue to a string (combo box 2). But if it's an integer, it should be stored as an integer.
  • Bind SelectedItem instead of SelectedValue (combo box 3 - my preferred solution)
  • Bind to a nullable integer (my second favorite solution)
    private int? _SelectedNameID;
    public int? SelectedNameID
    {
        get { return _SelectedNameID; }
        set { SetProperty(ref _SelectedNameID, value); }
    }
    
  • Set the bound integer to a non-zero value (-1) before repopulating the data table in InitializeNames. Guaranteed WTF during code review.
    private void InitializeNames()
    {
        SelectedNameID = -1;
        Names = CreateNamesTable();
        SelectedNameID = (int)Names.Rows[0]["ID"];
        SelectedNameIDString = Names.Rows[0]["ID"].ToString();
        SelectedName = Names.DefaultView[0];
    }
    



Monday, November 2, 2020

Difference in GetType between .Net Framework and .Net Core

 There is a small, but important, difference between .Net Framework and .Net Core in how the Type.GetType method works that is going to catch some people out.

In .Net Framework, if the assembly containing the type is not loaded, Type.GetType will load it. In .Net Core it will not. We can demonstrate this quite easily.

Start a new C# .Net Core project in Visual Studio and call it GetType. Rename the project to GetTypeCore. Make program.cs look like this.

using System;
namespace GetTypeCore
{
    class Program
    {
        static void Main(string[] args)
        {
            Type t = Type.GetType("GetMe.MyType,GetMe");
            Console.WriteLine(t.ToString());
        }
    }
}

Add a .Net Framework project called GetTypeFramework. Make program.cs look like this.

using System;
namespace GetTypeFramework
{
    class Program
    {
        static void Main(string[] args)
        {
            Type t = Type.GetType("GetMe.MyType,GetMe");
            Console.WriteLine(t.ToString());
            Console.ReadKey();
        }
    }
}

Add a .Net Framework project called GetMe and rename Class1 to MyType. It doesn't need any code.

Make GetTypeFramework the startup project and run it. You will see the expected output.


Now make GetTypeCore the startup project and run it. You will get an error because GetType returns null.


You need to explicitly load the assembly before you can use GetType on it. Change GetTypeCore's program.cs to this (changing the path as appropriate).

using System;
using System.Reflection;
namespace GetTypeCore
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly a = Assembly.LoadFrom(@"C:\Users\me\source\repos\GetType\GetMe\bin\Debug\GetMe.dll");
            Type t = Type.GetType("GetMe.MyType,GetMe");
            Console.WriteLine(t.ToString());
        }
    }
}

Now it works.




Monday, October 26, 2020

Explicit Project References

We have a background processor task that launches different processes using reflection. When debugging this, I will modify some code and then run a test. The modified code is not executed. This is because Visual Studio does not realize it needs to rebuild the modified code.

Visual Studio maintains a Dependency Tree based on the dependencies you specify for each project. If code in A instantiates code in B you have to specify a dependency. If you don't do this then the project won't build.


When you set the startup project(s) for the solution and hit Build, Visual Studio looks at the dependency tree for all the startup project(s) and rebuilds any that are out of date. This works well until we start using Reflection or Process.Start. 

Before we go any further, be aware that, in .Net Core, GetType from another assembly will not work unless that assembly has been explicitly loaded. This is a change from .Net Framework. 

Start a new .Net Core console app in Visual Studio and call it ReferenceTree. Add a .Net Core class library project called Dependency and rename Class1 to Dependency. Your solution explorer looks like this.


The Dependency project does something trivial.

namespace Dependency
{
    public class Something
    {
        public string DoSomethingAndReturnData()
        {
            return "Data";
        }
    }
}

The progam.cs will call this through reflection. Please feel free to alter the Assembly.Load as desired. The important thing to see here is that there is no explicit reference to the Dependency class.

 using System;

using System.Reflection;
namespace ReferenceTree
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.LoadFrom(@"C:\Users\Terry\source\repos\ReferenceTree\Dependency\bin\Debug\netcoreapp3.1\Dependency.dll");
            Type type = Type.GetType("Dependency.Something,Dependency");
            object o = Activator.CreateInstance(type);
            MethodInfo method = type.GetMethod("DoSomethingAndReturnData");
            Console.WriteLine(method.Invoke(o,null));
        }
    }
}

Run the program and you will see a console window like this.


Now go to the Something class and change "Data" to "DataX" and hit F5 to run the program again. You still see "Data" because Visual Studio doesn't know it needs to rebuild Dependency.dll.

Put a breakpoint on the return statement in the Something class and hit F5 again. It doesn't even hit your breakpoint, does it?

All you have to do is right-click on Dependencies in the ReferenceTree project in Solution Explorer and select Add Project Reference. You'll see the checkbox next to Dependency is unchecked because we don't have an explicit reference. Check the box.


Now hit F5 again. You will see the Dependency project being built before the console window opens with the correct value. If your breakpoint is still there, it will hit that on the way.



The takeaway from this blog entry is that you can use the Project References to allow your code to reference other projects and also to tell Visual Studio that you are referencing projects indirectly and they need to be included in the dependency tree.



Monday, October 5, 2020

Dependency Injection with Unity

It's time I looked at the most commonly used Dependency Injection framework in the Visual Studio world - Unity. I started by reading an excellent tutorial on the subject at tutorialsteacher.com and then modified my standard application -> configuration -> data provider -> logging provider application to use Unity.

Before we start today, I want to discuss which is better...

  1. Pass individual dependencies to constructors
  2. Pass the dependency container and allow the constructor to extract what it needs
Without dependency injection frameworks we only have option 1. But when we are collecting all our dependencies together we have the option of passing the collection and allowing the constructor to grab the dependencies it wants. This means the instantiating code needs to know less about the class being instantiated. This is a good thing.

If you only want to instantiate the class in a dependency injection framework and you are confident that framework will not be replaced, use option 2. Otherwise use option 1.

Although Unity makes registration of classes with non-default constructors simple, I like the idea of extension methods to encapsulate this functionality so it doesn't have to be repeated in different places. I'm going to continue to use this technique with Unity, even thought I don't strictly have to.

Unity provides good support for just-in-time instantiation but our application doesn't do that. We will be registering pre-instantiated instances. Also, Unity does not support just-in-time registration of abstract classes.

Start a new C#, .Net Core, console application called UnityCore. Use NuGet to add references to Unity and SqlClient. Here is the code that goes into program.cs. I have highlighted the important bits. You can see it's doing the same things as Microsoft.Extensions.DependencyInjection but with different words.

using System;
using System.Data.SqlClient;
using Unity;
 
namespace UnityCore
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer()
                .RegisterLoggingProvider<ProductionLoggingProvider>()
                .RegisterDataProvider<ProductionDataProvider>();
 
            Configuration configuration = new Configuration(container);
            bool IsProductionMode = configuration.GetSettings().isProductionMode;
 
            Console.WriteLine($"IsProductionMode is {IsProductionMode}");
            Console.WriteLine($"DataProvider is {configuration.GetDataProviderName()}");
            Console.WriteLine($"LoggingProvider is {configuration.GetLoggingProviderName()}");
            Console.WriteLine($"DataProvider's LoggingProvider is {configuration.GetDataProviderLoggingProviderName()}");
        }
    }
 
    public static class ServiceExtensions
    {
        public static IUnityContainer RegisterLoggingProvider<T>(this IUnityContainer containerwhere T:BaseLoggingProvider
        {
            container.RegisterInstance<BaseLoggingProvider>(Activator.CreateInstance(typeof(T)) as BaseLoggingProvider);
            return container;
        }
 
        public static IUnityContainer RegisterDataProvider<T>(this IUnityContainer containerwhere T:BaseDataProvider
        {
            container.RegisterInstance<BaseDataProvider>(Activator.CreateInstance(typeof(T), containeras BaseDataProvider);
            return container;
        }
    }
 
    public abstract class BaseLoggingProvider
    {
        public BaseLoggingProvider() { }
 
        abstract public string GetName();
        abstract public void Log(string s);
    }
 
    public class ProductionLoggingProvider : BaseLoggingProvider
    {
        public override string GetName()
        {
            return "Production logging provider";
        }
        public override void Log(string s)
        {
            // write to log file
        }
    }
 
    public class TestLoggingProvider : BaseLoggingProvider
    {
        public override string GetName()
        {
            return "Test logging provider";
        }
        public override void Log(string s)
        {
            Console.WriteLine(s);
        }
    }
 
    public class Settings
    {
        public bool isProductionMode;
    }
 
    public abstract class BaseDataProvider
    {
        internal BaseLoggingProvider loggingProvider = null;    // This would normally be protected
        public BaseDataProvider(IUnityContainer container)
        {
            this.loggingProvider = container.Resolve<BaseLoggingProvider>();
        }
        public abstract string GetName();
        public abstract void OpenConnection();
        public abstract Settings GetSettings();
        public abstract void CloseConnection();
        public abstract bool IsConnectionOpen();
    }
 
    public class ProductionDataProvider : BaseDataProvider
    {
        public ProductionDataProvider(IUnityContainer container) : base(container) { }
 
        private SqlConnection sqlConnection;
 
        public override void CloseConnection()
        {
            loggingProvider.Log("Closing connection");
            sqlConnection.Close();
            sqlConnection = null;
        }
 
        public override string GetName()
        {
            return "Production data provider";
        }
 
        public override Settings GetSettings()
        {
            loggingProvider.Log("Getting settings");
            if (IsConnectionOpen())
                return new Settings() { isProductionMode = true };
            else
                throw new Exception("Connection is not open");
        }
 
        public override bool IsConnectionOpen()
        {
            return (sqlConnection != null && sqlConnection.State == System.Data.ConnectionState.Open);
        }
 
        public override void OpenConnection()
        {
            loggingProvider.Log("Opening connection");
            sqlConnection = new SqlConnection("server=(local);database=master;trusted_connection=true");
            sqlConnection.Open();
        }
    }
 
    public class TestDataProvider : BaseDataProvider
    {
        public TestDataProvider(IUnityContainer container) : base(container) { }
 
        private bool isConnectionOpen = false;
 
        public override void CloseConnection()
        {
            loggingProvider.Log("Closing connection");
            isConnectionOpen = false;
        }
 
        public override string GetName()
        {
            return "Test data provider";
        }
 
        public override Settings GetSettings()
        {
            loggingProvider.Log("Getting settings");
            if (IsConnectionOpen())
                return new Settings() { isProductionMode = false };
            else
                throw new Exception("Connection is not open");
        }
 
        public override bool IsConnectionOpen()
        {
            return (isConnectionOpen);
        }
 
        public override void OpenConnection()
        {
            loggingProvider.Log("Opening connection");
            isConnectionOpen = true;
        }
    }
 
    public class Configuration
    {
        private BaseDataProvider dataProvider;
        private BaseLoggingProvider loggingProvider;
 
        public Configuration(IUnityContainer container)
        {
            this.dataProvider = container.Resolve<BaseDataProvider>();
            this.loggingProvider = container.Resolve<BaseLoggingProvider>();
        }
 
        public string GetDataProviderName()
        {
            return dataProvider.GetName();
        }
 
        public string GetLoggingProviderName()
        {
            return loggingProvider.GetName();
        }
 
        public string GetDataProviderLoggingProviderName()
        {
            return dataProvider.loggingProvider.GetName();
        }
 
        public Settings GetSettings()
        {
            Settings settings;
            dataProvider.OpenConnection();
            settings = dataProvider.GetSettings();
            dataProvider.CloseConnection();
            return settings;
        }
    }
}

If you run the program you will see the expected output.


Add an NUnit project called UnitCoreTest and rename Test1.cs to UnityTest.cs. Make the code look like this.

using NUnit.Framework;
using Unity;
using UnityCore;
 
namespace UnityCoreTest
{
    public class Tests
    {
        [Test]
        public void TestProvidersTest()
        {
            IUnityContainer container = new UnityContainer()
                .RegisterLoggingProvider<TestLoggingProvider>()
                .RegisterDataProvider<TestDataProvider>();
 
            Configuration configuration = new Configuration(container);
            bool IsProductionMode = configuration.GetSettings().isProductionMode;
 
            Assert.IsFalse(IsProductionMode);
            Assert.AreEqual("Test data provider"configuration.GetDataProviderName());
            Assert.AreEqual("Test logging provider"configuration.GetLoggingProviderName());
            Assert.AreEqual("Test logging provider"configuration.GetDataProviderLoggingProviderName());
        }
    }
}
Again, because we are using the RegisterDataProvider extension method neither the production nor the test code needs to know how the Data Provider is instantiated. Running the tests, we see they all pass, although the Unity test is slightly slower that the DependencyInjection test.


While I was reading the tutorial I noticed a slew of BMW ads, probably because the page contains many references to BMW. It's scary that Google is reading the web page I'm looking at and trying to figure out what I might be in the mood to buy. Somewhere, there's a database that says I'm interested in buying a BMW.


Right now there's probably a piece of code thinking "He's onto us!"