My editor templates for an empty list is not rendering - razor

I have a custom editor in my EditorTemplates folder for a IList<PersonRelations>. The Editor has this model:
#model IList<PersonRelation>
and in my entity is as this:
public IList<PersonRelation> Relations { get; set; }
this is how I called it in my view:
<div class="editor-field">
#Html.EditorFor(model => model.Relations)
</div>
and it's rendering the model if Relations is null.
But.. I want to declare my property in this way
private IList<PersonRelation> _relations;
public IList<PersonRelation> Relations
{
get { return _relations ?? (_relations = new List<PersonRelation>()); }
set { _relations = value; }
}
To avoid null references exceptions.
The thing is when the List is not null and has no elements, the editor is not being displayed at all.
In my editor I iterate through the elements but also I render another controls outside the loop, and I can't see any elements.
I'm missing something?

Solved.
When I changed the property, I forgot to decorate it with [UIHint("PersonRelations")]
which was in the original form of the property (my custom editor's file name is "PersonRelations.cshtml")
This is needed due it seems that the engine is not able to infer the editor for a collection, even when you have one, so you explicitly have to tell which one you want to use.

Related

Is there a convention or guideline for mutable state in a Blazor component?

Coming from React JS, I wonder if Blazor has the concepts of State and Property. See the code below:
<div>#field</div>
<button #onclick=#(() => field = Random.Shared.Next())>Change field</button>
<div>#Prop</div>
<button #onclick=#(() => Prop = Random.Shared.Next())>Change prop</button>
#code {
private int field;
[Parameter]
public int Prop { get; set; }
}
There is absolutely no difference between field and Prop, except that you can set Prop from the parent's template. I haven't been able to create a property that cannot be updated within the component, since the public setter is required.
It seems to be up to the team to decide whether it makes sense to update a property within the component or not.
Is there a convention/best practice/guideline about that?
Is there a convention/best practice/guideline about that?
Yes: do no do that.
Most 'official' is in the docs here:
Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time.
and here:
Generally, avoid creating components that write directly to their own component parameters. ...
but that is repeated and explained in many places.

Model binding with a child object

I have a class:
public class Application
{
....
public Deployment NewDeployment { get; set; }
....
}
I have an editor template for Deployment within the Application View folder.
The ApplicationViewModel has a SelectedApplication (of type Application), in my Index.cshtml where I use ApplicationViewModel as my Model, I have this call:
#using (Html.BeginForm("Create", "Deployment", new { #id = Model.SelectedId,
q = Model.Query }, FormMethod.Post, new { id = "form", role = "form" }))
{
#Html.EditorFor(m => m.SelectedApplication.NewDeployment)
}
Which then correctly renders out the control in my DisplayTemplates\Deployment.cshtml (though, it may just be pulling the display code and nothing in relation to the NewDeployment object's contents). All is well in the world until I go to submit. At this stage everything seems good. Controller looks like:
public class DeploymentController : Controller
{
[HttpPost]
public ActionResult Create(Deployment NewDeployment)
{
Deployment.CreateDeployment(NewDeployment);
return Redirect("/Application" + Request.Url.Query);
}
}
However, when it goes to DeploymentController -> Create, the object has nulls for values. If I move the NewDeployment object to ApplicationViewModel, it works fine with:
#Html.EditorFor(m => m.NewDeployment)
I looked at the output name/id which was basically SelectedApplication_NewDeployment, but unfortunately changing the Create signature to similar didn't improve the results. Is it possible to model bind to a child object and if so, how?
Your POST action should accept the same model your form is working with, i.e.:
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
Then, you'll be able to get at the deployment the same way as you did in the view:
model.SelectedApplication.NewDeployment
It was technically an accident that using #Html.EditorFor(m => m.NewDeployment) worked. The only reason it did is because the action accepted a parameter named NewDeployment. If the parameter had been named anything else, like just deployment. It would have also failed.
Per Stephen Muecke's comment and with slight modifications, I was able to find how to correct it:
[HttpPost]
public ActionResult Create ([Bind(Prefix="SelectedApplication.NewDeployment")] Deployment deployment)
{
// do things
}

MvvmCross (iOS) can't bind property in UILabel derived class

MvvmCross noob here. Does anyone know why I can't bind a property in a UILabel derived class?
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(_serverValue).For(p => p.Text).To(vm => vm.ServerListSelectedItem);
set.Bind(_serverValue).For(p => p.Visible).To(vm => vm.IsServerListAvailable);
set.Apply();
private class ServerValue : UILabel
{
public bool Visible
{
get { return !Hidden; }
set { Hidden = !value; LoginView.LayoutControls(); }
}
}
The text gets updated but the Visible property never does. Should I even expect that this should work?
Thanks,
Jon
MvvmCross uses Reflection - and Reflection is subject to .Net security rules.
Try making your control public rather than private to see if that helps -
public class ServerValue : UILabel ...`
Beyond that, there is also a registered custom binding for Visible in MvvmCross by default - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/MvxTouchBindingBuilder.cs#L45
registry.RegisterCustomBindingFactory<UIView>("Visible",
view =>
new MvxUIViewVisibleTargetBinding(view));
You may find that this conflicts with (and hides) your Visible property - custom bindings are checked before Reflection - so you may need to choose a different property name.
For more on binding to custom properties and more on custom bindings see N=19 and N=28 in http://mvvmcross.blogspot.com

IEnumerable in my ViewModel is not displayed with EditorForModel

ViewModel
[Validator(typeof(ProdutoCategoriaValidator))]
public class ProdutoCategoriaViewModel
{
[HiddenInput(DisplayValue = false)]
public Guid ID { get; set; }
public IEnumerable<SelectListItem> Tipos { get; set; } // <<<<------- Is not showing in my view
[AdditionalMetadata("data-bind", "event: { change: function(data) { Link(data.Nome()); }}")]
public string Nome { get; set; }
[DataType(DataType.Url)]
[AdditionalMetadata("Prefixo", "Produtos/{tipo-de-produto}#")]
public string Link { get; set; }
public int? Ordem { get; set; }
public ProdutoCategoriaViewModel()
{
ID = Guid.NewGuid();
}
}
Solution
View (_Formulario.cshtml)
#model ProdutoCategoriaViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { id="form-produtocategoria", data_bind = "submit: salvar" }))
{
#Html.AntiForgeryToken()
<legend>#Html.MvcSiteMap().SiteMapTitle()</legend>
<fieldset>
#Html.ValidationSummary(false, "Verifique os erros abaixo:")
#Html.EditorForModel()
</fieldset>
<div class="buttons">
#Html.ActionLink("Cancelar", "Index")
<input type="submit" value="SALVAR" />
</div>
}
SelectListItem.cshtml
#model IEnumerable<SelectListItem>
#Html.DropDownListFor(m => m, Model)
<p>Test</p>
Result
Full image: http://i.imgur.com/I7HxA.png
Notes
I've tried to put the attribute "UIHint" but still nothing is displayed!
Questions
What am I doing wrong?
By default when you use Html.EditorForModel don't expect this to recurse down to complex properties such as your Tipos property which is of type IEnumerable<SelectListItem>. Brad Wilson explained this in his blog post (more specifically read the Shallow Dive vs. Deep Dive section towards the end of the post). You will need to write a custom editor template for the Object type if you want this to happen.
Another possibility is to specify the template name:
#Html.EditorFor(x => x.Tipos, "SelectListItem")
Also bear in mind that your editor template for the SelectListItem is wrong because you are binding the DropDownListFor to the model as first argument. Don't forget that the first argument of this helper must be a scalar property that will be used to hold the selected value. You need a string or integer property on your view model for this. The second argument represents the collection.
Another important aspect about editor templates is that when you have a property of type IEnumerable<T> and an editor template called T.cshtml this editor template must be strongly typed to the T class and not IEnumerable<T> as you did with your SelectListItem.cshtml template. This doesn't apply if you use UIHint or specify the template name as second argument to the EditorFor helper. n this case the template will be typed to the collection.
So to recap, you could either implement a custom object editor template as Brad Wilson suggested that will recurse down to complex properties or you could modify your _Formulario.cshtml view to specify EditorFor each individual elements.
A #foreach loop renders something that looks right, but the resulting markup will have the same id for each row's controls. It also will not post the enumerable collection back with the model instance.
There are two ways to make this work such that you have a unique id for each item in the collection, and so that the collection is hydrated on postbacks:
1. Use the default editor template rather than a named one
// editor name parameter must be omitted; default editor template's model type
// should be a single instance of the child object, not an IEnumerable. This
// convention looks wrong, but it fully works:
#Html.EditorFor(parentObject => parentObject.Items)
2. Use a #for loop, not a #foreach:
#for (int i = 0; i < parentObject.Items.Count ; i++) {
// the model binder uses the indexer to disambiguate the collection items' controls:
#Html.EditorFor(c => Model.Items[i], "MyEditorTemplate")
}
This will not work, however:
// this will error out; the model type will not match the view's at runtime:
#Html.EditorFor(parentObject => parentObject.Items, "MyEditorTemplate")
Nor will this:
#foreach(var item in parentObject.Items) {
// this will render, but won't post the collection items back with the model instance:
#Html.EditorFor(c => item, "MyEditorTemplate")
}
For a detailed answer why this is, look at this question: MVC can't override EditorTemplate name when used in EditorFor for child object.

How to bind a different view html element to a model property?

I have a description in my model...
[Required]
[StringLength(1000, ErrorMessage="Description cant be more than 1000.")]
[DataType(DataType.Text)]
private string description;
public string Description
{
get { return description; }
set { description = value; }
}
And on my view I have...
#Html.TextAreaFor(e => e.Description, new { cols = "60", rows = "12", #class = "focusChanger" })
But actually I need the textarea to be called 'myDivId' but If I change the Id in the html attributes it wont bind to the model. What can I do?
If I change the Id in the html attributes it wont bind to the model
The id attribute has nothing to do with binding. It's value is never sent to the server. Feel free to change it. It's the name attribute of the generated <textbox> that is used to send the value to the server when the form is submitted.
There's two ways to do this. The first is to create a custom model binder. See here for more details.
Instead of that though, I suggest an easier way which is to create a viewmodel. The top voted answer in Bind formValue to property of different name, ASP.NET MVC will give you an idea on how to do that.