Getting the path to a C# member via a lambda expression

Are you looking for a way of getting the path to a C# property, or some other type of object member, as a string and want to do so in a strongly-typed manner?

For example, perhaps you are building your own API error response model and you want your error messages to contain member names that look something like the following, where the ‘path’ to the member is built up using dot notation.

Product.Name

Or perhaps something slightly more complex like this?

Product.Categories[0].Name

If so, read on and I will demonstrate how you can achieve this within an ASP.NET Core web application. If you need similar functionality outside of ASP.NET Core I’ll also point you in the right direction to achieve the same results.

Scenario

Let’s start by looking at an example scenario for demonstration purposes.

Imagine we have a set of C# classes that look similar to those shown in the following code listing.

public class Request
{
    public Product Product { getset; } = new Product();
}
 
public class Product
{
    public string Name { getset; } = "";
 
    public IList<Category> Categories { getset; } = new List<Category>();
 
    // Other properties omitted for brevity.
}
 
public class Category
{
    public string Name { getset; } = "";
 
    // Other properties omitted for brevity.
}

Looking at the above code, let’s assume that the Request class represents the request model that is being passed to an ASP.NET Core Web API Controller.

Request contains a single Product property.

Product contains two properties, named Name and Categories.

Categories is a property that holds a collection of Category objects which also have a Name property.

Let’s say that we are executing custom validation logic after the request reaches our API Controller and we want to return a specific error message in the response body. We want the error message to contain the path to the Name property for a specific item within the Categories collection that we have determined is invalid.

How could we generate the path to this property as a string so that the API client knows exactly which category has a problem?

For example, the error for the fourth category in the collection should look something like the following.

Member 'Product.Categories[3].Name' has exceeded the maximum length of 500 characters.

This is the question that will be answered in the following section.

Expression Provider

ASP.NET Core MVC contains classes that can help us to fulfil our requirements.

In fact, the standard MVC model state logic automatically generates validation errors containing member paths that are similar to what we have been looking at already, including the previous example containing the array index.

As it turns out, the logic that the MVC framework uses to achieve this can be accessed via the public ModelExpressionProvider class.

Below is the definition of a custom extension method that makes it more convenient to hook into this functionality.

using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.Linq.Expressions;
 
/// <summary>
/// Contains extension methods for <see cref="Object"/>.
/// </summary>
public static class ObjectExtensions
{
    #region Methods
 
    /// <summary>
    /// Gets the path to the specified member via a lambda expression.
    /// </summary>
    /// <typeparam name="T">The type of the object on which the member exists</typeparam>
    /// <typeparam name="TResult">The type returned by the member</typeparam>
    /// <param name="obj">The object on which the member exists</param>
    /// <param name="expression">The lambda expression</param>
    /// <param name="camelCase">Whether or not the result should use camel case</param>
    /// <returns>The path to the specified member as a string</returns>
    public static string GetMemberPath<TTResult>(
        this object obj, 
        Expression<Func<T, TResult>> expression,
        bool camelCase = false)
    {
        var provider       = new ModelExpressionProvider(new EmptyModelMetadataProvider());
        var expressionText = provider.GetExpressionText(expression);
 
        return camelCase ? expressionText.ToCamelCase() : expressionText;
    }
 
    #endregion
}

Note that I have left out the namespace declaration from the above code. You should add the namespace for your project before the class declaration.

The above code simply creates a new ModelExpressionProvider instance, passing in an instance of the EmptyModelMetadataProvider class and then calls the GetExpressionText method on the provider.

The expression text that is returned to the caller is converted to camel case if the camelCase argument is true. The camel case conversion relies on the following string extension method.

using System.Globalization;
 
/// <summary>
/// Contains extension methods for <see cref="String"/>.
/// </summary>
public static class StringExtensions
{
    /// <summary>
    /// Converts the specified name to camel case.
    /// </summary>
    /// <param name="name">The name to convert</param>
    /// <returns>The specified name with camel case</returns>
    public static string ToCamelCase(this string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        return string.Join(".", name.Split('.').Select(n => 
            char.ToLower(n[0], CultureInfo.InvariantCulture) + n.Substring(1)));
    }
}

Note that I have left out the namespace declaration from the above code. You should add the namespace for your project before the class declaration.

With the above in place, it is possible to get the path to a C# member in a convenient manner with code such as the following.

var request = new Request();
 
string memberPath = request.GetMemberPath<Requeststring>(r => r.Product.Name);
 
Console.WriteLine(memberPath);
 
// Output: Product.Name

Similarly, for a specific category name, the following code can be used.

var request = new Request();
 
string memberPath = request.GetMemberPath<Requeststring>(r => r.Product.Categories[0].Name);
 
Console.WriteLine(memberPath);
 
// Output: Product.Categories[0].Name

The GetMemberPath method makes it trivial to get the path to any publicly accessible C# member on an object. Additionally, since an expression is being used, the code is strongly typed such that if the name of the member changes in the future, the compiler will pick this up.

Alternatives

What if you’re not working in an ASP.NET Core context, what are the options?

MVC packages

One possible option is to add a specific MVC NuGet package to your project. This is usually not ideal since doing so will add quite a few dependencies that will not provide you with any other benefits.

However, if you do want to go down this path, the package you will need is as follows.

Microsoft.AspNetCore.Mvc.ViewFeatures

The NuGet package doesn’t offer up exactly the same API surface that you are provided within in an ASP.NET Core web app. Therefore, if you want to avail of an extension method that is similar to what was covered in the previous section, you will need to adjust the GetMemberPath method slightly to the following.

public static string GetMemberPath<TTResult>(
    this object obj, 
    Expression<Func<T, TResult>> expression,
    bool camelCase = false)
{
    var expressionText = ExpressionHelper.GetExpressionText(expression);
 
    return camelCase ? expressionText.ToCamelCase() : expressionText;
}

The NuGet package provides public access to the ExpressionHelper class which is marked as internal when working in an ASP.NET Core web app.

You should find that the above method produces exactly the same results as the extension method that was shown previously, as the ModelExpressionProvider class delegates to the ExpressionHelper.GetExpressionText method when its GetExpressionText method is called.

Standalone

If you don’t want to include any ASP.NET Core dependencies in your application, you will need to consider creating your own standalone solution for handling expressions.

Thankfully, there is inspiration readily available, since ASP.NET Core is open-source software.

I highly recommend that you check out the source code for the ExpressionHelper class on GitHub. At the time of writing, the class is less than 300 lines long. You’ll need some background on how expression trees work to fully understand what is going on, but this class could act as a solid basis for implementing your own solution.

Summary

In this article, I looked at how you can get the path to a C# member via a lambda expression.

I started by introducing a sample scenario where we had some C# classes containing properties that we want to get the path to in a strongly-typed manner. I then demonstrated to solve this problem by wrapping existing ASP.NET Core MVC functionality in an extension method.

Finally, I looked at how you could implement similar functionality outside of an ASP.NET Core application, either by installing a NuGet package or by rolling your own solution using expression trees.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments