Published on

Cấu trúc thư mục trong Revit API C#

Khi tiếp xúc với bất kỳ công nghệ mới nào, thói quen đầu tiên của mình sẽ là tìm hiểu về cấu trúc thư mục khi triển khai dự án. Cấu trúc thư mục cũng nói lên một phần kiến trúc, concept mà công nghệ đó đang cố gắng diễn đạt cũng như vấn đề mà nó đang giải quyết. Thực vậy, đa số các công nghệ mới ngày nay thường sẽ đề xuất luôn cấu trúc thư mục cho các bạn tham khảo. Việc có một cấu trúc "ổn" sẽ mang lại một số lợi ích có thể kể đến như:

  1. Dễ dàng mở rộng. Mở rộng dự án theo chiều ngang (nhiều tool hơn) và mở rộng dự án theo chiều dọc (tool trở nên phức tạp hơn).
  2. Dễ dàng nâng cấp, chỉnh sửa.
  3. Dễ dàng quản lý, tường minh dễ hiểu.
  4. Trải nghiệm phát triển (DX: developer experience) trở nên dễ chịu hơn (điều này là cực kỳ quan trọng với người lập trình, có cơ hội mình sẽ chia sẽ kỹ hơn).

Trong phạm vi bài viết, mình xin chia sẽ cách mình tổ chức cấu trúc thư mục khi triển khai dự án Revit API C#. Sau vài năm tìm tòi và thay đổi cấu trúc vài lần, cho tới hiện nay cấu trúc này của mình đã ổn định và mình không có điều chỉnh gì thêm nữa. Đây cũng là cấu trúc mình áp dụng cho vài dự án thương mại, có độ phức tạp cao nên mình nghĩ là "ổn" để chia sẽ cho các bạn tham khảo.

Xin lưu ý, đây không phải là "best practice", là kim chỉ nam cho các bạn. Hãy cân nhắc và tham khảo.

Mình xuất thân không phải là dân IT, bản thân cũng không áp dụng bất kỳ design pattern nào vào dự án của mình (mình cho rằng không cần thiết). Nên mình tập trung vào làm sao cấu trúc của mình tường minh, rõ ràng và dễ mở rộng. Bản thân cấu trúc này cũng đã "vay mượn" khá nhiều concept kỹ thuật mà mình thấy hay khi làm việc với các dự án server (.Net, nodejs) hoặc ứng dụng web (các framework như Angular, React, ...). Nên có thể sẽ hơi khó hiểu, nhưng mình sẽ cố gắng giải thích đơn giản nhất có thể.

Cấu trúc chung

folder-structure

Diễn giải

Nhìn vào tổng thể, các bạn có thể thấy mình nhóm các đối tượng vào cùng một nhóm công năng hoặc nghiệp vụ, có thể diễn giải như sau:

  1. Extensions: là các hàm mở rộng cho các object/ type có sẵn
folder-structure

Lấy ví dụ ArrayExtensions, mình hay sử dụng việc chia nhỏ một list thành nhiều list với mỗi list không qua n đối tượng. Nên mình viết riêng 1 extension cho việc đó.

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();
        }
    }
}

Và khi cần dùng thì mình chỉ cần

List<List<Element>> chunkList = model.Elements.SplitListIntoNLists(30);
  1. Fonts: là thư mục chứa các file font để mình sử dụng trong tool.

  2. Helper: đúng như tên gọi của nó "helper":"người giúp đỡ" là nơi mình chứa các hàm mình hay dùng trong các tool, việc quy hoạch một chỗ giúp mình dễ dàng nâng cấp, fix lỗi - sửa 1 nơi và sẽ apply cho tất cả các tool.

folder-structure

Lấy ví dụ UnitBaseUtils, là nơi chứa tất cả các hàm đổi đơn vị.

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
        }
    }
}

Và khi cần dùng thì mình chỉ cần

double roundNumber = UnitBaseUtils.MmToFt(round);
  1. Model: là nơi chứa tất cả các kiểu dữ liệu (data type) trong dự án.

  2. Resources: là nơi chứa tất cả các file tĩnh như hình ảnh, các icon sử dụng làm ribbon trong tool.

  3. Services: là nơi chứa tất cả các dịch vụ độc lập, hoàn chỉnh. Về bản chất cũng là các hàm mình sử dụng nhiều lần trong tool và không khác gì so với helper. Khác biệt ở đây là cách bản thân mình tự định nghĩa về các service. Helper là các hàm nhỏ phục vụ cho một nghiệp vụ chính lớn hơn, còn các service này là các hàm độc lập, giải quyết các nghiệp vụ độc lập và không liên quan (hoặc liên quan một phần) tới nghiệp vụ chính. Lấy ví dụ các service mình có như:

    • Cache data
    • Ghi log
    • Gửi/ nhận API
    • Ghi, xóa file ...
  4. Stores: là nơi chứa data tồn tại trong vòng đời ứng dụng từ khi chạy tới lúc kết thúc. Cụ thể với Revit API là từ lúc OnStarup tới OnShutdown, hiểu đơn giản là data chỉ tồn tại từ khi người dùng bật revit và kết thúc khi tắt revit. Có một số loại dữ liệu chỉ cần ghi nhận trong lúc người dùng sử dụng tool, có thể kể tới như log lỗi, xác thực, ... thì mình cho vào mục này.

folder-structure

Lấy ví dụ AuthenticateStore, là nơi mình chứa thông tin người dùng khi họ đã login vào tool của mình.

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: là nơi chứa các tool trong dự án, mình có áp dụng mô hình MVVM khi triển khai tool, có thể mình sẽ có bài chia sẽ riêng về mô hình này khi phát triển tool.
folder-structure

Tạm kết

Việc có một cấu trúc thư mục tốt giúp các bạn có trải nghiệm phát triển tuyệt vời hơn. Khi dự án trở nên phức tạp, hoặc khối lượng tăng lên thì các bạn cũng thể dễ dàng thích nghi mà không cần phải trăn trở việc điều chỉnh. Quá trình "đập đi xây lại" trong lập trình là vô cùng cần thiết, nó thể hiện dự án đang phát triển, bạn phải thay đổi để thích nghi. Và cũng là minh chứng cho việc chính bản thân bạn cũng đang phát triển, kiến thức bạn có tăng lên và cấu trúc cũ không còn phù hợp nữa. Hy vọng thông qua bài viết này có thể cung cấp cho các bạn thêm thông tin để tham khảo.