According to this answer from 2010, regarding mvc-2, it wasn't possible. What about now, in asp.net-core 2.2?
My usecase:
I have a BaseViewModel
that is being used by 2 views: TableView
(for users) and TableManagentView
(for admins). The BaseViewModel
is invoked by a ViewComponent
. Here are some samples:
BaseViewModel:
public class BaseViewModel {
[Display(Name = "Comment")
public string UserComment { get; set; }
}
TableView:
@await Component.InvokeAsync(nameof(Base), new { myObject = myObject, stringName = "User"})
TableManagementView:
@await Component.InvokeAsync(nameof(Base), new { myObject = myObject, stringName = "Admin"})
Base:
public class Base : ViewComponent
{
public IViewComponentResult Invoke(BaseViewModel myObjet, string invokingView)
{
// here i want to do something like that
if (invokingView == "User") {
myObject.UserComment.SetDisplayName("My Comment");
}
if (invokingView == "Admin") {
myObject.UserComment.SetDisplayName("User's Comment");
}
return View("BaseViewComponent", myObject);
}
}
BaseViewComponent:
@Html.DisplayNameFor(model => model.UserComment)
The BaseViewModel
is simplified, but there are a lot more attributes. The reason I want to do this is to avoid code duplication in both tables. The only thing that should change are the label names.
I've tried reflection
, but without success:
public IViewComponentResult Invoke(BaseViewModel myObject, string invokingView)
{
MemberInfo property = typeof(BaseViewModel).GetProperty("UserComment");
property.GetCustomAttributes(typeof(DisplayAttribute)).Cast<DisplayAttribute>().Single().Name = "test";
return View("BaseViewComponent", myObject);
}
The Name
doesn't change and remains "Comment"
from the initial setting.
If it's not possible to set the attribute name programmatically, what other solutions do I have? I'm thinking about ViewBag/ViewData
or TempData
, but this solution doesn't appeal to me. What would be the pro's and con's of that?
Extending on the comment I left, one way you could solve this is by having your
BaseViewModel
being an abstract class and have concrete classes deriving from it. SoUserViewModel
andAdminViewModel
. These two concrete classes would then be the models for bothTableView
andTableManagentView
and would be responsible for telling the "outside world" how to label fields.The base class has two main aspects (apart from your normal fields): An abstract
Dictionary<string, string>
which will contain the labels and a method to get the label from the list:string GetLabel(string propName)
. So something like this:Then you create the two deriving classes
User
andAdmin
:They only implement the
Dictionary<string, string>
and set the appropriate text for each field on the base class.Next, changing your
BaseViewComponent
to this:View:
ComponentView class (simpler now)
Finally, changing your views
TableView
andTableManagentView
to this:and the Controller to:
Now when you navigate to
TableView
, you'll pass aUserViewModel
to theBaseViewComponent
and it will figure it out the correct label. Introducing new fields will just now require you to change your viewmodels, adding a new entry to the Dictionary.It's not perfect, but I think it's an okay way to solve it. I'm by far not an MVC expert so maybe others can come up with a more natural way to do it as well. I also prepared a working sample app and pushed to GitHub. You can check it out here: aspnet-view-component-demo. Hope it helps somehow.