Is there a way to make a reflection for a view model property as an element with different name and id values on the html side.
That is the main question of what I want to achieve. So the basic introduction for the question is like:
1- I have a view model (as an example) which created for a filter operation in view side.
public class FilterViewModel
{
public string FilterParameter { get; set; }
}
2- I have a controller action which is created for GETting form values(here it is filter)
public ActionResult Index(FilterViewModel filter)
{
return View();
}
3- I have a view that a user can filter on some data and sends parameters via querystring over form submit.
@using (Html.BeginForm("Index", "Demo", FormMethod.Get))
{
@Html.LabelFor(model => model.FilterParameter)
@Html.EditorFor(model => model.FilterParameter)
<input type="submit" value="Do Filter" />
}
4- And what I want to see in rendered view output is
<form action="/Demo" method="get">
<label for="fp">FilterParameter</label>
<input id="fp" name="fp" type="text" />
<input type="submit" value="Do Filter" />
</form>
5- And as a solution I want to modify my view model like this:
public class FilterViewModel
{
[BindParameter("fp")]
[BindParameter("filter")] // this one extra alias
[BindParameter("param")] //this one extra alias
public string FilterParameter { get; set; }
}
So the basic question is about BindAttribute but the usage of complex type properties. But also if there is a built in way of doing this is much better. Built-in pros:
1- Usage with TextBoxFor, EditorFor, LabelFor and other strongly typed view model helpers can understand and communicate better with each other.
2- Url routing support
3- No framework by desing problems :
In general, we recommend folks don’t write custom model binders because they’re difficult to get right and they’re rarely needed. The issue I’m discussing in this post might be one of those cases where it’s warranted.
And also after some research I found these useful works:
Binding model property with different name
One step upgrade of first link
Result: But none of them give me my problems exact solution. I am looking for a strongly typed solution for this problem. Of course if you know any other way to go, please share.
Update
The underlying reasons why I want to do this are basically:
1- Everytime I want to change the html control name then I have to change PropertyName at compile time. (There is a difference Changing a property name between changing a string in code)
2- I want to hide (camouflage) real property names from end users. Most of times View Model property names same as mapped Entity Objects property names. (For developer readability reasons)
3- I don't want to remove the readability for developer. Think about lots of properties with like 2-3 character long and with mo meanings.
4- There are lots of view models written. So changing their names are going to take more time than this solution.
5- This is going to be better solution (in my POV) than others which are described in other questions until now.
Actually, there is a way to do it.
In ASP.NET binding metadata gathered by
TypeDescriptor
, not by reflection directly. To be more precious,AssociatedMetadataTypeTypeDescriptionProvider
is used, which, in turn, simply callsTypeDescriptor.GetProvider
with our model type as parameter:So, everything we need is to set our custom
TypeDescriptionProvider
for our model.Let's implement our custom provider. First of all, let's define attribute for custom property name:
If you already have attribute with desired name, you can reuse it. Attribute defined above is just an example. I prefer to use
JsonPropertyAttribute
because in most cases I work with json and Newtonsoft's library and want to define custom name only once.The next step is to define custom type descriptor. We will not implement whole type descriptor logic and use default implementation. Only property accessing will be overridden:
Also custom property descriptor need to be implemented. Again, everything except property name will be handled by default descriptor. Note,
NameHashCode
for some reason is a separate property. As name changed, so it's hash code need to be changed too:Finally, we need our custom
TypeDescriptionProvider
and way to bind it to our model type. By default,TypeDescriptionProviderAttribute
is designed to perform that binding. But in this case we will not able to get default provider that we want to use internally. In most cases, default provider will beReflectTypeDescriptionProvider
. But this is not guaranteed and this provider is inaccessible due to it's protection level - it'sinternal
. But we do still want to fallback to default provider.TypeDescriptor
also allow to explicitly add provider for our type viaAddProvider
method. That what we will use. But firstly, let's define our custom provider itself:The last step is to bind our provider to our model types. We can implement it in any way we want. For example, let's define some simple class, such as:
And either invoke that code via web activation:
Or simply call it in
Application_Start
method:But this is not the end of the story. :(
Consider following model:
If we try to write in
.cshtml
view something like:We will get
ArgumentException
:That because all helpers soon or later invoke
ModelMetadata.FromLambdaExpression
method. And this method take expression we provided (x => x.TestProperty
) and takes member name directly from member info and have no clue about any of our attributes, metadata (who cares, huh?):For
x => x.TestProperty
(wherex
isTestModel
) this method will returnTestProperty
, notactual_name
, but model metadata containsactual_name
property, have noTestProperty
. That is whythe property could not be found
error thrown.This is a design failure.
However despite this little inconvenience there are several workarounds, such as:
The easiest way is to access our members by theirs redefined names:
This is not good. No intellisense at all and as our model change we will have no any compilation errors. On any change anything can be broken and there is no easy way to detect that.
Another way is a bit more complex - we can create our own version of that helpers and forbid anybody from calling default helpers or
ModelMetadata.FromLambdaExpression
for model classes with renamed properties.Finally, combination of previous two would be preferred: write own analogue to get property name with redefinition support, then pass that into default helper. Something like this:
Compilation-time and intellisense support and no need to spend a lot of time for complete set of helpers. Profit!
Also everything described above work like a charm for model binding. During model binding process default binder also use metadata, gathered by
TypeDescriptor
.But I guess binding json data is the best use case. You know, lots of web software and standards use
lowercase_separated_by_underscores
naming convention. Unfortunately this is not usual convention for C#. Having classes with members named in different convention looks ugly and can end up in troubles. Especially when you have tools that whining every time about naming violation.ASP.NET MVC default model binder does not bind json to model the same way as it happens when you call newtonsoft's
JsonConverter.DeserializeObject
method. Instead, json parsed into dictionary. For example:will be translated into following dictionary:
And later these values along with others values from query string, route data and so on, collected by different implementations of
IValueProvider
, will be used by default binder to bind a model with help of metadata, gathered byTypeDescriptor
.So we came full circle from creating model, rendering, binding it back and use it.
The short answer is NO and long answer still NO. There is no built-in helper, attribute, model binder, whatever is it (Nothing out of box).
But what I did in before answer (I deleted it) was an awful solution that I realized yesterday. I am going to put it in github for who still wants to see (maybe it solves somebody problem) (I don't suggest it also!)
Now I searched it for again and I couldn't find anything helpful. If you are using something like AutoMapper or ValueInjecter like tool for mapping your ViewModel objects to Business objects and if you want to obfuscate that View Model parameters also, probably you are in some trouble. Of course you can do it but strongly typed html helpers are not going to help you alot. I even not talking about the if other developers taking branch and working over common view models.
Luckily my project (4 people working on it, and its commercial use for) not that big for now, so I decided to change View Model property names! (It is still lot work to do. Hundreds of view models to obfuscate their properties!!!) Thank you Asp.Net MVC !
There some ways in the links which I gave in question. But also if you still want to use the BindAlias attribute, I can only suggest you to use the following extension methods. At least you dont have to write same alias string which you write in BindAlias attribute.
Here it is:
When you want to use it:
In html:
So I am leaving this answer here like this. This is even not an answer (and there is no answer for MVC 5) but who searching in google for same problem might find useful this experience.
And here is the github repo: https://github.com/yusufuzun/so-view-model-bind-20869735