Sicherung
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using NHotkey;
|
||||
using NHotkey.Wpf;
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using FSI.Lib.Helpers;
|
||||
using FSI.Lib.CompareNetObjects;
|
||||
using FSI.Lib.Guis.SetSizePosExWindow;
|
||||
|
||||
namespace FSI.BT.Tools
|
||||
{
|
||||
@@ -31,13 +35,24 @@ namespace FSI.BT.Tools
|
||||
|
||||
Global.FrmRadialMenu = new FrmRadialMenu();
|
||||
|
||||
Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.WinCC(
|
||||
Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.ViewModel.ViewModelWinCC(
|
||||
Global.Settings.SieTiaWinCCMsgMgtAutostart,
|
||||
Global.Settings.SieTiaWinCCMsgMgtUpdateIntervall,
|
||||
Global.Settings.SieTiaWinCCMsgMgtWindowsName,
|
||||
Global.Settings.SieTiaWinCCMsgMgtClassName,
|
||||
Global.Settings.SieTiaWinCCMsgMgtBtnName
|
||||
);
|
||||
);
|
||||
|
||||
Global.Iba = new Lib.Guis.IbaDirSync.ViewModel.ViewModelIba(
|
||||
Global.Settings.IbaRecordDestinationath,
|
||||
Global.Settings.IbaRecordSourcePath,
|
||||
Global.Settings.IbaAutoSync
|
||||
);
|
||||
|
||||
Global.WindowMgt = new Lib.Guis.SetSizePosExWindow.ViewModel.ViewModelWindow();
|
||||
Global.WindowMgt.AutoStart = Global.Settings.WindowMgtAutostart;
|
||||
Global.WindowMgt.UpdateIntervall = Global.Settings.WindowMgtUpdateInterval;
|
||||
|
||||
}
|
||||
|
||||
private void ShowRadialMenu(object sender, HotkeyEventArgs e)
|
||||
@@ -60,7 +75,22 @@ namespace FSI.BT.Tools
|
||||
|
||||
private void Application_Exit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Global.Settings.Save();
|
||||
AppSettings tmpSetting = new AppSettings(Global.Settings.FileName);
|
||||
tmpSetting.Load();
|
||||
|
||||
CompareLogic compareLogic = new CompareLogic();
|
||||
ComparisonResult result = compareLogic.Compare(Global.Settings, tmpSetting);
|
||||
if (!result.AreEqual)
|
||||
{
|
||||
Global.Settings.Save();
|
||||
}
|
||||
|
||||
if (Global.Iba.RoboCopy != null)
|
||||
{
|
||||
Global.Iba.RoboCopy.Stop();
|
||||
Global.Iba.RoboCopy.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using FSI.Lib.WinSettings;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FSI.BT.Tools
|
||||
{
|
||||
public class AppSettings : XmlSettings
|
||||
{
|
||||
|
||||
|
||||
public AppSettings(string fileName) : base(fileName)
|
||||
{
|
||||
TimeStampFormat = "_yyyyMMdd_HHmmss";
|
||||
@@ -15,9 +16,9 @@ namespace FSI.BT.Tools
|
||||
[EncryptedSetting]
|
||||
public string[] Users { get; set; }
|
||||
[EncryptedSetting]
|
||||
public string[] Admins{ get; set; }
|
||||
public string[] Admins { get; set; }
|
||||
[EncryptedSetting]
|
||||
public string SuperAdmin{ get; set; }
|
||||
public string SuperAdmin { get; set; }
|
||||
public string TimeStampFormat { get; set; }
|
||||
public string[] SieSimaticManagerExe { get; set; }
|
||||
public string[] SieTiaV13Exe { get; set; }
|
||||
@@ -62,5 +63,16 @@ namespace FSI.BT.Tools
|
||||
public string SieTiaWinCCMsgMgtWindowsName { get; set; }
|
||||
public string SieTiaWinCCMsgMgtClassName { get; set; }
|
||||
public string SieTiaWinCCMsgMgtBtnName { get; set; }
|
||||
public bool IbaAutoSync { get; set; }
|
||||
public string IbaRecordSourcePath { get; set; }
|
||||
public string IbaRecordDestinationath { get; set; }
|
||||
public string[] WindowMgtName { get; set; }
|
||||
public string[] WindowMgtClassName { get; set; }
|
||||
public int WindowMgtUpdateInterval { get; set; }
|
||||
public bool WindowMgtAutostart { get; set; }
|
||||
public int[] WindowMgtX { get; set; }
|
||||
public int[] WindowMgtY { get; set; }
|
||||
public int[] WindowMgtHeight { get; set; }
|
||||
public int[] WindowMgtWight { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace FSI.BT.Tools.Commands
|
||||
Lib.Guis.AutoPw.FrmMain frmMain = new Lib.Guis.AutoPw.FrmMain()
|
||||
{
|
||||
CloseAtLostFocus = false,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
};
|
||||
frmMain.ShowDialog();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace FSI.BT.Tools.Commands
|
||||
{
|
||||
ShowPdf = false,
|
||||
CloseAtLostFocus = true,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
Path = Global.Settings.EplPrjPath,
|
||||
EplExes = Global.Settings.EplExe,
|
||||
};
|
||||
@@ -69,7 +69,7 @@ namespace FSI.BT.Tools.Commands
|
||||
{
|
||||
ShowPdf = true,
|
||||
CloseAtLostFocus = true,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
Path = Global.Settings.EplPdfPath,
|
||||
};
|
||||
frmMainEplPdf.Show();
|
||||
@@ -114,7 +114,7 @@ namespace FSI.BT.Tools.Commands
|
||||
{
|
||||
Password = GetType().Namespace.ToString(),
|
||||
CloseAtLostFocus = true,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
};
|
||||
frmMainDeEnCrypt.Show();
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FSI.BT.Tools.Commands
|
||||
{
|
||||
@@ -10,9 +11,33 @@ namespace FSI.BT.Tools.Commands
|
||||
public override void Execute(object parameter)
|
||||
{
|
||||
Gui.FrmProcesses frm = new Gui.FrmProcesses();
|
||||
frm.WinCC = Global.WinCC;
|
||||
frm.Iba = Global.Iba;
|
||||
Global.Window.Load();
|
||||
frm.WindowMgt = Global.WindowMgt;
|
||||
frm.Closed += Frm_Closed;
|
||||
frm.ShowDialog();
|
||||
}
|
||||
|
||||
private void Frm_Closed(object sender, System.EventArgs e)
|
||||
{
|
||||
Global.WinCC = ((Gui.FrmProcesses)sender).WinCC;
|
||||
|
||||
Global.Settings.SieTiaWinCCMsgMgtAutostart = Global.WinCC.WinCC.AutoStart;
|
||||
Global.Settings.SieTiaWinCCMsgMgtUpdateIntervall = Global.WinCC.WinCC.UpdateIntervall;
|
||||
Global.Settings.SieTiaWinCCMsgMgtWindowsName = Global.WinCC.WinCC.WindowsName;
|
||||
Global.Settings.SieTiaWinCCMsgMgtClassName = Global.WinCC.WinCC.WindowsClassName;
|
||||
Global.Settings.SieTiaWinCCMsgMgtBtnName = Global.WinCC.WinCC.ButtonName;
|
||||
|
||||
Global.Iba = ((Gui.FrmProcesses)sender).Iba;
|
||||
Global.Settings.IbaRecordDestinationath = Global.Iba.Iba.Destination;
|
||||
Global.Settings.IbaRecordSourcePath = Global.Iba.Iba.Source;
|
||||
Global.Settings.IbaAutoSync = Global.Iba.Iba.AutoStart;
|
||||
|
||||
Global.WindowMgt = ((Gui.FrmProcesses)sender).WindowMgt;
|
||||
Global.Window.Save();
|
||||
}
|
||||
|
||||
public override bool CanExecute(object parameter)
|
||||
{
|
||||
return Global.AdminRights;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<Settings>
|
||||
<Users>+I945AMzKKYBAAAAB21haWVyX3M=</Users>
|
||||
<Admins>e+Dt7FRUDDoBAAAAB21haWVyX3M=</Admins>
|
||||
|
||||
<TimeStampFormat>_yyyyMMdd_HHmmss</TimeStampFormat>
|
||||
|
||||
<SieSimaticManagerExe>C:\Program Files (x86)\Siemens\Step7\S7BIN\S7tgtopx.exe</SieSimaticManagerExe>
|
||||
<SieTiaV13Exe>C:\Program Files (x86)\Siemens\Automation\Portal V13\Bin\Siemens.Automation.Portal.exe</SieTiaV13Exe>
|
||||
<SieTiaV14Exe>C:\Program Files\Siemens\Automation\Portal V14\Bin\Siemens.Automation.Portal.exe</SieTiaV14Exe>
|
||||
@@ -29,6 +31,7 @@
|
||||
<VncExe>C:\Program Files\RealVNC\VNC Viewer\vncviewer.exe,c:\Users\maier_s\OneDrive - Fondium Group GmbH\Documents\Apps\VNC-Viewer-6.20.113-Windows-64bit.exe</VncExe>
|
||||
<VncAdrBookExe>C:\Program Files\RealVNC\VNC Viewer\vncaddrbook.exe</VncAdrBookExe>
|
||||
<IbaAnalyzerExe>C:\Program Files\iba\ibaAnalyzer\ibaAnalyzer.exe</IbaAnalyzerExe>
|
||||
|
||||
<ZentralWebUrl>http://desiaugetwf/web/?AspxAutoDetectCookieSupport=1</ZentralWebUrl>
|
||||
<SchichtbuchUrl>http://10.10.1.42/SKSchichtbuchWeb/de-DE/Plugin/ShiftBook/ShiftBook/IR</SchichtbuchUrl>
|
||||
<SPSUrl>http://10.10.1.42/SKChangeTrackerWeb/de-DE/Plugin/ChangeTracker</SPSUrl>
|
||||
@@ -39,11 +42,17 @@
|
||||
<GiteaUrl>http://desiaugetc7-088:3000/</GiteaUrl>
|
||||
<WikiUrl>http://desiaugetc7-088:3001/en/home</WikiUrl>
|
||||
<ErpUrl>https://mingle-portal.eu1.inforcloudsuite.com/FONDIUM_prd</ErpUrl>
|
||||
|
||||
<EplPdfPath>\\10.10.1.40\Betriebstechnik\Eplan</EplPdfPath>
|
||||
<EplPrjPath>\\fondium.org\DESI$\AUG_Abteilung\Betriebstechnik\EPL\P8\Data\Projekte\FSI\</EplPrjPath>
|
||||
|
||||
<SieTiaWinCCMsgMgtAutostart>true</SieTiaWinCCMsgMgtAutostart>
|
||||
<SieTiaWinCCMsgMgtUpdateIntervall>10</SieTiaWinCCMsgMgtUpdateIntervall>
|
||||
<SieTiaWinCCMsgMgtWindowsName></SieTiaWinCCMsgMgtWindowsName>
|
||||
<SieTiaWinCCMsgMgtClassName>#32770</SieTiaWinCCMsgMgtClassName>
|
||||
<SieTiaWinCCMsgMgtBtnName>Zur Kenntnis genommen</SieTiaWinCCMsgMgtBtnName>
|
||||
|
||||
<IbaAutoSync>true</IbaAutoSync>
|
||||
<IbaRecordSourcePath>d:\tmp</IbaRecordSourcePath>
|
||||
<IbaRecordDestinationath>c:\tmp</IbaRecordDestinationath>
|
||||
</Settings>
|
||||
@@ -18,7 +18,7 @@ namespace FSI.BT.Tools
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
_isOpenHome = true;
|
||||
tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor;
|
||||
tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor + "b";
|
||||
}
|
||||
|
||||
#region Home
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FSI.Lib.Guis.SieTiaWinCCMsgMgt;
|
||||
using FSI.Lib.WinSettings;
|
||||
using FSI.Lib.Guis.IbaDirSync.ViewModel;
|
||||
using FSI.Lib.Guis.SetSizePosExWindow.ViewModel;
|
||||
using FSI.Lib.Guis.SieTiaWinCCMsgMgt.ViewModel;
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
|
||||
namespace FSI.BT.Tools
|
||||
@@ -8,10 +9,45 @@ namespace FSI.BT.Tools
|
||||
{
|
||||
public static FrmRadialMenu FrmRadialMenu { get; set; }
|
||||
public static TaskbarIcon TaskbarIcon { get; set; }
|
||||
public static WinCC WinCC { get; set; }
|
||||
public static ViewModelWinCC WinCC { get; set; }
|
||||
public static AppSettings Settings { get; set; }
|
||||
public static ViewModelIba Iba { get; set; }
|
||||
public static ViewModelWindow WindowMgt { get; set; }
|
||||
public static bool UserRights { get; set; }
|
||||
public static bool AdminRights { get; set; }
|
||||
public static bool SuperAdminRights { get; set; }
|
||||
|
||||
public static class Window
|
||||
{
|
||||
public static void Load()
|
||||
{
|
||||
for (int i = 0; i < Global.Settings.WindowMgtName.Length; i++)
|
||||
{
|
||||
WindowMgt.Windows.Add(new Lib.Guis.SetSizePosExWindow.Model.Window
|
||||
{
|
||||
Name = Global.Settings.WindowMgtName[i],
|
||||
ClassName = Global.Settings.WindowMgtClassName[i],
|
||||
Height = Global.Settings.WindowMgtHeight[i],
|
||||
Width = Global.Settings.WindowMgtWight[i],
|
||||
X = Global.Settings.WindowMgtX[i],
|
||||
Y = Global.Settings.WindowMgtY[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
for (int i = 0; i < Global.WindowMgt.Windows.Count; i++)
|
||||
{
|
||||
Global.Settings.WindowMgtName[i] = Global.WindowMgt.Windows[i].Name;
|
||||
Global.Settings.WindowMgtClassName[i] = Global.WindowMgt.Windows[i].ClassName;
|
||||
Global.Settings.WindowMgtHeight[i] = Global.WindowMgt.Windows[i].Height;
|
||||
Global.Settings.WindowMgtWight[i] = Global.WindowMgt.Windows[i].Width;
|
||||
Global.Settings.WindowMgtX[i] = Global.WindowMgt.Windows[i].X;
|
||||
Global.Settings.WindowMgtY[i] = Global.WindowMgt.Windows[i].Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,311 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:FSI.BT.Tools.Gui"
|
||||
mc:Ignorable="d"
|
||||
Title="FrmProcesses"
|
||||
Height="450"
|
||||
Width="800">
|
||||
Title="FSI.BT.Tools Prozesse"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Height="Auto"
|
||||
Width="Auto">
|
||||
<Grid>
|
||||
<TabControl>
|
||||
<TabItem Header="WinCC">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Fenster-Name:" Margin="5 5 5 5"/>
|
||||
<TextBox x:Name="TbWindowName"
|
||||
MinWidth="200"
|
||||
<TabItem Header="WinCC"
|
||||
DataContext="{Binding WinCC }">
|
||||
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*"
|
||||
MinWidth="200" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Fenster-Name:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding WinCC.WindowsName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal">
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Class-Name:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox x:Name="TbClassName"
|
||||
MinWidth="200"
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding WinCC.WindowsClassName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Schältfläche-Name:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding WinCC.ButtonName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Update-Intervall:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding WinCC.UpdateIntervall, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBlock Text="ms"
|
||||
Grid.Column="2"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Autostart:"
|
||||
Margin="5 5 5 5" />
|
||||
<CheckBox Grid.Column="1"
|
||||
IsChecked="{Binding WinCC.AutoStart, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="Start"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStart}"
|
||||
Margin="0 0 10 0" />
|
||||
<Button Content="Stop"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStop}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="IBA-Sync"
|
||||
DataContext="{Binding Iba }">
|
||||
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*"
|
||||
MinWidth="200" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Quell-Verzeichnis:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Source, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Ziel-Verzeichnis:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Autostart:"
|
||||
Margin="5 5 5 5" />
|
||||
<CheckBox Grid.Column="1"
|
||||
IsChecked="{Binding Iba.AutoStart, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="Start"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStart}"
|
||||
Margin="0 0 10 0" />
|
||||
<Button Content="Stop"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStop}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Fenster Mgt"
|
||||
DataContext="{Binding WindowMgt }"
|
||||
HorizontalAlignment="Left"
|
||||
Width="76">
|
||||
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Fenster:"
|
||||
Margin="5 5 5 5" />
|
||||
<ComboBox ItemsSource="{Binding Plcs}"
|
||||
SelectedItem="{Binding SelectedPlc, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
DisplayMemberPath="Name"
|
||||
SelectedIndex="0"
|
||||
Grid.Column="1"
|
||||
MinWidth="250"
|
||||
Margin="5 5 5 5" />
|
||||
<Separator Width="10"
|
||||
Grid.Column="2"
|
||||
Background="Transparent" />
|
||||
<Button Content="neu"
|
||||
Name="ABC"
|
||||
Command="{Binding CmdCreatNewConnection}"
|
||||
Grid.Column="3"
|
||||
MinWidth="50"
|
||||
Margin="5 5 5 5"/>
|
||||
<Button Content="löschen"
|
||||
Command="{Binding CmdDelConnection}"
|
||||
Grid.Column="4"
|
||||
Margin="5 5 5 5"
|
||||
MinWidth="50" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Fenster Name:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Klassen Name:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Fenster Höhe:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Fenster Breite:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="X-Position:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Y-Position:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Iba.Destination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Update-Intervall:"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding UpdateIntervall, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
<TextBlock Text="ms"
|
||||
Grid.Column="2"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
<Grid Margin="0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="a" />
|
||||
<ColumnDefinition Width="*" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Autostart:"
|
||||
Margin="5 5 5 5" />
|
||||
<CheckBox Grid.Column="1"
|
||||
IsChecked="{Binding AutoStart, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="5 5 5 5" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Content="Start"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStart}"
|
||||
Margin="0 0 10 0" />
|
||||
<Button Content="Stop"
|
||||
MinWidth="75"
|
||||
Command="{Binding CmdStop}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using FSI.Lib.Guis.IbaDirSync.ViewModel;
|
||||
using FSI.Lib.Guis.SetSizePosExWindow.ViewModel;
|
||||
using FSI.Lib.Guis.SieTiaWinCCMsgMgt.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -19,9 +22,15 @@ namespace FSI.BT.Tools.Gui
|
||||
/// </summary>
|
||||
public partial class FrmProcesses : Window
|
||||
{
|
||||
public ViewModelWinCC WinCC { get; set; }
|
||||
public ViewModelIba Iba { get; set; }
|
||||
|
||||
public ViewModelWindow WindowMgt { get; set; }
|
||||
|
||||
public FrmProcesses()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
<tb:TaskbarIcon.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Beenden"
|
||||
Command="{commands:ExitCommand}"/>
|
||||
<Separator />
|
||||
<MenuItem Header="Dienste"
|
||||
Command="{commands:ProcessCommand}" />
|
||||
<Separator />
|
||||
<MenuItem Header="Beenden"
|
||||
Command="{commands:ExitCommand}" />
|
||||
</ContextMenu>
|
||||
</tb:TaskbarIcon.ContextMenu>
|
||||
|
||||
|
||||
208
FSI.Lib/FSI.Lib/CompareNetObjects/Cache.cs
Normal file
208
FSI.Lib/FSI.Lib/CompareNetObjects/Cache.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Cache for properties, fields, and methods to speed up reflection
|
||||
/// </summary>
|
||||
internal static class Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Reflection Cache for property info
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache;
|
||||
|
||||
/// <summary>
|
||||
/// Reflection Cache for field info
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Type, FieldInfo[]> _fieldCache;
|
||||
|
||||
/// <summary>
|
||||
/// Reflection Cache for methods
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Type, MethodInfo[]> _methodList;
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor
|
||||
/// </summary>
|
||||
static Cache()
|
||||
{
|
||||
_propertyCache = new Dictionary<Type, PropertyInfo[]>();
|
||||
_fieldCache = new Dictionary<Type, FieldInfo[]>();
|
||||
_methodList = new Dictionary<Type, MethodInfo[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the cache
|
||||
/// </summary>
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock(_propertyCache)
|
||||
_propertyCache.Clear();
|
||||
|
||||
lock(_fieldCache)
|
||||
_fieldCache.Clear();
|
||||
|
||||
lock(_methodList)
|
||||
_methodList.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of the fields within a type
|
||||
/// </summary>
|
||||
/// <param name="config"> </param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<FieldInfo> GetFieldInfo(ComparisonConfig config, Type type)
|
||||
{
|
||||
lock (_fieldCache)
|
||||
{
|
||||
bool isDynamicType = TypeHelper.IsDynamicObject(type);
|
||||
|
||||
if (config.Caching && _fieldCache.ContainsKey(type))
|
||||
return _fieldCache[type];
|
||||
|
||||
FieldInfo[] currentFields;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
//All the implementation examples that I have seen for dynamic objects use private fields or properties
|
||||
if (( config.ComparePrivateFields || isDynamicType) && !config.CompareStaticFields)
|
||||
{
|
||||
List<FieldInfo> list = new List<FieldInfo>();
|
||||
Type t = type;
|
||||
do
|
||||
{
|
||||
list.AddRange(t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
t = t.BaseType;
|
||||
} while (t != null);
|
||||
currentFields = list.ToArray();
|
||||
}
|
||||
else if ((config.ComparePrivateFields || isDynamicType) && config.CompareStaticFields)
|
||||
{
|
||||
List<FieldInfo> list = new List<FieldInfo>();
|
||||
Type t = type;
|
||||
do
|
||||
{
|
||||
list.AddRange(
|
||||
t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
|
||||
BindingFlags.Static));
|
||||
t = t.BaseType;
|
||||
} while (t != null);
|
||||
currentFields = list.ToArray();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
currentFields = type.GetFields(); //Default is public instance and static
|
||||
|
||||
if (config.Caching)
|
||||
_fieldCache.Add(type, currentFields);
|
||||
|
||||
return currentFields;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a property
|
||||
/// </summary>
|
||||
/// <param name="config"> </param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="objectValue"></param>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public static object GetPropertyValue(ComparisonConfig config, Type type, object objectValue, string propertyName)
|
||||
{
|
||||
lock (_propertyCache)
|
||||
return GetPropertyInfo(config, type).First(o => o.Name == propertyName).GetValue(objectValue, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of the properties in a type
|
||||
/// </summary>
|
||||
/// <param name="config"> </param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<PropertyInfo> GetPropertyInfo(ComparisonConfig config, Type type)
|
||||
{
|
||||
lock (_propertyCache)
|
||||
{
|
||||
bool isDynamicType = TypeHelper.IsDynamicObject(type);
|
||||
|
||||
if (config.Caching && _propertyCache.ContainsKey(type))
|
||||
return _propertyCache[type];
|
||||
|
||||
PropertyInfo[] currentProperties;
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
if (!config.CompareStaticProperties)
|
||||
currentProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
else
|
||||
currentProperties = type.GetProperties(); //Default is public instance and static
|
||||
#else
|
||||
//All the implementation examples that I have seen for dynamic objects use private fields or properties
|
||||
if ((config.ComparePrivateProperties || isDynamicType) && !config.CompareStaticProperties)
|
||||
{
|
||||
currentProperties =
|
||||
type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
else if ((config.ComparePrivateProperties || isDynamicType) && config.CompareStaticProperties)
|
||||
{
|
||||
currentProperties =
|
||||
type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
|
||||
BindingFlags.Static);
|
||||
}
|
||||
else if (!config.CompareStaticProperties)
|
||||
{
|
||||
currentProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentProperties = type.GetProperties(); //Default is public instance and static
|
||||
}
|
||||
#endif
|
||||
|
||||
if (config.Caching)
|
||||
{
|
||||
_propertyCache.Add(type, currentProperties);
|
||||
}
|
||||
|
||||
return currentProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a method by name
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="methodName"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo GetMethod(Type type, string methodName)
|
||||
{
|
||||
lock (_methodList)
|
||||
return GetMethods(type).FirstOrDefault(m => m.Name == methodName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the cached methods for a type
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<MethodInfo> GetMethods(Type type)
|
||||
{
|
||||
lock (_methodList)
|
||||
{
|
||||
if (_methodList.ContainsKey(type))
|
||||
return _methodList[type];
|
||||
|
||||
MethodInfo[] myMethodInfo = type.GetMethods();
|
||||
_methodList.Add(type, myMethodInfo);
|
||||
return myMethodInfo;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
25
FSI.Lib/FSI.Lib/CompareNetObjects/CompareException.cs
Normal file
25
FSI.Lib/FSI.Lib/CompareNetObjects/CompareException.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// CompareException with a Result Property
|
||||
/// </summary>
|
||||
public class CompareException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// CompareException Constructor
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="message"></param>
|
||||
public CompareException(ComparisonResult result, string message) : base(message)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The comparison Result
|
||||
/// </summary>
|
||||
public ComparisonResult Result { get; private set; }
|
||||
}
|
||||
}
|
||||
71
FSI.Lib/FSI.Lib/CompareNetObjects/CompareExtensions.cs
Normal file
71
FSI.Lib/FSI.Lib/CompareNetObjects/CompareExtensions.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of BDD style comparison extensions for use with Testing Frameworks
|
||||
/// </summary>
|
||||
public static class CompareExtensions
|
||||
{
|
||||
|
||||
static CompareExtensions()
|
||||
{
|
||||
Config = new ComparisonConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alter the configuration for the comparison
|
||||
/// </summary>
|
||||
public static ComparisonConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Throws a CompareException if the classes are not equal
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="actual"></param>
|
||||
/// <param name="expected"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="compareConfig"></param>
|
||||
public static void ShouldCompare<T>(this T actual, T expected, string message = null, ComparisonConfig compareConfig = null)
|
||||
{
|
||||
var logic = new CompareLogic(compareConfig ?? Config);
|
||||
ComparisonResult result = logic.Compare(expected, actual);
|
||||
|
||||
if (!result.AreEqual)
|
||||
{
|
||||
throw new CompareException(result, BuildExpectedEqualMessage(message,result));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a CompareException if the classes are equal
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="actual"></param>
|
||||
/// <param name="expected"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="compareConfig"></param>
|
||||
public static void ShouldNotCompare<T>(this T actual, T expected, string message = null, ComparisonConfig compareConfig = null)
|
||||
{
|
||||
var logic = new CompareLogic(compareConfig ?? Config);
|
||||
ComparisonResult result = logic.Compare(expected, actual);
|
||||
|
||||
if (result.AreEqual)
|
||||
{
|
||||
throw new CompareException(result, BuildExpectedNotEqualMessage(message, result));
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildExpectedEqualMessage(string message, ComparisonResult result)
|
||||
{
|
||||
message = message ?? "Objects expected to be equal";
|
||||
return message + Environment.NewLine + result.DifferencesString + Environment.NewLine;
|
||||
}
|
||||
|
||||
private static string BuildExpectedNotEqualMessage(string message, ComparisonResult result)
|
||||
{
|
||||
message = message ?? "Objects expected NOT to be equal";
|
||||
return message + Environment.NewLine + result.DifferencesString + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
315
FSI.Lib/FSI.Lib/CompareNetObjects/CompareLogic.cs
Normal file
315
FSI.Lib/FSI.Lib/CompareNetObjects/CompareLogic.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
// This software is provided free of charge from Kellerman Software.
|
||||
// It may be used in any project, including commercial for sale projects.
|
||||
//
|
||||
// Check out our other great software at:
|
||||
// http://www.FSI.Lib.com
|
||||
// * Free Quick Reference Pack for Developers
|
||||
// * Free Sharp Zip Wrapper
|
||||
// * .NET Caching Library
|
||||
// * .NET Code Generator
|
||||
// * .NET Email Validation Library
|
||||
// * .NET Encryption Library
|
||||
// * .NET Excel Reports (Create Excel reports without excel being installed)
|
||||
// * .NET FTP Library
|
||||
// * .NET Link Tracker
|
||||
// * .NET Logging Library
|
||||
// * .NET PGP Library
|
||||
// * .NET SFTP Library
|
||||
// * .NET Word Reports (Create reports based on Microsoft Word files without having Microsoft Word installed)
|
||||
// * AccessDiff (Detects data, code, and form differences)
|
||||
// * Cheap Reports for Shopify
|
||||
// * Cheap Taxes for Shopify
|
||||
// * Connection String Creator
|
||||
// * Config Helper Pro (Read and write to the registry, config files, and INI files with 100% managed code)
|
||||
// * CSV Reports (CSV Reader, Writer)
|
||||
// * Easy Database Creator
|
||||
// * File Search Library
|
||||
// * Installerific (create Windows, Chocolatey, and Portable App installers)
|
||||
// * Knight Data Access Layer (ORM, LINQ Provider, Generator)
|
||||
// * Name Parser
|
||||
// * Ninja Database Pro (Object Relational database for .NET)
|
||||
// * Ninja Database Lite (Document database for .NET)
|
||||
// * Ninja WinRT Database (Object database for Windows 8 Runtime, Windows Phone 8)
|
||||
// * NUnit Test Generator
|
||||
// * Search Databases
|
||||
// * Source Code Search Tool
|
||||
// * Themed Winform Wizard
|
||||
// * Unused Stored Procedures
|
||||
// * User Agent Parser
|
||||
// * USPS Street Standardization Library
|
||||
// * What's Changed? (Compare words, strings, streams, and text files)
|
||||
|
||||
#region Includes
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if !NETSTANDARD
|
||||
using System.Runtime.Serialization.Json;
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD
|
||||
using FSI.Lib.CompareNetObjects.Properties;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region License
|
||||
//Microsoft Public License (Ms-PL)
|
||||
|
||||
//This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
|
||||
|
||||
//1. Definitions
|
||||
|
||||
//The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
|
||||
//A "contribution" is the original software, or any additions or changes to the software.
|
||||
|
||||
//A "contributor" is any person that distributes its contribution under this license.
|
||||
|
||||
//"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
//2. Grant of Rights
|
||||
|
||||
//(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
|
||||
//(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
//3. Conditions and Limitations
|
||||
|
||||
//(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
|
||||
//(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
|
||||
//(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
|
||||
//(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
|
||||
//(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
||||
#endregion
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that allows comparison of two objects of the same type to each other. Supports classes, lists, arrays, dictionaries, child comparison and more.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// CompareLogic compareLogic = new CompareLogic();
|
||||
///
|
||||
/// Person person1 = new Person();
|
||||
/// person1.DateCreated = DateTime.Now;
|
||||
/// person1.Name = "Greg";
|
||||
///
|
||||
/// Person person2 = new Person();
|
||||
/// person2.Name = "John";
|
||||
/// person2.DateCreated = person1.DateCreated;
|
||||
///
|
||||
/// ComparisonResult result = compareLogic.Compare(person1, person2);
|
||||
///
|
||||
/// if (!result.AreEqual)
|
||||
/// Console.WriteLine(result.DifferencesString);
|
||||
///
|
||||
/// </example>
|
||||
public class CompareLogic : ICompareLogic
|
||||
{
|
||||
#region Class Variables
|
||||
|
||||
private ComparisonConfig _config;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The default configuration
|
||||
/// </summary>
|
||||
public ComparisonConfig Config
|
||||
{
|
||||
get => _config;
|
||||
set
|
||||
{
|
||||
var verifyConfig = new VerifyConfig();
|
||||
verifyConfig.Verify(value);
|
||||
_config = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Set up defaults for the comparison
|
||||
/// </summary>
|
||||
public CompareLogic()
|
||||
{
|
||||
Config = new ComparisonConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass in the configuration
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public CompareLogic(ComparisonConfig config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
|
||||
/// <summary>
|
||||
/// Set up defaults for the comparison
|
||||
/// </summary>
|
||||
/// <param name="useAppConfigSettings">If true, use settings from the app.config</param>
|
||||
public CompareLogic(bool useAppConfigSettings)
|
||||
{
|
||||
Config = new ComparisonConfig();
|
||||
|
||||
if (useAppConfigSettings)
|
||||
SetupWithAppConfigSettings();
|
||||
}
|
||||
|
||||
private void SetupWithAppConfigSettings()
|
||||
{
|
||||
Config.MembersToIgnore = Settings.Default.MembersToIgnore == null
|
||||
? new List<string>()
|
||||
: new List<string>((IEnumerable<string>)Settings.Default.MembersToIgnore);
|
||||
|
||||
if (Settings.Default.MembersToIgnore != null)
|
||||
{
|
||||
foreach (var member in Settings.Default.MembersToIgnore)
|
||||
{
|
||||
Config.MembersToIgnore.Add(member);
|
||||
}
|
||||
}
|
||||
|
||||
Config.CompareStaticFields = Settings.Default.CompareStaticFields;
|
||||
Config.CompareStaticProperties = Settings.Default.CompareStaticProperties;
|
||||
|
||||
Config.ComparePrivateProperties = Settings.Default.ComparePrivateProperties;
|
||||
Config.ComparePrivateFields = Settings.Default.ComparePrivateFields;
|
||||
|
||||
Config.CompareChildren = Settings.Default.CompareChildren;
|
||||
Config.CompareReadOnly = Settings.Default.CompareReadOnly;
|
||||
Config.CompareFields = Settings.Default.CompareFields;
|
||||
Config.IgnoreCollectionOrder = Settings.Default.IgnoreCollectionOrder;
|
||||
Config.CompareProperties = Settings.Default.CompareProperties;
|
||||
Config.Caching = Settings.Default.Caching;
|
||||
Config.AutoClearCache = Settings.Default.AutoClearCache;
|
||||
Config.MaxDifferences = Settings.Default.MaxDifferences;
|
||||
Config.IgnoreUnknownObjectTypes = Settings.Default.IgnoreUnknownObjectTypes;
|
||||
Config.IgnoreObjectDisposedException = Settings.Default.IgnoreObjectDisposedException;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Compare two objects of the same type to each other.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Check the Differences or DifferencesString Properties for the differences.
|
||||
/// Default MaxDifferences is 1 for performance
|
||||
/// </remarks>
|
||||
/// <param name="expectedObject">The expected object value to compare</param>
|
||||
/// <param name="actualObject">The actual object value to compare</param>
|
||||
/// <returns>True if they are equal</returns>
|
||||
public ComparisonResult Compare(object expectedObject, object actualObject)
|
||||
{
|
||||
ComparisonResult result = new ComparisonResult(Config);
|
||||
|
||||
result.Watch.Start();
|
||||
|
||||
Config.PopulateHashSets();
|
||||
|
||||
RootComparer rootComparer = RootComparerFactory.GetRootComparer();
|
||||
|
||||
CompareParms parms = new CompareParms
|
||||
{
|
||||
Config = Config,
|
||||
Result = result,
|
||||
Object1 = expectedObject,
|
||||
Object2 = actualObject,
|
||||
BreadCrumb = string.Empty
|
||||
};
|
||||
|
||||
rootComparer.Compare(parms);
|
||||
|
||||
if (Config.AutoClearCache)
|
||||
ClearCache();
|
||||
|
||||
result.Watch.Stop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reflection properties and fields are cached. By default this cache is cleared automatically after each compare.
|
||||
/// </summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
Cache.ClearCache();
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Save the current configuration to the passed stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public void SaveConfiguration(Stream stream)
|
||||
{
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
|
||||
ser.WriteObject(stream, Config);
|
||||
|
||||
if (stream.CanSeek && stream.Position > 0)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the current configuration from a json stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public void LoadConfiguration(Stream stream)
|
||||
{
|
||||
if (stream.CanSeek && stream.Position > 0)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
|
||||
Config = (ComparisonConfig)ser.ReadObject(stream);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Load the current configuration from a json stream
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public void LoadConfiguration(string filePath)
|
||||
{
|
||||
using (FileStream stream = new FileStream(filePath, FileMode.Open))
|
||||
{
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
|
||||
Config = (ComparisonConfig)ser.ReadObject(stream);
|
||||
Config.DifferenceCallback = d => { };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the current configuration to a json file
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public void SaveConfiguration(string filePath)
|
||||
{
|
||||
using (FileStream stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ComparisonConfig));
|
||||
ser.WriteObject(stream, Config);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
358
FSI.Lib/FSI.Lib/CompareNetObjects/CompareObjects.cs
Normal file
358
FSI.Lib/FSI.Lib/CompareNetObjects/CompareObjects.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
//Provided for backward compatibility from 1.7.4
|
||||
|
||||
#region Includes
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic instead", true)]
|
||||
public class CompareObjects
|
||||
{
|
||||
#region Class Variables
|
||||
|
||||
private readonly CompareLogic _logic;
|
||||
private ComparisonResult _result;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic instead", true)]
|
||||
public CompareObjects()
|
||||
{
|
||||
_logic = new CompareLogic();
|
||||
_result = new ComparisonResult(_logic.Config);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic instead", true)]
|
||||
public CompareObjects(bool useAppConfigSettings)
|
||||
{
|
||||
_logic = new CompareLogic(useAppConfigSettings);
|
||||
_result = new ComparisonResult(_logic.Config);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Obsolete Use the ComparisonResult.ElapsedMilliseconds returned from CompareLogic.Compare
|
||||
/// </summary>
|
||||
[Obsolete("Use the ComparisonResult.ElapsedMilliseconds returned from CompareLogic.Compare", true)]
|
||||
public long ElapsedMilliseconds
|
||||
{
|
||||
get { return _result.ElapsedMilliseconds; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.ShowBreadcrumb instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.ShowBreadcrumb instead", true)]
|
||||
public bool ShowBreadcrumb
|
||||
{
|
||||
get { return _logic.Config.ShowBreadcrumb; }
|
||||
set { _logic.Config.ShowBreadcrumb = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.MembersToIgnore for members or CompareLogic.Config.ClassTypesToIgnore instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.MembersToIgnore for members or CompareLogic.Config.ClassTypesToIgnore instead", true)]
|
||||
public List<string> ElementsToIgnore
|
||||
{
|
||||
get { return _logic.Config.MembersToIgnore.ToList(); }
|
||||
set
|
||||
{
|
||||
_logic.Config.MembersToIgnore.Clear();
|
||||
foreach (var item in value)
|
||||
{
|
||||
_logic.Config.MembersToIgnore.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.MembersToInclude or CompareLogic.Config.ClassTypesToInclude instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.MembersToInclude or CompareLogic.Config.ClassTypesToInclude instead", true)]
|
||||
public List<string> ElementsToInclude
|
||||
{
|
||||
get { return _logic.Config.MembersToInclude.ToList(); }
|
||||
set
|
||||
{
|
||||
_logic.Config.MembersToInclude.Clear();
|
||||
foreach (var item in value)
|
||||
{
|
||||
_logic.Config.MembersToInclude.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Security restriction in Silverlight prevents getting private properties and fields
|
||||
#if !NETSTANDARD
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.ComparePrivateProperties instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.ComparePrivateProperties instead", true)]
|
||||
public bool ComparePrivateProperties
|
||||
{
|
||||
get { return _logic.Config.ComparePrivateProperties; }
|
||||
set { _logic.Config.ComparePrivateProperties = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.ComparePrivateFields instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.ComparePrivateFields instead", true)]
|
||||
public bool ComparePrivateFields
|
||||
{
|
||||
get { return _logic.Config.ComparePrivateFields; }
|
||||
set { _logic.Config.ComparePrivateFields = value; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareStaticProperties instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareStaticProperties instead", true)]
|
||||
public bool CompareStaticProperties
|
||||
{
|
||||
get { return _logic.Config.CompareStaticProperties; }
|
||||
set { _logic.Config.CompareStaticProperties = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareStaticFields instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareStaticFields instead", true)]
|
||||
public bool CompareStaticFields
|
||||
{
|
||||
get { return _logic.Config.CompareStaticFields; }
|
||||
set { _logic.Config.CompareStaticFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareChildren instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareChildren instead", true)]
|
||||
public bool CompareChildren
|
||||
{
|
||||
get { return _logic.Config.CompareChildren; }
|
||||
set { _logic.Config.CompareChildren = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareReadOnly instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareReadOnly instead", true)]
|
||||
public bool CompareReadOnly
|
||||
{
|
||||
get { return _logic.Config.CompareReadOnly; }
|
||||
set { _logic.Config.CompareReadOnly = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareFields instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareFields instead", true)]
|
||||
public bool CompareFields
|
||||
{
|
||||
get { return _logic.Config.CompareFields; }
|
||||
set { _logic.Config.CompareFields = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.IgnoreCollectionOrder instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.IgnoreCollectionOrder instead", true)]
|
||||
public bool IgnoreCollectionOrder
|
||||
{
|
||||
get { return _logic.Config.IgnoreCollectionOrder; }
|
||||
set { _logic.Config.IgnoreCollectionOrder = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CompareProperties instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CompareProperties instead", true)]
|
||||
public bool CompareProperties
|
||||
{
|
||||
get { return _logic.Config.CompareProperties; }
|
||||
set { _logic.Config.CompareProperties = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.MaxDifferences instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.MaxDifferences instead", true)]
|
||||
public int MaxDifferences
|
||||
{
|
||||
get { return _logic.Config.MaxDifferences; }
|
||||
set { _logic.Config.MaxDifferences = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use the ComparisonResult.Differences returned from CompareLogic.Compare
|
||||
/// </summary>
|
||||
[Obsolete("Use the ComparisonResult.Differences returned from CompareLogic.Compare", true)]
|
||||
public List<Difference> Differences
|
||||
{
|
||||
get { return _result.Differences; }
|
||||
set { _result.Differences = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use the ComparisonResult.DifferencesString returned from CompareLogic.Compare
|
||||
/// </summary>
|
||||
[Obsolete("Use the ComparisonResult.DifferencesString returned from CompareLogic.Compare", true)]
|
||||
public string DifferencesString
|
||||
{
|
||||
get { return _result.DifferencesString; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.AutoClearCache instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.AutoClearCache instead", true)]
|
||||
public bool AutoClearCache
|
||||
{
|
||||
get { return _logic.Config.AutoClearCache; }
|
||||
set { _logic.Config.AutoClearCache = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.Caching instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.Caching instead", true)]
|
||||
public bool Caching
|
||||
{
|
||||
get { return _logic.Config.Caching; }
|
||||
set { _logic.Config.Caching = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.AttributesToIgnore instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.AttributesToIgnore instead", true)]
|
||||
public List<Type> AttributesToIgnore
|
||||
{
|
||||
get { return _logic.Config.AttributesToIgnore.ToList(); }
|
||||
set
|
||||
{
|
||||
_logic.Config.AttributesToIgnore.Clear();
|
||||
foreach (var item in value)
|
||||
{
|
||||
_logic.Config.AttributesToIgnore.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.IgnoreObjectTypes instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.IgnoreObjectTypes instead", true)]
|
||||
public bool IgnoreObjectTypes
|
||||
{
|
||||
get { return _logic.Config.IgnoreObjectTypes; }
|
||||
set { _logic.Config.IgnoreObjectTypes = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CustomComparers instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CustomComparers", true)]
|
||||
public Func<Type, bool> IsUseCustomTypeComparer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CustomComparers instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CustomComparers", true)]
|
||||
public Action<CompareObjects, object, object, string> CustomComparer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.ExpectedName instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.ExpectedName instead", true)]
|
||||
public string ExpectedName
|
||||
{
|
||||
get { return _logic.Config.ExpectedName; }
|
||||
set { _logic.Config.ExpectedName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.ActualName instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.ActualName instead", true)]
|
||||
public string ActualName
|
||||
{
|
||||
get { return _logic.Config.ActualName; }
|
||||
set { _logic.Config.ActualName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.DifferenceCallback instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.DifferenceCallback instead", true)]
|
||||
public Action<Difference> DifferenceCallback
|
||||
{
|
||||
get { return _logic.Config.DifferenceCallback; }
|
||||
set { _logic.Config.DifferenceCallback = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Config.CollectionMatchingSpec instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Config.CollectionMatchingSpec instead", true)]
|
||||
public Dictionary<Type, IEnumerable<string>> CollectionMatchingSpec
|
||||
{
|
||||
get { return _logic.Config.CollectionMatchingSpec; }
|
||||
set { _logic.Config.CollectionMatchingSpec = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.Compare instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.Compare instead", true)]
|
||||
public bool Compare(object object1, object object2)
|
||||
{
|
||||
_result = _logic.Compare(object1, object2);
|
||||
|
||||
return _result.AreEqual;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete Use CompareLogic.ClearCache instead
|
||||
/// </summary>
|
||||
[Obsolete("Use CompareLogic.ClearCache instead", true)]
|
||||
public void ClearCache()
|
||||
{
|
||||
_logic.ClearCache();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
71
FSI.Lib/FSI.Lib/CompareNetObjects/CompareParms.cs
Normal file
71
FSI.Lib/FSI.Lib/CompareNetObjects/CompareParms.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare Parameters
|
||||
/// </summary>
|
||||
public class CompareParms
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration settings
|
||||
/// </summary>
|
||||
public ComparisonConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the first object
|
||||
/// </summary>
|
||||
public Type Object1Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the second object
|
||||
/// </summary>
|
||||
public Type Object2Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The declared type of the first object in its parent. e.g. IList<T>
|
||||
/// </summary>
|
||||
public Type Object1DeclaredType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The declared type of the second object in its parent. e.g. IList<T>
|
||||
/// </summary>
|
||||
public Type Object2DeclaredType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Details about the comparison
|
||||
/// </summary>
|
||||
public ComparisonResult Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the parent object1
|
||||
/// </summary>
|
||||
public object ParentObject1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the parent object2
|
||||
/// </summary>
|
||||
public object ParentObject2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first object to be compared
|
||||
/// </summary>
|
||||
public object Object1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The second object to be compared
|
||||
/// </summary>
|
||||
public object Object2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The breadcrumb in the tree
|
||||
/// </summary>
|
||||
public string BreadCrumb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom comparer used to assert <para>Object1</para>
|
||||
/// </summary>
|
||||
public BaseTypeComparer CustomPropertyComparer { get; set; }
|
||||
}
|
||||
}
|
||||
650
FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonConfig.cs
Normal file
650
FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonConfig.cs
Normal file
@@ -0,0 +1,650 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
#if !NETSTANDARD
|
||||
using System.Runtime.Serialization;
|
||||
#endif
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataContract]
|
||||
#endif
|
||||
public class ComparisonConfig
|
||||
{
|
||||
#region Class Variables
|
||||
private Action<Difference> _differenceCallback;
|
||||
private int _maxStructDepth;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Default Constructor
|
||||
/// </summary>
|
||||
public ComparisonConfig()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
internal HashSet<Type> AttributesToIgnoreSet { get; set; }
|
||||
internal HashSet<string> MembersToIgnoreSet { get; set; }
|
||||
internal HashSet<string> MembersToIncludeSet { get; set; }
|
||||
internal HashSet<Type> ClassTypesToIgnoreSet { get; set; }
|
||||
internal HashSet<Type> ClassTypesToIncludeSet { get; set; }
|
||||
internal HashSet<Type> TypesToIgnoreSet { get; set; }
|
||||
internal HashSet<Type> TypesToIncludeSet { get; set; }
|
||||
internal HashSet<Type> RequiredAttributesToCompareSet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// By default Compare .NET Objects uses reference equal to identify objects.
|
||||
/// Versions 4.61 and older used the hash code. Setting this to true will identify objects by hash code instead of reference equals.
|
||||
/// The default is false
|
||||
/// </summary>
|
||||
public bool UseHashCodeIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When comparing strings or StringBuilder types, perform a case sensitive comparison. The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CaseSensitive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore exceptions when objects are disposed
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreObjectDisposedException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore millisecond differences between DateTime values or DateTimeOffset values. The default is 0 (any time difference will be shown).
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public int MaxMillisecondsDateDifference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When comparing DateTimeOffsets, offsets will be compared as well as the UtcDateTimes. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareDateTimeOffsetWithOffsets { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When comparing DateTimeOffsets, timezone difference will be ignored by changing both object to their UTC equivalent value. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreDateTimeOffsetTimezones { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When comparing struct, the depth to compare for children. The default is 2, the max is 5
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public int MaxStructDepth
|
||||
{
|
||||
get { return _maxStructDepth; }
|
||||
set
|
||||
{
|
||||
if (value < 1 || value > 5)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("MaxStructDepth", "Cannot be less than 1 or greater than 5");
|
||||
}
|
||||
|
||||
_maxStructDepth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, unknown object types will be ignored instead of throwing an exception. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreUnknownObjectTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, invalid indexers will be skipped. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool SkipInvalidIndexers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a class implements an interface then only members of the interface will be compared. The default is all members are compared.
|
||||
/// </summary>
|
||||
public List<Type> InterfaceMembers { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "InterfaceMembers")]
|
||||
private List<string> InterfaceMembersSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(InterfaceMembers);}
|
||||
set { InterfaceMembers = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Show breadcrumb at each stage of the comparision. The default is false.
|
||||
/// This is useful for debugging deep object graphs.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool ShowBreadcrumb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of class types to be ignored in the comparison. The default is to compare all class types.
|
||||
/// </summary>
|
||||
public List<Type> ClassTypesToIgnore { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "ClassTypesToIgnore")]
|
||||
private List<string> ClassTypesToIgnoreSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(ClassTypesToIgnore); }
|
||||
set { ClassTypesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Only these class types will be compared. The default is to compare all class types.
|
||||
/// </summary>
|
||||
/// <remarks>If you specify a class type here no other class types will be compared unless it is in this list.</remarks>
|
||||
public List<Type> ClassTypesToInclude { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "ClassTypesToInclude")]
|
||||
private List<string> ClassTypesToIncludeSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(ClassTypesToInclude); }
|
||||
set { ClassTypesToInclude = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A list of types to be ignored in the comparison. The default is to compare all types. A typical thing to not compare are GUIDs
|
||||
/// </summary>
|
||||
public List<Type> TypesToIgnore { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "TypesToIgnore")]
|
||||
private List<string> TypesToIgnoreSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(TypesToIgnore); }
|
||||
set { TypesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Only these types will be compared. The default is to compare all types.
|
||||
/// </summary>
|
||||
/// <remarks>If you specify a type here no others will be compared unless it is in this list. You must specify ALL Types that you want to compare.</remarks>
|
||||
public List<Type> TypesToInclude { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "TypesToInclude")]
|
||||
private List<string> TypesToIncludeSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(TypesToInclude); }
|
||||
set { TypesToInclude = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Ignore Data Table Names, Data Table Column Names, properties, or fields by name during the comparison. Case sensitive. The default is to compare all members.
|
||||
/// </summary>
|
||||
/// <example>MembersToIgnore.Add("CreditCardNumber");
|
||||
/// MembersToIgnore.Add("Invoice.InvoiceGuid");
|
||||
/// MembersToIgnore.Add("*Id");
|
||||
/// </example>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public List<string> MembersToIgnore { get ; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore property during the comparison. Property is specific to the generic type.
|
||||
/// </summary>
|
||||
/// <param name="ignoredProperty"></param>
|
||||
/// <typeparam name="TClass"></typeparam>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <example>IgnoreProperty<Person>(x => x.Name)</example>
|
||||
public void IgnoreProperty<TClass>(Expression<Func<TClass, object>> ignoredProperty)
|
||||
{
|
||||
LambdaExpression lambda = ignoredProperty;
|
||||
MemberExpression memberExpression;
|
||||
|
||||
if (lambda.Body is UnaryExpression unaryExpression)
|
||||
{
|
||||
memberExpression = unaryExpression.Operand as MemberExpression ??
|
||||
// catches methods, maybe other things
|
||||
throw new ArgumentException(
|
||||
$"IgnoreProperty can only be used with properties. {ignoredProperty} is not a property.");
|
||||
}
|
||||
else
|
||||
{
|
||||
memberExpression = (MemberExpression) lambda.Body;
|
||||
}
|
||||
|
||||
var propInfo = memberExpression.Member as PropertyInfo;
|
||||
if (propInfo == null)
|
||||
// catches fields, maybe other things
|
||||
{
|
||||
throw new ArgumentException($"IgnoreProperty can only be used with properties. {ignoredProperty} is not a property.");
|
||||
}
|
||||
|
||||
var name = propInfo.Name;
|
||||
MembersToIgnore.Add(typeof(TClass).Name + "." + name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Define a Custom Property Comparer using a lambda expression
|
||||
/// </summary>
|
||||
/// <typeparam name="TClass"></typeparam>
|
||||
/// <param name="customProperty"></param>
|
||||
/// <param name="validator"></param>
|
||||
public void CustomPropertyComparer<TClass>(Expression<Func<TClass, object>> customProperty, BaseTypeComparer validator)
|
||||
{
|
||||
LambdaExpression lambda = customProperty;
|
||||
MemberExpression memberExpression;
|
||||
|
||||
if (lambda.Body is UnaryExpression unaryExpression)
|
||||
{
|
||||
memberExpression = unaryExpression.Operand as MemberExpression ??
|
||||
// catches methods, maybe other things
|
||||
throw new ArgumentException(
|
||||
$"Custom property comparer can only be used with properties. {customProperty} is not a property.");
|
||||
}
|
||||
else
|
||||
{
|
||||
memberExpression = (MemberExpression)lambda.Body;
|
||||
}
|
||||
|
||||
var propInfo = memberExpression.Member as PropertyInfo;
|
||||
if (propInfo == null)
|
||||
// catches fields, maybe other things
|
||||
{
|
||||
throw new ArgumentException($"Custom property comparer can only be used with properties. {customProperty} is not a property.");
|
||||
}
|
||||
|
||||
var name = propInfo.Name;
|
||||
CustomPropertyComparers.Add(typeof(TClass).Name + "." + name, validator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only compare elements by name for Data Table Names, Data Table Column Names, properties and fields. Case sensitive. The default is to compare all members.
|
||||
/// </summary>
|
||||
/// <example>MembersToInclude.Add("FirstName")</example>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public List<string> MembersToInclude { get; set; }
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// If true, private properties and fields will be compared. The default is false. Silverlight and WinRT restricts access to private variables.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool ComparePrivateProperties { get; set; }
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// If true, private fields will be compared. The default is false. Silverlight and WinRT restricts access to private variables.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool ComparePrivateFields { get; set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If true, static properties will be compared. The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareStaticProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, static fields will be compared. The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareStaticFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, child objects will be compared. The default is true.
|
||||
/// If false, and a list or array is compared list items will be compared but not their children.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareChildren { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, compare read only properties (only the getter is implemented). The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareReadOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, compare fields of a class (see also CompareProperties). The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, compare each item within a collection to every item in the other. The default is false. WARNING: setting this to true significantly impacts performance.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreCollectionOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, compare properties of a class (see also CompareFields). The default is true.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool CompareProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of differences to detect. The default is 1 for performance reasons.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public int MaxDifferences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of differences to detect when comparing byte arrays. The default is 1.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public int MaxByteArrayDifferences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reflection properties and fields are cached. By default this cache is cleared after each compare. Set to false to keep the cache for multiple compares.
|
||||
/// </summary>
|
||||
/// <seealso cref="Caching"/>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool AutoClearCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// By default properties and fields for types are cached for each compare. By default this cache is cleared after each compare.
|
||||
/// </summary>
|
||||
/// <seealso cref="AutoClearCache"/>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool Caching { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of attributes to ignore a class, property or field
|
||||
/// </summary>
|
||||
/// <example>AttributesToIgnore.Add(typeof(XmlIgnoreAttribute));</example>
|
||||
public List<Type> AttributesToIgnore { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "AttributesToIgnore")]
|
||||
private List<string> AttributesToIgnoreSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(AttributesToIgnore); }
|
||||
set { AttributesToIgnore = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If a property or field don't have at least one of the attributes in this list, it will be ignored
|
||||
/// </summary>
|
||||
/// <example>RequiredAttributesToCompare.Add(typeof(XmlIgnoreAttribute));</example>
|
||||
public List<Type> RequiredAttributesToCompare { get; set; }
|
||||
#if !NETSTANDARD
|
||||
[DataMember(Name = "RequiredAttributesToCompare")]
|
||||
private List<string> RequiredAttributesToCompareSerializer
|
||||
{
|
||||
get { return TypeHelper.ListOfTypesSerializer(RequiredAttributesToCompare); }
|
||||
set { RequiredAttributesToCompare = TypeHelper.ListOfTypesDeserializer(value); }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If true, objects will be compared ignore their type diferences. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreObjectTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In the differences string, this is the name for expected name. The default is: Expected
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public string ExpectedName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In the differences string, this is the name for the actual name. The default is: Actual
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public string ActualName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked each time the comparer finds a difference. The default is no call back.
|
||||
/// </summary>
|
||||
public Action<Difference> DifferenceCallback
|
||||
{
|
||||
get { return _differenceCallback; }
|
||||
set
|
||||
{
|
||||
if (null != value)
|
||||
{
|
||||
_differenceCallback = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This property is used when IgnoreCollectionOrder is set to true, otherwise it has no effect.
|
||||
/// Sometimes one wants to match items between collections by some key first, and then
|
||||
/// compare the matched objects. Without this, the comparer basically says there is no
|
||||
/// match in collection B for any given item in collection A that doesn't Compare with a result of true.
|
||||
/// The results of this aren't particularly useful for object graphs that are mostly the same, but not quite.
|
||||
/// Enter CollectionMatchingSpec
|
||||
///
|
||||
/// The enumerable strings should be property (not field, for now, to keep it simple) names of the
|
||||
/// Type when encountered that will be used for matching
|
||||
///
|
||||
/// You can use complex type properties, too, as part of the key to match. To match on all props/fields on
|
||||
/// such a matching key, Don't set this property (default comparer behavior)
|
||||
/// NOTE: types are looked up as exact. e.g. if foo is an entry in the dictionary and bar is a
|
||||
/// sub-class of foo, upon encountering a bar type, the comparer will not find the entry of foo
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public Dictionary<Type, IEnumerable<string>> CollectionMatchingSpec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of custom comparers that take priority over the built in comparers
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public List<BaseTypeComparer> CustomComparers { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A list of custom property comparers that take priority over the built in and type comparers
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public Dictionary<string, BaseTypeComparer> CustomPropertyComparers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, string.empty and null will be treated as equal for Strings and String Builder. The default is false.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool TreatStringEmptyAndNullTheSame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, leading and trailing whitespaces will be ignored for Strings and String Builder. The default is false.
|
||||
/// </summary>
|
||||
#if !DNCORE
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreStringLeadingTrailingWhitespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The precision to compare double values. The default is 0.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public double DoublePrecision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The precision to compare decimal values. The default is 0.
|
||||
/// </summary>
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public decimal DecimalPrecision { get; set; }
|
||||
|
||||
#if !NETSTANDARD
|
||||
[DataMember]
|
||||
#endif
|
||||
public bool IgnoreConcreteTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, properties that are defined in the actual object but missing in the expected object will not be flagged as differences. Default is true.
|
||||
/// </summary>
|
||||
public bool IgnoreMissingProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, fields that are defined in the actual object but missing in the expected object will not be flagged as differences. Default is true.
|
||||
/// </summary>
|
||||
public bool IgnoreMissingFields { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
//These hash sets are used for performance
|
||||
internal void PopulateHashSets()
|
||||
{
|
||||
AttributesToIgnoreSet = new HashSet<Type>((AttributesToIgnore ?? new List<Type>()).Distinct());
|
||||
MembersToIgnoreSet = new HashSet<String>((MembersToIgnore ?? new List<String>()).Distinct());
|
||||
MembersToIncludeSet = new HashSet<String>((MembersToInclude ?? new List<String>()).Distinct());
|
||||
ClassTypesToIgnoreSet = new HashSet<Type>((ClassTypesToIgnore ?? new List<Type>()).Distinct());
|
||||
ClassTypesToIncludeSet = new HashSet<Type>((ClassTypesToInclude ?? new List<Type>()).Distinct());
|
||||
TypesToIgnoreSet = new HashSet<Type>((TypesToIgnore ?? new List<Type>()).Distinct());
|
||||
TypesToIncludeSet = new HashSet<Type>((TypesToInclude ?? new List<Type>()).Distinct());
|
||||
RequiredAttributesToCompareSet = new HashSet<Type>((RequiredAttributesToCompare ?? new List<Type>()).Distinct());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Backing member that supports <see cref="HasWildcardMembersToExclude"/>
|
||||
/// </summary>
|
||||
private bool? _hasWildcardInMembersToIgnore;
|
||||
|
||||
/// <summary>
|
||||
/// Computed value of whether or not exclusion list has wildcards.
|
||||
/// </summary>
|
||||
public bool HasWildcardMembersToExclude()
|
||||
{
|
||||
if (_hasWildcardInMembersToIgnore.HasValue)
|
||||
{
|
||||
return _hasWildcardInMembersToIgnore.Value;
|
||||
}
|
||||
|
||||
_hasWildcardInMembersToIgnore = MembersToIgnoreSet.Any(x => x.IndexOf("*") > -1);
|
||||
return _hasWildcardInMembersToIgnore.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the configuration to the default values
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
AttributesToIgnore = new List<Type>();
|
||||
RequiredAttributesToCompare = new List<Type>();
|
||||
_differenceCallback = d => { };
|
||||
|
||||
MembersToIgnore = new List<string>();
|
||||
_hasWildcardInMembersToIgnore = null;
|
||||
|
||||
MembersToInclude = new List<string>();
|
||||
ClassTypesToIgnore = new List<Type>();
|
||||
ClassTypesToInclude = new List<Type>();
|
||||
TypesToIgnore = new List<Type>();
|
||||
TypesToInclude = new List<Type>();
|
||||
|
||||
CompareStaticFields = true;
|
||||
CompareStaticProperties = true;
|
||||
#if !NETSTANDARD1_3
|
||||
ComparePrivateProperties = false;
|
||||
ComparePrivateFields = false;
|
||||
#endif
|
||||
CustomPropertyComparers = new Dictionary<string, BaseTypeComparer>();
|
||||
CompareChildren = true;
|
||||
CompareReadOnly = true;
|
||||
CompareFields = true;
|
||||
CompareDateTimeOffsetWithOffsets = false;
|
||||
IgnoreCollectionOrder = false;
|
||||
CompareProperties = true;
|
||||
Caching = true;
|
||||
AutoClearCache = true;
|
||||
IgnoreObjectTypes = false;
|
||||
MaxDifferences = 1;
|
||||
ExpectedName = "Expected";
|
||||
ActualName = "Actual";
|
||||
CustomComparers = new List<BaseTypeComparer>();
|
||||
TreatStringEmptyAndNullTheSame = false;
|
||||
InterfaceMembers = new List<Type>();
|
||||
SkipInvalidIndexers = false;
|
||||
MaxByteArrayDifferences = 1;
|
||||
CollectionMatchingSpec = new Dictionary<Type, IEnumerable<string>>();
|
||||
IgnoreUnknownObjectTypes = false;
|
||||
MaxStructDepth = 2;
|
||||
CaseSensitive = true;
|
||||
IgnoreStringLeadingTrailingWhitespace = false;
|
||||
IgnoreMissingProperties = true;
|
||||
IgnoreMissingFields = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
245
FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonResult.cs
Normal file
245
FSI.Lib/FSI.Lib/CompareNetObjects/ComparisonResult.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Details about the comparison
|
||||
/// </summary>
|
||||
public class ComparisonResult
|
||||
{
|
||||
#region Class Variables
|
||||
private string _differencesString;
|
||||
/// <summary>
|
||||
/// Keep track of parent objects in the object hierarchy by using reference equals
|
||||
/// </summary>
|
||||
private readonly Dictionary<object, int> _referenceParents = new Dictionary<object, int>();
|
||||
private readonly Dictionary<int, int> _hashParents = new Dictionary<int, int>();
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Set the configuration for the comparison
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public ComparisonResult(ComparisonConfig config)
|
||||
{
|
||||
Config = config;
|
||||
Differences = new List<Difference>();
|
||||
Watch = new Stopwatch();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// Keep track of the depth of structs being compared. Used with ComparisonConfig.MaxStructDepth
|
||||
/// </summary>
|
||||
internal int CurrentStructDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration
|
||||
/// </summary>
|
||||
public ComparisonConfig Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to time how long the comparison took
|
||||
/// </summary>
|
||||
internal Stopwatch Watch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time in milliseconds it took for the comparison
|
||||
/// </summary>
|
||||
public long ElapsedMilliseconds
|
||||
{
|
||||
get { return Watch.ElapsedMilliseconds; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The differences found during the compare
|
||||
/// </summary>
|
||||
public List<Difference> Differences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The differences found in a string suitable for a textbox
|
||||
/// </summary>
|
||||
public string DifferencesString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrEmpty(_differencesString))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(4096);
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendFormat("Begin Differences ({0} differences):{1}", Differences.Count, Environment.NewLine);
|
||||
|
||||
foreach (Difference item in Differences)
|
||||
{
|
||||
sb.AppendLine(item.ToString());
|
||||
}
|
||||
|
||||
sb.AppendFormat("End Differences (Maximum of {0} differences shown).", Config.MaxDifferences);
|
||||
|
||||
_differencesString = sb.ToString();
|
||||
}
|
||||
|
||||
return _differencesString;
|
||||
}
|
||||
set { _differencesString = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the objects are equal
|
||||
/// </summary>
|
||||
public bool AreEqual
|
||||
{
|
||||
get { return Differences.Count == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the number of differences has reached the maximum
|
||||
/// </summary>
|
||||
public bool ExceededDifferences
|
||||
{
|
||||
get { return Differences.Count >= Config.MaxDifferences; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// Add parent, handle references count
|
||||
/// </summary>
|
||||
/// <param name="objectReference"></param>
|
||||
public void AddParent(object objectReference)
|
||||
{
|
||||
if (objectReference == null)
|
||||
return;
|
||||
|
||||
if (Config.UseHashCodeIdentifier && _hashParents == null)
|
||||
return;
|
||||
|
||||
if (!Config.UseHashCodeIdentifier && _referenceParents == null)
|
||||
return;
|
||||
|
||||
if (Config.UseHashCodeIdentifier)
|
||||
{
|
||||
int hash = objectReference.GetHashCode();
|
||||
|
||||
if (hash == 0)
|
||||
return;
|
||||
|
||||
if (!_hashParents.ContainsKey(hash))
|
||||
{
|
||||
_hashParents.Add(hash, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hashParents[hash]++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_referenceParents.ContainsKey(objectReference))
|
||||
{
|
||||
_referenceParents.Add(objectReference, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_referenceParents[objectReference]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Remove parent, handle references count
|
||||
/// </summary>
|
||||
/// <param name="objectReference"></param>
|
||||
public void RemoveParent(object objectReference)
|
||||
{
|
||||
if (objectReference == null)
|
||||
return;
|
||||
|
||||
if (Config.UseHashCodeIdentifier && _hashParents == null)
|
||||
return;
|
||||
|
||||
if (!Config.UseHashCodeIdentifier && _referenceParents == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (Config.UseHashCodeIdentifier)
|
||||
{
|
||||
int hash = objectReference.GetHashCode();
|
||||
|
||||
if (_hashParents.ContainsKey(hash))
|
||||
{
|
||||
if (_hashParents[hash] <= 1)
|
||||
{
|
||||
_hashParents.Remove(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hashParents[hash]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_referenceParents.ContainsKey(objectReference))
|
||||
{
|
||||
if (_referenceParents[objectReference] <= 1)
|
||||
{
|
||||
_referenceParents.Remove(objectReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
_referenceParents[objectReference]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we have encountered this parent before
|
||||
/// </summary>
|
||||
/// <param name="objectReference"></param>
|
||||
/// <returns></returns>
|
||||
protected internal bool IsParent(object objectReference)
|
||||
{
|
||||
if (objectReference == null)
|
||||
return false;
|
||||
|
||||
if (Config.UseHashCodeIdentifier && _hashParents == null)
|
||||
return false;
|
||||
|
||||
if (!Config.UseHashCodeIdentifier && _referenceParents == null)
|
||||
return false;
|
||||
|
||||
if (Config.UseHashCodeIdentifier)
|
||||
{
|
||||
int hash = objectReference.GetHashCode();
|
||||
|
||||
if (hash == 0)
|
||||
return false;
|
||||
|
||||
return _hashParents.ContainsKey(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _referenceParents.ContainsKey(objectReference);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
85
FSI.Lib/FSI.Lib/CompareNetObjects/CustomValidationLogic.cs
Normal file
85
FSI.Lib/FSI.Lib/CompareNetObjects/CustomValidationLogic.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
|
||||
/// <summary>
|
||||
/// Get custom validator based on property
|
||||
/// </summary>
|
||||
public static class CustomValidationLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// Get validator for a member of an expando object
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static BaseTypeComparer CustomValidatorForDynamicMember(ComparisonConfig config, string name, Type type)
|
||||
{
|
||||
BaseTypeComparer customValidator = null;
|
||||
|
||||
if (config.CustomPropertyComparers.Count == 0)
|
||||
{
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
//Only compare specific member names
|
||||
customValidator = GetValidatorByName(config, name);
|
||||
if (customValidator != null)
|
||||
return customValidator;
|
||||
|
||||
//Get by type.membername
|
||||
customValidator = GetValidatorByName(config, type.Name + "." + name);
|
||||
if (customValidator != null)
|
||||
return customValidator;
|
||||
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get validator for a member
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <returns></returns>
|
||||
public static BaseTypeComparer CustomValidatorForMember(ComparisonConfig config, MemberInfo info, Type objectType)
|
||||
{
|
||||
BaseTypeComparer customValidator = null;
|
||||
|
||||
if (config.CustomPropertyComparers.Count == 0)
|
||||
{
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
//Get by objecttype.membername
|
||||
customValidator = GetValidatorByName(config, objectType.Name + "." + info.Name);
|
||||
if (customValidator != null)
|
||||
return customValidator;
|
||||
|
||||
//Get by declaringType.membername
|
||||
if (info.DeclaringType != null)
|
||||
{
|
||||
customValidator = GetValidatorByName(config, info.DeclaringType.Name + "." + info.Name);
|
||||
if (customValidator != null)
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
//Get exactly by the name of the member
|
||||
customValidator = GetValidatorByName(config, info.Name);
|
||||
if (customValidator != null)
|
||||
return customValidator;
|
||||
|
||||
return customValidator;
|
||||
}
|
||||
|
||||
private static BaseTypeComparer GetValidatorByName(ComparisonConfig config, string name)
|
||||
{
|
||||
config.CustomPropertyComparers.TryGetValue(name, out var comparer);
|
||||
return comparer;
|
||||
}
|
||||
}
|
||||
}
|
||||
203
FSI.Lib/FSI.Lib/CompareNetObjects/Difference.cs
Normal file
203
FSI.Lib/FSI.Lib/CompareNetObjects/Difference.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Detailed information about the difference
|
||||
/// </summary>
|
||||
public class Difference
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of Expected Object
|
||||
/// </summary>
|
||||
public string ExpectedName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of Actual Object
|
||||
/// </summary>
|
||||
public string ActualName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent property name
|
||||
/// </summary>
|
||||
public string ParentPropertyName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PropertyName.EndsWith("]") && PropertyName.Contains("["))
|
||||
{
|
||||
int lastLeftSquare = PropertyName.LastIndexOf('[');
|
||||
|
||||
return PropertyName.Substring(0, lastLeftSquare);
|
||||
}
|
||||
|
||||
if (PropertyName.Contains("."))
|
||||
{
|
||||
int lastPeriod = PropertyName.LastIndexOf('.');
|
||||
|
||||
if (lastPeriod > 0)
|
||||
return PropertyName.Substring(0, lastPeriod);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The breadcrumb of the property leading up to the value
|
||||
/// </summary>
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The child property name
|
||||
/// </summary>
|
||||
public string ChildPropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object1 Value as a string
|
||||
/// </summary>
|
||||
public string Object1Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object2 Value as a string
|
||||
/// </summary>
|
||||
public string Object2Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the first object
|
||||
/// </summary>
|
||||
public string Object1TypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the second object
|
||||
/// </summary>
|
||||
public string Object2TypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the parent of object1
|
||||
/// </summary>
|
||||
public object ParentObject1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the parent of object2
|
||||
/// </summary>
|
||||
public object ParentObject2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object1 as a reference
|
||||
/// </summary>
|
||||
public object Object1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Object2 as a reference
|
||||
/// </summary>
|
||||
public object Object2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefix to put on the beginning of the message
|
||||
/// </summary>
|
||||
public string MessagePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Item and property name only
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetShortItem()
|
||||
{
|
||||
string message;
|
||||
|
||||
if (!String.IsNullOrEmpty(PropertyName))
|
||||
{
|
||||
if (String.IsNullOrEmpty(ChildPropertyName))
|
||||
{
|
||||
message = String.Format("{0}", PropertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = String.Format("{0}.{1}",
|
||||
PropertyName,
|
||||
ChildPropertyName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message = String.Format("{0} != {1}",
|
||||
ExpectedName,
|
||||
ActualName);
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(MessagePrefix))
|
||||
message = String.Format("{0}: {1}", MessagePrefix, message);
|
||||
|
||||
message = message.Replace("..", ".");
|
||||
message = message.Replace(".[", "[");
|
||||
|
||||
return message;
|
||||
}
|
||||
/// <summary>
|
||||
/// The type and index of what is compared
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetWhatIsCompared()
|
||||
{
|
||||
string message;
|
||||
|
||||
if (!String.IsNullOrEmpty(PropertyName))
|
||||
{
|
||||
if (String.IsNullOrEmpty(ChildPropertyName))
|
||||
{
|
||||
message = String.Format("Types [{3},{4}], Item {0}.{2} != {1}.{2}",
|
||||
ExpectedName,
|
||||
ActualName,
|
||||
PropertyName,
|
||||
Object1TypeName,
|
||||
Object2TypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = String.Format("Types [{4},{5}], Item {0}.{2}.{3} != {1}.{2}.{3}",
|
||||
ExpectedName,
|
||||
ActualName,
|
||||
PropertyName,
|
||||
ChildPropertyName,
|
||||
Object1TypeName,
|
||||
Object2TypeName);
|
||||
}
|
||||
}
|
||||
else if (!String.IsNullOrEmpty(ChildPropertyName))
|
||||
{
|
||||
message = String.Format("Types [{2}.{4},{3}.{4}], Item {0} != {1}",
|
||||
ExpectedName,
|
||||
ActualName,
|
||||
Object1TypeName,
|
||||
Object2TypeName,
|
||||
ChildPropertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = String.Format("Types [{2},{3}], Item {0} != {1}",
|
||||
ExpectedName,
|
||||
ActualName,
|
||||
Object1TypeName,
|
||||
Object2TypeName);
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(MessagePrefix))
|
||||
message = String.Format("{0}: {1}", MessagePrefix, message);
|
||||
|
||||
message = message.Replace("..", ".");
|
||||
message = message.Replace(".[", "[");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nicely formatted string
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("{0}, Values ({1},{2})", GetWhatIsCompared(), Object1Value, Object2Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
FSI.Lib/FSI.Lib/CompareNetObjects/EnvironmentHelper.cs
Normal file
21
FSI.Lib/FSI.Lib/CompareNetObjects/EnvironmentHelper.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Environment helper class
|
||||
/// </summary>
|
||||
public static class EnvironmentHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if we are running in Windows
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsWindows()
|
||||
{
|
||||
string windir = Environment.GetEnvironmentVariable("windir");
|
||||
return !string.IsNullOrEmpty(windir) && windir.Contains(@"\") && Directory.Exists(windir);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
FSI.Lib/FSI.Lib/CompareNetObjects/ExcludeLogic.cs
Normal file
220
FSI.Lib/FSI.Lib/CompareNetObjects/ExcludeLogic.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Exclude types depending upon the configuration
|
||||
/// </summary>
|
||||
public static class ExcludeLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// Exclude a member of an expando object
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ShouldExcludeDynamicMember(ComparisonConfig config, string name, Type type)
|
||||
{
|
||||
//Only compare specific member names
|
||||
if (config.MembersToIncludeSet.Count > 0 && !config.MembersToIncludeSet.Contains(name))
|
||||
return true;
|
||||
|
||||
if (config.MembersToIgnoreSet.Count > 0)
|
||||
{
|
||||
//Ignore by type.membername
|
||||
if (type != null
|
||||
&& config.MembersToIgnoreSet.Contains(type.Name + "." + name))
|
||||
return true;
|
||||
|
||||
//Ignore exactly by the name of the member
|
||||
if (config.MembersToIgnoreSet.Count > 0 && config.MembersToIgnoreSet.Contains(name))
|
||||
return true;
|
||||
|
||||
//Wildcard member
|
||||
if (config.HasWildcardMembersToExclude() && ExcludedByWildcard(config, name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the property or field should be excluded
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="objectType"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ShouldExcludeMember(ComparisonConfig config, MemberInfo info, Type objectType)
|
||||
{
|
||||
//Only compare specific member names
|
||||
if (config.MembersToIncludeSet.Count > 0 && !config.MembersToIncludeSet.Contains(info.Name))
|
||||
return true;
|
||||
|
||||
if (config.MembersToIgnoreSet.Count > 0)
|
||||
{
|
||||
//Ignore by objecttype.membername
|
||||
if (config.MembersToIgnoreSet.Contains(objectType.Name + "." + info.Name))
|
||||
return true;
|
||||
|
||||
//Ignore by declaringType.membername
|
||||
if (info.DeclaringType != null
|
||||
&& config.MembersToIgnoreSet.Contains(info.DeclaringType.Name + "." + info.Name))
|
||||
return true;
|
||||
|
||||
//Ignore exactly by the name of the member
|
||||
if (config.MembersToIgnoreSet.Count > 0 && config.MembersToIgnoreSet.Contains(info.Name))
|
||||
return true;
|
||||
|
||||
//Wildcard member
|
||||
if (config.HasWildcardMembersToExclude() && ExcludedByWildcard(config, info.Name))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (IgnoredByAttribute(config, info))
|
||||
return true;
|
||||
|
||||
if (IgnoredByLackOfAttribute(config, info))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the property or field should be exluded by wilcard
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ExcludedByWildcard(ComparisonConfig config, string name)
|
||||
{
|
||||
foreach (var memberWildcard in config.MembersToIgnore)
|
||||
{
|
||||
string small;
|
||||
|
||||
if (memberWildcard.StartsWith("*") && memberWildcard.EndsWith("*") && memberWildcard.Length > 2)
|
||||
{
|
||||
small = memberWildcard.Substring(1, memberWildcard.Length - 2);
|
||||
if (name.Contains(small))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (memberWildcard.StartsWith("*")
|
||||
&& memberWildcard.Length >= 2
|
||||
&& name.EndsWith(memberWildcard.Substring(1)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (memberWildcard.EndsWith("*")
|
||||
&& memberWildcard.Length >= 2
|
||||
&& name.StartsWith(memberWildcard.Substring(0, memberWildcard.Length - 2)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the class should be exluded by Attribute
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="t1"></param>
|
||||
/// <param name="t2"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ShouldExcludeClass(ComparisonConfig config, Type t1, Type t2)
|
||||
{
|
||||
//Only include specific class types
|
||||
if (config.ClassTypesToIncludeSet.Count > 0
|
||||
&& (!config.ClassTypesToIncludeSet.Contains(t1)
|
||||
|| !config.ClassTypesToIncludeSet.Contains(t2)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Ignore specific class types
|
||||
if (config.ClassTypesToIgnoreSet.Count > 0
|
||||
&& (config.ClassTypesToIgnoreSet.Contains(t1)
|
||||
|| config.ClassTypesToIgnoreSet.Contains(t2)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//The class is ignored by an attribute
|
||||
if (IgnoredByAttribute(config, t1.GetTypeInfo()) || IgnoredByAttribute(config, t2.GetTypeInfo()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the class type should be excluded based on the configuration
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="t1"></param>
|
||||
/// <param name="t2"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ShouldExcludeType(ComparisonConfig config, Type t1, Type t2)
|
||||
{
|
||||
//Only include specific types
|
||||
if (config.TypesToIncludeSet.Count > 0
|
||||
&& (!config.TypesToIncludeSet.Contains(t1)
|
||||
|| !config.TypesToIncludeSet.Contains(t2)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Ignore specific types
|
||||
if (config.TypesToIgnoreSet.Count > 0
|
||||
&& (config.TypesToIgnoreSet.Contains(t1)
|
||||
|| config.TypesToIgnoreSet.Contains(t2)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any type has attributes that should be bypassed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IgnoredByAttribute(ComparisonConfig config, MemberInfo info)
|
||||
{
|
||||
//Prevent loading attributes when AttributesToIgnore is empty
|
||||
if (config.AttributesToIgnoreSet.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributes = info.GetCustomAttributes(true);
|
||||
|
||||
return attributes.Any(a => config.AttributesToIgnoreSet.Contains(a.GetType()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any type lacks attributes that should be required
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IgnoredByLackOfAttribute(ComparisonConfig config, MemberInfo info)
|
||||
{
|
||||
//Prevent loading attributes when RequiredAttributesToCompare is empty
|
||||
if (config.RequiredAttributesToCompareSet.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributes = info.GetCustomAttributes(true);
|
||||
|
||||
return !attributes.Any(a => config.RequiredAttributesToCompareSet.Contains(a.GetType()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
44
FSI.Lib/FSI.Lib/CompareNetObjects/FileHelper.cs
Normal file
44
FSI.Lib/FSI.Lib/CompareNetObjects/FileHelper.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for files and directories
|
||||
/// </summary>
|
||||
public static class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the current directory of the executing assembly
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetCurrentDirectory()
|
||||
{
|
||||
#if NETSTANDARD
|
||||
string basePath = AppContext.BaseDirectory;
|
||||
#else
|
||||
string basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
#endif
|
||||
return PathSlash(basePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the passed string ends with a directory separator character unless the string is blank.
|
||||
/// </summary>
|
||||
/// <param name="path">The string to append the backslash to.</param>
|
||||
/// <returns>String with a "/" on the end</returns>
|
||||
public static String PathSlash(string path)
|
||||
{
|
||||
string separator = Convert.ToString(Path.DirectorySeparatorChar);
|
||||
|
||||
if (path.Length == 0)
|
||||
return path;
|
||||
else if (path.EndsWith(separator))
|
||||
return path;
|
||||
else
|
||||
return path + separator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
118
FSI.Lib/FSI.Lib/CompareNetObjects/HtmlConfig.cs
Normal file
118
FSI.Lib/FSI.Lib/CompareNetObjects/HtmlConfig.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Config object for HtmlReport
|
||||
/// </summary>
|
||||
public class HtmlConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The header value of the Bread Crumb column
|
||||
/// </summary>
|
||||
public string BreadCrumbColumName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The header value of the Expected column
|
||||
/// </summary>
|
||||
public string ExpectedColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The header value of the Actual column
|
||||
/// </summary>
|
||||
public string ActualColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the output will be complete html, if false, it will just be the table
|
||||
/// </summary>
|
||||
public bool GenerateFullHtml { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting this will overwrite the default html header (html, head, body tags)
|
||||
/// </summary>
|
||||
public string HtmlHeader
|
||||
{
|
||||
get { return _htmlHeader.Replace("%TITLE%", HtmlTitle).Replace("%CSS%", Style); }
|
||||
set { _htmlHeader = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting this will overwrite the default html footer (closing body, html tags)
|
||||
/// </summary>
|
||||
public string HtmlFooter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the page - only visible if GenerateFullHtml == true
|
||||
/// </summary>
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CSS Style of the page - only used if the GenerateFullHtml == true
|
||||
/// </summary>
|
||||
public string Style { get; set; }
|
||||
|
||||
private string _htmlHeader;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, sets default values
|
||||
/// </summary>
|
||||
public HtmlConfig()
|
||||
{
|
||||
// set all the defaults
|
||||
BreadCrumbColumName = "Bread Crumb";
|
||||
ExpectedColumnName = "Expected";
|
||||
ActualColumnName = "Actual";
|
||||
GenerateFullHtml = false;
|
||||
HtmlTitle = "Document";
|
||||
_htmlHeader = _header;
|
||||
HtmlFooter = _footer;
|
||||
Style = _css;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends to the existing Style value
|
||||
/// </summary>
|
||||
/// <param name="css">Any css to append</param>
|
||||
public void IncludeCustomCSS(string css)
|
||||
{
|
||||
Style += css;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the existing Style value
|
||||
/// </summary>
|
||||
/// <param name="css">Any css to use</param>
|
||||
public void ReplaceCSS(string css)
|
||||
{
|
||||
Style = css;
|
||||
}
|
||||
|
||||
private const string _header = @"<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""UTF-8"">
|
||||
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
|
||||
<meta http-equiv=""X-UA-Compatible"" content=""ie=edge"">
|
||||
<title>%TITLE%</title>
|
||||
<style>
|
||||
%CSS%
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
";
|
||||
|
||||
private const string _footer = @"</body>
|
||||
</html>";
|
||||
|
||||
private const string _css = @"
|
||||
//.diff-crumb {background: gray;}
|
||||
.diff-table tr:nth-child(odd) { background-color:#eee; }
|
||||
.diff-table tr:nth-child(even) { background-color:#fff; }
|
||||
//.diff-expected {color: red;}
|
||||
.diff-actual {color:red;}
|
||||
";
|
||||
}
|
||||
}
|
||||
30
FSI.Lib/FSI.Lib/CompareNetObjects/ICompareLogic.cs
Normal file
30
FSI.Lib/FSI.Lib/CompareNetObjects/ICompareLogic.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for mocking
|
||||
/// </summary>
|
||||
public interface ICompareLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// The default configuration
|
||||
/// </summary>
|
||||
ComparisonConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compare two objects of the same type to each other.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Check the Differences or DifferencesString Properties for the differences.
|
||||
/// Default MaxDifferences is 1 for performance
|
||||
/// </remarks>
|
||||
/// <param name="expectedObject">The expected object value to compare</param>
|
||||
/// <param name="actualObject">The actual object value to compare</param>
|
||||
/// <returns>True if they are equal</returns>
|
||||
ComparisonResult Compare(object expectedObject, object actualObject);
|
||||
|
||||
/// <summary>
|
||||
/// Reflection properties and fields are cached. By default this cache is cleared automatically after each compare.
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using FSI.Lib.CompareNetObjects;
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.IgnoreOrderTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic for comparing lists that are out of order based on a key
|
||||
/// </summary>
|
||||
public class IgnoreOrderLogic : BaseComparer
|
||||
{
|
||||
private readonly RootComparer _rootComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IgnoreOrderLogic"/> class.
|
||||
/// </summary>
|
||||
/// <param name="rootComparer">The root comparer.</param>
|
||||
public IgnoreOrderLogic(RootComparer rootComparer)
|
||||
{
|
||||
_rootComparer = rootComparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the enumerators and ignores the order
|
||||
/// </summary>
|
||||
public void CompareEnumeratorIgnoreOrder(CompareParms parms, bool countsDifferent)
|
||||
{
|
||||
CompareOutOfOrder(parms, false);
|
||||
}
|
||||
|
||||
private class InstanceCounter
|
||||
{
|
||||
public InstanceCounter(object value, int counter)
|
||||
{
|
||||
Counter = counter;
|
||||
ObjectValue = value;
|
||||
}
|
||||
|
||||
public int Counter;
|
||||
public readonly object ObjectValue;
|
||||
}
|
||||
|
||||
private void CompareOutOfOrder(CompareParms parms, bool reverseCompare)
|
||||
{
|
||||
bool differenceDetected = false;
|
||||
int list1Count = 0;
|
||||
int list2Count = 0;
|
||||
|
||||
IEnumerator enumerator1;
|
||||
IEnumerator enumerator2;
|
||||
|
||||
var list1 = new Dictionary<string, InstanceCounter>();
|
||||
var list2 = new Dictionary<string, InstanceCounter>();
|
||||
|
||||
// Store matching spec for each type.
|
||||
var matchingSpec1 = new Dictionary<Type, List<string>>();
|
||||
var matchingSpec2 = new Dictionary<Type, List<string>>();
|
||||
|
||||
// Determine an explicit fallback to be used if the first element in an enumerable is null.
|
||||
Type fallbackType1 = parms.Object1Type != null ? (TypeHelper.IsGenericType(parms.Object1Type) ? parms.Object1Type.GetGenericArguments()[0] : parms.Object1Type.GetElementType()) : null;
|
||||
Type fallbackType2 = parms.Object2Type != null ? (TypeHelper.IsGenericType(parms.Object2Type) ? parms.Object2Type.GetGenericArguments()[0] : parms.Object2Type.GetElementType()) : null;
|
||||
|
||||
if (!reverseCompare)
|
||||
{
|
||||
enumerator1 = ((IEnumerable)parms.Object1).GetEnumerator();
|
||||
enumerator2 = ((IEnumerable)parms.Object2).GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
enumerator1 = ((IEnumerable)parms.Object2).GetEnumerator();
|
||||
enumerator2 = ((IEnumerable)parms.Object1).GetEnumerator();
|
||||
}
|
||||
|
||||
while (enumerator1.MoveNext())
|
||||
{
|
||||
var data = enumerator1.Current;
|
||||
if (data != null
|
||||
&& parms.Config.ClassTypesToIgnore.Contains(data.GetType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataType1 = data?.GetType() ?? fallbackType1;
|
||||
if (!matchingSpec1.ContainsKey(dataType1))
|
||||
matchingSpec1.Add(dataType1, GetMatchingSpec(parms.Result, dataType1));
|
||||
|
||||
var matchingIndex = GetMatchIndex(parms.Result, matchingSpec1[dataType1], data);
|
||||
if (!list1.ContainsKey(matchingIndex))
|
||||
list1.Add(matchingIndex, new InstanceCounter(data, 1));
|
||||
else
|
||||
list1[matchingIndex].Counter++;
|
||||
|
||||
list1Count++;
|
||||
}
|
||||
|
||||
while (enumerator2.MoveNext())
|
||||
{
|
||||
var data = enumerator2.Current;
|
||||
if (data != null
|
||||
&& parms.Config.ClassTypesToIgnore.Contains(data.GetType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataType2 = data?.GetType() ?? fallbackType2;
|
||||
if (!matchingSpec2.ContainsKey(dataType2))
|
||||
matchingSpec2.Add(dataType2, GetMatchingSpec(parms.Result, dataType2));
|
||||
|
||||
var matchingIndex = GetMatchIndex(parms.Result, matchingSpec2[dataType2], data);
|
||||
if (!list2.ContainsKey(matchingIndex))
|
||||
list2.Add(matchingIndex, new InstanceCounter(data, 1));
|
||||
else
|
||||
list2[matchingIndex].Counter++;
|
||||
|
||||
list2Count++;
|
||||
}
|
||||
|
||||
while (list1.Count > 0)
|
||||
{
|
||||
KeyValuePair<string, InstanceCounter> item1 = list1.First();
|
||||
|
||||
string currentBreadCrumb = $"{parms.BreadCrumb}[{item1.Key}]";
|
||||
|
||||
bool bothObjectValuesNull = item1.Value.ObjectValue == null
|
||||
&& list2.ContainsKey(item1.Key)
|
||||
&& list2[item1.Key].ObjectValue == null;
|
||||
|
||||
object item2Value = list2.ContainsKey(item1.Key) ? list2[item1.Key].ObjectValue : null;
|
||||
|
||||
if (bothObjectValuesNull)
|
||||
{
|
||||
if (--list2[item1.Key].Counter == 0)
|
||||
list2.Remove(item1.Key); // Matched, so remove from dictionary so we don't double-dip on it
|
||||
}
|
||||
else if (item2Value != null)
|
||||
{
|
||||
CompareParms childParams = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = item1.Value.ObjectValue,
|
||||
Object2 = item2Value,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
_rootComparer.Compare(childParams);
|
||||
if (--list2[item1.Key].Counter == 0)
|
||||
list2.Remove(item1.Key); // Matched, so remove from dictionary so we don't double-dip on it
|
||||
}
|
||||
else
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = currentBreadCrumb,
|
||||
Object1Value = reverseCompare ? "(null)" : NiceString(item1.Value.ObjectValue),
|
||||
Object2Value = reverseCompare ? NiceString(item1.Value.ObjectValue) : "(null)",
|
||||
ChildPropertyName = "Item",
|
||||
Object1 = reverseCompare ? null : item1.Value.ObjectValue,
|
||||
Object2 = reverseCompare ? item1.Value.ObjectValue : null
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
differenceDetected = true;
|
||||
}
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
if (--list1[item1.Key].Counter == 0)
|
||||
list1.Remove(item1.Key);
|
||||
}
|
||||
|
||||
while (list2.Count > 0)
|
||||
{
|
||||
var item2 = list2.First();
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = $"{parms.BreadCrumb}[{item2.Key}]",
|
||||
Object1Value = reverseCompare ? NiceString(item2.Value.ObjectValue) : "(null)",
|
||||
Object2Value = reverseCompare ? "(null)" : NiceString(item2.Value.ObjectValue),
|
||||
ChildPropertyName = "Item",
|
||||
Object1 = reverseCompare ? item2.Value.ObjectValue : null,
|
||||
Object2 = reverseCompare ? null : item2.Value.ObjectValue
|
||||
};
|
||||
AddDifference(parms.Result, difference);
|
||||
differenceDetected = true;
|
||||
list2.Remove(item2.Key);
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
|
||||
//This use case one of the lists has a duplicate value
|
||||
if (!differenceDetected && list1Count != list2Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = list1Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = list2Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMatchIndex(ComparisonResult result, List<string> spec, object currentObject)
|
||||
{
|
||||
if (currentObject == null)
|
||||
return "(null)";
|
||||
|
||||
List<PropertyInfo> properties = Cache.GetPropertyInfo(result.Config, currentObject.GetType()).ToList();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
foreach (var item in spec)
|
||||
{
|
||||
var info = properties.FirstOrDefault(o => o.Name == item);
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Invalid CollectionMatchingSpec. No such property {item} for type {currentObject.GetType().Name} ");
|
||||
}
|
||||
|
||||
// Ensure that we will not compare indexer property.
|
||||
var indexParameters = info.GetIndexParameters();
|
||||
if (indexParameters.Length > 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Invalid object {currentObject.GetType().Name} to compare. Object with indexers cannot be compared when IgnoreCollectionOrder = true.",
|
||||
nameof(currentObject)
|
||||
);
|
||||
}
|
||||
var propertyValue = info.GetValue(currentObject, null);
|
||||
|
||||
if (result.Config.TreatStringEmptyAndNullTheSame && info.PropertyType == typeof(string) && propertyValue == null)
|
||||
{
|
||||
propertyValue = string.Empty;
|
||||
}
|
||||
else if (propertyValue != null && result.Config.CaseSensitive == false && info.PropertyType == typeof(string))
|
||||
{
|
||||
propertyValue = ((string)propertyValue).ToLowerInvariant();
|
||||
}
|
||||
|
||||
if (propertyValue == null)
|
||||
{
|
||||
sb.AppendFormat("{0}:(null),", item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var decimals = BitConverter.GetBytes(decimal.GetBits(result.Config.DecimalPrecision)[3])[2];
|
||||
var formatString = $"{{0}}:{{1{(TypeHelper.IsDecimal(propertyValue) ? $":N{decimals}" : string.Empty)}}},";
|
||||
|
||||
sb.Append(string.Format(formatString, item, propertyValue));
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length == 0)
|
||||
sb.Append(RespectNumberToString(currentObject));
|
||||
|
||||
return sb.ToString().TrimEnd(',');
|
||||
}
|
||||
|
||||
private static string RespectNumberToString(object o)
|
||||
{
|
||||
|
||||
#if NETSTANDARD
|
||||
string typeString = o.GetType().Name;
|
||||
|
||||
switch (typeString)
|
||||
{
|
||||
case "Decimal":
|
||||
return ((decimal)o).ToString("G29");
|
||||
case "Double":
|
||||
return ((double)o).ToString("G");
|
||||
case "Single":
|
||||
return ((float)o).ToString("G");
|
||||
default:
|
||||
return o.ToString();
|
||||
}
|
||||
#else
|
||||
switch (Type.GetTypeCode(o.GetType()))
|
||||
{
|
||||
case TypeCode.Decimal:
|
||||
return ((decimal)o).ToString("G29");
|
||||
case TypeCode.Double:
|
||||
return ((double)o).ToString("G");
|
||||
case TypeCode.Single:
|
||||
return ((float)o).ToString("G");
|
||||
default:
|
||||
return o.ToString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private List<string> GetMatchingSpec(ComparisonResult result, Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return new List<string> { "(null)" };
|
||||
|
||||
//The user defined a key for the order
|
||||
var matchingBasePresent = result.Config.CollectionMatchingSpec.Keys.FirstOrDefault(k => k.IsAssignableFrom(type));
|
||||
if (matchingBasePresent != null)
|
||||
{
|
||||
return result.Config.CollectionMatchingSpec.First(p => p.Key == matchingBasePresent).Value.ToList();
|
||||
}
|
||||
|
||||
//Make a key out of primitive types, date, decimal, string, guid, and enum of the class
|
||||
List<string> list = Cache.GetPropertyInfo(result.Config, type)
|
||||
.Where(o => o.CanWrite && !ExcludeLogic.ShouldExcludeMember(result.Config, o, o.DeclaringType) && (TypeHelper.IsSimpleType(o.PropertyType) || TypeHelper.IsEnum(o.PropertyType)))
|
||||
.Select(o => o.Name).ToList();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.IgnoreOrderTypes
|
||||
{
|
||||
internal class IndexerCollectionLooper : IEnumerable
|
||||
{
|
||||
private readonly object _indexer;
|
||||
private readonly PropertyInfo _info;
|
||||
private readonly int _cnt;
|
||||
|
||||
public IndexerCollectionLooper(object obj, PropertyInfo info, int cnt)
|
||||
{
|
||||
_indexer = obj;
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
_info = info;
|
||||
_cnt = cnt;
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
for (var i = 0; i < _cnt; i++)
|
||||
{
|
||||
object value = _info.GetValue(_indexer, new object[] { i });
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
FSI.Lib/FSI.Lib/CompareNetObjects/LogicEqualityComparer.cs
Normal file
43
FSI.Lib/FSI.Lib/CompareNetObjects/LogicEqualityComparer.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// LogicEqualityComparer: Initial contribution by David Rieman
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>Implements methods to support the comparison of objects for equality, in a customizable fashion.</summary>
|
||||
public class LogicEqualityComparer : LogicEqualityComparer<object> { }
|
||||
|
||||
/// <summary>Implements methods to support the comparison of objects for equality, in a customizable fashion.</summary>
|
||||
/// <typeparam name="T">The comparison object type.</typeparam>
|
||||
public class LogicEqualityComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
private readonly CompareLogic _comparer = new CompareLogic();
|
||||
|
||||
/// <summary>Defines the configuration and logic by which Equals comparisons will be performed.</summary>
|
||||
public CompareLogic CompareLogic
|
||||
{
|
||||
get { return _comparer; }
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether the base object hashes should be used.</summary>
|
||||
/// <remarks>
|
||||
/// False by default to allow CompareLogic to evaluate equivalence of otherwise instance-sensitive hashing objects.
|
||||
/// NOTE: Any object which doesn't override GetHashCode will behave this way, so this property should generally be left false.
|
||||
/// </remarks>
|
||||
public bool UseObjectHashes { get; set; }
|
||||
|
||||
/// <summary>Compare two objects of the same type to each other.</summary>
|
||||
/// <returns>True if the objects are considered equivalent, according to the current CompareLogic.</returns>
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return CompareLogic.Compare(x, y).AreEqual;
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the hash of the specified object.</summary>
|
||||
/// <param name="obj">The object to retrieve a hash for.</param>
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
return UseObjectHashes ? obj.GetHashCode() : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
FSI.Lib/FSI.Lib/CompareNetObjects/ProcessHelper.cs
Normal file
69
FSI.Lib/FSI.Lib/CompareNetObjects/ProcessHelper.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
#if !NETSTANDARD1
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for processes
|
||||
/// </summary>
|
||||
public static class ProcessHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute an external program.
|
||||
/// </summary>
|
||||
/// <param name="executablePath">Path and filename of the executable.</param>
|
||||
/// <param name="arguments">Arguments to pass to the executable.</param>
|
||||
/// <param name="windowStyle">Window style for the process (hidden, minimized, maximized, etc).</param>
|
||||
/// <param name="waitUntilFinished">Wait for the process to finish.</param>
|
||||
/// <returns>Exit Code</returns>
|
||||
public static int Shell(string executablePath, string arguments, ProcessWindowStyle windowStyle, bool waitUntilFinished)
|
||||
{
|
||||
string fileName = "";
|
||||
|
||||
try
|
||||
{
|
||||
Process process = new Process();
|
||||
string assemblyPath = Path.Combine(FileHelper.GetCurrentDirectory(), Path.GetFileName(executablePath) ?? string.Empty);
|
||||
|
||||
//Look for the file in the executing assembly directory
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
fileName = assemblyPath;
|
||||
process.StartInfo.FileName = assemblyPath;
|
||||
}
|
||||
else // if there is no path to the file, an error will be thrown
|
||||
{
|
||||
fileName = executablePath;
|
||||
process.StartInfo.FileName = executablePath;
|
||||
}
|
||||
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
process.StartInfo.WindowStyle = windowStyle;
|
||||
|
||||
//Start the Process
|
||||
process.Start();
|
||||
|
||||
if (waitUntilFinished)
|
||||
{
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
if (waitUntilFinished)
|
||||
return process.ExitCode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
string message = string.Format("Shell Fail: {0} {1}", fileName, arguments);
|
||||
throw new ApplicationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
216
FSI.Lib/FSI.Lib/CompareNetObjects/Properties/Settings.Designer.cs
generated
Normal file
216
FSI.Lib/FSI.Lib/CompareNetObjects/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,216 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
public global::System.Collections.Specialized.StringCollection MembersToIgnore {
|
||||
get {
|
||||
return ((global::System.Collections.Specialized.StringCollection)(this["MembersToIgnore"]));
|
||||
}
|
||||
set {
|
||||
this["MembersToIgnore"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
public global::System.Collections.Specialized.StringCollection AttributesToIgnore {
|
||||
get {
|
||||
return ((global::System.Collections.Specialized.StringCollection)(this["AttributesToIgnore"]));
|
||||
}
|
||||
set {
|
||||
this["AttributesToIgnore"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareStaticFields {
|
||||
get {
|
||||
return ((bool)(this["CompareStaticFields"]));
|
||||
}
|
||||
set {
|
||||
this["CompareStaticFields"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareStaticProperties {
|
||||
get {
|
||||
return ((bool)(this["CompareStaticProperties"]));
|
||||
}
|
||||
set {
|
||||
this["CompareStaticProperties"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool ComparePrivateProperties {
|
||||
get {
|
||||
return ((bool)(this["ComparePrivateProperties"]));
|
||||
}
|
||||
set {
|
||||
this["ComparePrivateProperties"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool ComparePrivateFields {
|
||||
get {
|
||||
return ((bool)(this["ComparePrivateFields"]));
|
||||
}
|
||||
set {
|
||||
this["ComparePrivateFields"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareChildren {
|
||||
get {
|
||||
return ((bool)(this["CompareChildren"]));
|
||||
}
|
||||
set {
|
||||
this["CompareChildren"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareReadOnly {
|
||||
get {
|
||||
return ((bool)(this["CompareReadOnly"]));
|
||||
}
|
||||
set {
|
||||
this["CompareReadOnly"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareFields {
|
||||
get {
|
||||
return ((bool)(this["CompareFields"]));
|
||||
}
|
||||
set {
|
||||
this["CompareFields"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool CompareProperties {
|
||||
get {
|
||||
return ((bool)(this["CompareProperties"]));
|
||||
}
|
||||
set {
|
||||
this["CompareProperties"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool Caching {
|
||||
get {
|
||||
return ((bool)(this["Caching"]));
|
||||
}
|
||||
set {
|
||||
this["Caching"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool AutoClearCache {
|
||||
get {
|
||||
return ((bool)(this["AutoClearCache"]));
|
||||
}
|
||||
set {
|
||||
this["AutoClearCache"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("1")]
|
||||
public int MaxDifferences {
|
||||
get {
|
||||
return ((int)(this["MaxDifferences"]));
|
||||
}
|
||||
set {
|
||||
this["MaxDifferences"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool IgnoreCollectionOrder {
|
||||
get {
|
||||
return ((bool)(this["IgnoreCollectionOrder"]));
|
||||
}
|
||||
set {
|
||||
this["IgnoreCollectionOrder"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool IgnoreUnknownObjectTypes {
|
||||
get {
|
||||
return ((bool)(this["IgnoreUnknownObjectTypes"]));
|
||||
}
|
||||
set {
|
||||
this["IgnoreUnknownObjectTypes"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool IgnoreObjectDisposedException {
|
||||
get {
|
||||
return ((bool)(this["IgnoreObjectDisposedException"]));
|
||||
}
|
||||
set {
|
||||
this["IgnoreObjectDisposedException"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="FSI.Lib.CompareNetObjects.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="MembersToIgnore" Type="System.Collections.Specialized.StringCollection" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="AttributesToIgnore" Type="System.Collections.Specialized.StringCollection" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="CompareStaticFields" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="CompareStaticProperties" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="ComparePrivateProperties" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="ComparePrivateFields" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="CompareChildren" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="CompareReadOnly" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="CompareFields" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="CompareProperties" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="Caching" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="AutoClearCache" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="MaxDifferences" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">1</Value>
|
||||
</Setting>
|
||||
<Setting Name="IgnoreCollectionOrder" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="IgnoreUnknownObjectTypes" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="IgnoreObjectDisposedException" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
70
FSI.Lib/FSI.Lib/CompareNetObjects/PropertyEntity.cs
Normal file
70
FSI.Lib/FSI.Lib/CompareNetObjects/PropertyEntity.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic class for holding a Property Info, or Dynamic Info
|
||||
/// </summary>
|
||||
public class PropertyEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PropertyEntity()
|
||||
{
|
||||
Indexers = new List<ParameterInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, this is a dynamic property
|
||||
/// </summary>
|
||||
public bool IsDynamic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the property
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value of the property
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Let me reflect on this day
|
||||
/// </summary>
|
||||
public Type ReflectedType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the parent
|
||||
/// </summary>
|
||||
public Type DeclaringType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the property
|
||||
/// </summary>
|
||||
public Type PropertyType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the property can be read from
|
||||
/// </summary>
|
||||
public bool CanRead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the property can be written to
|
||||
/// </summary>
|
||||
public bool CanWrite { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indexers for the property
|
||||
/// </summary>
|
||||
public List<ParameterInfo> Indexers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the property info
|
||||
/// </summary>
|
||||
public PropertyInfo PropertyInfo { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract Base Duel File Report that has default Output
|
||||
/// </summary>
|
||||
public abstract class BaseDualFileReport : IDualFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Create two difference files and compare in WinMerge
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of the differences</param>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
public virtual void OutputFiles(List<Difference> differences, string expectedFilePath, string actualFilePath)
|
||||
{
|
||||
|
||||
if (differences == null)
|
||||
throw new ArgumentNullException("differences");
|
||||
|
||||
StringBuilder sb1 = new StringBuilder(differences.Count*80);
|
||||
StringBuilder sb2 = new StringBuilder(differences.Count*80);
|
||||
|
||||
bool anyLongerThan80 = differences.Any(o =>
|
||||
o.Object1Value.Replace(Environment.NewLine, "|").Length > 80
|
||||
|| o.Object2Value.Replace(Environment.NewLine, "|").Length > 80);
|
||||
|
||||
foreach (var difference in differences)
|
||||
{
|
||||
if (anyLongerThan80)
|
||||
{
|
||||
sb1.Append(difference.GetShortItem());
|
||||
sb1.Append(", ");
|
||||
sb1.AppendLine(difference.Object1Value.Replace(Environment.NewLine, "|"));
|
||||
|
||||
sb2.Append(difference.GetShortItem());
|
||||
sb2.Append(", ");
|
||||
sb2.AppendLine(difference.Object2Value.Replace(Environment.NewLine, "|"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb1.Append(difference.Object1Value.Replace(Environment.NewLine, "|"));
|
||||
sb1.Append(", ");
|
||||
sb1.AppendLine(difference.GetShortItem());
|
||||
|
||||
sb2.Append(difference.Object2Value.Replace(Environment.NewLine, "|"));
|
||||
sb2.Append(", ");
|
||||
sb2.AppendLine(difference.GetShortItem());
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(expectedFilePath)))
|
||||
expectedFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), expectedFilePath);
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(actualFilePath)))
|
||||
actualFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), actualFilePath);
|
||||
|
||||
File.WriteAllText(expectedFilePath, sb1.ToString());
|
||||
File.WriteAllText(actualFilePath, sb2.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launch application to compare two files
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path for the expected file results</param>
|
||||
/// <param name="actualFilePath">The path for the actual file results</param>
|
||||
public abstract void LaunchApplication(string expectedFilePath, string actualFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Output files and launch Beyond Compare
|
||||
/// </summary>
|
||||
public class BeyondCompareReport : BaseDualFileReport
|
||||
{
|
||||
private const string APPLICATION_NAME = "BCompare.exe";
|
||||
|
||||
#if NETSTANDARD1
|
||||
/// <summary>
|
||||
/// Throw a NotSupported exception if we are running under .NET Standard 1.0
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
public override void LaunchApplication(string expectedFilePath, string actualFilePath)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Launch Beyond Compare
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
public override void LaunchApplication(string expectedFilePath, string actualFilePath)
|
||||
{
|
||||
if (!EnvironmentHelper.IsWindows())
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(expectedFilePath)))
|
||||
expectedFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), expectedFilePath);
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(actualFilePath)))
|
||||
actualFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), actualFilePath);
|
||||
|
||||
string args = string.Format("\"{0}\" \"{1}\"", expectedFilePath, actualFilePath);
|
||||
|
||||
string beyondComparePath = FindBeyondCompare();
|
||||
|
||||
if (String.IsNullOrEmpty(beyondComparePath))
|
||||
throw new FileNotFoundException(APPLICATION_NAME);
|
||||
|
||||
ProcessHelper.Shell(beyondComparePath, args, ProcessWindowStyle.Normal, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Find the path of the Beyond Compare executable
|
||||
/// </summary>
|
||||
/// <returns>The path or null if not found</returns>
|
||||
public string FindBeyondCompare()
|
||||
{
|
||||
//It should be in the Program Files (x86) directory
|
||||
string programFilesPath = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
|
||||
|
||||
if (!String.IsNullOrEmpty(programFilesPath))
|
||||
{
|
||||
string[] directories = Directory.GetDirectories(programFilesPath, "Beyond Compare*");
|
||||
|
||||
foreach (var directory in directories.OrderByDescending(o => o))
|
||||
{
|
||||
string[] files = Directory.GetFiles(directory, APPLICATION_NAME);
|
||||
|
||||
if (files.Any())
|
||||
return files.First();
|
||||
}
|
||||
}
|
||||
|
||||
programFilesPath = Environment.GetEnvironmentVariable("ProgramFiles");
|
||||
|
||||
if (!String.IsNullOrEmpty(programFilesPath))
|
||||
{
|
||||
string[] directories = Directory.GetDirectories(programFilesPath, "Beyond Compare*");
|
||||
|
||||
foreach (var directory in directories.OrderByDescending(o => o))
|
||||
{
|
||||
string[] files = Directory.GetFiles(directory, APPLICATION_NAME);
|
||||
|
||||
if (files.Any())
|
||||
return files.First();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
131
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/CsvReport.cs
Normal file
131
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/CsvReport.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a CSV file of the differences and launch the default CSV handler (usually Excel)
|
||||
/// </summary>
|
||||
public class CsvReport : ISingleFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Output the differences to a file
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="filePath">The file path</param>
|
||||
public void OutputFile(List<Difference> differences, string filePath)
|
||||
{
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(filePath)))
|
||||
filePath = Path.Combine(FileHelper.GetCurrentDirectory(), filePath);
|
||||
|
||||
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
using (TextWriter writer = new StreamWriter(fileStream))
|
||||
{
|
||||
WriteItOut(differences, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteItOut(List<Difference> differences, TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("Bread Crumb,Expected,Actual");
|
||||
|
||||
foreach (var difference in differences)
|
||||
{
|
||||
writer.Write("{0},", EscapeString(difference.GetShortItem()));
|
||||
writer.Write("{0},", EscapeString(difference.Object1Value));
|
||||
writer.WriteLine("{0}", EscapeString(difference.Object2Value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a stream
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="stream">An output stream</param>
|
||||
public void OutputStream(List<Difference> differences, Stream stream)
|
||||
{
|
||||
TextWriter writer = new StreamWriter(stream);
|
||||
WriteItOut(differences, writer);
|
||||
writer.Flush();
|
||||
|
||||
if (stream.CanSeek)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a string
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <returns>A string</returns>
|
||||
public string OutputString(List<Difference> differences)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(differences.Count*40);
|
||||
TextWriter writer = new StringWriter(sb);
|
||||
WriteItOut(differences, writer);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#if NETSTANDARD1
|
||||
|
||||
/// <summary>
|
||||
/// Throws a NotSupportedException when run under .NET Standard 1
|
||||
/// </summary>
|
||||
/// <param name="filePath">The differences file</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Launch the application associated with CSV files
|
||||
/// </summary>
|
||||
/// <param name="filePath">The differences file</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
if (!EnvironmentHelper.IsWindows())
|
||||
throw new NotSupportedException();
|
||||
|
||||
ProcessHelper.Shell(filePath, string.Empty, ProcessWindowStyle.Normal, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Escape special characters
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string EscapeString(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
|
||||
string data = value.ToString();
|
||||
|
||||
// CSV rules: http://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules
|
||||
// From the rules:
|
||||
// 1. if the data has quote, escape the quote in the data
|
||||
// 2. if the data contains the delimiter (in our case ','), double-quote it
|
||||
// 3. if the data contains the new-line, double-quote it.
|
||||
|
||||
if (data.Contains("\""))
|
||||
{
|
||||
data = data.Replace("\"", "\"\"");
|
||||
}
|
||||
|
||||
if (data.Contains(",") || data.Contains(Environment.NewLine))
|
||||
{
|
||||
data = String.Format("{0}{1}{2}", "\"", data, "\"");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/HtmlReport.cs
Normal file
142
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/HtmlReport.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an HTML file of the differences and launch the default HTML handler
|
||||
/// </summary>
|
||||
public class HtmlReport : ISingleFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor, sets up Config object
|
||||
/// </summary>
|
||||
public HtmlReport()
|
||||
{
|
||||
Config = new HtmlConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HtmlReport Configuration
|
||||
/// </summary>
|
||||
public HtmlConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a file
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="filePath">The file path</param>
|
||||
public void OutputFile(List<Difference> differences, string filePath)
|
||||
{
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(filePath)))
|
||||
filePath = Path.Combine(FileHelper.GetCurrentDirectory(), filePath);
|
||||
|
||||
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
using (TextWriter writer = new StreamWriter(fileStream))
|
||||
{
|
||||
WriteItOut(differences, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteItOut(List<Difference> differences, TextWriter writer)
|
||||
{
|
||||
if (Config.GenerateFullHtml)
|
||||
{
|
||||
writer.Write(Config.HtmlHeader);
|
||||
}
|
||||
|
||||
writer.WriteLine("<table class=\"diff-table\">");
|
||||
writer.WriteLine("<thead><th>{0}</th><th>{1}</th><th>{2}</th></thead>",
|
||||
Config.BreadCrumbColumName,
|
||||
Config.ExpectedColumnName,
|
||||
Config.ActualColumnName);
|
||||
|
||||
foreach (var difference in differences)
|
||||
{
|
||||
writer.WriteLine(
|
||||
"<tr><td class=\"diff-crumb\">{0}</td><td class=\"diff-expected\">{1}</td><td class=\"diff-actual\">{2}</td></tr>",
|
||||
EscapeString(difference.GetShortItem()),
|
||||
EscapeString(difference.Object1Value),
|
||||
EscapeString(difference.Object2Value));
|
||||
}
|
||||
|
||||
writer.WriteLine("</table>");
|
||||
|
||||
if (Config.GenerateFullHtml)
|
||||
{
|
||||
writer.Write(Config.HtmlFooter);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD1
|
||||
/// <summary>
|
||||
/// Throw a NotSupported exception if we are running under .NET Standard 1.0
|
||||
/// </summary>
|
||||
/// <param name="filePath">The differences file</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Launch the HTML Report
|
||||
/// </summary>
|
||||
/// <param name="filePath">The differences file</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
if (!EnvironmentHelper.IsWindows())
|
||||
throw new NotSupportedException();
|
||||
|
||||
ProcessHelper.Shell(filePath, string.Empty, ProcessWindowStyle.Normal, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a stream
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="stream">An output stream</param>
|
||||
public void OutputStream(List<Difference> differences, Stream stream)
|
||||
{
|
||||
TextWriter writer = new StreamWriter(stream);
|
||||
WriteItOut(differences, writer);
|
||||
writer.Flush();
|
||||
|
||||
if (stream.CanSeek)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a string
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <returns>A string</returns>
|
||||
public string OutputString(List<Difference> differences)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(differences.Count * 40);
|
||||
TextWriter writer = new StringWriter(sb);
|
||||
WriteItOut(differences, writer);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escape special characters
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string EscapeString(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
|
||||
return WebHelper.HtmlEncode(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
25
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/IDualFileReport.cs
Normal file
25
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/IDualFileReport.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Define a dual file report like Beyond Compare, WinMerge etc.
|
||||
/// </summary>
|
||||
public interface IDualFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Output the differences to two files
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
void OutputFiles(List<Difference> differences, string expectedFilePath, string actualFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Launch the comparison application
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
void LaunchApplication(string expectedFilePath, string actualFilePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a Single File Report
|
||||
/// </summary>
|
||||
public interface ISingleFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Output the differences to a file
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="filePath">The file path</param>
|
||||
void OutputFile(List<Difference> differences, string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a stream
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="stream">Where to write to</param>
|
||||
void OutputStream(List<Difference> differences, Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a string
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <returns>A string</returns>
|
||||
string OutputString(List<Difference> differences);
|
||||
|
||||
/// <summary>
|
||||
/// Launch the application for showing the file
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path</param>
|
||||
void LaunchApplication(string filePath);
|
||||
|
||||
}
|
||||
}
|
||||
126
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/UserFriendlyReport.cs
Normal file
126
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/UserFriendlyReport.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Report for showing differences to an end user
|
||||
/// </summary>
|
||||
public class UserFriendlyReport : ISingleFileReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserFriendlyReport"/> class.
|
||||
/// </summary>
|
||||
public UserFriendlyReport()
|
||||
{
|
||||
ChangedToText = "CHANGED TO ->";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text in between the values. Defaults to: CHANGED TO ->
|
||||
/// </summary>
|
||||
public string ChangedToText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a file
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="filePath">The file path</param>
|
||||
public void OutputFile(List<Difference> differences, string filePath)
|
||||
{
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(filePath)))
|
||||
filePath = Path.Combine(FileHelper.GetCurrentDirectory(), filePath);
|
||||
|
||||
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
using (TextWriter writer = new StreamWriter(fileStream))
|
||||
{
|
||||
WriteItOut(differences, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatProperty(Difference difference)
|
||||
{
|
||||
string shortItem = difference.GetShortItem();
|
||||
StringBuilder sb = new StringBuilder(shortItem.Length);
|
||||
string[] words = shortItem.Split(new [] {'.'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var word in words)
|
||||
{
|
||||
sb.Append(StringHelper.InsertSpaces(word));
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
private void WriteItOut(List<Difference> differences, TextWriter writer)
|
||||
{
|
||||
foreach (var difference in differences)
|
||||
{
|
||||
writer.Write("{0}: ", FormatProperty(difference));
|
||||
writer.Write("{0} {1} ", difference.Object1Value, ChangedToText);
|
||||
writer.WriteLine("{0}", difference.Object2Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a stream
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <param name="stream">Where to write to</param>
|
||||
public void OutputStream(List<Difference> differences, Stream stream)
|
||||
{
|
||||
TextWriter writer = new StreamWriter(stream);
|
||||
WriteItOut(differences, writer);
|
||||
writer.Flush();
|
||||
|
||||
if (stream.CanSeek)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output the differences to a string
|
||||
/// </summary>
|
||||
/// <param name="differences">A list of differences</param>
|
||||
/// <returns>A string</returns>
|
||||
public string OutputString(List<Difference> differences)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(differences.Count * 40);
|
||||
TextWriter writer = new StringWriter(sb);
|
||||
WriteItOut(differences,writer);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#if NETSTANDARD1
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception when running under .NET Standard 1.0
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Launch the application for showing the file
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path</param>
|
||||
public void LaunchApplication(string filePath)
|
||||
{
|
||||
if (!EnvironmentHelper.IsWindows())
|
||||
throw new NotSupportedException();
|
||||
|
||||
ProcessHelper.Shell(filePath, string.Empty, ProcessWindowStyle.Normal, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
120
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/WinMergeReport.cs
Normal file
120
FSI.Lib/FSI.Lib/CompareNetObjects/Reports/WinMergeReport.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
#if !NETSTANDARD
|
||||
using Microsoft.Win32;
|
||||
#endif
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Output files and launch WinMerge
|
||||
/// </summary>
|
||||
public class WinMergeReport : BaseDualFileReport
|
||||
{
|
||||
private const string APPLICATION_NAME = "WinMergeU.exe";
|
||||
|
||||
#if NETSTANDARD1
|
||||
/// <summary>
|
||||
/// Throw a NotSupported exception if we are running under .NET Standard 1.0
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
public override void LaunchApplication(string expectedFilePath, string actualFilePath)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Launch the WinMerge
|
||||
/// </summary>
|
||||
/// <param name="expectedFilePath">The path to write the expected results</param>
|
||||
/// <param name="actualFilePath">The path to write the actual results</param>
|
||||
public override void LaunchApplication(string expectedFilePath, string actualFilePath)
|
||||
{
|
||||
if (!EnvironmentHelper.IsWindows())
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(expectedFilePath)))
|
||||
expectedFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), expectedFilePath);
|
||||
|
||||
if (String.IsNullOrEmpty(Path.GetDirectoryName(actualFilePath)))
|
||||
actualFilePath = Path.Combine(FileHelper.GetCurrentDirectory(), actualFilePath);
|
||||
|
||||
string args = string.Format("\"{0}\" \"{1}\"", expectedFilePath, actualFilePath);
|
||||
|
||||
string winMergePath = FindWinMerge();
|
||||
|
||||
if (String.IsNullOrEmpty(winMergePath))
|
||||
throw new FileNotFoundException(APPLICATION_NAME);
|
||||
|
||||
ProcessHelper.Shell(winMergePath, args, ProcessWindowStyle.Normal, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Find the path of the WinMerge executable
|
||||
/// </summary>
|
||||
/// <returns>The path or null if not found</returns>
|
||||
public string FindWinMerge()
|
||||
{
|
||||
RegistryKey registryKey =
|
||||
Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WinMergeU.exe");
|
||||
|
||||
if (registryKey == null)
|
||||
return null;
|
||||
|
||||
object value = registryKey.GetValue("");
|
||||
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Find the path of the WinMerge executable
|
||||
/// </summary>
|
||||
/// <returns>The path or null if not found</returns>
|
||||
public string FindWinMerge()
|
||||
{
|
||||
//It should be in the Program Files (x86) directory
|
||||
string programFilesPath = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
|
||||
|
||||
if (!String.IsNullOrEmpty(programFilesPath))
|
||||
{
|
||||
string[] directories = Directory.GetDirectories(programFilesPath, "WinMerge");
|
||||
|
||||
foreach (var directory in directories.OrderByDescending(o => o))
|
||||
{
|
||||
string[] files = Directory.GetFiles(directory, APPLICATION_NAME);
|
||||
|
||||
if (files.Any())
|
||||
return files.First();
|
||||
}
|
||||
}
|
||||
|
||||
programFilesPath = Environment.GetEnvironmentVariable("ProgramFiles");
|
||||
|
||||
if (!String.IsNullOrEmpty(programFilesPath))
|
||||
{
|
||||
string[] directories = Directory.GetDirectories(programFilesPath, "WinMerge");
|
||||
|
||||
foreach (var directory in directories.OrderByDescending(o => o))
|
||||
{
|
||||
string[] files = Directory.GetFiles(directory, APPLICATION_NAME);
|
||||
|
||||
if (files.Any())
|
||||
return files.First();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
135
FSI.Lib/FSI.Lib/CompareNetObjects/RootComparer.cs
Normal file
135
FSI.Lib/FSI.Lib/CompareNetObjects/RootComparer.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The base comparer which contains all the type comparers
|
||||
/// </summary>
|
||||
public class RootComparer : BaseComparer
|
||||
{
|
||||
#region Properties
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A list of the type comparers
|
||||
/// </summary>
|
||||
internal List<BaseTypeComparer> TypeComparers { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compare two objects
|
||||
/// </summary>
|
||||
public bool Compare(CompareParms parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (parms.Object1 == null && parms.Object2 == null)
|
||||
return true;
|
||||
|
||||
Type t1 = parms.Object1 != null ? parms.Object1.GetType() : null;
|
||||
Type t2 = parms.Object2 != null ? parms.Object2.GetType() : null;
|
||||
|
||||
if (ExcludeLogic.ShouldExcludeType(parms.Config, t1, t2))
|
||||
return true;
|
||||
|
||||
BaseTypeComparer customComparer = parms.Config.CustomComparers.FirstOrDefault(o => o.IsTypeMatch(t1, t2));
|
||||
|
||||
if (customComparer != null)
|
||||
{
|
||||
customComparer.CompareType(parms);
|
||||
}
|
||||
else if (parms.CustomPropertyComparer != null)
|
||||
{
|
||||
parms.CustomPropertyComparer.CompareType(parms);
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseTypeComparer typeComparer = TypeComparers.FirstOrDefault(o => o.IsTypeMatch(t1, t2));
|
||||
|
||||
if (typeComparer != null)
|
||||
{
|
||||
if (parms.Config.IgnoreObjectTypes || !TypesDifferent(parms, t1, t2))
|
||||
{
|
||||
typeComparer.CompareType(parms);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (EitherObjectIsNull(parms)) return false;
|
||||
|
||||
if (!parms.Config.IgnoreObjectTypes && t1 != null)
|
||||
throw new NotSupportedException("Cannot compare object of type " + t1.Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
if (!parms.Config.IgnoreObjectDisposedException)
|
||||
throw;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return parms.Result.AreEqual;
|
||||
}
|
||||
|
||||
private bool TypesDifferent(CompareParms parms, Type t1, Type t2)
|
||||
{
|
||||
//Objects must be the same type and not be null
|
||||
if (!parms.Config.IgnoreObjectTypes
|
||||
&& parms.Object1 != null
|
||||
&& parms.Object2 != null
|
||||
&& t1 != t2)
|
||||
{
|
||||
//Only care if they are in the same inheritance hierarchy or decleared as the same interface.
|
||||
if (parms.Config.IgnoreConcreteTypes
|
||||
&& (parms.Object1DeclaredType != null
|
||||
&& parms.Object2DeclaredType != null
|
||||
&& parms.Object1DeclaredType == parms.Object2DeclaredType
|
||||
|| (t1.IsAssignableFrom(t2) || t2.IsAssignableFrom(t1))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = t1.FullName,
|
||||
Object2Value = t2.FullName,
|
||||
ChildPropertyName = "GetType()",
|
||||
MessagePrefix = "Different Types",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool EitherObjectIsNull(CompareParms parms)
|
||||
{
|
||||
//Check if one of them is null
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
{
|
||||
AddDifference(parms);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
77
FSI.Lib/FSI.Lib/CompareNetObjects/RootComparerFactory.cs
Normal file
77
FSI.Lib/FSI.Lib/CompareNetObjects/RootComparerFactory.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using FSI.Lib.CompareNetObjects.TypeComparers;
|
||||
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory to create a root comparer
|
||||
/// </summary>
|
||||
public static class RootComparerFactory
|
||||
{
|
||||
#region Class Variables
|
||||
private static readonly object _locker = new object();
|
||||
private static RootComparer _rootComparer;
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the current root comparer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static RootComparer GetRootComparer()
|
||||
{
|
||||
lock(_locker)
|
||||
if (_rootComparer == null)
|
||||
_rootComparer= BuildRootComparer();
|
||||
|
||||
return _rootComparer;
|
||||
}
|
||||
|
||||
private static RootComparer BuildRootComparer()
|
||||
{
|
||||
_rootComparer = new RootComparer();
|
||||
|
||||
_rootComparer.TypeComparers = new List<BaseTypeComparer>();
|
||||
|
||||
_rootComparer.TypeComparers.Add(new StringComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DateComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DecimalComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DoubleComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new PointerComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new SimpleTypeComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new ReadOnlyCollectionComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new ListComparer(_rootComparer));
|
||||
|
||||
#if NETFULL || NETSTANDARD2_0 || NETSTANDARD2_1
|
||||
_rootComparer.TypeComparers.Add(new IpEndPointComparer(_rootComparer));
|
||||
#endif
|
||||
|
||||
_rootComparer.TypeComparers.Add(new RuntimeTypeComparer(_rootComparer));
|
||||
|
||||
#if !NETSTANDARD
|
||||
_rootComparer.TypeComparers.Add(new FontComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DatasetComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DataTableComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DataRowComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DataColumnComparer(_rootComparer));
|
||||
#endif
|
||||
_rootComparer.TypeComparers.Add(new EnumerableComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new ByteArrayComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DictionaryComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new HashSetComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new CollectionComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new UriComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new StringBuilderComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new ClassComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new DateTimeOffSetComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new TimespanComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new StructComparer(_rootComparer));
|
||||
_rootComparer.TypeComparers.Add(new ImmutableArrayComparer(_rootComparer));
|
||||
|
||||
return _rootComparer;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
78
FSI.Lib/FSI.Lib/CompareNetObjects/StringHelper.cs
Normal file
78
FSI.Lib/FSI.Lib/CompareNetObjects/StringHelper.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods for manipulating strings
|
||||
/// </summary>
|
||||
public static class StringHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Insert spaces into a string
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// OrderDetails = Order Details
|
||||
/// 10Net30 = 10 Net 30
|
||||
/// FTPHost = FTP Host
|
||||
/// </example>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string InsertSpaces(string input)
|
||||
{
|
||||
bool isLastUpper = true;
|
||||
|
||||
if (String.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder(input.Length + (input.Length / 2));
|
||||
|
||||
//Replace underline with spaces
|
||||
input = input.Replace("_", " ");
|
||||
input = input.Replace(" ", " ");
|
||||
|
||||
//Trim any spaces
|
||||
input = input.Trim();
|
||||
|
||||
if (String.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
char[] chars = input.ToCharArray();
|
||||
|
||||
sb.Append(chars[0]);
|
||||
|
||||
for (int i = 1; i < chars.Length; i++)
|
||||
{
|
||||
bool isUpperOrNumberOrDash = (chars[i] >= 'A' && chars[i] <= 'Z') || (chars[i] >= '0' && chars[i] <= '9') || chars[i] == '-';
|
||||
bool isNextCharLower = i < chars.Length - 1 && (chars[i + 1] >= 'a' && chars[i + 1] <= 'z');
|
||||
bool isSpace = chars[i] == ' ';
|
||||
bool isLower = (chars[i] >= 'a' && chars[i] <= 'z');
|
||||
|
||||
//There was a space already added
|
||||
if (isSpace)
|
||||
{
|
||||
}
|
||||
//Look for upper case characters that have lower case characters before
|
||||
//Or upper case characters where the next character is lower
|
||||
else if ((isUpperOrNumberOrDash && isLastUpper == false)
|
||||
|| (isUpperOrNumberOrDash && isNextCharLower))
|
||||
{
|
||||
sb.Append(' ');
|
||||
isLastUpper = true;
|
||||
}
|
||||
else if (isLower)
|
||||
{
|
||||
isLastUpper = false;
|
||||
}
|
||||
|
||||
sb.Append(chars[i]);
|
||||
|
||||
}
|
||||
|
||||
//Replace double spaces
|
||||
sb.Replace(" ", " ");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Extensions for Type to provide backward compatibility between latest and older .net Framework APIs.
|
||||
/// </summary>
|
||||
public static class TypeBackwardsCompatibilityExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Function to provide compilation compatibility between older code and newer style.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>The type.</returns>
|
||||
public static Type GetTypeInfo(this Type type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
179
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/BaseComparer.cs
Normal file
179
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/BaseComparer.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Common functionality for all Comparers
|
||||
/// </summary>
|
||||
public class BaseComparer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Add a breadcrumb to an existing breadcrumb
|
||||
/// </summary>
|
||||
/// <param name="config">Comparison configuration</param>
|
||||
/// <param name="existing">The existing breadcrumb</param>
|
||||
/// <param name="name">The field or property name</param>
|
||||
/// <returns>The new breadcrumb</returns>
|
||||
protected string AddBreadCrumb(ComparisonConfig config, string existing, string name)
|
||||
{
|
||||
return AddBreadCrumb(config, existing, name, string.Empty, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a breadcrumb to an existing breadcrumb
|
||||
/// </summary>
|
||||
/// <param name="config">The comparison configuration</param>
|
||||
/// <param name="existing">The existing breadcrumb</param>
|
||||
/// <param name="name">The property or field name</param>
|
||||
/// <param name="extra">Extra information to output after the name</param>
|
||||
/// <param name="index">The index for an array, list, or row</param>
|
||||
/// <returns>The new breadcrumb</returns>
|
||||
protected string AddBreadCrumb(ComparisonConfig config, string existing, string name, string extra, int index)
|
||||
{
|
||||
return AddBreadCrumb(config, existing, name, extra, index >= 0 ? index.ToString(CultureInfo.InvariantCulture) : null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a breadcrumb to an existing breadcrumb
|
||||
/// </summary>
|
||||
/// <remarks>This originally used a string builder which had lower performance</remarks>
|
||||
/// <param name="config">Comparison configuration</param>
|
||||
/// <param name="existing">The existing breadcrumb</param>
|
||||
/// <param name="name">The field or property name</param>
|
||||
/// <param name="extra">Extra information to append after the name</param>
|
||||
/// <param name="index">The index if it is an array, list, row etc.</param>
|
||||
/// <returns>The new breadcrumb</returns>
|
||||
protected string AddBreadCrumb(ComparisonConfig config, string existing, string name, string extra, string index)
|
||||
{
|
||||
if (config == null)
|
||||
throw new ArgumentNullException("config");
|
||||
|
||||
bool useIndex = !String.IsNullOrEmpty(index);
|
||||
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
bool useName = name.Length > 0;
|
||||
string stringResult = existing;
|
||||
|
||||
if (useName)
|
||||
{
|
||||
//Do not put a period at the beginning
|
||||
if (stringResult.Length > 0)
|
||||
{
|
||||
stringResult += ".";
|
||||
}
|
||||
|
||||
stringResult += name;
|
||||
}
|
||||
|
||||
stringResult += extra;
|
||||
|
||||
if (useIndex)
|
||||
{
|
||||
// ReSharper disable RedundantAssignment
|
||||
int result = -1;
|
||||
// ReSharper restore RedundantAssignment
|
||||
stringResult += String.Format(Int32.TryParse(index, out result) ? "[{0}]" : "[\"{0}\"]", index);
|
||||
}
|
||||
|
||||
if (config.ShowBreadcrumb)
|
||||
{
|
||||
#if (DEBUG) || NETSTANDARD
|
||||
Console.WriteLine(stringResult);
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD && !DEBUG
|
||||
Trace.WriteLine(stringResult);
|
||||
#endif
|
||||
}
|
||||
|
||||
return stringResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a difference for the current parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
protected void AddDifference(CompareParms parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException("parameters");
|
||||
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parameters.ParentObject1,
|
||||
ParentObject2 = parameters.ParentObject2,
|
||||
PropertyName = parameters.BreadCrumb,
|
||||
Object1Value = NiceString(parameters.Object1),
|
||||
Object2Value = NiceString(parameters.Object2),
|
||||
Object1 = parameters.Object1,
|
||||
Object2 = parameters.Object2
|
||||
};
|
||||
|
||||
AddDifference(parameters.Result,difference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a difference to the result
|
||||
/// </summary>
|
||||
/// <param name="difference">The difference to add to the result</param>
|
||||
/// <param name="result">The comparison result</param>
|
||||
protected void AddDifference(ComparisonResult result, Difference difference)
|
||||
{
|
||||
if (result == null)
|
||||
throw new ArgumentNullException("result");
|
||||
|
||||
if (difference == null)
|
||||
throw new ArgumentNullException("difference");
|
||||
|
||||
difference.ActualName = result.Config.ActualName;
|
||||
difference.ExpectedName = result.Config.ExpectedName;
|
||||
|
||||
difference.Object1TypeName = difference.Object1 != null && difference.Object1 != null
|
||||
? difference.Object1.GetType().Name : "null";
|
||||
|
||||
difference.Object2TypeName = difference.Object2 != null && difference.Object2 != null
|
||||
? difference.Object2.GetType().Name : "null";
|
||||
|
||||
result.Differences.Add(difference);
|
||||
result.Config.DifferenceCallback(difference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert an object to a nicely formatted string
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected string NiceString(object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value == null)
|
||||
return "(null)";
|
||||
|
||||
#if !NETSTANDARD
|
||||
if (value == DBNull.Value)
|
||||
return "System.DBNull.Value";
|
||||
#endif
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Common functionality for all Type Comparers
|
||||
/// </summary>
|
||||
public abstract class BaseTypeComparer : BaseComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// A reference to the root comparer as newed up by the RootComparerFactory
|
||||
/// </summary>
|
||||
public RootComparer RootComparer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor that references the root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
protected BaseTypeComparer(RootComparer rootComparer)
|
||||
{
|
||||
RootComparer = rootComparer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If true the type comparer will handle the comparison for the type
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool IsTypeMatch(Type type1, Type type2);
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two objects
|
||||
/// </summary>
|
||||
public abstract void CompareType(CompareParms parms);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two byte arrays
|
||||
/// </summary>
|
||||
public class ByteArrayComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Protected constructor that references the root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer">The root comparer.</param>
|
||||
public ByteArrayComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true the type comparer will handle the comparison for the type
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns><c>true</c> if it is a byte array; otherwise, <c>false</c>.</returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsByteArray(type1)
|
||||
&& TypeHelper.IsByteArray(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two byte array objects
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms == null || parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
if (ListsHaveDifferentCounts(parms))
|
||||
return;
|
||||
|
||||
CompareItems(parms);
|
||||
}
|
||||
|
||||
private bool ListsHaveDifferentCounts(CompareParms parms)
|
||||
{
|
||||
IList ilist1 = parms.Object1 as IList;
|
||||
IList ilist2 = parms.Object2 as IList;
|
||||
|
||||
if (ilist1 == null)
|
||||
throw new ArgumentException("parms.Object1");
|
||||
|
||||
if (ilist2 == null)
|
||||
throw new ArgumentException("parms.Object2");
|
||||
|
||||
//Objects must be the same length
|
||||
if (ilist1.Count != ilist2.Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = ilist1.Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = ilist2.Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = ilist1,
|
||||
Object2 = ilist2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CompareItems(CompareParms parms)
|
||||
{
|
||||
int count = 0;
|
||||
int differenceCount = 0;
|
||||
IEnumerator enumerator1 = ((IList) parms.Object1).GetEnumerator();
|
||||
IEnumerator enumerator2 = ((IList) parms.Object2).GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
byte? b1 = enumerator1.Current as byte?;
|
||||
byte? b2 = enumerator2.Current as byte?;
|
||||
|
||||
if (b1 != b2)
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, count);
|
||||
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
PropertyName = currentBreadCrumb,
|
||||
Object1Value = NiceString(b1),
|
||||
Object2Value = NiceString(b2),
|
||||
Object1 = b1,
|
||||
Object2 = b2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
differenceCount++;
|
||||
|
||||
if (differenceCount >= parms.Result.Config.MaxByteArrayDifferences)
|
||||
return;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two objects of type class
|
||||
/// </summary>
|
||||
public class ClassComparer : BaseTypeComparer
|
||||
{
|
||||
private readonly PropertyComparer _propertyComparer;
|
||||
private readonly FieldComparer _fieldComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the class comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer">The root comparer instantiated by the RootComparerFactory</param>
|
||||
public ClassComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
_propertyComparer = new PropertyComparer(rootComparer);
|
||||
_fieldComparer = new FieldComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the both objects are a class
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return (TypeHelper.IsClass(type1) && TypeHelper.IsClass(type2))
|
||||
|| (TypeHelper.IsInterface(type1) && TypeHelper.IsInterface(type2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two classes
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
//Custom classes that implement IEnumerable may have the same hash code
|
||||
//Ignore objects with the same hash code
|
||||
if (!(parms.Object1 is IEnumerable)
|
||||
&& ReferenceEquals(parms.Object1, parms.Object2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
Type t2 = parms.Object2.GetType();
|
||||
|
||||
//Check if the class type should be excluded based on the configuration
|
||||
if (ExcludeLogic.ShouldExcludeClass(parms.Config, t1, t2))
|
||||
return;
|
||||
|
||||
parms.Object1Type = t1;
|
||||
parms.Object2Type = t2;
|
||||
|
||||
//Compare the properties
|
||||
if (parms.Config.CompareProperties)
|
||||
_propertyComparer.PerformCompareProperties(parms);
|
||||
|
||||
//Compare the fields
|
||||
if (parms.Config.CompareFields)
|
||||
_fieldComparer.PerformCompareFields(parms);
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two collections of different types.
|
||||
/// </summary>
|
||||
public class CollectionComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// The main constructor.
|
||||
/// </summary>
|
||||
public CollectionComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are collections.
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return typeof(ICollection).IsAssignableFrom(type1) && typeof(ICollection).IsAssignableFrom(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two collections.
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
Type t2 = parms.Object2.GetType();
|
||||
|
||||
//Check if the class type should be excluded based on the configuration
|
||||
if (ExcludeLogic.ShouldExcludeClass(parms.Config, t1, t2))
|
||||
return;
|
||||
|
||||
parms.Object1Type = t1;
|
||||
parms.Object2Type = t2;
|
||||
|
||||
bool countsDifferent = CollectionsDifferentCount(parms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
if (parms.Config.IgnoreCollectionOrder)
|
||||
{
|
||||
IgnoreOrderLogic logic = new IgnoreOrderLogic(RootComparer);
|
||||
logic.CompareEnumeratorIgnoreOrder(parms, countsDifferent);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareItems(parms);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareItems(CompareParms parms)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
IEnumerator enumerator1 = ((ICollection)parms.Object1).GetEnumerator();
|
||||
IEnumerator enumerator2 = ((ICollection)parms.Object2).GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, count);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Current,
|
||||
Object2 = enumerator2.Current,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CollectionsDifferentCount(CompareParms parms)
|
||||
{
|
||||
//Get count by reflection since we can't cast it to HashSet<>
|
||||
int count1 = ((ICollection)parms.Object1).Count;
|
||||
int count2 = ((ICollection)parms.Object2).Count;
|
||||
|
||||
//Objects must be the same length
|
||||
if (count1 != count2)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = count1.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = count2.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two generic objects
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">The type of the first object</typeparam>
|
||||
/// <typeparam name="T2">The type of the second object</typeparam>
|
||||
public class CustomComparer<T1, T2> : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Method to evaluate the results, return true if two objects are equal
|
||||
/// </summary>
|
||||
public Func<T1, T2, bool> Compare = (t1, t2) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public CustomComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a default root comparer
|
||||
/// </summary>
|
||||
public CustomComparer() : this(RootComparerFactory.GetRootComparer())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a the predication with a default root comparer
|
||||
/// </summary>
|
||||
/// <param name="compare">A function to determine if two objects are equal</param>
|
||||
public CustomComparer(Func<T1, T2, bool> compare) : this(RootComparerFactory.GetRootComparer(), compare)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a the predication with a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer">The root comparer</param>
|
||||
/// <param name="compare">Method to determine if two objects are equal</param>
|
||||
public CustomComparer(RootComparer rootComparer, Func<T1, T2, bool> compare) : this(rootComparer)
|
||||
{
|
||||
Compare = compare;
|
||||
}
|
||||
/// <summary>
|
||||
/// Compare two objects
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (!Compare((T1)parms.Object1, (T2)parms.Object2))
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns true if both objects match their types
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return type1 == typeof(T1) && type2 == typeof(T2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#if !NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare a data column
|
||||
/// </summary>
|
||||
public class DataColumnComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DataColumnComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types compared are a DataColumn
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDataColumn(type1) && TypeHelper.IsDataColumn(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare a Data Column
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
DataColumn col1 = (DataColumn)parms.Object1;
|
||||
DataColumn col2 = (DataColumn)parms.Object2;
|
||||
|
||||
CompareProp(parms, col1, col2, col1.AllowDBNull, col2.AllowDBNull, "AllowDBNull");
|
||||
CompareProp(parms, col1, col2, col1.AutoIncrement, col2.AutoIncrement, "AutoIncrement");
|
||||
CompareProp(parms, col1, col2, col1.AutoIncrementSeed, col2.AutoIncrementSeed, "AutoIncrementSeed");
|
||||
CompareProp(parms, col1, col2, col1.AutoIncrementStep, col2.AutoIncrementStep, "AutoIncrementStep");
|
||||
CompareProp(parms, col1, col2, col1.Caption, col2.Caption, "Caption");
|
||||
CompareProp(parms, col1, col2, col1.ColumnName, col2.ColumnName, "ColumnName");
|
||||
CompareProp(parms, col1, col2, col1.DataType, col2.DataType, "DataType");
|
||||
CompareProp(parms, col1, col2, col1.DefaultValue, col2.DefaultValue, "DefaultValue");
|
||||
CompareProp(parms, col1, col2, col1.Expression, col2.Expression, "Expression");
|
||||
CompareProp(parms, col1, col2, col1.MaxLength, col2.MaxLength, "MaxLength");
|
||||
CompareProp(parms, col1, col2, col1.Namespace, col2.Namespace, "Namespace");
|
||||
CompareProp(parms, col1, col2, col1.Ordinal, col2.Ordinal, "Ordinal");
|
||||
CompareProp(parms, col1, col2, col1.Prefix, col2.Prefix, "Prefix");
|
||||
CompareProp(parms, col1, col2, col1.ReadOnly, col2.ReadOnly, "ReadOnly");
|
||||
CompareProp(parms, col1, col2, col1.Unique, col2.Unique, "Unique");
|
||||
}
|
||||
|
||||
private void CompareProp<T>(CompareParms parms, DataColumn col1, DataColumn col2, T prop1, T prop2, string propName)
|
||||
{
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, propName);
|
||||
|
||||
CompareParms childParms = new CompareParms();
|
||||
childParms.Result = parms.Result;
|
||||
childParms.Config = parms.Config;
|
||||
childParms.BreadCrumb = currentBreadCrumb;
|
||||
childParms.ParentObject1 = col1;
|
||||
childParms.ParentObject2 = col2;
|
||||
childParms.Object1 = prop1;
|
||||
childParms.Object2 = prop2;
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,135 @@
|
||||
#if !NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare all columns in a data row
|
||||
/// </summary>
|
||||
public class DataRowComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DataRowComparer(RootComparer rootComparer)
|
||||
: base(rootComparer)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is a DataRow
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDataRow(type1) && TypeHelper.IsDataRow(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two data rows
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
DataRow dataRow1 = parms.Object1 as DataRow;
|
||||
DataRow dataRow2 = parms.Object2 as DataRow;
|
||||
|
||||
//This should never happen, null check happens one level up
|
||||
if (dataRow1 == null || dataRow2 == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Math.Min(dataRow2.Table.Columns.Count, dataRow1.Table.Columns.Count); i++)
|
||||
{
|
||||
//Only compare specific column names
|
||||
if (parms.Config.MembersToIncludeSet.Count > 0 && !parms.Config.MembersToIncludeSet.Contains(dataRow1.Table.Columns[i].ColumnName))
|
||||
continue;
|
||||
|
||||
//If we should ignore it, skip it
|
||||
if (parms.Config.MembersToIncludeSet.Count == 0 && parms.Config.MembersToIgnoreSet.Contains(dataRow1.Table.Columns[i].ColumnName))
|
||||
continue;
|
||||
|
||||
//If we should ignore read only, skip it
|
||||
if (!parms.Config.CompareReadOnly && dataRow1.Table.Columns[i].ReadOnly)
|
||||
continue;
|
||||
|
||||
//Both are null
|
||||
if (dataRow1.IsNull(i) && dataRow2.IsNull(i))
|
||||
continue;
|
||||
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, dataRow1.Table.Columns[i].ColumnName);
|
||||
|
||||
//Check if one of them is null
|
||||
if (dataRow1.IsNull(i))
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = currentBreadCrumb,
|
||||
Object1Value = "(null)",
|
||||
Object2Value = NiceString(dataRow2[i]),
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataRow2.IsNull(i))
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = currentBreadCrumb,
|
||||
Object1Value = NiceString(dataRow1[i]),
|
||||
Object2Value = "(null)",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if one of them is deleted
|
||||
if (dataRow1.RowState == DataRowState.Deleted ^ dataRow2.RowState == DataRowState.Deleted)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = currentBreadCrumb,
|
||||
Object1Value = dataRow1.RowState.ToString(),
|
||||
Object2Value = dataRow2.RowState.ToString(),
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
return;
|
||||
}
|
||||
|
||||
CompareParms childParms = new CompareParms();
|
||||
childParms.Result = parms.Result;
|
||||
childParms.Config = parms.Config;
|
||||
childParms.ParentObject1 = parms.Object1;
|
||||
childParms.ParentObject2 = parms.Object2;
|
||||
childParms.Object1 = dataRow1[i];
|
||||
childParms.Object2 = dataRow2[i];
|
||||
childParms.BreadCrumb = currentBreadCrumb;
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
#if !NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare all rows in a data table
|
||||
/// </summary>
|
||||
public class DataTableComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DataTableComparer(RootComparer rootComparer)
|
||||
: base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are of type DataTable
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDataTable(type1) && TypeHelper.IsDataTable(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two datatables
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
DataTable dataTable1 = parms.Object1 as DataTable;
|
||||
DataTable dataTable2 = parms.Object2 as DataTable;
|
||||
|
||||
//This should never happen, null check happens one level up
|
||||
if (dataTable1 == null || dataTable2 == null)
|
||||
return;
|
||||
|
||||
//Only compare specific table names
|
||||
if (parms.Config.MembersToInclude.Count > 0 && !parms.Config.MembersToInclude.Contains(dataTable1.TableName))
|
||||
return;
|
||||
|
||||
//If we should ignore it, skip it
|
||||
if (parms.Config.MembersToInclude.Count == 0 && parms.Config.MembersToIgnore.Contains(dataTable1.TableName))
|
||||
return;
|
||||
|
||||
//There must be the same amount of rows in the datatable
|
||||
if (dataTable1.Rows.Count != dataTable2.Rows.Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = dataTable1.Rows.Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = dataTable2.Rows.Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Rows.Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ColumnsDifferent(parms)) return;
|
||||
|
||||
CompareEachRow(parms);
|
||||
}
|
||||
|
||||
private bool ColumnsDifferent(CompareParms parms)
|
||||
{
|
||||
DataTable dataTable1 = parms.Object1 as DataTable;
|
||||
DataTable dataTable2 = parms.Object2 as DataTable;
|
||||
|
||||
if (dataTable1 == null)
|
||||
throw new ArgumentException("parms.Object1");
|
||||
|
||||
if (dataTable2 == null)
|
||||
throw new ArgumentException("parms.Object2");
|
||||
|
||||
if (dataTable1.Columns.Count != dataTable2.Columns.Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = dataTable1.Columns.Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = dataTable2.Columns.Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Columns.Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var i in Enumerable.Range(0, dataTable1.Columns.Count))
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "Columns", string.Empty, i);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = dataTable1.Columns[i],
|
||||
Object2 = dataTable2.Columns[i],
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CompareEachRow(CompareParms parms)
|
||||
{
|
||||
DataTable dataTable1 = parms.Object1 as DataTable;
|
||||
DataTable dataTable2 = parms.Object2 as DataTable;
|
||||
|
||||
if (dataTable1 == null)
|
||||
throw new ArgumentException("parms.Object1");
|
||||
|
||||
if (dataTable2 == null)
|
||||
throw new ArgumentException("parms.Object2");
|
||||
|
||||
for (int i = 0; i < Math.Min(dataTable1.Rows.Count, dataTable2.Rows.Count); i++)
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "Rows", string.Empty, i);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = dataTable1.Rows[i],
|
||||
Object2 = dataTable2.Rows[i],
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
#if !NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare all tables and all rows in all tables
|
||||
/// </summary>
|
||||
public class DatasetComparer : BaseTypeComparer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DatasetComparer(RootComparer rootComparer)
|
||||
: base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are data sets
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDataset(type1) && TypeHelper.IsDataset(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two data sets
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
DataSet dataSet1 = parms.Object1 as DataSet;
|
||||
DataSet dataSet2 = parms.Object2 as DataSet;
|
||||
|
||||
//This should never happen, null check happens one level up
|
||||
if (dataSet1 == null || dataSet2 == null)
|
||||
return;
|
||||
|
||||
if (TableCountsDifferent(parms, dataSet2, dataSet1)) return;
|
||||
|
||||
CompareEachTable(parms, dataSet1, dataSet2);
|
||||
}
|
||||
|
||||
private bool TableCountsDifferent(CompareParms parms, DataSet dataSet2, DataSet dataSet1)
|
||||
{
|
||||
if (dataSet1.Tables.Count != dataSet2.Tables.Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = dataSet1.Tables.Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = dataSet2.Tables.Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Tables.Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CompareEachTable(CompareParms parms, DataSet dataSet1, DataSet dataSet2)
|
||||
{
|
||||
for (int i = 0; i < Math.Min(dataSet1.Tables.Count, dataSet2.Tables.Count); i++)
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "Tables", string.Empty,
|
||||
dataSet1.Tables[i].TableName);
|
||||
|
||||
CompareParms childParms = new CompareParms();
|
||||
childParms.Result = parms.Result;
|
||||
childParms.Config = parms.Config;
|
||||
childParms.BreadCrumb = currentBreadCrumb;
|
||||
childParms.ParentObject1 = dataSet1;
|
||||
childParms.ParentObject2 = dataSet2;
|
||||
childParms.Object1 = dataSet1.Tables[i];
|
||||
childParms.Object2 = dataSet2.Tables[i];
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare dates with the option to ignore based on milliseconds
|
||||
/// </summary>
|
||||
public class DateComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DateComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are DateTime
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDateTime(type1) && TypeHelper.IsDateTime(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two DateTime variables
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
DateTime date1 = (DateTime) parms.Object1;
|
||||
DateTime date2 = (DateTime) parms.Object2;
|
||||
|
||||
if (date1.Kind != date2.Kind)
|
||||
{
|
||||
date1 = date1.ToUniversalTime();
|
||||
date2 = date2.ToUniversalTime();
|
||||
}
|
||||
|
||||
if (Math.Abs(date1.Subtract(date2).TotalMilliseconds) > parms.Config.MaxMillisecondsDateDifference)
|
||||
AddDifference(parms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare DateTimeOffsets with the ability to ignore millisecond differences
|
||||
/// </summary>
|
||||
public class DateTimeOffSetComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DateTimeOffSetComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDateTimeOffset(type1) && TypeHelper.IsDateTimeOffset(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
DateTimeOffset date1 = (DateTimeOffset)parms.Object1;
|
||||
DateTimeOffset date2 = (DateTimeOffset)parms.Object2;
|
||||
|
||||
if (parms.Config.IgnoreDateTimeOffsetTimezones)
|
||||
{
|
||||
date1 = date1.ToUniversalTime();
|
||||
date2 = date2.ToUniversalTime();
|
||||
}
|
||||
|
||||
if (parms.Config.CompareDateTimeOffsetWithOffsets
|
||||
&& Math.Abs((date1 - date2).TotalMilliseconds) > parms.Config.MaxMillisecondsDateDifference)
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
else if (!parms.Config.CompareDateTimeOffsetWithOffsets)
|
||||
{
|
||||
DateTime date1NoOffset = new DateTime(date1.Year, date1.Month, date1.Day, date1.Hour, date1.Minute, date1.Second);
|
||||
date1NoOffset = date1NoOffset.AddMilliseconds(date1.Millisecond);
|
||||
|
||||
DateTime date2NoOffset = new DateTime(date2.Year, date2.Month, date2.Day, date2.Hour, date2.Minute, date2.Second);
|
||||
date2NoOffset = date2NoOffset.AddMilliseconds(date2.Millisecond);
|
||||
|
||||
if (Math.Abs(date1NoOffset.Subtract(date2NoOffset).TotalMilliseconds) > parms.Config.MaxMillisecondsDateDifference)
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare Decimal values with the ability to specify the precision
|
||||
/// </summary>
|
||||
public class DecimalComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DecimalComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are double
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDecimal(type1) && TypeHelper.IsDecimal(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two decimals
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
decimal decimal1 = (decimal)parms.Object1;
|
||||
decimal decimal2 = (decimal)parms.Object2;
|
||||
|
||||
if (Math.Abs(decimal1 - decimal2) > parms.Config.DecimalPrecision)
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two dictionaries
|
||||
/// </summary>
|
||||
public class DictionaryComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DictionaryComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are dictionaries
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return ((TypeHelper.IsIDictionary(type1) || type1 == null) &&
|
||||
(TypeHelper.IsIDictionary(type2) || type2 == null) &&
|
||||
!(type1 == null && type2 == null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two dictionaries
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
//Objects must be the same length
|
||||
bool countsDifferent = DictionaryCountsDifferent(parms);
|
||||
|
||||
if (countsDifferent && parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
bool shouldCompareByKeys = ShouldCompareByKeys(parms);
|
||||
|
||||
if (shouldCompareByKeys)
|
||||
{
|
||||
CompareByKeys(parms);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parms.Config.IgnoreCollectionOrder)
|
||||
{
|
||||
IgnoreOrderLogic logic = new IgnoreOrderLogic(RootComparer);
|
||||
logic.CompareEnumeratorIgnoreOrder(parms, countsDifferent);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareByEnumerator(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is to handle funky situation of having a complex object as a key
|
||||
/// (In this case a dictionary as a key)
|
||||
/// https://github.com/GregFinzer/Compare-Net-Objects/issues/222
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
/// <returns></returns>
|
||||
private static bool ShouldCompareByKeys(CompareParms parms)
|
||||
{
|
||||
bool shouldCompareByKeys = true;
|
||||
|
||||
if (parms.Object1 != null)
|
||||
{
|
||||
var dict1 = ((IDictionary) parms.Object1);
|
||||
|
||||
if (dict1.Keys.Count > 0)
|
||||
{
|
||||
var enumerator1 = ((IDictionary) parms.Object1).GetEnumerator();
|
||||
enumerator1.MoveNext();
|
||||
shouldCompareByKeys =
|
||||
enumerator1.Key != null && TypeHelper.IsSimpleType(enumerator1.Key.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
return shouldCompareByKeys;
|
||||
}
|
||||
|
||||
|
||||
private void CompareByKeys(CompareParms parms)
|
||||
{
|
||||
var dict1 = ((IDictionary)parms.Object1);
|
||||
var dict2 = ((IDictionary)parms.Object2);
|
||||
|
||||
if (dict1 != null)
|
||||
{
|
||||
foreach (var key in dict1.Keys)
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "[" +key.ToString()+ "].Value");
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = dict1[key],
|
||||
Object2 = (dict2 != null) && dict2.Contains(key) ? dict2[key] : null,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dict2 != null)
|
||||
{
|
||||
foreach (var key in dict2.Keys)
|
||||
{
|
||||
if (dict1 != null && dict1.Contains(key))
|
||||
continue;
|
||||
|
||||
var currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb,
|
||||
"[" + key.ToString() + "].Value");
|
||||
|
||||
var childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = null,
|
||||
Object2 = dict2[key],
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareByEnumerator(CompareParms parms)
|
||||
{
|
||||
var enumerator1 = ((IDictionary)parms.Object1).GetEnumerator();
|
||||
var enumerator2 = ((IDictionary)parms.Object2).GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "Key");
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Key,
|
||||
Object2 = enumerator2.Key,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, "Value");
|
||||
|
||||
childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Value,
|
||||
Object2 = enumerator2.Value,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private bool DictionaryCountsDifferent(CompareParms parms)
|
||||
{
|
||||
IDictionary iDict1 = parms.Object1 as IDictionary;
|
||||
IDictionary iDict2 = parms.Object2 as IDictionary;
|
||||
|
||||
int iDict1Count = (iDict1 == null) ? 0 : iDict1.Count;
|
||||
int iDict2Count = (iDict2 == null) ? 0 : iDict2.Count;
|
||||
|
||||
if (iDict1Count == iDict2Count)
|
||||
return false;
|
||||
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = iDict1Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = iDict2Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = iDict1,
|
||||
Object2 = iDict2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare Double values with the ability to specify the precision
|
||||
/// </summary>
|
||||
public class DoubleComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public DoubleComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are double
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsDouble(type1) && TypeHelper.IsDouble(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two doubles
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
Double double1 = (Double)parms.Object1;
|
||||
Double double2 = (Double)parms.Object2;
|
||||
|
||||
double diff = double1 - double2;
|
||||
if ((Math.Abs(diff) > parms.Config.DoublePrecision)
|
||||
|| (Double.IsNaN(diff) && (!Double.IsNaN(double1) || !Double.IsNaN(double2))))
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare to enum values
|
||||
/// </summary>
|
||||
public class EnumComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor with a default root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public EnumComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are of type enum
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsEnum(type1) && TypeHelper.IsEnum(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two enums
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (parms.Object1.ToString() != parms.Object2.ToString())
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two LINQ enumerators
|
||||
/// </summary>
|
||||
public class EnumerableComparer :BaseTypeComparer
|
||||
{
|
||||
private readonly ListComparer _compareIList;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public EnumerableComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
_compareIList = new ListComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if either object is of type LINQ Enumerator
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
if (type1 == null || type2 == null)
|
||||
return false;
|
||||
|
||||
return TypeHelper.IsEnumerable(type1) || TypeHelper.IsEnumerable(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two objects that implement LINQ Enumerator
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
var oldObject1 = parms.Object1;
|
||||
var oldObject2 = parms.Object2;
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
Type t2 = parms.Object2.GetType();
|
||||
|
||||
var l1 = TypeHelper.IsEnumerable(t1) ? ConvertEnumerableToList(parms.Object1) : parms.Object1;
|
||||
var l2 = TypeHelper.IsEnumerable(t2) ? ConvertEnumerableToList(parms.Object2) : parms.Object2;
|
||||
|
||||
parms.Object1 = l1;
|
||||
parms.Object2 = l2;
|
||||
|
||||
_compareIList.CompareType(parms);
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(oldObject1);
|
||||
parms.Result.RemoveParent(oldObject2);
|
||||
}
|
||||
}
|
||||
|
||||
private object ConvertEnumerableToList(object source)
|
||||
{
|
||||
var type = source.GetType();
|
||||
|
||||
if (type.IsArray)
|
||||
return source;
|
||||
|
||||
Type enumerableGenArg = null;
|
||||
foreach (var inter in type.GetInterfaces())
|
||||
{
|
||||
if (inter.GetTypeInfo().IsGenericType && inter.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
enumerableGenArg = inter.GetGenericArguments()[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (enumerableGenArg == null)
|
||||
{
|
||||
#if NETSTANDARD
|
||||
throw new Exception("Cannot get IEnumerable definition");
|
||||
#else
|
||||
throw new ApplicationException("Cannot get IEnumerable definition");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
MethodInfo toList = typeof(Enumerable).GetMethod("ToList");
|
||||
MethodInfo constructedToList = toList.MakeGenericMethod(enumerableGenArg);
|
||||
object resultList = constructedToList.Invoke(null, new[] { source });
|
||||
|
||||
return resultList;
|
||||
}
|
||||
}
|
||||
}
|
||||
131
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/FieldComparer.cs
Normal file
131
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/FieldComparer.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare all the fields of a class or struct (Note: inherits from BaseComparer instead of TypeComparer).
|
||||
/// </summary>
|
||||
public class FieldComparer : BaseComparer
|
||||
{
|
||||
private readonly RootComparer _rootComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public FieldComparer(RootComparer rootComparer)
|
||||
{
|
||||
_rootComparer = rootComparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the fields of a class
|
||||
/// </summary>
|
||||
public void PerformCompareFields(CompareParms parms)
|
||||
{
|
||||
var currentFields = GetCurrentFields(parms);
|
||||
|
||||
foreach (FieldInfo item in currentFields)
|
||||
{
|
||||
CompareField(parms, item);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareField(CompareParms parms, FieldInfo item)
|
||||
{
|
||||
//Skip if this is a shallow compare
|
||||
if (!parms.Config.CompareChildren && TypeHelper.CanHaveChildren(item.FieldType))
|
||||
return;
|
||||
|
||||
//Skip if it should be excluded based on the configuration
|
||||
if (ExcludeLogic.ShouldExcludeMember(parms.Config, item, parms.Object1Type))
|
||||
return;
|
||||
|
||||
//If we ignore types then we must get correct FieldInfo object
|
||||
FieldInfo secondFieldInfo = GetSecondFieldInfo(parms, item);
|
||||
|
||||
//If the field does not exist, and we are ignoring the object types, skip it - unless we have set IgnoreMissingFields = true
|
||||
if ((parms.Config.IgnoreObjectTypes || parms.Config.IgnoreConcreteTypes) && secondFieldInfo == null && parms.Config.IgnoreMissingFields)
|
||||
return;
|
||||
|
||||
object objectValue1 = item.GetValue(parms.Object1);
|
||||
object objectValue2 = secondFieldInfo != null ? secondFieldInfo.GetValue(parms.Object2) : null;
|
||||
|
||||
bool object1IsParent = objectValue1 != null && (objectValue1 == parms.Object1 || parms.Result.IsParent(objectValue1));
|
||||
bool object2IsParent = objectValue2 != null && (objectValue2 == parms.Object2 || parms.Result.IsParent(objectValue2));
|
||||
|
||||
//Skip fields that point to the parent
|
||||
if ((TypeHelper.IsClass(item.FieldType) || TypeHelper.IsInterface(item.FieldType))
|
||||
&& (object1IsParent || object2IsParent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, item.Name);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = objectValue1,
|
||||
Object2 = objectValue2,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
_rootComparer.Compare(childParms);
|
||||
}
|
||||
|
||||
private static FieldInfo GetSecondFieldInfo(CompareParms parms, FieldInfo item)
|
||||
{
|
||||
FieldInfo secondFieldInfo = null;
|
||||
if (parms.Config.IgnoreObjectTypes || parms.Config.IgnoreConcreteTypes)
|
||||
{
|
||||
IEnumerable<FieldInfo> secondObjectFieldInfos = Cache.GetFieldInfo(parms.Config, parms.Object2Type);
|
||||
|
||||
foreach (var fieldInfo in secondObjectFieldInfos)
|
||||
{
|
||||
if (fieldInfo.Name != item.Name) continue;
|
||||
|
||||
secondFieldInfo = fieldInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
secondFieldInfo = item;
|
||||
|
||||
return secondFieldInfo;
|
||||
}
|
||||
|
||||
private static IEnumerable<FieldInfo> GetCurrentFields(CompareParms parms)
|
||||
{
|
||||
IEnumerable<FieldInfo> currentFields = null;
|
||||
|
||||
//Interface Member Logic
|
||||
if (parms.Config.InterfaceMembers.Count > 0)
|
||||
{
|
||||
Type[] interfaces = parms.Object1Type.GetInterfaces();
|
||||
|
||||
foreach (var type in parms.Config.InterfaceMembers)
|
||||
{
|
||||
if (interfaces.Contains(type))
|
||||
{
|
||||
currentFields = Cache.GetFieldInfo(parms.Config, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFields == null)
|
||||
currentFields = Cache.GetFieldInfo(parms.Config, parms.Object1Type);
|
||||
return currentFields;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#if !NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Class FontDescriptorComparer.
|
||||
/// </summary>
|
||||
public class FontComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Protected constructor that references the root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer">The root comparer.</param>
|
||||
public FontComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true the type comparer will handle the comparison for the type
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns><c>true</c> if [is type match] [the specified type1]; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="System.NotImplementedException"></exception>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsFont(type1) && TypeHelper.IsFont(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two fonts
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
Font font1 = parms.Object1 as Font;
|
||||
Font font2 = parms.Object2 as Font;
|
||||
|
||||
if (font1 == null || font2 == null)
|
||||
return;
|
||||
|
||||
CompareProp(parms, font1.Bold, font2.Bold, "Bold");
|
||||
CompareProp(parms, font1.FontFamily.Name, font2.FontFamily.Name, "FontFamily.Name");
|
||||
CompareProp(parms, font1.OriginalFontName, font2.OriginalFontName, "OriginalFontName");
|
||||
CompareProp(parms, font1.Size, font2.Size, "Size");
|
||||
CompareProp(parms, font1.SizeInPoints, font2.SizeInPoints, "SizeInPoints");
|
||||
CompareProp(parms, font1.Strikeout, font2.Strikeout, "Strikeout");
|
||||
CompareProp(parms, font1.Style, font2.Style, "Style");
|
||||
CompareProp(parms, font1.SystemFontName, font2.SystemFontName, "SystemFontName");
|
||||
CompareProp(parms, font1.Underline, font2.Underline, "Underline");
|
||||
CompareProp(parms, font1.Unit, font2.Unit, "Unit");
|
||||
}
|
||||
|
||||
private void CompareProp(CompareParms parms, object prop1, object prop2, string propName)
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, propName);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = prop1,
|
||||
Object2 = prop2,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two hash sets
|
||||
/// </summary>
|
||||
public class HashSetComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public HashSetComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are hash sets
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsHashSet(type1) && TypeHelper.IsHashSet(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two hash sets
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
parms.Object1Type = t1;
|
||||
|
||||
Type t2 = parms.Object2.GetType();
|
||||
parms.Object2Type = t2;
|
||||
|
||||
bool countsDifferent = HashSetsDifferentCount(parms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
if (parms.Config.IgnoreCollectionOrder)
|
||||
{
|
||||
IgnoreOrderLogic logic = new IgnoreOrderLogic(RootComparer);
|
||||
logic.CompareEnumeratorIgnoreOrder(parms, countsDifferent);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareItems(parms);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareItems(CompareParms parms)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
//Get enumerators by reflection
|
||||
MethodInfo method1Info = Cache.GetMethod(parms.Object1Type, "GetEnumerator");
|
||||
IEnumerator enumerator1 = (IEnumerator)method1Info.Invoke(parms.Object1, null);
|
||||
|
||||
MethodInfo method2Info = Cache.GetMethod(parms.Object2Type, "GetEnumerator");
|
||||
IEnumerator enumerator2 = (IEnumerator) method2Info.Invoke(parms.Object2, null);
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, count);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Current,
|
||||
Object2 = enumerator2.Current,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HashSetsDifferentCount(CompareParms parms)
|
||||
{
|
||||
//Get count by reflection since we can't cast it to HashSet<>
|
||||
int hashSet1Count = (int) Cache.GetPropertyValue(parms.Result.Config, parms.Object1Type, parms.Object1, "Count");
|
||||
int hashSet2Count = (int)Cache.GetPropertyValue(parms.Result.Config, parms.Object2Type, parms.Object2, "Count");
|
||||
|
||||
//Objects must be the same length
|
||||
if (hashSet1Count != hashSet2Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = hashSet1Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = hashSet2Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares System.Collections.Immutable.ImmutableArray
|
||||
/// </summary>
|
||||
public class ImmutableArrayComparer : BaseTypeComparer
|
||||
{
|
||||
private readonly ListComparer _listComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public ImmutableArrayComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
_listComparer = new ListComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both are immutable arrays with same generic argument type
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsImmutableArray(type1) && TypeHelper.IsImmutableArray(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two immutable arrays
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
parms.Object1 = ((IList) parms.Object1).Cast<object>().ToArray();
|
||||
parms.Object2 = ((IList) parms.Object2).Cast<object>().ToArray();
|
||||
|
||||
_listComparer.CompareType(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare an integer indexer (Note, inherits from BaseComparer, not TypeComparer)
|
||||
/// </summary>
|
||||
public class IndexerComparer : BaseComparer
|
||||
{
|
||||
private readonly RootComparer _rootComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public IndexerComparer(RootComparer rootComparer)
|
||||
{
|
||||
_rootComparer = rootComparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare an integer indexer
|
||||
/// </summary>
|
||||
public void CompareIndexer(CompareParms parms, PropertyEntity info, PropertyEntity secondObjectInfo)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
#if !NETSTANDARD
|
||||
var type = info.ReflectedType;
|
||||
var type2 = secondObjectInfo.ReflectedType;
|
||||
#else
|
||||
var type = info.DeclaringType;
|
||||
var type2 = secondObjectInfo.DeclaringType;
|
||||
#endif
|
||||
if (type == null || type2 == null)
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
int indexerCount1 = (int)type.GetProperty("Count").GetGetMethod().Invoke(parms.Object1, new object[] { });
|
||||
int indexerCount2 = (int)type2.GetProperty("Count").GetGetMethod().Invoke(parms.Object2, new object[] { });
|
||||
|
||||
bool differentCounts = IndexersHaveDifferentLength(parms, info, indexerCount1, indexerCount2);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
if (parms.Config.IgnoreCollectionOrder)
|
||||
{
|
||||
var enumerable1 = new IndexerCollectionLooper(parms.Object1, info.PropertyInfo, indexerCount1);
|
||||
var enumerable2 = new IndexerCollectionLooper(parms.Object2, secondObjectInfo.PropertyInfo, indexerCount2);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerable1,
|
||||
Object2 = enumerable2,
|
||||
BreadCrumb = parms.BreadCrumb
|
||||
};
|
||||
|
||||
IgnoreOrderLogic logic = new IgnoreOrderLogic(_rootComparer);
|
||||
logic.CompareEnumeratorIgnoreOrder(childParms, differentCounts);
|
||||
}
|
||||
else
|
||||
{
|
||||
string currentCrumb;
|
||||
|
||||
// Run on indexer
|
||||
for (int i = 0; i < indexerCount1; i++)
|
||||
{
|
||||
currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name, string.Empty, i);
|
||||
object objectValue1 = info.PropertyInfo.GetValue(parms.Object1, new object[] { i });
|
||||
object objectValue2 = null;
|
||||
|
||||
if (i < indexerCount2)
|
||||
objectValue2 = secondObjectInfo.PropertyInfo.GetValue(parms.Object2, new object[] { i });
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = objectValue1,
|
||||
Object2 = objectValue2,
|
||||
BreadCrumb = currentCrumb
|
||||
};
|
||||
|
||||
_rootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
|
||||
if (indexerCount1 < indexerCount2)
|
||||
{
|
||||
for (int j = indexerCount1; j < indexerCount2; j++)
|
||||
{
|
||||
currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name, string.Empty, j);
|
||||
object objectValue2 = secondObjectInfo.PropertyInfo.GetValue(parms.Object2, new object[] { j });
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = null,
|
||||
Object2 = objectValue2,
|
||||
BreadCrumb = currentCrumb
|
||||
};
|
||||
|
||||
_rootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IndexersHaveDifferentLength(CompareParms parms, PropertyEntity info, int indexerCount1, int indexerCount2)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
if (indexerCount1 != indexerCount2)
|
||||
{
|
||||
string currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name);
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = currentCrumb,
|
||||
Object1Value = indexerCount1.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = indexerCount2.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#if NETFULL || NETSTANDARD2_0 || NETSTANDARD2_1
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two IP End Points
|
||||
/// </summary>
|
||||
public class IpEndPointComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public IpEndPointComparer(RootComparer rootComparer)
|
||||
: base(rootComparer)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are an IP End Point
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsIpEndPoint(type1) && TypeHelper.IsIpEndPoint(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two IP End Points
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
IPEndPoint ipEndPoint1 = parms.Object1 as IPEndPoint;
|
||||
IPEndPoint ipEndPoint2 = parms.Object2 as IPEndPoint;
|
||||
|
||||
//Null check happens above
|
||||
if (ipEndPoint1 == null || ipEndPoint2 == null)
|
||||
return;
|
||||
|
||||
ComparePort(parms, ipEndPoint1, ipEndPoint2);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
CompareAddress(parms, ipEndPoint1, ipEndPoint2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ComparePort(CompareParms parms, IPEndPoint ipEndPoint1, IPEndPoint ipEndPoint2)
|
||||
{
|
||||
if (ipEndPoint1.Port != ipEndPoint2.Port)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = ipEndPoint1.Port.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = ipEndPoint2.Port.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Port",
|
||||
Object1 = ipEndPoint1,
|
||||
Object2 = ipEndPoint2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareAddress(CompareParms parms, IPEndPoint ipEndPoint1, IPEndPoint ipEndPoint2)
|
||||
{
|
||||
if (ipEndPoint1.Address.ToString() != ipEndPoint2.Address.ToString())
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = ipEndPoint1.Address.ToString(),
|
||||
Object2Value = ipEndPoint2.Address.ToString(),
|
||||
ChildPropertyName = "Address",
|
||||
Object1 = ipEndPoint1,
|
||||
Object2 = ipEndPoint2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
228
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/ListComparer.cs
Normal file
228
FSI.Lib/FSI.Lib/CompareNetObjects/TypeComparers/ListComparer.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare objects that implement IList
|
||||
/// </summary>
|
||||
public class ListComparer : BaseTypeComparer
|
||||
{
|
||||
private readonly PropertyComparer _propertyComparer;
|
||||
private readonly FieldComparer _fieldComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public ListComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
_propertyComparer = new PropertyComparer(rootComparer);
|
||||
_fieldComparer = new FieldComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects implement IList
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsIList(type1) && TypeHelper.IsIList(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two objects that implement IList
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
Type t2 = parms.Object2.GetType();
|
||||
|
||||
//Check if the class type should be excluded based on the configuration
|
||||
if (ExcludeLogic.ShouldExcludeClass(parms.Config, t1, t2))
|
||||
return;
|
||||
|
||||
parms.Object1Type = t1;
|
||||
parms.Object2Type = t2;
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
bool countsDifferent = ListsHaveDifferentCounts(parms);
|
||||
|
||||
// If items is collections, need to use default compare logic, not ignore order logic.
|
||||
// We cannot ignore order for nested collections because we will get an reflection exception.
|
||||
// May be need to display some warning or write about this behavior in documentation.
|
||||
if (parms.Config.IgnoreCollectionOrder && !ChildShouldBeComparedWithoutOrder(parms))
|
||||
{
|
||||
// TODO: allow IndexerComparer to works with types (now it works only with properties).
|
||||
IgnoreOrderLogic ignoreOrderLogic = new IgnoreOrderLogic(RootComparer);
|
||||
ignoreOrderLogic.CompareEnumeratorIgnoreOrder(parms, countsDifferent);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareItems(parms);
|
||||
}
|
||||
|
||||
//Properties on the root of a collection
|
||||
CompareProperties(parms);
|
||||
CompareFields(parms);
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CompareFields(CompareParms parms)
|
||||
{
|
||||
if (parms.Config.CompareFields)
|
||||
{
|
||||
_fieldComparer.PerformCompareFields(parms);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareProperties(CompareParms parms)
|
||||
{
|
||||
if (parms.Config.CompareProperties)
|
||||
{
|
||||
_propertyComparer.PerformCompareProperties(parms, true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ListsHaveDifferentCounts(CompareParms parms)
|
||||
{
|
||||
IList ilist1 = parms.Object1 as IList;
|
||||
IList ilist2 = parms.Object2 as IList;
|
||||
|
||||
if (ilist1 == null)
|
||||
throw new ArgumentException("parms.Object1");
|
||||
|
||||
if (ilist2 == null)
|
||||
throw new ArgumentException("parms.Object2");
|
||||
|
||||
try
|
||||
{
|
||||
//Objects must be the same length
|
||||
if (ilist1.Count != ilist2.Count)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = ilist1.Count.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = ilist2.Count.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = ilist1,
|
||||
Object2 = ilist2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
if (!parms.Config.IgnoreObjectDisposedException)
|
||||
throw;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ChildShouldBeComparedWithoutOrder(CompareParms parms)
|
||||
{
|
||||
IEnumerator enumerator1 = ((IEnumerable)parms.Object1).GetEnumerator();
|
||||
|
||||
// We should ensure that all items is enumerable, list or dictionary.
|
||||
bool hasItems = false;
|
||||
var results = new List<bool>();
|
||||
while (enumerator1.MoveNext())
|
||||
{
|
||||
hasItems = true;
|
||||
|
||||
if (enumerator1.Current is null)
|
||||
continue;
|
||||
|
||||
Type type = enumerator1.Current.GetType();
|
||||
bool shouldCompareAndIgnoreOrder =
|
||||
TypeHelper.IsEnumerable(type) ||
|
||||
TypeHelper.IsIList(type) ||
|
||||
TypeHelper.IsIDictionary(type);
|
||||
|
||||
results.Add(shouldCompareAndIgnoreOrder);
|
||||
}
|
||||
|
||||
// Take into account that items can be objects with mixed types.
|
||||
// Throw an exception that this case is unsupported.
|
||||
if (hasItems && results.Count > 0)
|
||||
{
|
||||
bool firstResult = results[0];
|
||||
if (results.Any(x => x != firstResult))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Collection has nested collections and some other types. " +
|
||||
"IgnoreCollectionOrder should be false for such cases."
|
||||
);
|
||||
}
|
||||
|
||||
return firstResult;
|
||||
}
|
||||
|
||||
// If all items is null, we can compare as usual.
|
||||
// Order does not change anything in this case.
|
||||
return hasItems;
|
||||
}
|
||||
|
||||
private void CompareItems(CompareParms parms)
|
||||
{
|
||||
int count = 0;
|
||||
IEnumerator enumerator1 = ((IList) parms.Object1).GetEnumerator();
|
||||
IEnumerator enumerator2 = ((IList) parms.Object2).GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, count);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Current,
|
||||
Object2 = enumerator2.Current,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare to pointers
|
||||
/// </summary>
|
||||
public class PointerComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public PointerComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are a pointer
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsPointer(type1) && TypeHelper.IsPointer(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two pointers
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if ((parms.Object1 is IntPtr && parms.Object2 is IntPtr && ((IntPtr)parms.Object1) != ((IntPtr)parms.Object2))
|
||||
|| (parms.Object1 is UIntPtr && parms.Object2 is UIntPtr && ((UIntPtr)parms.Object1) != ((UIntPtr)parms.Object2)))
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two properties (Note: inherits from BaseComparer instead of TypeComparer).
|
||||
/// </summary>
|
||||
public class PropertyComparer : BaseComparer
|
||||
{
|
||||
private readonly RootComparer _rootComparer;
|
||||
private readonly IndexerComparer _indexerComparer;
|
||||
private static readonly string[] _baseList = { "Count", "Capacity", "Item" };
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public PropertyComparer(RootComparer rootComparer)
|
||||
{
|
||||
_rootComparer = rootComparer;
|
||||
_indexerComparer = new IndexerComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the properties of a class
|
||||
/// </summary>
|
||||
public void PerformCompareProperties(CompareParms parms, bool ignoreBaseList= false)
|
||||
{
|
||||
List<PropertyEntity> object1Properties = GetCurrentProperties(parms, parms.Object1, parms.Object1Type);
|
||||
List<PropertyEntity> object2Properties = GetCurrentProperties(parms, parms.Object2, parms.Object2Type);
|
||||
|
||||
foreach (PropertyEntity propertyEntity in object1Properties)
|
||||
{
|
||||
if (ignoreBaseList && _baseList.Contains(propertyEntity.Name))
|
||||
continue;
|
||||
|
||||
CompareProperty(parms, propertyEntity, object2Properties);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare a single property of a class
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="object2Properties"></param>
|
||||
private void CompareProperty(CompareParms parms, PropertyEntity info, List<PropertyEntity> object2Properties)
|
||||
{
|
||||
//If we can't read it, skip it
|
||||
if (info.CanRead == false)
|
||||
return;
|
||||
|
||||
//Skip if this is a shallow compare
|
||||
if (!parms.Config.CompareChildren && TypeHelper.CanHaveChildren(info.PropertyType))
|
||||
return;
|
||||
|
||||
//Skip if it should be excluded based on the configuration
|
||||
if (info.PropertyInfo != null && ExcludeLogic.ShouldExcludeMember(parms.Config, info.PropertyInfo, info.DeclaringType))
|
||||
return;
|
||||
|
||||
//This is a dynamic property to be excluded on an expando object
|
||||
if (info.IsDynamic && ExcludeLogic.ShouldExcludeDynamicMember(parms.Config, info.Name, info.DeclaringType))
|
||||
return;
|
||||
|
||||
//If we should ignore read only, skip it
|
||||
if (!parms.Config.CompareReadOnly && info.CanWrite == false)
|
||||
return;
|
||||
|
||||
//If we ignore types then we must get correct PropertyInfo object
|
||||
PropertyEntity secondObjectInfo = GetSecondObjectInfo(info, object2Properties);
|
||||
|
||||
//If the property does not exist, and we are ignoring the object types, skip it - unless we have set IgnoreMissingProperties = true
|
||||
if ((parms.Config.IgnoreObjectTypes || parms.Config.IgnoreConcreteTypes) && secondObjectInfo == null && parms.Config.IgnoreMissingProperties)
|
||||
return;
|
||||
|
||||
//Check if we have custom function to validate property
|
||||
BaseTypeComparer customComparer = null;
|
||||
if (info.PropertyInfo != null)
|
||||
customComparer = CustomValidationLogic.CustomValidatorForMember(parms.Config, info.PropertyInfo, info.DeclaringType)
|
||||
?? CustomValidationLogic.CustomValidatorForDynamicMember(parms.Config, info.Name, info.DeclaringType);
|
||||
|
||||
object objectValue1;
|
||||
object objectValue2;
|
||||
if (!IsValidIndexer(parms.Config, info, parms.BreadCrumb))
|
||||
{
|
||||
objectValue1 = info.Value;
|
||||
objectValue2 = secondObjectInfo != null ? secondObjectInfo.Value : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_indexerComparer.CompareIndexer(parms, info, secondObjectInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
bool object1IsParent = objectValue1 != null && (objectValue1 == parms.Object1 || parms.Result.IsParent(objectValue1));
|
||||
bool object2IsParent = objectValue2 != null && (objectValue2 == parms.Object2 || parms.Result.IsParent(objectValue2));
|
||||
|
||||
//Skip properties where both point to the corresponding parent
|
||||
if ((TypeHelper.IsClass(info.PropertyType) || TypeHelper.IsInterface(info.PropertyType) || TypeHelper.IsStruct(info.PropertyType))
|
||||
&& (object1IsParent && object2IsParent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = objectValue1,
|
||||
Object2 = objectValue2,
|
||||
BreadCrumb = currentBreadCrumb,
|
||||
CustomPropertyComparer = customComparer,
|
||||
Object1DeclaredType = info?.PropertyType,
|
||||
Object2DeclaredType = secondObjectInfo?.PropertyType
|
||||
};
|
||||
|
||||
_rootComparer.Compare(childParms);
|
||||
}
|
||||
|
||||
private static PropertyEntity GetSecondObjectInfo(PropertyEntity info, List<PropertyEntity> object2Properties)
|
||||
{
|
||||
foreach (var object2Property in object2Properties)
|
||||
{
|
||||
if (info.Name == object2Property.Name)
|
||||
return object2Property;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> GetCurrentProperties(CompareParms parms, object objectValue, Type objectType)
|
||||
{
|
||||
return HandleDynamicObject(objectValue, objectType)
|
||||
?? HandleInterfaceMembers(parms, objectValue, objectType)
|
||||
?? HandleNormalProperties(parms, objectValue, objectType);
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> HandleNormalProperties(CompareParms parms, object objectValue, Type objectType)
|
||||
{
|
||||
IEnumerable<PropertyInfo> properties = Cache.GetPropertyInfo(parms.Result.Config, objectType);
|
||||
return AddPropertyInfos(parms, objectValue, objectType, properties);
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> AddPropertyInfos(CompareParms parms,
|
||||
object objectValue,
|
||||
Type objectType,
|
||||
IEnumerable<PropertyInfo> properties)
|
||||
{
|
||||
List<PropertyEntity> currentProperties = new List<PropertyEntity>();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (ExcludeLogic.ShouldExcludeMember(parms.Config, property, objectType))
|
||||
continue;
|
||||
|
||||
if (!property.CanRead && !parms.Config.CompareReadOnly)
|
||||
continue;
|
||||
|
||||
PropertyEntity propertyEntity = new PropertyEntity();
|
||||
propertyEntity.IsDynamic = false;
|
||||
propertyEntity.Name = property.Name;
|
||||
propertyEntity.CanRead = property.CanRead;
|
||||
propertyEntity.CanWrite = property.CanWrite;
|
||||
propertyEntity.PropertyType = property.PropertyType;
|
||||
#if !NETSTANDARD
|
||||
propertyEntity.ReflectedType = property.ReflectedType;
|
||||
#endif
|
||||
propertyEntity.Indexers.AddRange(property.GetIndexParameters());
|
||||
propertyEntity.DeclaringType = objectType;
|
||||
|
||||
if (propertyEntity.CanRead && (propertyEntity.Indexers.Count == 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
propertyEntity.Value = property.GetValue(objectValue, null);
|
||||
}
|
||||
catch (System.Reflection.TargetInvocationException)
|
||||
{
|
||||
}
|
||||
catch (System.NotSupportedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
propertyEntity.PropertyInfo = property;
|
||||
|
||||
currentProperties.Add(propertyEntity);
|
||||
}
|
||||
|
||||
return currentProperties;
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> HandleInterfaceMembers(CompareParms parms, object objectValue, Type objectType)
|
||||
{
|
||||
List<PropertyEntity> currentProperties = new List<PropertyEntity>();
|
||||
|
||||
if (parms.Config.InterfaceMembers.Count > 0)
|
||||
{
|
||||
Type[] interfaces = objectType.GetInterfaces();
|
||||
|
||||
foreach (var type in parms.Config.InterfaceMembers)
|
||||
{
|
||||
if (interfaces.Contains(type))
|
||||
{
|
||||
var properties = Cache.GetPropertyInfo(parms.Result.Config, type);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
PropertyEntity propertyEntity = new PropertyEntity();
|
||||
propertyEntity.IsDynamic = false;
|
||||
propertyEntity.Name = property.Name;
|
||||
propertyEntity.CanRead = property.CanRead;
|
||||
propertyEntity.CanWrite = property.CanWrite;
|
||||
propertyEntity.PropertyType = property.PropertyType;
|
||||
propertyEntity.Indexers.AddRange(property.GetIndexParameters());
|
||||
propertyEntity.DeclaringType = objectType;
|
||||
|
||||
if (propertyEntity.Indexers.Count == 0)
|
||||
{
|
||||
propertyEntity.Value = property.GetValue(objectValue, null);
|
||||
}
|
||||
|
||||
propertyEntity.PropertyInfo = property;
|
||||
currentProperties.Add(propertyEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentProperties.Count == 0)
|
||||
return null;
|
||||
|
||||
return currentProperties;
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> HandleDynamicObject(object objectValue, Type objectType)
|
||||
{
|
||||
if (TypeHelper.IsExpandoObject(objectValue))
|
||||
{
|
||||
return AddExpandoPropertyValues(objectValue, objectType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<PropertyEntity> AddExpandoPropertyValues(Object objectValue, Type objectType)
|
||||
{
|
||||
IDictionary<string, object> expandoPropertyValues = objectValue as IDictionary<string, object>;
|
||||
|
||||
if (expandoPropertyValues == null)
|
||||
return new List<PropertyEntity>();
|
||||
|
||||
List<PropertyEntity> currentProperties = new List<PropertyEntity>();
|
||||
foreach (var propertyValue in expandoPropertyValues)
|
||||
{
|
||||
PropertyEntity propertyEntity = new PropertyEntity();
|
||||
propertyEntity.IsDynamic = true;
|
||||
propertyEntity.Name = propertyValue.Key;
|
||||
propertyEntity.Value = propertyValue.Value;
|
||||
propertyEntity.CanRead = true;
|
||||
propertyEntity.CanWrite = true;
|
||||
propertyEntity.DeclaringType = objectType;
|
||||
|
||||
if (propertyValue.Value == null)
|
||||
{
|
||||
propertyEntity.PropertyType = null;
|
||||
propertyEntity.ReflectedType = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyEntity.PropertyType = propertyValue.GetType();
|
||||
propertyEntity.ReflectedType = propertyEntity.PropertyType;
|
||||
}
|
||||
|
||||
currentProperties.Add(propertyEntity);
|
||||
}
|
||||
|
||||
return currentProperties;
|
||||
}
|
||||
|
||||
private bool IsValidIndexer(ComparisonConfig config, PropertyEntity info, string breadCrumb)
|
||||
{
|
||||
if (info.Indexers.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.Indexers.Count > 1)
|
||||
{
|
||||
if (config.SkipInvalidIndexers)
|
||||
return false;
|
||||
|
||||
throw new Exception("Cannot compare objects with more than one indexer for object " + breadCrumb);
|
||||
}
|
||||
|
||||
if (info.Indexers[0].ParameterType != typeof(Int32))
|
||||
{
|
||||
if (config.SkipInvalidIndexers)
|
||||
return false;
|
||||
|
||||
throw new Exception("Cannot compare objects with a non integer indexer for object " + breadCrumb);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
var type = info.ReflectedType;
|
||||
#else
|
||||
var type = info.DeclaringType;
|
||||
#endif
|
||||
if (type == null)
|
||||
{
|
||||
if (config.SkipInvalidIndexers)
|
||||
return false;
|
||||
|
||||
throw new Exception("Cannot compare objects with a null indexer for object " + breadCrumb);
|
||||
}
|
||||
|
||||
if (type.GetProperty("Count") == null
|
||||
|| type.GetProperty("Count").PropertyType != typeof(Int32))
|
||||
{
|
||||
if (config.SkipInvalidIndexers)
|
||||
return false;
|
||||
|
||||
throw new Exception("Indexer must have a corresponding Count property that is an integer for object " + breadCrumb);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two ReadOnlyCollections.
|
||||
/// </summary>
|
||||
public class ReadOnlyCollectionComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// The main constructor.
|
||||
/// </summary>
|
||||
public ReadOnlyCollectionComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are ReadOnlyCollections.
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsReadOnlyCollection(type1) && TypeHelper.IsReadOnlyCollection(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two ReadOnlyCollections.
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (!parms.Config.CompareReadOnly)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
parms.Result.AddParent(parms.Object1);
|
||||
parms.Result.AddParent(parms.Object2);
|
||||
|
||||
Type t1 = parms.Object1.GetType();
|
||||
Type t2 = parms.Object2.GetType();
|
||||
|
||||
//Check if the class type should be excluded based on the configuration
|
||||
if (ExcludeLogic.ShouldExcludeClass(parms.Config, t1, t2))
|
||||
return;
|
||||
|
||||
parms.Object1Type = t1;
|
||||
parms.Object2Type = t2;
|
||||
|
||||
bool countsDifferent = CollectionsDifferentCount(parms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
if (parms.Config.IgnoreCollectionOrder)
|
||||
{
|
||||
IgnoreOrderLogic logic = new IgnoreOrderLogic(RootComparer);
|
||||
logic.CompareEnumeratorIgnoreOrder(parms, countsDifferent);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareItems(parms);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.RemoveParent(parms.Object1);
|
||||
parms.Result.RemoveParent(parms.Object2);
|
||||
}
|
||||
}
|
||||
|
||||
private void CompareItems(CompareParms parms)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
IEnumerator enumerator1 = ((ICollection)parms.Object1).GetEnumerator();
|
||||
IEnumerator enumerator2 = ((ICollection)parms.Object2).GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext())
|
||||
{
|
||||
string currentBreadCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, string.Empty, string.Empty, count);
|
||||
|
||||
CompareParms childParms = new CompareParms
|
||||
{
|
||||
Result = parms.Result,
|
||||
Config = parms.Config,
|
||||
ParentObject1 = parms.Object1,
|
||||
ParentObject2 = parms.Object2,
|
||||
Object1 = enumerator1.Current,
|
||||
Object2 = enumerator2.Current,
|
||||
BreadCrumb = currentBreadCrumb
|
||||
};
|
||||
|
||||
RootComparer.Compare(childParms);
|
||||
|
||||
if (parms.Result.ExceededDifferences)
|
||||
return;
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CollectionsDifferentCount(CompareParms parms)
|
||||
{
|
||||
//Get count by reflection since we can't cast it to HashSet<>
|
||||
int count1 = ((ICollection)parms.Object1).Count;
|
||||
int count2 = ((ICollection)parms.Object2).Count;
|
||||
|
||||
//Objects must be the same length
|
||||
if (count1 != count2)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = count1.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = count2.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Count",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two runtime types
|
||||
/// </summary>
|
||||
public class RuntimeTypeComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public RuntimeTypeComparer(RootComparer rootComparer)
|
||||
: base(rootComparer)
|
||||
{}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are of type runtme type
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsRuntimeType(type1) && TypeHelper.IsRuntimeType(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two runtime types
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
Type t1 = (Type)parms.Object1;
|
||||
Type t2 = (Type)parms.Object2;
|
||||
|
||||
if (t1.FullName != t2.FullName)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = t1.FullName,
|
||||
Object2Value = t2.FullName,
|
||||
ChildPropertyName = "FullName",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare primitive types (long, int, short, byte etc.) and DateTime, decimal, and Guid
|
||||
/// </summary>
|
||||
public class SimpleTypeComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public SimpleTypeComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a simple type
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsSimpleType(type1) && TypeHelper.IsSimpleType(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two simple types
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
//This should never happen, null check happens one level up
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
return;
|
||||
|
||||
IComparable valOne = parms.Object1 as IComparable;
|
||||
|
||||
if (valOne == null)
|
||||
throw new Exception("Expected value does not implement IComparable");
|
||||
|
||||
if (valOne.CompareTo(parms.Object2) != 0)
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two StringBuilders
|
||||
/// </summary>
|
||||
public class StringBuilderComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public StringBuilderComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are a StringBuilder
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return (TypeHelper.IsStringBuilder(type1) && TypeHelper.IsStringBuilder(type2))
|
||||
|| (TypeHelper.IsStringBuilder(type1) && type2 == null)
|
||||
|| (TypeHelper.IsStringBuilder(type2) && type1 == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two string builders
|
||||
/// </summary>
|
||||
/// <param name="parms"></param>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (parms.Config.TreatStringEmptyAndNullTheSame
|
||||
&& ((parms.Object1 == null && parms.Object2 != null && parms.Object2.ToString() == string.Empty)
|
||||
|| (parms.Object2 == null && parms.Object1 != null && parms.Object1.ToString() == string.Empty)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string object1String = parms.Object1.ToString();
|
||||
string object2String = parms.Object2.ToString();
|
||||
|
||||
if (parms.Config.IgnoreStringLeadingTrailingWhitespace)
|
||||
{
|
||||
object1String = object1String.Trim();
|
||||
object2String = object2String.Trim();
|
||||
}
|
||||
|
||||
if (!parms.Config.CaseSensitive)
|
||||
{
|
||||
if (!String.Equals(object1String, object2String, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
else if (object1String != object2String)
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two strings
|
||||
/// </summary>
|
||||
public class StringComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public StringComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are a string or if one is a string and one is a a null
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return (TypeHelper.IsString(type1) && TypeHelper.IsString(type2))
|
||||
|| (TypeHelper.IsString(type1) && type2 == null)
|
||||
|| (TypeHelper.IsString(type2) && type1 == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two strings
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (parms.Config.TreatStringEmptyAndNullTheSame
|
||||
&& ((parms.Object1 == null && parms.Object2 != null && parms.Object2.ToString() == string.Empty)
|
||||
|| (parms.Object2 == null && parms.Object1 != null && parms.Object1.ToString() == string.Empty)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OneOfTheStringsIsNull(parms)) return;
|
||||
|
||||
string string1 = parms.Object1 as string;
|
||||
string string2 = parms.Object2 as string;
|
||||
|
||||
if (parms.Config.IgnoreStringLeadingTrailingWhitespace)
|
||||
{
|
||||
string1 = string1.Trim();
|
||||
string2 = string2.Trim();
|
||||
}
|
||||
|
||||
if (!parms.Config.CaseSensitive)
|
||||
{
|
||||
if (!String.Equals(string1, string2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
else if (string1 != string2)
|
||||
{
|
||||
AddDifference(parms);
|
||||
}
|
||||
}
|
||||
|
||||
private bool OneOfTheStringsIsNull(CompareParms parms)
|
||||
{
|
||||
if (parms.Object1 == null || parms.Object2 == null)
|
||||
{
|
||||
AddDifference(parms);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two structs
|
||||
/// </summary>
|
||||
public class StructComparer : BaseTypeComparer
|
||||
{
|
||||
private readonly PropertyComparer _propertyComparer;
|
||||
private readonly FieldComparer _fieldComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public StructComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
_propertyComparer = new PropertyComparer(rootComparer);
|
||||
_fieldComparer = new FieldComparer(rootComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are of type struct
|
||||
/// </summary>
|
||||
/// <param name="type1"></param>
|
||||
/// <param name="type2"></param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsStruct(type1) && TypeHelper.IsStruct(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two structs
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (parms.Result.CurrentStructDepth >= parms.Config.MaxStructDepth)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
parms.Result.CurrentStructDepth++;
|
||||
parms.Object1Type = parms.Object1.GetType();
|
||||
parms.Object2Type = parms.Object2.GetType();
|
||||
|
||||
if (parms.Config.CompareFields)
|
||||
_fieldComparer.PerformCompareFields(parms);
|
||||
|
||||
if (parms.Config.CompareProperties)
|
||||
_propertyComparer.PerformCompareProperties(parms);
|
||||
}
|
||||
finally
|
||||
{
|
||||
parms.Result.CurrentStructDepth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Logic to compare two timespans
|
||||
/// </summary>
|
||||
public class TimespanComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public TimespanComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both objects are timespans
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsTimespan(type1) && TypeHelper.IsTimespan(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two timespans
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
if (((TimeSpan)parms.Object1).Ticks != ((TimeSpan)parms.Object2).Ticks)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = ((TimeSpan)parms.Object1).Ticks.ToString(CultureInfo.InvariantCulture),
|
||||
Object2Value = ((TimeSpan)parms.Object2).Ticks.ToString(CultureInfo.InvariantCulture),
|
||||
ChildPropertyName = "Ticks",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects.TypeComparers
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare two URIs
|
||||
/// </summary>
|
||||
public class UriComparer : BaseTypeComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor that takes a root comparer
|
||||
/// </summary>
|
||||
/// <param name="rootComparer"></param>
|
||||
public UriComparer(RootComparer rootComparer) : base(rootComparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both types are a URI
|
||||
/// </summary>
|
||||
/// <param name="type1">The type of the first object</param>
|
||||
/// <param name="type2">The type of the second object</param>
|
||||
/// <returns></returns>
|
||||
public override bool IsTypeMatch(Type type1, Type type2)
|
||||
{
|
||||
return TypeHelper.IsUri(type1) && TypeHelper.IsUri(type2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two URIs
|
||||
/// </summary>
|
||||
public override void CompareType(CompareParms parms)
|
||||
{
|
||||
Uri uri1 = parms.Object1 as Uri;
|
||||
Uri uri2 = parms.Object2 as Uri;
|
||||
|
||||
//This should never happen, null check happens one level up
|
||||
if (uri1 == null || uri2 == null)
|
||||
return;
|
||||
|
||||
if (uri1.OriginalString != uri2.OriginalString)
|
||||
{
|
||||
Difference difference = new Difference
|
||||
{
|
||||
ParentObject1 = parms.ParentObject1,
|
||||
ParentObject2 = parms.ParentObject2,
|
||||
PropertyName = parms.BreadCrumb,
|
||||
Object1Value = NiceString(uri1.OriginalString),
|
||||
Object2Value = NiceString(uri2.OriginalString),
|
||||
ChildPropertyName = "OriginalString",
|
||||
Object1 = parms.Object1,
|
||||
Object2 = parms.Object2
|
||||
};
|
||||
|
||||
AddDifference(parms.Result, difference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
562
FSI.Lib/FSI.Lib/CompareNetObjects/TypeHelper.cs
Normal file
562
FSI.Lib/FSI.Lib/CompareNetObjects/TypeHelper.cs
Normal file
@@ -0,0 +1,562 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
#if !NETSTANDARD1
|
||||
using System.Dynamic;
|
||||
#endif
|
||||
#if !NETSTANDARD
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
#endif
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods for detecting types and converting types
|
||||
/// </summary>
|
||||
public static class TypeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if it is a dynamic object
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDynamicObject(Type type)
|
||||
{
|
||||
#if !NETSTANDARD1
|
||||
return typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is an expando object
|
||||
/// </summary>
|
||||
/// <param name="objectValue">The object value.</param>
|
||||
public static bool IsExpandoObject(object objectValue)
|
||||
{
|
||||
if (objectValue == null)
|
||||
return false;
|
||||
|
||||
if (IsDynamicObject(objectValue.GetType()))
|
||||
{
|
||||
IDictionary<string, object> expandoPropertyValues = objectValue as IDictionary<string, object>;
|
||||
return expandoPropertyValues != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if it is a byte array
|
||||
/// </summary>
|
||||
public static bool IsByteArray(Type type)
|
||||
{
|
||||
return IsIList(type) && (
|
||||
typeof(IEnumerable<byte>).IsAssignableFrom(type) ||
|
||||
typeof(IEnumerable<byte?>).IsAssignableFrom(type)
|
||||
)
|
||||
&& type != typeof(sbyte[]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type can have children
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool CanHaveChildren(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return !IsSimpleType(type)
|
||||
&& !IsTimespan(type)
|
||||
&& !IsDateTimeOffset(type)
|
||||
&& !IsEnum(type)
|
||||
&& !IsPointer(type)
|
||||
&& !IsStringBuilder(type)
|
||||
&& (IsClass(type)
|
||||
|| IsInterface(type)
|
||||
|| IsArray(type)
|
||||
|| IsIDictionary(type)
|
||||
|| IsIList(type)
|
||||
|| IsStruct(type)
|
||||
|| IsHashSet(type)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the type is an array
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsArray(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.IsArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the type is an System.Collections.Immutable.ImmutableArray
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsImmutableArray(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.Namespace == "System.Collections.Immutable"
|
||||
&& type.Name == "ImmutableArray`1";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the type is a System.Collections.ObjectModel.ReadOnlyCollection
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsReadOnlyCollection(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return (type.Namespace == "System.Collections.ObjectModel"
|
||||
&& type.Name == "ReadOnlyCollection`1");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if it is a struct
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsStruct(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.GetTypeInfo().IsValueType && !IsSimpleType(type) && !IsImmutableArray(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a timespan
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsTimespan(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(TimeSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a class
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsClass(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.GetTypeInfo().IsClass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is an interface
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsInterface(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
#if PORTABLE
|
||||
return type.IsInterface;
|
||||
#else
|
||||
return type.GetTypeInfo().IsInterface;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a URI
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsUri(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return (typeof(Uri).IsAssignableFrom(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a pointer
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsPointer(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(IntPtr) || type == typeof(UIntPtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is an enum
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsEnum(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.GetTypeInfo().IsEnum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a dictionary
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsIDictionary(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return (typeof(IDictionary).IsAssignableFrom(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a hashset
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsHashSet(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type.GetTypeInfo().IsGenericType
|
||||
&& type.GetTypeInfo().GetGenericTypeDefinition() == typeof(HashSet<>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a List
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsIList(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return typeof(IList).IsAssignableFrom(type) && !IsImmutableArray(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is an Enumerable
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsEnumerable(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
#if !NETSTANDARD
|
||||
var toCheck = type.ReflectedType;
|
||||
#else
|
||||
var toCheck = type.DeclaringType;
|
||||
#endif
|
||||
return toCheck != null && toCheck == typeof(Enumerable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a Double
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDouble(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(Double);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a Decimal
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDecimal(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(Decimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a Decimal or Nullable Decimal
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDecimal(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
return value is decimal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a DateTime
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDateTime(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof (DateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
public static bool IsDateTimeOffset(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(DateTimeOffset) || type== typeof(System.Nullable<DateTimeOffset>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a StringBuilder
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsStringBuilder(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(StringBuilder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a string
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsString(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(string);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the type is a primitive type, date, decimal, string, or GUID
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSimpleType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
if (type.GetTypeInfo().IsGenericType && type.GetTypeInfo().GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
type = Nullable.GetUnderlyingType(type);
|
||||
}
|
||||
|
||||
return type.GetTypeInfo().IsPrimitive
|
||||
|| type == typeof(DateTime)
|
||||
|| type == typeof(string)
|
||||
|| type == typeof(Guid)
|
||||
|| type == typeof(Decimal)
|
||||
|| type.GetTypeInfo().IsEnum;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Type is a Runtime type
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsRuntimeType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return (typeof(Type).IsAssignableFrom(type));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a generic type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public static bool IsGenericType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
#if NETSTANDARD1
|
||||
return type.GetTypeInfo().IsGenericType;
|
||||
#else
|
||||
return type.IsGenericType;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if NETFULL || NETSTANDARD2_0 || NETSTANDARD2_1
|
||||
/// <summary>
|
||||
/// Returns true if the type is an IPEndPoint
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsIpEndPoint(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(IPEndPoint);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Returns true if the type is a dataset
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDataset(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(DataSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a data table
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDataTable(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(DataTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a data row
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDataRow(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(DataRow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Type is Data Column
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsDataColumn(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(DataColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type is a font
|
||||
/// </summary>
|
||||
/// <param name="type">The type1.</param>
|
||||
public static bool IsFont(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
return type == typeof(Font);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Turn a list of types into a list of string types
|
||||
/// </summary>
|
||||
/// <param name="types"></param>
|
||||
/// <returns></returns>
|
||||
public static List<string> ListOfTypesSerializer(List<Type> types)
|
||||
{
|
||||
if (types == null || !types.Any())
|
||||
return new List<string>();
|
||||
|
||||
List<string> result = new List<string>();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type != null)
|
||||
{
|
||||
result.Add(type.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn a list of string into a list of types
|
||||
/// </summary>
|
||||
/// <param name="stringList"></param>
|
||||
/// <returns></returns>
|
||||
public static List<Type> ListOfTypesDeserializer(List<string> stringList)
|
||||
{
|
||||
if (stringList == null || !stringList.Any())
|
||||
return new List<Type>();
|
||||
|
||||
List<Type> result = new List<Type>();
|
||||
|
||||
foreach (var item in stringList)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item))
|
||||
{
|
||||
result.Add(Type.GetType(item));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
65
FSI.Lib/FSI.Lib/CompareNetObjects/VerifyConfig.cs
Normal file
65
FSI.Lib/FSI.Lib/CompareNetObjects/VerifyConfig.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Used internally to verify the config settings before comparing
|
||||
/// </summary>
|
||||
public class VerifyConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the specified configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">The configuration.</param>
|
||||
public void Verify(ComparisonConfig config)
|
||||
{
|
||||
VerifySpec(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the collection matching spec.
|
||||
/// </summary>
|
||||
/// <param name="config">The configuration.</param>
|
||||
/// <exception cref="Exception">
|
||||
/// </exception>
|
||||
public void VerifySpec(ComparisonConfig config)
|
||||
{
|
||||
if (config.CollectionMatchingSpec == null)
|
||||
return;
|
||||
|
||||
foreach (var kvp in config.CollectionMatchingSpec)
|
||||
{
|
||||
if (TypeHelper.IsIList(kvp.Key))
|
||||
{
|
||||
string msg = string.Format(
|
||||
"Collection Matching Spec Type {0} should be a class, not a List. Expected something like Customer, not List<Customer>. See https://github.com/GregFinzer/Compare-Net-Objects/wiki/Comparing-Lists-of-Different-Lengths",
|
||||
kvp.Key.Name);
|
||||
throw new ArgumentException(msg, nameof(config));
|
||||
}
|
||||
|
||||
if (!TypeHelper.IsClass(kvp.Key) && !TypeHelper.IsInterface(kvp.Key))
|
||||
{
|
||||
string msg = string.Format(
|
||||
"Collection matching spec Type {0} should be a class or an interface. See https://github.com/GregFinzer/Compare-Net-Objects/wiki/Comparing-Lists-of-Different-Lengths",
|
||||
kvp.Key.Name);
|
||||
throw new ArgumentException(msg, nameof(config));
|
||||
}
|
||||
|
||||
List<PropertyInfo> propertyInfos = Cache.GetPropertyInfo(config, kvp.Key).ToList();
|
||||
|
||||
foreach (var index in kvp.Value)
|
||||
{
|
||||
if (propertyInfos.All(o => o.Name != index))
|
||||
{
|
||||
string msg = string.Format("Collection Matching Spec cannot find property {0} of type {1}",
|
||||
index, kvp.Key.Name);
|
||||
throw new ArgumentException(msg, nameof(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
FSI.Lib/FSI.Lib/CompareNetObjects/WebHelper.cs
Normal file
57
FSI.Lib/FSI.Lib/CompareNetObjects/WebHelper.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace FSI.Lib.CompareNetObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for web related methods
|
||||
/// </summary>
|
||||
public static class WebHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML-encodes a string and returns the encoded string.
|
||||
/// </summary>
|
||||
/// <param name="text">The text string to encode. </param>
|
||||
/// <returns>The HTML-encoded text.</returns>
|
||||
public static string HtmlEncode(string text)
|
||||
{
|
||||
if (text == null)
|
||||
return null;
|
||||
|
||||
StringBuilder sb = new StringBuilder(text.Length);
|
||||
|
||||
int len = text.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
switch (text[i])
|
||||
{
|
||||
case '<':
|
||||
sb.Append("<");
|
||||
break;
|
||||
case '>':
|
||||
sb.Append(">");
|
||||
break;
|
||||
case '"':
|
||||
sb.Append(""");
|
||||
break;
|
||||
case '&':
|
||||
sb.Append("&");
|
||||
break;
|
||||
default:
|
||||
if (text[i] > 159)
|
||||
{
|
||||
// decimal numeric entity
|
||||
sb.Append("&#");
|
||||
sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture));
|
||||
sb.Append(";");
|
||||
}
|
||||
else
|
||||
sb.Append(text[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
FSI.Lib/FSI.Lib/CompareNetObjects/app.config
Normal file
54
FSI.Lib/FSI.Lib/CompareNetObjects/app.config
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<section name="FSI.Lib.CompareNetObjects.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup><userSettings>
|
||||
<FSI.Lib.CompareNetObjects.Properties.Settings>
|
||||
<setting name="CompareStaticFields" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="CompareStaticProperties" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="ComparePrivateProperties" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="ComparePrivateFields" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="CompareChildren" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="CompareReadOnly" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="CompareFields" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="CompareProperties" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="Caching" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="AutoClearCache" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="MaxDifferences" serializeAs="String">
|
||||
<value>1</value>
|
||||
</setting>
|
||||
<setting name="IgnoreCollectionOrder" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="IgnoreUnknownObjectTypes" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="IgnoreObjectDisposedException" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
</FSI.Lib.CompareNetObjects.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ContentFile Version="2010" Moniker="..\bin\Debug\FSI.Lib.Compare-NET-Objects.dll" DocType="DX.NET" IsLocalizationDisabled="false">
|
||||
<RelatedProject>documentation.dxp</RelatedProject>
|
||||
<State>
|
||||
<Attributes>
|
||||
<Attribute key="dtscrollpos" valuetype="integer">0</Attribute>
|
||||
<Attribute key="inheritedMembers" valuetype="string">on</Attribute>
|
||||
<Attribute key="protectedMembers" valuetype="string">on</Attribute>
|
||||
</Attributes>
|
||||
</State>
|
||||
<LastSelectedItem>FSI.Lib.Compare-NET-Objects</LastSelectedItem>
|
||||
<item qn="FSI.Lib.Compare-NET-Objects">
|
||||
<project_introduction ChangeDateTime="2012-07-20T18:35:02.4868852Z"><DIV class=wikidoc><A href="http://www.FSI.Lib.com"><IMG style="BORDER-BOTTOM: medium none; BORDER-LEFT: medium none; BORDER-TOP: medium none; BORDER-RIGHT: medium none" title="Kellerman Software Logo" alt="Kellerman Software Logo" src="http://www.FSI.Lib.com/images/logo.gif"></A><BR><BR><B>Project Description</B><BR>What you have been waiting for. Perform a deep compare of any two .NET objects using reflection. Shows the differences between the two objects. <BR><BR><B>NuGet Package</B><BR><A href="http://www.nuget.org/packages/CompareNETObjects"><FONT color=#0066cc>http://www.nuget.org/packages/CompareNETObjects</FONT></A><BR><BR>Features
|
||||
<UL>
|
||||
<LI>Compare Primitive Types
|
||||
<LI>Compare Structs
|
||||
<LI>Compare IList Objects
|
||||
<LI>Compare Single and Multi-Dimensional Arrays
|
||||
<LI>Compare IDictionary Objects
|
||||
<LI>Compare Publicly visible Class Fields and Properties
|
||||
<LI>Compare Children
|
||||
<LI>Handling for Trees with Children Pointing To Parents
|
||||
<LI>Compare Enums
|
||||
<LI>Compare Timespans
|
||||
<LI>Compare Guids
|
||||
<LI>Compare Classes that Implement IList with Indexers
|
||||
<LI>Compare DataSet Data
|
||||
<LI>Compare DataTable Data
|
||||
<LI>Compare DataRow Data
|
||||
<LI>Compare LinearGradient
|
||||
<LI>Compare HashSet
|
||||
<LI>Compare URI
|
||||
<LI>Compare Types of Type (RuntimeType)
|
||||
<LI>Source code in both C# and in VB.NET
|
||||
<LI>NUnit Test Project Included
|
||||
<LI>Ability to load settings from a config file for use with powershell
|
||||
<LI>Several configuration options for comparing private elements, and ignoring specific elements.
|
||||
<LI>Property and Field Info reflection caching for increased performance</LI></UL></DIV></project_introduction>
|
||||
<project_gettingstarted ChangeDateTime="2012-12-21T18:22:31.6648108Z"><P class=wikidoc><STRONG>Important</STRONG></P>
|
||||
<DIV class=wikidoc>
|
||||
<UL>
|
||||
<LI>By default for performance reasons, Compare .NET Objects only detects the first difference.&nbsp; To capture all differences set MaxDifferences to the maximum differences desired.
|
||||
<LI>After the comparison, the differences are in the Differences list or in the DifferencesString properties.
|
||||
<LI>By default, a deep comparison is performed.&nbsp; To perform a shallow comparison, set CompareChildren = false
|
||||
<LI>By default, private properties and fields are not compared.&nbsp; Set ComparePrivateProperties and ComparePrivateFields to true to override this behavior. </LI></UL></DIV>
|
||||
<P>&nbsp;</P>
|
||||
<P><STRONG>C# Example</STRONG></P>
|
||||
<DIV style="BACKGROUND-COLOR: white; COLOR: black"><PRE><SPAN style="COLOR: green">//This is the comparison class</SPAN>
|
||||
CompareObjects compareObjects = <SPAN style="COLOR: blue">new</SPAN> CompareObjects();
|
||||
|
||||
<SPAN style="COLOR: green">//Create a couple objects to compare</SPAN>
|
||||
Person person1 = <SPAN style="COLOR: blue">new</SPAN> Person();
|
||||
person1.DateCreated = DateTime.Now;
|
||||
person1.Name = <SPAN style="COLOR: #a31515">"Greg"</SPAN>;
|
||||
|
||||
Person person2 = <SPAN style="COLOR: blue">new</SPAN> Person();
|
||||
person2.Name = <SPAN style="COLOR: #a31515">"John"</SPAN>;
|
||||
person2.DateCreated = person1.DateCreated;
|
||||
|
||||
<SPAN style="COLOR: green">//These will be different, write out the differences</SPAN>
|
||||
<SPAN style="COLOR: blue">if</SPAN> (!compareObjects.Compare(person1, person2))
|
||||
Console.WriteLine(compareObjects.DifferencesString);
|
||||
</PRE></DIV>
|
||||
<P><STRONG></STRONG>&nbsp;</P>
|
||||
<P>&nbsp;</P>
|
||||
<P><STRONG>VB.NET Example</STRONG></P>
|
||||
<DIV style="BACKGROUND-COLOR: white; COLOR: black"><PRE><SPAN style="COLOR: green">'This is the comparison class</SPAN>
|
||||
<SPAN style="COLOR: blue">Dim</SPAN> compareObjects <SPAN style="COLOR: blue">As</SPAN> <SPAN style="COLOR: blue">New</SPAN> CompareObjects()
|
||||
|
||||
<SPAN style="COLOR: green">'Create a couple objects to compare</SPAN>
|
||||
<SPAN style="COLOR: blue">Dim</SPAN> person1 <SPAN style="COLOR: blue">As</SPAN> <SPAN style="COLOR: blue">New</SPAN> Person()<BR>person1.DateCreated = <SPAN style="COLOR: blue">Date</SPAN>.Now
|
||||
person1.Name = <SPAN style="COLOR: #a31515">"Greg"</SPAN>
|
||||
|
||||
<SPAN style="COLOR: blue">Dim</SPAN> person2 <SPAN style="COLOR: blue">As</SPAN> <SPAN style="COLOR: blue">New</SPAN> Person()
|
||||
person2.Name = <SPAN style="COLOR: #a31515">"John"</SPAN>
|
||||
person2.DateCreated = person1.DateCreated
|
||||
|
||||
<SPAN style="COLOR: green">'These will be different, write out the differences</SPAN>
|
||||
<SPAN style="COLOR: blue">If</SPAN> <SPAN style="COLOR: blue">Not</SPAN> compareObjects.Compare(person1, person2) <SPAN style="COLOR: blue">Then</SPAN>
|
||||
Console.WriteLine(compareObjects.DifferencesString)
|
||||
<SPAN style="COLOR: blue">End</SPAN> <SPAN style="COLOR: blue">If</SPAN>
|
||||
</PRE></DIV></project_gettingstarted>
|
||||
</item>
|
||||
<item qn="FSI.Lib.Compare-NET-Objects~FSI.Lib.CompareNetObjects_namespace">
|
||||
<summary ChangeDateTime="2012-07-20T18:37:25.5550682Z">Compare .NET Objects</summary>
|
||||
</item>
|
||||
<Filters>
|
||||
<CONTENTSTORECOMMON>
|
||||
<IsIncludedItemsOnly>true</IsIncludedItemsOnly>
|
||||
</CONTENTSTORECOMMON>
|
||||
<CONTENTSTORELOCALIZATION>
|
||||
<IsPrimaryLocaleContentPreviewEnabled>true</IsPrimaryLocaleContentPreviewEnabled>
|
||||
</CONTENTSTORELOCALIZATION>
|
||||
<DX.NETCONTENTSTOREFILTER>
|
||||
<ShowProtectedMembers>true</ShowProtectedMembers>
|
||||
<ShowProtectedFriendMembers>true</ShowProtectedFriendMembers>
|
||||
<ShowFriendMembers>false</ShowFriendMembers>
|
||||
<ShowPrivateMembers>false</ShowPrivateMembers>
|
||||
<ShowInheritedMembers>false</ShowInheritedMembers>
|
||||
<IsSourceCodeContentVisible>true</IsSourceCodeContentVisible>
|
||||
</DX.NETCONTENTSTOREFILTER>
|
||||
</Filters>
|
||||
</ContentFile>
|
||||
1071
FSI.Lib/FSI.Lib/CompareNetObjects/documentation/documentation.dxp
Normal file
1071
FSI.Lib/FSI.Lib/CompareNetObjects/documentation/documentation.dxp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApplicationWorkspace>
|
||||
<RecentItems>
|
||||
<RecentItem Key="0d9294fc-870e-4eb0-8d4e-a2fc0feef44f.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="2169f8f6-cac4-40ca-93b2-f02fefe798a9.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="eb1abafe-c230-45d7-b8dc-7bf3f5c20140.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="ebeae5b6-88ec-43e3-a898-92db1bcc4ba0.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="c669abc0-d388-48b8-ac62-15d9e7b5c797.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="b3f5ba5b-0359-4a92-b95c-8d4e952b3c5f.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="5380cb06-997d-4df1-8129-cfa28761d20f.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="cb91e07d-611c-4efa-84e5-a92187c24b81.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="7ceb9cfa-d0df-4c9c-b9df-ab271bccb7f7.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="8aaa1a6f-70a8-4a3d-8b69-dc429b86377a.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="10fb8965-c607-4150-be93-4219657192f9.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="7a032303-9805-4638-b34c-69a6db868ffc.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="0d061814-6a12-4d44-bb74-a2809f19fce6.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="6e4dfa3a-41cc-4f4f-933d-acc4ea832f0b.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="c5780969-16a4-48b1-8dac-34c0292912df.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="a6674676-6015-4a01-a478-a0ea8fe0510a.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="eee6a46b-ecf1-47b6-8a06-6f7e574c06ae.-" IsPinned="false" Type="Innovasys.ContentAuthoring.Topics.RecentTopic" Assembly="Innovasys.ContentAuthoring.Topics">
|
||||
<HandlerName>TOPICS</HandlerName>
|
||||
<Category>Topics</Category>
|
||||
</RecentItem>
|
||||
<RecentItem Key="FSI.Lib.Compare-NET-Objects.dxc.-" IsPinned="false" Type="Innovasys.DocumentX.ContentFiles.RecentContentFile" Assembly="Innovasys.DocumentX.ContentFiles">
|
||||
<HandlerName>CONTENT FILES</HandlerName>
|
||||
<Category>Content Files</Category>
|
||||
</RecentItem>
|
||||
</RecentItems>
|
||||
<ProjectProfileApplicationPlugin DesignTimeProjectProfile="39caa9b0-295d-4fcc-9037-4bc5f2195e31" DesignTimeProjectLocale="-" />
|
||||
</ApplicationWorkspace>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
BIN
FSI.Lib/FSI.Lib/CompareNetObjects/documentation/images/logo.png
Normal file
BIN
FSI.Lib/FSI.Lib/CompareNetObjects/documentation/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -35,5 +35,355 @@
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure" Version="2.0.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="TraceTool\Viewer\clientaccesspolicy.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\crossdomain.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\debug.bat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\DotNetWrapper.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\DotNetWrapper.pdb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\FastMM_FullDebugMode.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TraceTool.drc">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TraceTool.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TraceTool.exe - debug.lnk">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\tracetool.jmin.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\tracetool.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TraceTool.map">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TracetoolConfig.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TracetoolConfigProd.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\TraceTool_Icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\Fleck.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\Microsoft.Win32.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\netstandard.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.AppContext.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Collections.Concurrent.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Collections.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Collections.NonGeneric.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Collections.Specialized.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ComponentModel.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ComponentModel.EventBasedAsync.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ComponentModel.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ComponentModel.TypeConverter.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Console.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Data.Common.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.Contracts.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.Debug.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.FileVersionInfo.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.Process.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.StackTrace.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.TextWriterTraceListener.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.Tools.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.TraceSource.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Diagnostics.Tracing.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Drawing.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Dynamic.Runtime.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Globalization.Calendars.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Globalization.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Globalization.Extensions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.Compression.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.Compression.ZipFile.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.FileSystem.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.FileSystem.DriveInfo.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.FileSystem.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.FileSystem.Watcher.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.IsolatedStorage.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.MemoryMappedFiles.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.Pipes.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.IO.UnmanagedMemoryStream.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Linq.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Linq.Expressions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Linq.Parallel.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Linq.Queryable.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Http.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.NameResolution.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.NetworkInformation.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Ping.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Requests.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Security.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.Sockets.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.WebHeaderCollection.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.WebSockets.Client.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Net.WebSockets.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ObjectModel.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Reflection.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Reflection.Extensions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Reflection.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Resources.Reader.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Resources.ResourceManager.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Resources.Writer.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.CompilerServices.VisualC.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Extensions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Handles.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.InteropServices.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.InteropServices.RuntimeInformation.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Numerics.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Serialization.Formatters.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Serialization.Json.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Serialization.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Runtime.Serialization.Xml.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Claims.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Cryptography.Algorithms.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Cryptography.Csp.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Cryptography.Encoding.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Cryptography.Primitives.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Cryptography.X509Certificates.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.Principal.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Security.SecureString.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Text.Encoding.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Text.Encoding.Extensions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Text.RegularExpressions.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.Overlapped.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.Tasks.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.Tasks.Parallel.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.Thread.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.ThreadPool.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Threading.Timer.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.ValueTuple.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.ReaderWriter.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.XDocument.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.XmlDocument.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.XmlSerializer.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.XPath.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\System.Xml.XPath.XDocument.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\TraceTool.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\TraceTool.pdb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\WebsockPlugin.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TraceTool\Viewer\WebSock\WebsockPlugin.pdb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
<PasswordBox x:Name="TbPw"
|
||||
Margin=" 5 5 5 5"
|
||||
MinWidth="150"
|
||||
PasswordChanged="TbPw_PasswordChanged"
|
||||
KeyDown="TbPw_KeyDown"/>
|
||||
PasswordChanged="TbPw_PasswordChanged"
|
||||
KeyDown="TbPw_KeyDown" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
|
||||
@@ -66,14 +66,24 @@ namespace FSI.Lib.Guis.AutoPw
|
||||
|
||||
private void TbPw_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key != Key.Return && e.Key != Key.Enter)
|
||||
return;
|
||||
|
||||
if (BtnOk.IsEnabled == true)
|
||||
{
|
||||
{
|
||||
PwOk = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
PwOk = false;
|
||||
}
|
||||
Close();
|
||||
|
||||
}
|
||||
|
||||
private void TbPw_KeyDown_1(object sender, KeyEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
FSI.Lib/FSI.Lib/Guis/IbaDirSync/Model/Iba.cs
Normal file
15
FSI.Lib/FSI.Lib/Guis/IbaDirSync/Model/Iba.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.IbaDirSync.Model
|
||||
{
|
||||
public class Iba
|
||||
{
|
||||
public string Source { get; set; }
|
||||
public string Destination { get; set; }
|
||||
public bool AutoStart { get; set; }
|
||||
}
|
||||
}
|
||||
109
FSI.Lib/FSI.Lib/Guis/IbaDirSync/ViewModel/ViewModelIba.cs
Normal file
109
FSI.Lib/FSI.Lib/Guis/IbaDirSync/ViewModel/ViewModelIba.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using FSI.Lib.Guis.IbaDirSync.Model;
|
||||
using FSI.Lib.MVVM;
|
||||
using FSI.Lib.Tools.RoboSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.Guis.IbaDirSync.ViewModel
|
||||
{
|
||||
public class ViewModelIba : ViewModelBase
|
||||
{
|
||||
private ICommand _cmdStart;
|
||||
private ICommand _cmdStop;
|
||||
|
||||
private Iba _iba;
|
||||
|
||||
public ViewModelIba()
|
||||
{
|
||||
Iba = new Model.Iba();
|
||||
Init();
|
||||
}
|
||||
|
||||
public ViewModelIba(string destination, string source, bool autoStart)
|
||||
{
|
||||
_iba = new Model.Iba()
|
||||
{
|
||||
Destination = destination,
|
||||
Source = source,
|
||||
AutoStart = autoStart,
|
||||
};
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_cmdStart = new RelayCommand<object>(ExecuteStart, CanExecuteStart);
|
||||
_cmdStop = new RelayCommand<object>(ExecuteStop, CanExecuteStop);
|
||||
|
||||
RoboCopy = new Lib.Tools.RoboSharp.RoboCommand();
|
||||
|
||||
if (_iba.AutoStart)
|
||||
ExecuteStart(null);
|
||||
}
|
||||
|
||||
public Model.Iba Iba
|
||||
{
|
||||
get
|
||||
{
|
||||
return _iba;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_iba != value & _iba != null)
|
||||
{
|
||||
_iba = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RoboCommand RoboCopy { get; set; }
|
||||
|
||||
public ICommand CmdStart
|
||||
{
|
||||
get { return _cmdStart; }
|
||||
set => _cmdStart = value;
|
||||
}
|
||||
|
||||
private bool CanExecuteStop(object obj)
|
||||
{
|
||||
return RoboCopy.IsRunning;
|
||||
}
|
||||
|
||||
private void ExecuteStop(object obj)
|
||||
{
|
||||
if (RoboCopy != null)
|
||||
{
|
||||
RoboCopy.Stop();
|
||||
// RoboCopy.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand CmdStop
|
||||
{
|
||||
get { return _cmdStop; }
|
||||
set => _cmdStop = value;
|
||||
}
|
||||
|
||||
|
||||
private bool CanExecuteStart(object obj)
|
||||
{
|
||||
return !RoboCopy.IsRunning;
|
||||
}
|
||||
|
||||
private void ExecuteStart(object obj)
|
||||
{
|
||||
RoboCopy.CopyOptions.Source = Iba.Source;
|
||||
RoboCopy.CopyOptions.Destination = Iba.Destination;
|
||||
RoboCopy.CopyOptions.CopySubdirectories = true;
|
||||
RoboCopy.CopyOptions.MonitorSourceChangesLimit = 1;
|
||||
RoboCopy.StopIfDisposing = true;
|
||||
|
||||
RoboCopy.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,6 @@ namespace FSI.Lib.Guis.Prj.Mgt
|
||||
|
||||
private void Main_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
if (ShowPdf)
|
||||
{
|
||||
Title = "FSI PDF-Auswahl";
|
||||
|
||||
@@ -4,7 +4,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
18
FSI.Lib/FSI.Lib/Guis/SetSizePosExWindow/Model/Window.cs
Normal file
18
FSI.Lib/FSI.Lib/Guis/SetSizePosExWindow/Model/Window.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.SetSizePosExWindow.Model
|
||||
{
|
||||
public class Window
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using FSI.Lib.Guis.SetSizePosExWindow.Model;
|
||||
using FSI.Lib.MVVM;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.Guis.SetSizePosExWindow.ViewModel
|
||||
{
|
||||
public class ViewModelWindow : ViewModelBase
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr PostMessage(IntPtr hwndParent, int msg, int wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool SetWindowPos(IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter, int X, int Y, int cx, int cy,
|
||||
SetWindowPosFlags uFlags);
|
||||
|
||||
// Define the SetWindowPosFlags enumeration.
|
||||
[Flags()]
|
||||
private enum SetWindowPosFlags : uint
|
||||
{
|
||||
SynchronousWindowPosition = 0x4000,
|
||||
DeferErase = 0x2000,
|
||||
DrawFrame = 0x0020,
|
||||
FrameChanged = 0x0020,
|
||||
HideWindow = 0x0080,
|
||||
DoNotActivate = 0x0010,
|
||||
DoNotCopyBits = 0x0100,
|
||||
IgnoreMove = 0x0002,
|
||||
DoNotChangeOwnerZOrder = 0x0200,
|
||||
DoNotRedraw = 0x0008,
|
||||
DoNotReposition = 0x0200,
|
||||
DoNotSendChangingEvent = 0x0400,
|
||||
IgnoreResize = 0x0001,
|
||||
IgnoreZOrder = 0x0004,
|
||||
ShowWindow = 0x0040,
|
||||
}
|
||||
|
||||
private CancellationTokenSource tokenSource = null;
|
||||
private CancellationToken token;
|
||||
private Task task = null;
|
||||
|
||||
private BackgroundWorker backgroundWorker;
|
||||
private ICommand _cmdStart;
|
||||
private ICommand _cmdStop;
|
||||
private ObservableCollection<Model.Window> _windows;
|
||||
private int _updateIntervall;
|
||||
private bool _autoStart;
|
||||
|
||||
public ViewModelWindow()
|
||||
{
|
||||
_windows = new ObservableCollection<Model.Window>();
|
||||
|
||||
|
||||
Init();
|
||||
|
||||
if (AutoStart)
|
||||
{
|
||||
ExecuteStart(null);
|
||||
}
|
||||
}
|
||||
|
||||
public ViewModelWindow(bool autoStart)
|
||||
{
|
||||
_windows = new ObservableCollection<Model.Window>();
|
||||
|
||||
Init();
|
||||
|
||||
AutoStart = autoStart;
|
||||
|
||||
if (AutoStart)
|
||||
{
|
||||
ExecuteStart(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_cmdStart = new RelayCommand<object>(ExecuteStart, CanExecuteStart);
|
||||
_cmdStop = new RelayCommand<object>(ExecuteStop, CanExecuteStop);
|
||||
|
||||
backgroundWorker = new BackgroundWorker
|
||||
{
|
||||
WorkerReportsProgress = true,
|
||||
WorkerSupportsCancellation = true
|
||||
};
|
||||
backgroundWorker.DoWork += BackgroundWorker_DoWork;
|
||||
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
|
||||
}
|
||||
|
||||
public ObservableCollection<Model.Window> Windows
|
||||
{
|
||||
get
|
||||
{
|
||||
return _windows;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_windows != value & _windows != null)
|
||||
{
|
||||
_windows = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int UpdateIntervall
|
||||
{
|
||||
get
|
||||
{
|
||||
return _updateIntervall;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_updateIntervall != value & _updateIntervall != null)
|
||||
{
|
||||
_updateIntervall = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool AutoStart
|
||||
{
|
||||
get
|
||||
{
|
||||
return _autoStart;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_autoStart != value & _autoStart != null)
|
||||
{
|
||||
_autoStart = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand CmdStart
|
||||
{
|
||||
get { return _cmdStart; }
|
||||
set => _cmdStart = value;
|
||||
}
|
||||
|
||||
private bool CanExecuteStop(object obj)
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
private void ExecuteStop(object obj)
|
||||
{
|
||||
if (backgroundWorker.WorkerSupportsCancellation == true)
|
||||
{
|
||||
backgroundWorker.CancelAsync();
|
||||
}
|
||||
Status = false;
|
||||
}
|
||||
|
||||
public ICommand CmdStop
|
||||
{
|
||||
get { return _cmdStop; }
|
||||
set => _cmdStop = value;
|
||||
}
|
||||
|
||||
private bool CanExecuteStart(object obj)
|
||||
{
|
||||
return !Status;
|
||||
}
|
||||
|
||||
private void ExecuteStart(object obj)
|
||||
{
|
||||
if (backgroundWorker.IsBusy != true)
|
||||
{
|
||||
backgroundWorker.RunWorkerAsync();
|
||||
}
|
||||
Status = true;
|
||||
}
|
||||
|
||||
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
|
||||
{
|
||||
BackgroundWorker worker = sender as BackgroundWorker;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (worker.CancellationPending == true)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Window window in Windows)
|
||||
{
|
||||
// Perform a time consuming operation and report progress.
|
||||
Thread.Sleep(UpdateIntervall);
|
||||
|
||||
// Find windos by Name
|
||||
var windowHandle = FindWindow(window.ClassName, window.Name);
|
||||
|
||||
// Set windows size and position
|
||||
SetWindowPos(windowHandle, IntPtr.Zero, window.X, window.Y, window.Width, window.Height, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Status { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
/// </summary>
|
||||
public partial class FrmMain : Window
|
||||
{
|
||||
public WinCC WinCC { get; set; }
|
||||
//public WinCC WinCC { get; set; }
|
||||
|
||||
public FrmMain()
|
||||
{
|
||||
@@ -28,30 +28,30 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
|
||||
private void btnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinCC.Start();
|
||||
//WinCC.Start();
|
||||
CtrlMgt();
|
||||
}
|
||||
|
||||
private void btnStop_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WinCC.Stop();
|
||||
//WinCC.Stop();
|
||||
CtrlMgt();
|
||||
}
|
||||
|
||||
private void CtrlMgt()
|
||||
{
|
||||
if (WinCC.Status)
|
||||
{
|
||||
btnStart.IsEnabled = false;
|
||||
btnStop.IsEnabled = true;
|
||||
pgProgress.IsIndeterminate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
btnStart.IsEnabled = true;
|
||||
btnStop.IsEnabled = false;
|
||||
pgProgress.IsIndeterminate = false;
|
||||
}
|
||||
//if (WinCC.Status)
|
||||
//{
|
||||
// btnStart.IsEnabled = false;
|
||||
// btnStop.IsEnabled = true;
|
||||
// pgProgress.IsIndeterminate = true;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// btnStart.IsEnabled = true;
|
||||
// btnStop.IsEnabled = false;
|
||||
// pgProgress.IsIndeterminate = false;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
FSI.Lib/FSI.Lib/Guis/SieTiaWinCCMsgMgt/Model/WinCC.cs
Normal file
17
FSI.Lib/FSI.Lib/Guis/SieTiaWinCCMsgMgt/Model/WinCC.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt.Model
|
||||
{
|
||||
public class WinCC
|
||||
{
|
||||
public bool AutoStart { get; set; }
|
||||
public int UpdateIntervall { get; set; }
|
||||
public string WindowsName { get; set; }
|
||||
public string WindowsClassName { get; set; }
|
||||
public string ButtonName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FSI.Lib.Guis.SieTiaWinCCMsgMgt.Model;
|
||||
using FSI.Lib.MVVM;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt.ViewModel
|
||||
{
|
||||
public class WinCC
|
||||
public class ViewModelWinCC : MVVM.ViewModelBase
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
@@ -17,9 +17,8 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
|
||||
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
@@ -30,45 +29,54 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
|
||||
static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
const int BM_CLICK = 0x00F5; //message for Click which is a constant
|
||||
const int WM_LBUTTONDOWN = 0x0201; //message for Mouse down
|
||||
const int WM_LBUTTONUP = 0x0202; // message for Mouse up
|
||||
|
||||
|
||||
private CancellationTokenSource tokenSource = null;
|
||||
private CancellationToken token;
|
||||
private Task task = null;
|
||||
|
||||
private BackgroundWorker backgroundWorker;
|
||||
|
||||
private BackgroundWorker backgroundWorker;
|
||||
private ICommand _cmdStart;
|
||||
private ICommand _cmdStop;
|
||||
private WinCC _winCC;
|
||||
|
||||
|
||||
public bool AutoStart { get; set; }
|
||||
public int UpdateIntervall { get; set; }
|
||||
public string WindowsName { get; set; }
|
||||
public string WindowsClassName { get; set; }
|
||||
public string ButtonName { get; set; }
|
||||
|
||||
public WinCC()
|
||||
public ViewModelWinCC()
|
||||
{
|
||||
backgroundWorker = new BackgroundWorker
|
||||
{
|
||||
WorkerReportsProgress = true,
|
||||
WorkerSupportsCancellation = true
|
||||
};
|
||||
backgroundWorker.DoWork += BackgroundWorker_DoWork;
|
||||
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
|
||||
|
||||
|
||||
WinCC = new Model.WinCC();
|
||||
Init();
|
||||
}
|
||||
|
||||
public WinCC(bool autoStart, int updateIntervall, string windowsName, string windowsClassName, string buttonName)
|
||||
public ViewModelWinCC(bool autoStart, int updateIntervall, string windowsName, string windowsClassName, string buttonName)
|
||||
{
|
||||
AutoStart = autoStart;
|
||||
UpdateIntervall = updateIntervall;
|
||||
WindowsName = windowsName;
|
||||
WindowsClassName = windowsClassName;
|
||||
ButtonName = buttonName;
|
||||
_winCC = new Model.WinCC
|
||||
{
|
||||
AutoStart = autoStart,
|
||||
UpdateIntervall = updateIntervall,
|
||||
WindowsName = windowsName,
|
||||
WindowsClassName = windowsClassName,
|
||||
ButtonName = buttonName
|
||||
};
|
||||
|
||||
//_seletctedWinCC = WinCC;
|
||||
|
||||
Init();
|
||||
|
||||
if (WinCC.AutoStart)
|
||||
{
|
||||
ExecuteStart(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_cmdStart = new RelayCommand<object>(ExecuteStart, CanExecuteStart);
|
||||
_cmdStop = new RelayCommand<object>(ExecuteStop, CanExecuteStop);
|
||||
|
||||
backgroundWorker = new BackgroundWorker
|
||||
{
|
||||
@@ -77,11 +85,63 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
};
|
||||
backgroundWorker.DoWork += BackgroundWorker_DoWork;
|
||||
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
|
||||
}
|
||||
|
||||
if (AutoStart)
|
||||
public Model.WinCC WinCC
|
||||
{
|
||||
get
|
||||
{
|
||||
Start();
|
||||
return _winCC;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_winCC != value & _winCC != null)
|
||||
{
|
||||
_winCC = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand CmdStart
|
||||
{
|
||||
get { return _cmdStart; }
|
||||
set => _cmdStart = value;
|
||||
}
|
||||
|
||||
private bool CanExecuteStop(object obj)
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
private void ExecuteStop(object obj)
|
||||
{
|
||||
if (backgroundWorker.WorkerSupportsCancellation == true)
|
||||
{
|
||||
backgroundWorker.CancelAsync();
|
||||
}
|
||||
Status = false;
|
||||
}
|
||||
|
||||
public ICommand CmdStop
|
||||
{
|
||||
get { return _cmdStop; }
|
||||
set => _cmdStop = value;
|
||||
}
|
||||
|
||||
|
||||
private bool CanExecuteStart(object obj)
|
||||
{
|
||||
return !Status;
|
||||
}
|
||||
|
||||
private void ExecuteStart(object obj)
|
||||
{
|
||||
if (backgroundWorker.IsBusy != true)
|
||||
{
|
||||
backgroundWorker.RunWorkerAsync();
|
||||
}
|
||||
Status = true;
|
||||
}
|
||||
|
||||
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
@@ -103,41 +163,24 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
||||
else
|
||||
{
|
||||
// Perform a time consuming operation and report progress.
|
||||
System.Threading.Thread.Sleep(UpdateIntervall);
|
||||
Thread.Sleep(WinCC.UpdateIntervall);
|
||||
|
||||
// Find windos by Name
|
||||
var windowHandle = FindWindow(WindowsClassName, WindowsName);
|
||||
var windowHandle = FindWindow(WinCC.WindowsClassName, WinCC.WindowsName);
|
||||
|
||||
if (windowHandle != IntPtr.Zero && IsWindowVisible(windowHandle))
|
||||
{
|
||||
SetForegroundWindow(windowHandle);
|
||||
var btnHandle = FindWindowEx(windowHandle, IntPtr.Zero, null, ButtonName);
|
||||
SendMessage(btnHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
|
||||
var btnHandle = FindWindowEx(windowHandle, IntPtr.Zero, null, WinCC.ButtonName);
|
||||
SendMessage(btnHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (backgroundWorker.IsBusy != true)
|
||||
{
|
||||
backgroundWorker.RunWorkerAsync();
|
||||
}
|
||||
Status = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (backgroundWorker.WorkerSupportsCancellation == true)
|
||||
{
|
||||
backgroundWorker.CancelAsync();
|
||||
}
|
||||
Status = false;
|
||||
}
|
||||
|
||||
public bool Status { get; set; }
|
||||
|
||||
|
||||
private void WinCCMsgMgt()
|
||||
{
|
||||
while (true)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user