Lion
Lion

Reputation: 17898

Use nameof() to get a attributepath for ModelState.AddModelError()

I like to use nameof() in my ASP.NET Core application when setting mvc errors using ModelState.AddModelError(). This reduces hidden errors since I pass the name of an attribute here. Compared to a string, this has the benefit that a compiler error is thrown when the attribute was renamed.

Example

class MyModel{
    public string Name{get;set;}
}
public class MyController:Controller{
    public IActionResult Test(MyModel model) {
        // Will hiddenly fail when "Name" is renamed to something else
        ModelState.AddModelError("Name", "The Name field is required!");

        // Better: Using the auto renaming feature of VS, this will be updated if "Name" was renamed
        ModelState.AddModelError(nameof(model.Name), "The Name field is required!");
    }
}

This works, until I have another entity:

class MyModel{
    public string Name{get;set;}
    public MyOtherModel OtherModel{get;set;}
}
class MyOtherModel{
    public string OtherName{get;set;}
}

Now I want to get the name of OtherModel. The problem: ASP.NET requires BaseClass.ChildClass pattern here, so OtherModel.OtherName in this example. Using the same logic like above:

public class MyController:Controller{
    public IActionResult Test(MyModel model) {
        ModelState.AddModelError(nameof(model.MyOtherModel.OtherName), "The Name field is required!");
    }
}

will just give me the name of the child attribute, so OtherName in this example. But I need OtherModel.OtherName. Is there a clean way to get this without building the string itself?

This would be possible doing something like this:

string actionName = nameof(model.MyOtherModel) + "." + nameof(model.MyOtherModel.OtherName);

But not a very clean and intuitive way imho.

Upvotes: 3

Views: 828

Answers (2)

Sam Sippe
Sam Sippe

Reputation: 3190

A tweak to Neme's answer.

static string NameOfMember<TModel, TResult>(Expression<Func<TModel, TResult>> accessor)
{
    string str = accessor.Body.ToString();
    return str.Substring(str.IndexOf('.') + 1);
}

Usage:

ModelState.AddModelError(NameOfMember<Model, DateTime?>(model => model.Foo.Bar.Baz),
 "Baz is bad.");

Upvotes: 0

Neme
Neme

Reputation: 503

As others have suggested, you will probably have to write your own method to do this. This is what I came up with using Linq.Expressions:

string NameOfMember(Expression<Func<Model, object>> accessor)
{
    string str = accessor.Body.ToString();
    return str.Substring(str.IndexOf('.') + 1);
}

You would then use it like this: NameOfMember(model => model.MyOtherModel.OtherName)

I'm not familiar with ASP.NET but you can change Model to any type you want, including object.

As a side note, you shouldn't repeat "The Name field is required!" in your code. Maybe you can include that in your helper method as well.

Upvotes: 3

Related Questions