Kentico Xperience Design Patterns: Multiple Types Per File
Tags: xperience, kentico, aspnetcore, csharp
Published: 2021-06-01 13:06:07

---
title: Kentico Xperience Design Patterns: Multiple Types Per File
published: true
description: Learn why C# developers separate all their types into individual files and when it might be appropriate to do the opposite in our Kentico Xperience applications.
tags: xperience, kentico, aspnetcore, csharp
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

Conventions in the C# development community dictate that each class or interface should be in its own file.

I think this is, generally, a fine convention to follow, especially for newer developers who might not understand the trouble they can get into when projects grow in size and complexity.

However, I also think there are many good opportunities to break with this convention when following the best practices of co-location and [cohesion](https://www.geeksforgeeks.org/software-engineering-differences-between-coupling-and-cohesion/).

Let's explore this further...

## 📚 What Will We Learn?

- The Goal of 1 Class Per File
- Letter of the Law vs Spirit of the Law
- Things that Change Together, Stay Together
- Where Multiple Types per File make sense in Kentico Xperience

## 🧱 The Goal of 1 Class Per File

Back in the old times 👵🏽 (mid-2000s), C# was a new programming language that followed the patterns of languages like C++ and Java, and it was more verbose in syntax than it is today.

Here's an example made-up C# class from that era:

```csharp
public class Product
{
    private int _id;

    public Product(int id)
    {
       _id = id;
    }

    public int Id
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
        }
    }

    public bool IsValid
    {
        get
        {
            return _id > 0;
        }
    }
}
```

Ya, that's a lot of lines! Let's compare that to what it might look like as a C# 9 record type written today:

```csharp
public record Product(int Id)
{
    bool IsValid => Id > 0;
}
```

Yes, this is a contrived example, but I'm showing it to support the idea that C# code used to take up a lot more space, which meant each class definition of non-trivial complexity could be quite large 😯.

At that time, having multiple class definitions in one file could lead to difficult to navigate and understand files, and **given the two extremes of every class in its own file and every class in one file, the file-per-class convention is unquestionably better**.

One class-per-file can help developers focus on a unit of encapsulated behavior when working on a class. It's easy to tell how complex an individual class has become by looking at the line count of a file, and developers might establish a habit 😉 of defining clear functional boundaries for a class when it's not surrounded by others.

The true goals of the '1 class per file' rule are improving code organization and reducing code complexity, and I think these goals are mostly achieved with this coding convention. Of course, there are exceptions to every rule!

## ⚖ Letter of the Law vs Spirit of the Law

There is a philosophical concept called the [letter and spirit of the law](https://en.wikipedia.org/wiki/Letter_and_spirit_of_the_law):

> When one obeys the letter of the law but not the spirit, one is obeying the literal interpretation of the words (the "letter") of the law, but not necessarily the intent of those who wrote the law.

Always creating a file in C# for every type is following the 'letter of the law', but not necessarily the spirit or original intent of making code easier to navigate and understand.

Let's look at a couple of examples to support this idea...

### 🔎 Other Languages

Other programming languages, which scale to extremely large and complex applications, do not follow this convention. JavaScript and TypeScript applications frequently combine multiple types (functions, objects, interfaces) in the same file. There's a lot less ceremony 😎 compared to C# to create these reusable pieces of language functionality, which means they take up fewer lines of code.

F#, another .NET language, also frequently combines multiple types in the same file. This is largely because F# encourages the creation of many types to effectively [model the valid states of an application](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/) and its syntax for creating new types is much more terse than C# (mostly from the lack of curly braces).

These languages combine types in a single file when those types are related or are directly dependent on each other, and applications written in these languages do not become unmanageable by breaking the '1 type per file' rule. They still use helpful code organization techniques and are enjoyed by large communities of developers. They follow the spirit 👻 of the law - organizing code for better understanding and maintainability.

### 🎁 New C# Features

As we saw in the original comparison between modern C# and the C# that was written 15 years ago, we can see that C# 9 `record` types reduce boilerplate significantly for certain use cases.

I think `record` types are great 💪🏼 for traditional [data transfer objects](https://en.wikipedia.org/wiki/Data_transfer_object) (DTOs) and other simple classes.

Here's some `record` types from a Kentico Xperience site our team has running in production:

```csharp
public record FooterNavigationQuery : 
    IQuery<FooterNavigationQueryData>;

public record FooterNavigationQueryData(
    Maybe<ImageContent> Logo, 
    Maybe<string> FooterHTML, 
    IEnumerable<FooterNavigationItem> Children);

public record FooterNavigationItem(
    Maybe<LinkContent> Link, 
    IEnumerable<FooterNavigationItem> Children);
```

If we were to follow the letter of the law we would need to put each one of these `record` types in its own file. But that doesn't feel like it's following the spirit of the law.

1 line of code in a file (excluding namespaces) feels like we are being overly prescriptive and not considering the experience for other developers to have to open many different files 😩 to make updates to a feature or track down the source of a problem.

Even if we are using classes and not `record` types, we can write far less verbose syntax compared to older C#:

```csharp
public class Product
{
    public Product(int id) => Id = id;

    public int Id { get; set; }

    public bool IsValid => Id > 0;
}
```

The [expression bodied member/constructor](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members) in the class definition above remove many lines of curly braces while still expressing the same meaning as the more verbose class syntax.

If we have multiple related classes that are less than 10 lines long each, should all those go in separate files 😑? If we put them all in the same file, are we making the code harder to read and use or are we making it easier to navigate and modify 🤔?

---

I'd also like to mention some of the features coming in C# 10 (as demo'd in [this video from MS Build 2021](https://youtu.be/GJ_PaRNDe9E?t=1085)) are helping to remove boilerplate.

[File scoped namespaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md) remove the curly braces and indentation that comes with every new file and [global usings](https://jaliyaudagedara.blogspot.com/2021/05/c-100-introducing-global-usings.html) also let us centralize the most commonly used `using` statements for namespaces.

With these C# features, as the files our types are defined in become more and more empty, why don't we fill them up with multiple concise and related types 😁?

## 🤝🏽 Things that Change Together, Stay Together

Some software design patterns can lead us to creating large classes, and we are discouraged from combining multiple large classes in a single file as was mentioned earlier.

Implementing the [repository pattern](https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application) can result in very large classes 📚, since the repository will typically contain a method for every variation of data querying, and updating, which our application needs to perform on a given entity.

In Kentico Xperience, we are mostly writing code to read data out of the database and render it in HTML, so let's look at an example for an "Article" Page Type.

Our `ArticleRepository` might have 10 different methods for retrieving Articles and their related content from the database in various ways. Any class with 10 data access methods is going to be pretty large and if those methods use DTOs as parameters and return types, we aren't going to be inclined to include those DTO class definitions in the same file as our `ArticleRepository`, even though that repository class is the owner of those types:

```csharp
public class ArticleRepository
{
    public Article Get(int id)
    {
        // ...
    }

    public Article Get(Guid id)
    {
        // ...
    }

    public IEnumerable<Article> Get(ArticlesFilter filter)
    {
        // ...
    }

    public IEnumerable<Article> GetMostPopular()
    {
        // ...
    }

    // ...
}
```

> When I say the `ArticleRepository` is the owner of the DTOs like `ArticlesFilter` and `Article` above, I mean that the only reason those types would change is because the implementation details of `ArticleRepository` changes. They are concretely connected to each other.

It would be nice to keep the `Article` class close to the `ArticleRepository` class since the repository is the one place in the code base responsible for creating new `Article` instances 😊! [Cohesion](https://www.geeksforgeeks.org/software-engineering-differences-between-coupling-and-cohesion/) relates these types in a way that is beneficial to our application.

However, the large size of the `ArticleRepository` is already discouraging us from adding any more to its file 😒.

---

MVC Controller classes are another place where we see this 'bag of methods' approach to class design.

Controllers have action methods that, typically, return View Models, and some also have model-bound method parameter types.

These View Models and parameter types are only created in 1 place in the application - the Controller action methods - so we might be inclined to define those classes where they are being used, in the same file as the Controller.

However, Controllers often grow in size just like Repositories, so we are then not incentivized to do this 😕.

Sometimes I see the argument made that View Models and Controller classes should be separated because that 'separates the concerns'.

I'm not very convinced 🧐 by that statement because both the Controller and View Model (along with the View) are all part of the same layer of the application - [the Presentation concern](https://martinfowler.com/eaaDev/uiArchs.html#ModelViewController).

> Remember that all the pieces that make up the MVC acronym are not our entire 'application' and those pieces are not separate layers or concerns - they are all part of the same Presentation layer 🤓.

These types work extremely closely together and there's unlikely to be major changes to a View Model class without needing to also open the View and Controller files that use it.

## 🔬 Where Multiple Types per File Makes Sense

Hopefully I've been able to convince you 😄 that there are cases when keeping multiple types in the same file makes sense (assuming we're using certain C# language features or changing our architecture patterns slightly).

In a Kentico Xperience application we have many opportunities to break the 1 type-per-file rule and improve navigation and maintainability of an application...

### MVC Controllers, Action Method Parameters, and View Models

As we saw above, the namesake MVC components are a great candidate for merging into 1 file, however we likely need to take a different approach on Controller class authoring 😮.

Instead of creating extremely large Controller classes, we can create _many_ Controllers, each with 1 action method. This shrinks each Controller class and gives us room to add the View Model and action method parameter classes to the same file:

```csharp
public class CoffeeListController : Controller
{
    private readonly IPageRetriever retriever

    public CoffeeListController(
        IPageRetriever retriever) =>
        this.retriever = retriever;

    public async Task<ActionResult> Index(
        [FromQuery] CoffeeFilterParams filter,
        CancellationToken token)
    {
        var pages = await retriever.RetrieveAsync<CoffeePage>(
            q => q.OrderBy(filter.OrderBy).TopN(filter.Top),
            cancellationToken: token);

        var vm = new CoffeeListViewModel(pages);

        return View(vm);
    }
}

public record CoffeeFilterParams(int? Top, string? OrderBy);

public class CoffeeListViewModel
{
    public IEnumerable<string> Coffees { get; }

    public CoffeeListViewModel(IEnumerable<CoffeePage> pages) =>
        Pages = pages.Select(p => p.Fields.Name).ToArray();
}
```

> This would match the pattern that the [ApiEndpoints](https://github.com/ardalis/ApiEndpoints/) library establishes 🧐.

### View Components + View Models (Widgets / Sections)

Since View Components are a slimmed down design of the traditional MVC pattern, it also makes sense to combine the View Component and its View Model classes into a single file.

This approach is just as appropriate with Kentico Xperience Page Builder components, like Sections and Widgets, since these are made from View Components 🤓:

```csharp
public class FeaturedCoffeeWidget : ViewComponent
{
    public const string IDENTIFIER = "sandbox.widget.featured-coffee";

    private readonly IPageRetriever retriever;

    public FeaturedCoffeeWidget(IPageRetreiver retriever) =>
        this.retriever = retriever;

    public async Task<IViewComponentResult> InvokeAsync(
        ComponentViewModel<FeaturedCoffeeWidgetProperties> vm)
    {
        var nodeGuid = vm.Properties
            .Coffees
            .Select(c => c.NodeGuid)
            .FirstOrDefault();

        var pages = await retriever.RetrieveAsync<CoffeePage>(
            q => q.WhereEquals("NodeGUID", nodeGuid),
            HttpContext.RequestAborted);

        return pages.Any()
            ? View(new FeaturedCoffeeViewModel(pages.Single()))
            : View("_Error", $"Coffee [{nodeGuid}]");
    }
}

public class FeaturedCoffeeWidgetProperties : IWidgetProperties
{
    [EditingComponent(
        PageSelector.IDENTIFIER, Label = "Featured Coffee")]
    public IEnumerable<PageSelectorItem> Coffees { get; set; } = 
        Array.Empty<PageSelectorItem>();
}

public class FeaturedCoffeeViewModel
{
    public string CoffeeName { get; }

    public FeaturedCoffeeViewModel(CoffeePage coffee) =>
        CoffeeName = coffee.Fields.Name;
}
```

### Page Template Types

Another set of types I like to combine into a single file are those related to Page Templates. Using the [Page Template Utilities](https://github.com/wiredviews/xperience-page-template-utilities) NuGet package 👨🏾‍🏫, we typically create a Page Template Filter, Page Template Properties, and a Page Template registration attribute all in the same file for a given template.

We even combine multiple sets of these if the templates are used for the same Page Type:

```csharp
[assembly: RegisterPageTemplate(
    "Sandbox.CoffeePage_Default", "Coffee Page (Default)", 
    typeof(CoffeePageTemplateProperties))]

[assembly: RegisterPageTemplate(
    "Sandbox.CoffeePage_Featured", "Coffee Page (Featured)", 
    typeof(CoffeePageTemplateProperties))]

[assembly: RegisterPageTemplate(
    "Sandbox.CoffeePage_Sale", "Coffee Page (Sale)", 
    typeof(CoffeePageTemplateProperties))]

namespace Sandbox.Delivery.Web.Features.Coffee
{
    public class CoffeePageTemplateFilter : 
        PageTypePageTemplateFilter
    {
        public override string PageTypeClassName => 
            CoffeePage.CLASS_NAME;
    }

    public class CoffeePageTemplateProperties : 
        IPageTemplateProperties
    {
        [EditingComponent(
            TextBoxComponent.IDENTIFIER,
            Label = "CTA Color")]
        public string Color { get; set; } = "";
    }
}
```

### Mediatr, CQRS Query/Data Types

Instead of trying to make a large Repository class we could adopt an architectural pattern that is better at isolating each data access operation into its own class.

This could be accomplished using a library like [Mediatr](https://github.com/jbogard/MediatR) or something with more semantic meaning for database querying, like the [Xperience CQRS](https://github.com/wiredviews/xperience-community-cqrs) library I've been working on.

Both of these libraries follow the idea of implementing a common interface with 1 method and using C# generics to determine which implementation handles a given request or query. The request/query and response/data types are so closely bound to each other that it always seemed intuitive to me to define the in the same file 🤗:

```csharp
public record HomePageQuery : IQuery<HomePageQueryData>;

public record HomePageQueryData(
    string Title, 
    Maybe<string> BodyHTML, 
    Maybe<HomePageImageData> Image);

public record HomePageImageData(
    Guid ImageGuid, 
    string ImagePath);
```

### Interfaces with Single Implementations

Often, we might start out with interfaces that have a single default implementation, and the interface exists primarily to make pieces of our code more unit testable.

I see a lot of developers go and separate these types into different files (and even into different namespaces 😝!)

> Any time you find yourself making a project folder named `Interfaces`, pause and ask yourself why organizing code by whether its not an interface has meaning to the business goals of your app 😉.

If these types need to be testable but also only have a single implementation, it makes  sense to me to place them in the same file, and only separate them in the future if we end up with multiple implementations:

```csharp
public interface IClock
{
    DateTime Now();
}

public class SystemClock : IClock
{
    public DateTime Now() => DateTime.Now;
}
```

There's really no need to create separate files for these 4 line types because it doesn't benefit us organizationally or help reduce complexity or improve maintainability.

## Conclusion

As we've seen, the coding convention in C#, of creating a separate file for each type, has good intentions and generally good results. We've also seen that abiding by the letter of the law 🏛 isn't always as beneficial as following the spirit of the law.

Kentico Xperience is flexible enough in its ASP.NET Core MVC implementation to support a number of different architectural patterns and many of these patterns could benefit from combining multiple types in a single file.

Even if the 1-type-per-file C# coding convention is what you are most comfortable with, I recommend taking a step back from your code and considering 'could things be different?' or 'what could make my development experience better?' 🤔.

Conventions are good and so is questioning them 😁!


As always, thanks for reading 🙏!

## References

- [Coupling vs Cohesion](https://www.geeksforgeeks.org/software-engineering-differences-between-coupling-and-cohesion/)
- [Letter and Spirit of the Law](https://en.wikipedia.org/wiki/Letter_and_spirit_of_the_law)
- [F# Types Make Invalid States Unrepresentable](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/)
- [Data Transfer Object (DTO)](https://en.wikipedia.org/wiki/Data_transfer_object)
- [Expression Bodied Members in C#](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
- [.NET 6 deep dive; what's new and what's coming: MS Build 2021](https://youtu.be/GJ_PaRNDe9E)
- [File Scoped Namespaces: C# Proposals on GitHub](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md)
- [C# 10.0: Introducing Global Usings](https://jaliyaudagedara.blogspot.com/2021/05/c-100-introducing-global-usings.html)
- [Example Repository Pattern in C#](https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application)
- [MVC from Martin Fowler](https://martinfowler.com/eaaDev/uiArchs.html#ModelViewController)
- [Ardalis ApiEndpoints library](https://github.com/ardalis/ApiEndpoints/)
- [Xperience Page Template Utilities library](https://github.com/wiredviews/xperience-page-template-utilities)
- [Mediatr library](https://github.com/jbogard/MediatR)
- [Xperience CQRS library](https://github.com/wiredviews/xperience-community-cqrs)

---

<figcaption>Photo by <a href="https://unsplash.com/@jordanmadrid">Jordan Madrid
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)