Instantiating ModelExpression directly

1.3k views Asked by At

Let's say I have the following input tag which utilizes the built-in tag helper:

@model ProductViewModel

<label asp-for="Product.Id"></label>

In my case, this expands into the following:

<label for="Product_Id">Id</label>

I see that asp-for is expecting a ModelExpression:

enter image description here

In tag helper implementations, I often see a property like the following:

public ModelExpression For { get; set; }

It appears that this is automatically populated when the tag helper is used.

Is there a way to instantiate a ModelExpression directly in C#?

I.e. something like this:

var exp = new ModelExpression("Product.Id",...)

I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.

2

There are 2 answers

0
Zhi Lv On

As far as I know, you can specify that your property is to be set to the name of some property on the View's Model object by declaring your property with the ModelExpression type. This will enable any developer using your property to get IntelliSense support for entering a property name from the Model object. More importantly, your code will be passed the value of that property through the ModelExpression's Model property.

Sample code as below:

[HtmlTargetElement("employee-details")]
public class EmployeeDetailTagHelper : TagHelper
{
    [HtmlAttributeName("for-name")]
    public ModelExpression EmployeeName { get; set; }
    [HtmlAttributeName("for-designation")]
    public ModelExpression Designation { get; set; }
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "EmployeeDetails";
        output.TagMode = TagMode.StartTagAndEndTag;

        var sb = new StringBuilder();
        sb.AppendFormat("<span>Name: {0}</span> <br/>", this.EmployeeName.Model);
        sb.AppendFormat("<span>Designation: {0}</span>", this.Designation.Model);

        output.PreContent.SetHtmlContent(sb.ToString());
    }
}

Code in the View page:

@model WebApplication7.Models.EmployeeViewModel
<div class="row">
    <employee-details for-name="Name" for-designation="Designation"></employee-details>
</div>  

Code in the Model

public class EmployeeViewModel
{
    public string Name { get; set; }
    public string Designation { get; set; }
}

From above code, you can see that we could custom the attribute name. More detail information about using the ModelExpression, check the following links:

Creating Custom Tag Helpers With ASP.NET Core MVC

Expression names

I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.

Besides, do you mean you want to change the Product. Id to Product_Id, in my opinion, I'm not suggesting you change it, because generally we can use "_" as a separator in the property name. So, if we are using Product.Id, it means the Product's Id property, and the Product_Id means there have a Product_Id property.

3
Ber'Zophus On

To answer the question:

Is there a way to instantiate a ModelExpression directly in C#"

Yes you can, through IModelExpressionProvider and its CreateModelExpression method. You can get an instance of this interface through DI.

Now, if you're already in your view and working with tag helpers, Zhi Lv's answer is all you need, as the functionality is built-in and much easier to use. You only need IModelExpressionProvider for when you're in your Razor Page, Controller, or perhaps some custom middleware. Personally, I find this functionality useful for my Ajax handlers that need to return one of my ViewComponents that has a ModelExpression argument (so that I can easily call it from my Pages/Views too.)

To call CreateModelExpression, you'll need a strongly-typed instance of ViewData. In Razor Pages, this is as easy as casting the ViewData property to the strongly-typed instance of your PageModel's type (presuming you don't have a page model hierarchy):

var viewData = (ViewDataDictionary<IndexModel>)ViewData;

If you're using MVC and you're in the controller, that won't exist yet. Best you can do is make your own instance.

var viewData = new ViewDataDictionary<ErrorViewModel>(new EmptyModelMetadataProvider(),
    new ModelStateDictionary());

Once you get your strongly-typed ViewData instance, you can obtain your desired ModelExpression like this, just like using a lambda expression in your views:

var myPropertyEx = _modelExpressionProvider.CreateModelExpression(viewData,
    m => m.MyProperty);