ASP.NET MVC + Kendo: Binding to Hidden Properties - c#

I have a derived class with a property hiding the property in the parent class, like so:
public class Parent
{
public SomeObject Field { get; set; }
}
public class Child : Parent
{
public new SomeOtherObject Field{ get; set; }
}
Essentially, I'm binding a Kendo grid to an IEnumerable<Child> object and would like one of the columns to display the value of a property in Child.Field, i.e. Child.Field.SubField. However, when the code runs, it appears to be trying to bind to the super class' version of Field, which is null, hence throwing a NullReferenceException.
Is this expected behavior? If so, is there someway I can force MVC to bind to the child's version of the field? I was unable to find any documentation on either ASP.NET or Kendo about how they treat hidden properties.

Related

Model binder for abstract class reference within abstract class asp.net core mvc 3

I'm following along at Model binder for abstract class in asp.net core mvc 2, but my model doesn't bind the HostedControls in ConcreteControlHost (see below). If I change the type of HostedControls to ConcreteControlText[], it does bind. I'd like to keep it as AbstractControl[] so that I can host multiple types of control.
I know the abstract binder is working because MainModel.Controls binds.
While debugging the binding of ConcreteControlHost, binder._propertyBinders has an entry for HostedControls as follows:
{[ModelMetadata (Property: 'ConcreteControlHost.HostedControls' Type: 'AbstractControl[]'), {Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder<MyProject.AbstractControl>}]}
Following that property (Value.ElementBinder.Inner) eventually leads to AbstractModelBinder.
Breakpoints in AbstractModelBinder are not hit when binding the properties of ConcreteControlHost, but are when binding the properties of MainModel (as in, I get the hits for ConcreteControlHost, but not for ConcreteControlText).
This isn't related to In an Editor Template call another Editor Template with the same Model because it isn't the same model, and because everything renders correctly, it just doesn't bind. None of the ConcreteControlTexts referenced by HostedControls are referenced directly by MainModel.Controls.
public class MainModel {
public AbstractControl[] Controls;
}
public abstract class AbstractControl {
public string TypeName { get; set;}
}
public class ConcreteControlText: AbstractControl {
public string Text { get; set; }
}
public class ConcreteControlHost: AbstractControl {
public AbstractControl[] HostedControls { get; set; }
}
Does anyone see what I need to change to allow model-binding to work on ConcreteControlHost.HostedControls?
Turns out in my actual code, HostedControls had { get; private set; }. Removing the private modifier on the setter made it work.
Jeremy, thanks for looking into this.

ASP.NET MVC Binding with Remote Validation

I have a model ModelA with a member toBeRemoteChecked and a model MapToA with a member valueToMap. Whenever I create an instance of ModelA, I also need an instance of MapToA, so I have a model CreateModelA which includes a member modelA and a member valueToMap. When the form is submitted, I add the modelA to the database table ModelA and create and add an instance to MapToA which consists of an id of modelA and the valueToMap. In Terms of code
public class ModelA
{
[Key]
public int ID { get; set; }
[Required, Remote("isValid", "MyController", ErrorMessage = "not valid")]
public string toBeRemoteChecked { get; set; }
}
public class MapToA
{
[Key]
public int Map_ID { get; set; }
[Required]
public int modelAID { get; set; }
[Required]
public int valueToMap { get; set; }
}
public class CreateModelA
{
public ModelA modelA { get; set; };
public int valueToMap { get; set; };
}
When I edit an instance of ModelA, values in MapToA don't matter (and in most cases there's more than one instance of mapToA with the same modelA id), but the remote validation of toBeRemoteChecked remains important.
My Problem: binding for the validation method:
public ActionResult isValid(string toBeRemoteChecked) { ... }
If I leave it as it is, it is working when editing a ModelA, but not when I'm creating a ModelA via CreateModelA (I always get null value in toBeRemoteChecked). When I use the BindPrefix
public ActionResult isValid([Bind(Prefix = "modelA.toBeRemoteChecked")] string toBeRemoteChecked) { ... }
it is working when I create a ModelA, but not when I'm editing it.
When I try to change the "name" in the Create.cshtml by adding a ... #Name = "toBeRemoteChecked" ... (instead of the modelA.toBeRemoteChecked that's created by the HTML helper) in the htmlAttributes of the #Html.TextBoxFor, then validation is working, but the binding of the value to the table get's lost and I get the error when the values are saved to the database (null value).
So, how do I achieve the different binding for creating and editing?
So far, my workaround is to make ModelA and CreateModelA : IValidatableObject and check the member toBeRemoteChecked in my public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) method. But that one displays the error messages on top of the form and not at the place of the TextFor box.
So: best solution: how to do the binding that the remote validation works in both cases?
Second best: how to display the error messages of IValidatableObject near the object where it belongs to (and get the error messages right at hand, not after submitting)
Different ideas or solutions: welcome.
Thanks.
An interesting issue, and similar to this question, which as an result I reported a issue at Codeplex, but it has not been resolved yet. The link includes a suggested modification to the jquery.validate.js file which would solve this (it strips the prefix) but that means you would need to maintain it whenever you update the script so not really desirable.
One option would be to change CreateModelA to inherit from ModelA and just add the int valueToMap property so that you never have a prefix - your always using #Html.TextBoxFor(m => m.toBeRemoteChecked) instead of #Html.TextBoxFor(m => m.modelA.toBeRemoteChecked)
Also, [Remote] is client side only validation, which means you still need to perform the validation in the server when you post. So you could just accept that you don't have client side validation for the property, and instead add a ModelState error in the POST methods(s) for the property and return the view so that its displayed in the associated ValidationMessageFor() element
Side note: The fact your model has a [Key] attribute suggests this is a data model, not a view model, and [Remote] is a view specific attribute. You should be using view models, especially when editing data. (refer What is ViewModel in MVC?)
I found a solution without inheritance (and without view models) that solves my binding problem with just little change to my code.
There's two ways of binding for remote validation, you can either just pass the member that has to be remote checked
public ActionResult isValid(string toBeRemoteChecked) { ... }
or you can pass the instance of the class of that member.
public ActionResult isValid(ModelA modelA) { ... }
Inside the second variant, of course, you have to replace toBeRemoteChecked with modelA.toBeRemoteChecked. On this second version the binding works in both cases - when editing and also when creating my instance of ModelA in the context above. In order to make the binding work, it's crucial that the parameter name of the remote validation method matches the member name in the CreateModelA, i.e. modelA in my case.
In case you have a very complex model, you can just initialize the parameter modelA with the members you want to use by using bind/include, i.e. in my case I'd use
public ActionResult isValid([Bind(Include = "toBeRemoteChecked")] ModelA modelA) { ... }
By default (without Include), all other members will remain null or have a default value - so you need to use Include only if you need other members for validation as well - in my case, I would have the same when omitting the Include)

How do I prevent Entity Framework forcing validation on child object properties?

I have an ASP.NET MVC 3 project that uses .Net 4.0 Framework and Entity Framework 4.4. I am using the code first w/ migrations approach for the Entity Framework.
I have an object A that has a property of object B. object A really just needs to know the id for object B, but MVC is enforcing all annotation validations on object B as I've marked it required in object A (which it is). ModelState.IsValid is always returning false because some validations are failing on object B when the form is submitted.
Example:
public class FormField
{
public int Id { get; set; }
[MaxLength(50)]
[Required]
[DisplayName("Field Name")]
public string Name { get; set; }
[Required]
public Form Form { get; set; }
}
public class Form
{
public Form()
{
Fields = new List<FormField>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<FormField> Fields { get; set; }
}
This isn't a problem when editing an existing FormField as I can just put a hidden field with the Form.Name property on the page (this still strikes me as something that should be unnecessary). The issue arises when creating a new FormField. I display a drop down list of forms (this is populated from my view model), and make that field point to the FormField.Form.Id property. ASP .NET MVC is still expecting formField.Form.Name (as this was marked as required on the Form object).
If I remove the "[Required]" annotation from the Form field of the FormField object, the validations wouldn't fire, but this would make the foreign key to Form.Id nullable in the database, which it shouldn't be.
Any thoughts? I'm probably doing something wrong here but I'm not entirely sure what.
I removed the [Required] annotation on FormField.Form. That didn't fix the issue. I tried adding "[Bind(Exclude = "vm.FormField.Form")]" to the parameter being posted. This stops the binding, but ensures I'm still left with the validation errors in ModelState.
Ultimately, I had to do:
ModelState.Remove("FormField.Form.Id");
ModelState.Remove("FormField.Form.Name");
And this prevented the ModelState errors I was getting. As I really only need the Form.Id, I had a property in my ViewModel "SelectedForm" and use this property for that value.
So...this works, but...this seems a pretty tedious solution as if I had more required fields on the FormField.Form object, I would have to list each one as well as hard code the property name in a string form.
Can anyone think of a way to refine this approach?
Maybe you could remove the [Required] attribute and instead use the EF fluent API to configure this:
modelBuilder.Entity<FormField>().Property(ff => ff.Form).IsRequired();

Accessing a dynamic field from another field template in ASP.NET Dynamic Data

Is there a way to access the value of a dynamic field from another dynamic field template?
For example let's say that in my model I have something like this:
//auto generated by my data context
partial class Foo
{
public string Name { get; set; }
public bool ShouldGenerateNameDynamically { get; set ; }
}
[MetadataType(typeof(FooMetadata))]
partial class Foo{}
class FooMetadata
{
[UIHint("DynamicName")]
public object Name { get; set; }
}
So based on the value of ShouldGenerateNameDynamically I'd like to enable or disable and assign a calculated value to the Name field in my custom DynamicName template either in the PreRender or OnDataBinding events
So is there any way to send like an event across the dynamic data templates or access another field from a field template?

Set which property in POCO to display when databinding to drop down

If having a List filled with objects of the following type:
public class Person {
public string Name { get; set; }
public string Ssn { get; set; }
}
How can one set the value of the property Name to be what's beeing displayed within an ASP.NET DropDown, without setting any properties on the ASP.NET DropDown object? Of course, one way would be to ovveride ToString() on the class Person, but is there any other way to accomplish the same thing?
Thanx!
You can create new control derived from DropDown (e.g. DropDownEx), introduce new attributes e.g. DisplayFieldAttribute, ValueFieldAttribute. Then use TypeDescriptor.GetProperties to enumerate properties and analyze attributes for binding them respectively.

Categories

Resources