C# .NET MVVM – Software Architecture and Naming Conventions

cdesignmvvmnetwpf

I am currently working on a software using C# .NET and WPF with the MVVM pattern.

Though the software is almost done, I have been discussing our architecture for a long time and I would like to get your opinions/advices on the convention/good practices side.

The current architecture is as follows:

  • Project/
    • DataContext/
    • Models/
      • Engine3D/
    • Utilities/
      • Converters/
      • Statics/
    • ViewModels/
      • Grids/
      • Menus/
      • Windows/
    • Views/
      • Grids/
      • Menus/
      • Windows/

Models folder:

Contains the Models from the MVVM pattern.

The Models folder is organized by feature, meaning that it has nested folders named after a feature or context, for example Models/Engine3D for every Model related to 3D.

We never append the "Model" word as a suffix, the files are named [Feature].cs
For example, Project.cs, Model3D.cs, Animation.cs, etc…

Views folder:

Contains the Views from the MVVM pattern.

The Views folder is organized by WPF Control, meaning that it has nested folders named after a WPF Control, for example Views/Windows for every View related to a WPF Window, or View/Grids, etc…

The files are named [Feature][ControlType].xaml with:

  • [Feature]: The "feature" implemented/described by the View, for example "CreateProject" for a feature creating a Project, Display3DModel for a Grid displaying a Model3D (using 3D libraries), etc…
  • [ControlType]: The type of "xaml" control represented by the file, being a Window, a UserControl, a Grid, etc… As an example, a Window displaying the System Preferences would be called SystemPreferencesWindow.xaml, and a Grid holding the list of all the Projects would be ProjectsListGrid.xaml.
    For example, CreateProjectWindow.xaml, SystemPreferencesWindow.xaml, Display3DModelGrid.xaml, ProjectsListGrid.xaml

ViewModels folder:

Contains the ViewModels from the MVVM pattern.

The ViewModels folder copies the Views folder in terms of architecture, meaning that it has nested folders named after a WPF Control, for example ViewModels/Windows for every ViewModel related to a WPF Window, or ViewModels/Grids.

A ViewModel file has the same name as its related View file, with the suffix "ViewModel": the files are named [Feature][ControlType]ViewModel.cs with:

For example, based on View => ViewModel:
CreateProjectWindow.xaml => CreateProjectWindowViewModel.cs, OpenProjectWindow.xaml => OpenProjectWindowViewModel.cs, etc…

DataContext folder:

Contains the DataContext files, ie. files related to a certain context in the software; these are classes that are neither ViewModels or Models, are mostly accessible "staticly" and correspond to a very specific context in the software execution flow so don't really belong in the Utilities folder.
These classes are for example the Session class holding session values concerning the user like the web technologies' sessions.

Utilities folder:

Contains a set of files/classes tools of various goals :

  • Definition classes, giving system mechanisms to the overall software such as ObservableObject.cs, DelegateCommand.cs and RelayCommand.cs which permit MVVM data binding, etc…
  • Utilities/Converters folder: Converters classes…
  • Utilities/Statices folder: Contains static operations classes taking an input and returning an output, for example FileOperations.cs, XWindow.cs (don't bother with the name, it's project's specific) that regroups System.Windows operations like InvokeWindow() and CloseWindow() so that it's easier to manipulate System.Windows in the code, Model3DImporter.cs which imports 3D Models very easily, SerializationManager.cs for serialization operations, etc…

Classes with constants:

Some classes need constants values and since these values must be accessed from pretty much everywhere in the code, we didn't store these constant values in the related classes but in a "sister" class.

These "sister" classes are as follows:

// The sister class
public static class Model3DConstants
{
    private static readonly IList<String> supportedFileExtensions = ...
}

// The original class
public class Model3D
{
}

As I mentioned in the beginning of my message, I would like your ideas/advices on our architecture/naming conventions, because I am sure that we are missing something out but don't know what.

Are we doing the complete opposite of what should be done in a correct C# .NET MVVM software using WPF, what are the flaws and/or stuff that we should modify

Do not hesitate to participate in the debate neither or even ask me questions if I am unclear about the above description.

Best Answer

This may not answer everything you are asking, but it's too much for a comment.

Your solution's organization (I use the term organization rather than architecture) should reflect how you use it. It should be efficient for your use. For instance, when you want to make a change to a Menu does a developer have to open files from View/Menus and also from ViewModels/Menus to make the change? (This is inefficient.) Or do you have designers who only ever open Views, and developers who only ever open ViewModels?

This is why single team projects might prefer a feature-oriented approach (with related Views and View Models in the same feature folder) whereas large projects with multiple teams might prefer a component-oriented approach like your current structure. (This may also be the reason for Conway's law.)

I make a distinction (at least mentally) between Utilities and Infrastructure. Infrastructure code is a core part of your application's inner workings that is used by policy (e.g. "We always use RelayCommand on these WPF controls." -- UI Infrastructure). Whereas Utilities are generally useful purpose-built functions like perhaps FileOperations. I'm not suggesting that you create a new Infrastructure project or rename anything. Just make sure that all your "must use" components are in a highly accessible place, which it sounds like they are.

Utility code is at greatest risk for being forgotten or ignored. So make sure that what you put there is truly useful and time-saving. If you pollute it with a lot of minor helpers, other developers will not take the time to familiarize themselves with it and miss the opportunity to save time.

And then there are Helpers which are what I deem reusable code which is helpful across a few specific components. Maybe it's shared code used for related features. I generally keep these close to the calling code if possible and not in Utilities. This way, the code is not far off when a developer needs to work on that feature, and it doesn't add cruft to the more highly-reusable Utilities.

I look at it like: developers must be familiar with Infrastructure, should be familiar with Utilities, and may be familiar with a given feature's Helpers (if they work on that feature).