I have a complex class and the code looks like this:
public class Person
{
public Address address;
public Address GetAddress { get; set; } //Property
// Constructor
public Person() { address = new Address(); }
}
public class Address
{
// Properties
public String Street { get; set; }
public String City { get; set; }
// Constructor
public Address() { Street = "Madison"; City = "NY"; }
}
My Action Methods(Trimmed Version) are as follows:
public ActionResult Index()
{
Person person = new Person();
return View(person);
}
[HttpPost]
public ActionResult Index(Person person)
{
ViewBag.City = person.address.City;
return View();
}
From my View/.cshtml file:
I can access values for address through Model's instance or the property.
When I bring up the View for the 1st time:
If I access values through instance #Html.TextBoxFor(m => m.address.Street) -- I get the values set in the class constructor - Madison and NY.
If I access it through property - #Html.TextBoxFor(m => m.GetAddress.Street), I get blank values.
Inside my Controller Action method- public ActionResult Index(Person person):
If I access values using instance - person.address.street/city, I get the old values that were assigned to my model - Madison and NY.
If I access values using property - person.GetAddress.Street, I get the updated values that were entered from View.
Thanks in advance.
If I access it through property - #Html.TextBoxFor(m => m.GetAddress.Street), I get blank values
That's because you're never setting the Person.GetAddress property (an odd name for a property, sounds like it should be a method). Therefore you're getting your blank value.
If I access values using property - person.GetAddress.Street, I get the updated values that were entered from View
It sounds like you have the Html.TextBoxFor(m => m.GetAddress.Street), in which case you're populating that property value. Then when you POST you are pulling the person.GetAddress.Street which includes the inputted data.
The reason that GetAddress is giving you a blank text box is because it's not set. It looks like you might want that to be actually giving you the Person's address, so you could change it like so:
GetAddress
{
get { return address; }
}
Or alternatively, simply change the address field to a property:
public Address address { get; set; }
This gives you the ability to get rid of GetAddress altogether if you so choose.
If you were setting GetAddress with your text box, that would cause it to store the values that you pass in. Since, in your code sample, GetAddress is a standalone property that isn't actually attached to the address field of the Person class, then having a text box tied to it will only change the value of the GetAddress property rather than the actual address property as you expected. You might find that my alternative solution for question 1 would solve this problem for you.
Your property is not auto-linked to your field. GetAddress { get; set; } auto-generates a field, and the property acts as syntactic sugar for accessing those. If you just want to wrap address you could do the following:
public Address GetAddress
{
get
{
return address;
}
set
{
address = value;
}
}
Alternately, you can just get rid of your field, and do (which gives you public getters and setters):
public Address Address { get; set; }
Related
What I'd like to achive is to be able to modiy certain (string) values after they were binded to a property but they are being validated in .NET Core 3.1.
Example poco class:
public class MyPoco
{
[TrimContent]
[MinLength(2)]
public string? FirstName { get; set; }
[TrimContent]
[MinLength(2)]
public string? Surname { get; set; }
[TrimContent]
[LowerCase]
public string? EmailAddress { get; set; }
}
So let's say a form is posted to the MVC controller and the values entered are
" F " for the first name and " S " as the surname, " My.Email#Address.Com ".
They should be modified, i.e. trimmed to "F" and "S" and the MinLength=2 should be alerted i.e. Also: I can avoid all the Trim() statements in my code.
My idea is, that when using a "TrimContentAttribute" (and other attributes that "correct" the values in some way), all values that have been set by previous BindingSourceValueProviders and then are being processed, but before the validation kicks in.
Also attributes marked with LowerCase, should automatically be "ToLower()", so the email address would be "my.email#address.com".
So it the idea would be to declrative approch other than having all the Trim() und ToLowerCase() methods all over the code where the entity is used.
The only idea I came up with so far to write a custom source as described in
Model Binding in ASP.NET Core - Additional sources. But I actually would like to rely on all the default values providers.
Note: There are validators on client side in action as well, but I'd like to have a solution also on the server side.
a new attribute can be created
public class MinLengthWithTrim : MinLengthAttribute
{
public MinLengthWithTrim(int length) : base(length)
{
}
public override bool IsValid(object? value)
{
var str = value as string;
if (str == null)
{
return false;
}
return base.IsValid(str.Trim());
}
}
Using:
[MinLengthWithTrim(10)]
public string Name { get; set; }
hopefully I'm missing something obvious, but I have a bit of an issue with some code I've written, and it's not feeling like I've written my code correctly.
So, let's say I have the following Model:
public class SampleViewModel {
[Required]
public string Property1 { get; set; }
[Required]
[EmailAddress]
public string Property2 { get; set; }
public IList<AbstractModel> Items { get; set; }
}
And then I have this abstract view model:
public abstract AbstractModel {
[Required(ErrorMessage = "This field is required")]
public virtual string Value { get; set; }
}
And these concrete view models:
public ConcreteModel1 : AbstractModel { }
public ConcreteModel2 : AbstractModel { }
Within my Controller, I have the following code (this is actually being done elsewhere, but for this sample, this is fine):
var model = new SampleViewModel();
var fields = new List<AbstractModel>() {
new ConcreteModel1() { Value = model.Property1 },
new ConcreteModel2() { Value = model.Property2 },
};
model.Fields = fields;
return View(model);
Within the SampleViewModel partial view (as I have one for each view model type), I have the following:
#model SampleViewModel
#for(var i = 0; i < Model.Items; i++) {
#Html.EditorFor(m => Model.Items[i])
}
Lets say that I also have a distinct partial view (with very different layout requirements) per each AbstractModel.
Example for the ConcreteModel1:
#model ConcreteModel1
#Html.TextboxFor(m => m.Value)
And for the ConcreteModel2:
#model ConcreteModel2
#Html.DisplayFor(m => m.Value)
This is all working, but as I've had to pass the ViewModel's properties (Property1) into the AbstractModel, I have lost the connection between the view and the underlying model. I have been able to bind the form fields back to the model, using a custom Model Binder, but the main thing I'm missing are the model validators which have been added to the SampleViewModel class.
Ideally I want this information to be available to the AbstractModel. Validation is happening, but I'm only getting basic validation on the client (via AbstractModel's Value required attribute), but I'd like to be able to pass along validation needs from my SampleViewModel into the AbstractModel.
Expectation
What I'd really like to happen is for the AbstractModel's Value property to somehow impersonate the property that is passed into it, so that it is just acting as a proxy to the original model, but has just reshaped the SampleViewModel (or specifically it's Property1 property).
So the important thing is, considering the following creation of my fields:
var fields = new List<AbstractModel>() {
new ConcreteModel1() { Value = model.Property1 },
new ConcreteModel2() { Value = model.Property2 },
};
How do the AbstractModels know that their Values are supposed to be: Required, and also Required and an EmailAddress, based on the properties that are used to create them?
Thank you for your input.
I am attempting to rid my code of the dependency on System.Web.Mvc, since I have extracted my model out to a separate .dll (outside of the MVC project) so that it can be shared among many projects.
I am attempting to create a "custom SelectListItem" which is simply just a class with two properties of string Text and string Value and looks like:
public class DropDownListItem
{
public string Text { get; set; }
public string Value { get; set; }
}
However, I am trying to find a way to pass this object into a DropDownListFor in my view. Is there anyway to manipulate this so that I can do something like:
#Html.DropDownListFor(x => x.Value, Model.Values, new { #class="form-control" })
Where Models.Values is of type List<DropDownListItem>.
Yo don't need to create a DropDownListItem class - instead you can create your own POCO (Plain-Old-C#-Object) to contain List Options:
For example, let say you have the following class :
class MyClass
{
public int Id {get; set; }
public string name {get; set; }
}
to have dropDownlist which contains a list of MyClass, you can simply use :
#Html.DropDownListFor(model => model.Entity.Id, new SelectList(MyClass, "Id", "Name"))
The id represent the value of the selection, and the name will be the text displayed within the DropDownList.
Hope this helps!
You can put the list item in viewbag and get the value in your view like these:
#Html.DropDownList("Ligas")
and your codebehind
Modelo.AdminSitio = Modelo.GetAdminSitio();
ViewBag.Ligas = new SelectList(Modelo.AdminSitio.ListadoLigas, "Value", "Text");
when listdoLigas is equals
ListadoLigas= (from x in db.Ligas select new DropDownListItem { Value = x.Oid_Liga, Text = x.NombreDeLiga}).ToList();
When I am changing the "model => model.id" to "model => model.Supplierid" i am getting below error
"The parameter 'expression' must evaluate to an IEnumerable when
multiple selection is allowed."
please have look on below code
// this my model class
public class clslistbox{
public int id { get; set; }
public int Supplierid { get; set; }
public List<SuppDocuments> lstDocImgs { get; set; }
public class SuppDocuments
{
public string Title { get; set; }
public int documentid { get; set; }
}
public List<SuppDocuments> listDocImages()
{
List<SuppDocuments> _lst = new List<SuppDocuments>();
SuppDocuments _supp = new SuppDocuments();
_supp.Title = "title";
_supp.documentid = 1;
_lst.Add(_supp);
return _lst;
}
}
// this my controller
[HttpGet]
public ActionResult AddEditSupplier(int id)
{
clslistbox _lst = new clslistbox();
_lst.lstDocImgs= _lst.listDocImages();
return View(_lst);
}
// this is view where i am binding listboxfor
#model clslistbox
#using (Html.BeginForm("AddEditSupplier", "Admin", FormMethod.Post))
{
#Html.ListBoxFor(model => model.id, new SelectList(Model.lstDocImgs, "documentid", "title"))
}
Can anyone see the reason for it?
I think the changing of the property in the expression here is a red-herring - it won't work in either case.
Update
However, see at the end of my answer for some probably needlessly detailed exposition on why you didn't get an error first-time round.
End Update
You're using ListBoxFor - which is used to provide users with multiple selection capabilities - but you're trying to bind that to an int property - which cannot support multiple selection. (It needs to be an IEnumerable<T> at least to be able to bind a list box to it by default in MVC)
I think you mean to be using DropDownListFor - i.e. to display a list of items from which only one can be selected?
If you're actually looking for single-selection semantics in a listbox, that's trickier to do in MVC because it's Html helpers are geared entirely around listboxes being for multiple selection. Someone else on SO has asked a question about how to get a dropdown to look like a list box: How do I create a ListBox in ASP.NET MVC with single selection mode?.
Or you could generate the HTML for such a listbox yourself.
(Update) - Potentially needlessly detailed exposition(!)
The reason you don't get an exception first time round is probably because there was no value for id in ModelState when the HTML was generated. Here's the reflected MVC source (from SelectExtensions.SelectInternal) that's of interest (the GetSelectListWithDefaultValue call at the end is the source of your exception):
object obj =
allowMultiple ? htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string[])) :
htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string));
if (!flag && obj == null && !string.IsNullOrEmpty(name))
{
obj = htmlHelper.ViewData.Eval(name);
}
if (obj != null)
{
selectList =
SelectExtensions.GetSelectListWithDefaultValue(selectList, obj, allowMultiple);
}
Note first that the control variable allowMultiple is true in your case, because you've called ListBoxFor. selectList is the SelectList you create and pass as the second parameter. One of the things that MVC (unfortunately in some cases) does is to use ModelState to modify the select list you pass when re-displaying a view in order to ensure that values which were set in ModelState via a POST are re-selected when the view is reloaded (this is useful when page validation fails because you won't copy the values to your underlying model from ModelState, but the page should still show those values as being selected).
So as you can see on the first line, the model's current value for the expression/field you pass is fished out of model state; either as a string array or as a string. If that fails (returns null)then it makes another go to execute the expression (or similar) to grab the model value. If it gets a non-null value from there, it calls SelectExtensions.GetSelectListWithDefaultValue.
As I say - what you're trying to do will ultimately not work in either the case of Id or SupplierId (because they would need to be IEnumerable) but I believe this ModelState->Eval process is yielding a null value when you use Id, so the process of getting an 'adjusted' SelectList is skipped - so the exception doesn't get raised. The same is not true when you use SupplierId because I'll wager that there's either a value in ModelState at that point, or the ViewData.Eval successfully gets an integer value.
Not throwing an exception is not the same as working!.
End update
Try changing your property from int to int[]
public class SuppDocuments
{
public string Title { get; set; }
public int documentid { get; set; }
}
Assuming above is the class used for binding the model , try changing the documentid property as below
public class SuppDocuments
{
public string Title { get; set; }
public int[] documentid { get; set; }
}
I have two MVC models that look like this:
public class OtherModel
{
[Required]
[Display(Name = "Another ID")]
public int id{ get; set; }
}
public class MyModel
{
[Required]
[Display(Name = "ID")]
public int id { get; set; }
public PlayerModel otherModel = new OtherModel ();
}
My controller has an [HttpPost] action called USE that looks like this:
[HttpPost]
public ActionResult Use(MyModel myModel)
{
/// myModel.otherModel.id is 0 here!!
}
This action takes in a MyModel. When my form is being posted, the otherModel variable contains a 0 for the id value. Now, the view that contains the form is handed a MyModel and actually displays the otherModel.id on the page. The problem is the post action is not
properly marshalling the form data into the otherModel object and I have no clue why.
Another note: When I examine the form data headers for the post, I clearly see otherModel.id with the value that I expect.
Why is this data not appearing correctly within my otherModel object?
Thank You in advance!
Did you registered binder in Global.asax.cs?
public static void RegisterBinders(ModelBinderDictionary binders)
{
binders.Add(typeof(MyModel), new MyModelBinder());
// other binders
}
This is called in Application_Start like the following:
protected void Application_Start()
{
RegisterBinders(ModelBinders.Binders);
}
PS: I assumed you are using a custom model binder. In case you are using automatic binding see if you respect the naming conventions.
Instead of initializing otherModel with a new object at the line PlayerModel otherModel = new OtherModel();, use a property public PlayerModel otherModel { get; set; }. otherModel needs a property setter for the model binder to assign the value properly. This may require you to also change how you populate the otherModel property when displaying the view - construct an object and assign it explicitly in either the displaying controller method or some other function that hydrates the model.
I had an almost identical issue. The fix for me was as Matt mentioned, to make the inner object a property with the needed accessors.
public class OuterModel
{
public OuterModel ()
{
AuxData= new InnerModel();
}
public InnerModel AuxData{ get; set; }
}
public class InnerModel
{
Int Id {get; set;}
}