This is an excellent question, and its applicability is broader than the author may have anticipated.
Many a WPF project has turned into spaghetti goo because of n00b programmers who do not quite understand MVVM, and the importance of separation between application logic and presentation logic.
Most WPF programmers put views and viewmodels side by side in the same directory. Every single WPF project I have ever seen is structured like that. It may be convenient because the source files are always next to each other, but it is dead wrong, because it means that both views and viewmodels reside in the same project, which means that the WPF assemblies are available to application logic, which means that there is no separation between application logic and presentation logic. (Which is what MVVM was mainly invented to address.)
That's how you get tests running on a CI/CD server eternally stuck waiting for someone to click OK on an application-modal message box, and other hilarious situations like that.
To prevent this, it is prudent to put all application logic in a separate project, and refrain from having that project reference any WPF assemblies. This may necessitate some infrastructure work, like abstracting the MessageBox
facility, the Dispatcher
, etc. and providing (injecting) these abstractions to the application logic, but it is very well worth doing.
Once you have accomplished this separation between application logic and presentation logic, the next question is how to prevent n00b programmers from mixing them together again.
For example, as soon as someone needs to work with the concept of colors in application logic, they won't even blink before referencing the WPF assemblies to start making use of System.Windows.Color
. The question is how to prevent that.
Other answers to this question suggest using third-party tools, using code analysis, and even parsing project files. User "Ben" wrote in his answer that he solved the problem programmatically, but he did not show any code.
So, I had to write the code. Here is how I did it:
Assert( noProhibitedAssembliesAreReferencedAssertion() );
.
.
.
private static bool noProhibitedAssembliesAreReferencedAssertion()
{
ImmutableList<string> names = Assembly.GetExecutingAssembly()
.GetReferencedAssemblies()
.Select( a => a.Name )
.Where( isProhibitedAssemblyName )
.ToImmutableList();
if( names.Count > 0 )
{
dumpReferencedAssembliesOfModule( "Executing assembly (should not reference prohibited assemblies)",
Assembly.GetExecutingAssembly() );
dumpReferencedAssembliesOfModule( "Calling assembly (can reference any assembly)",
Assembly.GetCallingAssembly() );
throw new AssertionFailureException( $"The application logic references prohibited assemblies: {names.MakeString( ", " )}" );
}
return true;
}
private static readonly string[] prohibitedAssemblyNames =
{
"System.Windows", //
"PresentationFramework", //
"WindowsBase", //
"System.Xaml", //
"PresentationCore", //
"System.Printing", //
"ReachFramework", //
"MahApps.Metro", //
"SciChart.Charting", //
"SciChart.Charting3D", //
"SciChart.Core", //
"SciChart.Drawing", //
"SciChart.Data"
};
private static bool isProhibitedAssemblyName( string assemblyName )
{
return prohibitedAssemblyNames.Contains( assemblyName );
}
private static void dumpReferencedAssembliesOfModule( string message, Assembly assembly )
{
logger.Info( $"{message}: {assembly.FullName}" );
logger.Info( " GetModules():" );
foreach( Module module in assembly.GetModules() )
logger.Info( $" {module.Name} {module.Assembly}" );
logger.Info( " GetReferencedAssemblies():" );
foreach( var referencedAssembly in assembly.GetReferencedAssemblies() )
logger.Info( $" {referencedAssembly.Name}" );
}
The above code sits in the main application logic object, (your MainViewModel
or equivalent,) and checks which assemblies have been referenced by the application logic assembly. If it finds any "prohibited" assemblies, it deliberately fails. This code runs during application startup, but also in application logic tests, when the tests instantiate the MainViewModel
.
Be sure to accompany this code with great big huge warning comments telling anyone that if it fails, they should contact you before changing anything.
Replacing assembly names with regular expression patterns is left as an exercise to the reader.