5 min read

Folder structure in Revit API C#

Folder structure in Revit API C#

Everytime i learn new technology, my habit is looking for "best practice" of folder structure. Folder structure itself mention a part of that technology architect also concept it trying to delivery. In fact, there are a lot of new techonology document metion directly how to structure folder with their framework.
Having a "good" folder structure give you a lot of advantages, such as:

  1. Easy to scale up, in both ways horizontal (add more tools) also vertical (complexity of tool).
  2. Easy to maintenance and fixing.
  3. Easy to manage, very clearly to understand.
  4. Your DX (developer experience) will be brought to the whole new lever (DX is very important with us, who do programing i will drop an article about it).

In this article, i will show you how i structure my Revit API C# project. After couple of years, with changing my folder structure several times, for now my structure folder is more "stable". This is also a structure i applied for my commercialize project, which have high complexity. So i think it have enough "reputation" to sharing with you, guys.
Disclaimer: This is not a best practice, you only should take it like reference.

My background is not an IT engineer, so i do not try apply something too complicated to my project like "design pattern" in anyways. I just focus on how to my project can be easily expanded and cleary as most as possible.
The structure itself also 'borrows' quite a few technical concepts that I find useful when working with server projects (.Net, Node.js) or web applications (frameworks like Angular, React, ...). So it might be a bit difficult to understand, but i'll try to explain it in the simplest way possible.

General structure

folder-structure.png

Break it down

Looking at the big picture, you can see that I group objects into the same functional or business group, which can be interpreted as follows:

  1. Extensions: is extension methods for object/ type.

extension.png

For instance: ArrayExtensions, i often use the practice of splitting a list into multiple lists, with each list containing no more than n objects. So I've written a separate extension for that purpose.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RevitPlugin.Extensions
{
    public static class ArrayExtensions
    {
        public static List<List<T>> SplitListIntoNLists<T>(this List<T> list, int n)
        {
            int chunkSize = (int)Math.Ceiling((double)list.Count / n);

            return Enumerable.Range(0, n)
                .Select(i => list.Skip(i * chunkSize).Take(chunkSize).ToList())
                .ToList();
        }
    }
}

And when i need it, just call like this:

List<List<Element>> chunkList = model.Elements.SplitListIntoNLists(30);
  1. Fonts: where store font files.

  2. Helper: where i store the functions i frequently use in various tools, centralizing them makes it easy for me to upgrade and fix bugs - fix in one place and apply to all tools.

helper.png

For instance UnitBaseUtils, where i stored all change unit functions.

using Autodesk.Revit.DB;

namespace RevitPlugin.Helper.RevitUtils
{
    public static class UnitBaseUtils
    {
        public static double MmToFt(double a)
        {
#if REVIT2021
                return UnitUtils.ConvertToInternalUnits(a,UnitTypeId.Millimeters);
#elif REVIT2022 || REVIT2023 || REVIT2024
            return UnitUtils.ConvertToInternalUnits(a, UnitTypeId.Millimeters);
#else
            return UnitUtils.Convert(a, DisplayUnitType.DUT_MILLIMETERS, DisplayUnitType.DUT_DECIMAL_FEET);
#endif
        }
        public static double FtToMm(double a)
        {
#if REVIT2021
                return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.Millimeters);
#elif REVIT2022 || REVIT2023 || REVIT2024
                return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.Millimeters);
#else
            return UnitUtils.Convert(a, DisplayUnitType.DUT_DECIMAL_FEET, DisplayUnitType.DUT_MILLIMETERS);
#endif
        }
        public static double F3toM3(double a)
        {
#if REVIT2021
                return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.CubicMeters);
#elif REVIT2022 || REVIT2023 || REVIT2024
            return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.CubicMeters);
#else
            return UnitUtils.Convert(a, DisplayUnitType.DUT_CUBIC_FEET, DisplayUnitType.DUT_CUBIC_METERS);
#endif
        }
        public static double F2toM2(double a)
        {
#if REVIT2021
                return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.SquareMeters);
#elif REVIT2022 || REVIT2023 || REVIT2024
            return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.SquareMeters);
#else
            return UnitUtils.Convert(a, DisplayUnitType.DUT_SQUARE_FEET, DisplayUnitType.DUT_SQUARE_METERS);
#endif
        }
        public static double RadToDegree(double a)
        {
#if REVIT2021
                return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.Degrees);
#elif REVIT2022 || REVIT2023 || REVIT2024
            return UnitUtils.ConvertFromInternalUnits(a, UnitTypeId.Degrees);
#else
            return UnitUtils.Convert(a, DisplayUnitType.DUT_RADIANS, DisplayUnitType.DUT_DECIMAL_DEGREES);
#endif
        }
    }
}

And when i need it, just call like this:

double roundNumber = UnitBaseUtils.MmToFt(round);
  1. Model: where i store all data type in project.

  2. Resources: where i store all static files, such as: image, icon,...

  3. Services: This is where all the standalone, complete services are stored. Essentially, they are functions I use repeatedly in my tools, not much different from helpers. The difference lies in how I define these services myself. Helpers are small functions serving a larger main business, while these services are independent functions that address independent and unrelated (or partially related) tasks to the main business.

For example, some services I have include:

- Cache data
- Log system
- Call API
- I/O file

...

  1. Stores: This is where data exists throughout the application's lifecycle, from when it starts running to when it ends. Specifically with the Revit API, it's from OnStartup to OnShutdown, simply understood as data only existing from when the user launches Revit to when they close it. There are some types of data that only need to be recorded while the user is using the tool, such as error logs, authentication, etc., and those are placed in this category.

stores.png

For instance AuthenticateStore, where i store authentication data from user, when they logged in.

using RevitPlugin.Model.UserProfile;

namespace RevitPlugin.Stores
{
    public class AuthenticateStore
    {
        public static UserProfileModel Me { get; set; } = new UserProfileModel();
        public static bool IsAuthed { get; set; } = false;
    }
}

  1. Tools: where i stored all tools, my tool also applied MVVM pattern, i will talk about it later.

tools.png

Conclusion

Having a good directory structure helps you have a better development experience. As projects become more complex or grow in volume, you can easily adapt without worrying about adjustments. The process of 'breaking down and rebuilding' in programming is essential; it shows that the project is evolving, and you must change to adapt. It's also evidence that you yourself are developing; your knowledge is increasing, and the old structure is no longer suitable. Hopefully, through this article, you can gain additional insights to reference.