Null viewmodel passed to controller - asp.net-core

I have a basic 'create' view scaffolded from a domain model, so it is typed to the model
#model TblProduct
<form asp-controller="Product" asp-action="Create">
...
<input asp-for="Artist" class="form-control" />
...
I'm trying to add functionality and use a view model instead, and I'm starting with a very basic viewmodel with only that domain model within it:
public class ProductViewModel
{
public TblProduct P { get; set; }
}
Now I've changed the 'create' view to use the view model instead
#model ProductViewModel
<form asp-controller="Product" asp-action="Create">
...
<input asp-for="P.Artist" class="form-control" />
...
So I expect the model to be valid given that (aside from editing the variable names) I'm populating all the same fields from the form, and effectively no other fields have been added to the model.
An error occurs when I post the form, I pass a ProductViewModel parameter to the create method but on inspection it is null. However ModelState.IsValid is true. So the code tries to write to the db and fails.
public async Task<IActionResult> Create([Bind("ID,Artist,ProductTitle... (long list removed)...] ProductViewModel productAndItems)
{
var prod = productAndItems.P;
if (ModelState.IsValid)
{
_context.Add(prod);
...FAIL
Any idea what I should be checking here - what am I missing?
How do I get the view (typed to a viewmodel) to pass the model data to the controller? And if it's null, how can ModelState.IsValid be true? In the example above I have debugged, the parameter passed in productAndItems is null.

Your current Bind attribute is looking for the following properties ID,Artist,ProductTitle... (White-list) and it's not finding them so therefore it's ignore everything and treating it is as a (Black-list) item.
You can either decorate your ProductViewModel with the Bind attribute as follows:
[Bind(Include = "P")]
public class ProductViewModel
{
public TblProduct P { get; set; }
}
This will of course mean all the properties in the TblProduct will be bound when submitting
If you do not want all of the properties to be bound on submit for TblProduct then you can decorate the TblProduct with the Bind attribute as follows
public class ProductViewModel
{
public TblProduct P { get; set; }
}
[Bind(Include = "ID,Artist,ProductTitle")]
public class TblProduct
{
public int ID { get; set; }
public string Artist { get; set; }
public string ProductTitle { get; set; }
public string ProductSubTitle { get; set; } //we will not include this in our (White-list)
//more props
}
More reading at MSDN

you need to add a name attribute to your form so the controller will pick it up.

Related

Problem when submit a form with select tag helper asp.net core 6

I want to use select tag helper to choose a role for creating account in my web app.
The following is my code
[HttpGet]
public ActionResult Create()
{
var model = new AccountCreateViewModel()
{
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
};
return View(model);
}
The following is the code in view of the select.
<div class="form-floating">
<select asp-for="RoleId" asp-items="#Model.Roles" class="form-select"></select>
<label asp-for="RoleId"></label>
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<input type="submit" class="w-100 btn btn-lg btn-primary" />
The following is my model
public class AccountCreateViewModel
{
public RegisterModel.InputModel Input { get; set; } = new();
[StringLength(50)]
[Required]
public string FullName { get; set; }
[DataType(DataType.Date)]
public DateTime? BirthDate { get; set; } = null;
[StringLength(80)]
public string Address { get; set; }
[Required]
[Display(Name = "Role")]
public string RoleId { get; set; }
public List<SelectListItem> Roles { get; set; }
}
However, after I submit the form, then the controller check the model state, and it is invalid. I have debugged and all the fields is valid except Roles.
So, someone can give me a solution for this situation?
model state debugging
Apply [ValidateNever] attribute to remove validate of the Roles on the server side. When applied to a property, the validation system excludes that property.
public class AccountCreateViewModel
{
...
[ValidateNever]
public List<SelectListItem> Roles { get; set; }
}
Consider that a SelectList takes an object of type IEnumerable as the first argument in the constructor. Make sure that you give it an IEnumerable or List in this section:
Roles = new SelectList(_roleManager.Roles, "Id", "Name").ToList()
This code may help you:
Roles = new SelectList(_roleManager.Roles.ToList(), "Id", "Name")
If _roleManager.Roles returns an IEnumerable or List, you don't need the .ToList()

ASP.NET Core - Entity Framework Core: Access relationship when posting a typed form

I am posting a form in ASP.NET Core. I have an HTML element that shows a list of colors. The values of the select's options are mapped to ids in the color table. The problem is that I want to access the name of color in the back-end but I am getting a null reference. How can I remedy this in a good way?
Model
Carro
{
[Column("id_color")]
public int IdColor { get; set; }
[ForeignKey("IdColor")]
public Color Color { get; set;}
}
[Table("color")]
public class Color
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("color_name")]
public string ColorName{ get; set; }
}
}
My select html element is using a typed Car form.
#model Car
<form asp-route="store_car" enctype="multipart/form-data">
<div>
<label>Cor</label>
<select asp-for="IdColor" class="form-control" id="selectCoresCarro"></select>
</div>
My Action
[HttpPost]
[Route("/carros/anunciar", Name = "store_car")]
public IActionResult CadastrarCarro(Car car){
car.Color.ColorName; // Color is undefined.
}
Can someone give a good solution for this. I didn't want to write code to query the database.

Passing Model via dropDownListFor() from view to controller

I am trying to pass a Model from my view to my controller using dropDownListFor.
After choosing something from the list it sends the model to my controller but it's content NULL.
This is what i have for the Model
public class Model
{
public int ModelId { get; set; }
[Required]
public string Name { get; set; }
}
This is what my ViewModel looks like
public class ModelVM
{
public List<Model> Models;
public Model SelectModel { get; set; }
public IEnumerable<SelectListItem> ModelItems
{
get { return new SelectList(Models, "ModelId", "Name"); }
}
}
The controller when i put data in the ViewModel looks like this
public ActionResult Index()
{
ModelVM modelVM= new ModelVM()
{
Models = manager.GetAllModels().ToList()
};
return View(modelVM);
}
Finally this is what i have in the View for the dropDownList
#using (Html.BeginForm("Home", "Home", FormMethod.Post))
{
#Html.DropDownListFor(model => model.SelectModel, Model.ModelItems)
<input type="submit" value="Go" />
}
So this is supposed to send Model to my controller.
But when i check the content of the passed Model in the controller, everything is NULL which isn't supposed to be because when i debug the view and check for the content of ModelItems, everything in the ModelItems is there.
Here is when i check the content of the passed Model
public ActionResult Home(Model model) <<<<<<<<<< Content == NULL
{
return View();
}
A <select> element on posts back a single value. Html has no concept of what your c# Model class is so you cannot bind to a complex object. You need to bind to the ModelId property of Model. Your view model should be
public class ModelVM
{
[Display(Name = "Model")]
public int SelectedModel { get; set; }
public SelectList ModelItems { get; set ;}
}
and in the controller
public ActionResult Index()
{
ModelVM modelVM = new ModelVM()
{
ModelItems = new SelectList(manager.GetAllModels(), "ModelId", "Name")
// SelectedModel = ? if you want to preselect an item
};
return View(modelVM);
}
and in the view
#Html.LabelFor(m => m.SelectedModel)
#Html.DropDownListFor(m => m.SelectedModel, Model.ModelItems)
and in the post method your model will be correctly bound with the ID of the selected model
Trying passing in type ModelVM to the Home action as that is the type being passed to Index view.
Instead of model.SelectModel in the drop-down declaration, you only need an int ID to capture which ID is selected.

post complex viewmodels using ajax.beginForm MVC4 only getting null

im very new to mvc, lest say i have a viewmodel that cotains objects like
public class vm_set_rol
{
public IEnumerable<SelectListItem> roles { get; set; }
public Rol_User rol { get; set; }
}
rol is an object like:
public class Rol_User
{
public int idUser { get; set; }
public int Role { get; set; }
public int GrantedBy { get; set; }
public bool canGrant { get; set; }
public DateTime ExpirationDate { get; set; }
}
so i have a form on a view to let the user select 1 role from a roles dropdown and select a date and a checkbox somthing like:
<div class="ModalContainer">
#using (Ajax.BeginForm(new AjaxOptions
{
UpdateTargetId = "gestionRolContainer",
Url = "Permiso/Test",
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
}
)
)
{
<fieldset>
<legend>#Res_String.RolLabel</legend>
<span>ROL:</span><br />#Html.DropDownListFor(m => m.rol, Model.roles, new {#id="AdmPermUserRolesDropDown" })
<br />
#Html.CheckBoxFor(m => m.rol.conceder ,Model.rol.conceder) <span>Delegate?</span>
<br />
<input type="submit" class="buttonClass" value="OK" />
</fieldset>
}
</div>
the problem is that i only get null values, if i create some other property on the model like a string or int, those are posted back ok,
i kind of understad why objects are not posted back, bust is there any workaround??? or put a object on the modes is just wrong and i shuld declare the propertis on the viewmodel instead of an object???
Your dropdown is incorrectly bound. It should be bound to a scalar property to hold the selected value:
#Html.DropDownListFor(
m => m.rol.Role,
Model.roles,
new { id = "AdmPermUserRolesDropDown" }
)
As far as the Roles collection property is concerned, it will always be null in your controller action because this list is never sent to the server when you submit a form. Only the selected value is sent. So if you need to redisplay this view once again you will have to populate the Roles collection property in your HttpPost action the same way you did in your GET action.
Also your checkbox is bound to some m => m.rol.conceder property which doesn't exist in the view model you have shown. I guess you meant using the canGrant boolean property. Also you don't need to provide as second parameter to the CheckBoxFor helper the value. It will be inferred from the lambda expression:
#Html.CheckBoxFor(m => m.rol.canGrant) <span>Delegate?</span>
Last but not least, since you are using an Ajax.BeginForm make sure that you have referenced the jquery.unobtrusive-ajax.js script in your view.

Enumerable objects in ViewBag in MVC4

I have the below model say F:
public partial class F
{
[Key, Display(Name = "Id")]
public int FId { get; set; }
public int RId { get; set; }
public int FTId { get; set; }
public string C { get; set; }
public string U { get; set; }
public string D { get; set; }
[ScaffoldColumn(false)]
public System.DateTimeOffset Created { get; set; }
}
In the controller I have to read all the records of 'F' from database and assign those to an enumerable list of records.
For ex:
ViewBag.Cs = enumerable C column items (textbox)
ViewBag.Us= enumerable U column items (textbox)
ViewBag.FTIDs = enumerable FTId column items (this has to be a dropdown)
In my I have to show
#Html.Textbox(Cs);
#Html.Dropdown(FTIDs);
I gave only textbox and dropdows as an example, there could be many other controls like datetimes, checkboxes etc.,
I should be able to written each column as list a in viewbag and show it in MVC View.
Can somebody advise if this achievable and how?
Many thanks...
Don't use a viewbag for something like this - instead strongly bind your view to a model. Only use a view bag if you have something really small to pass.. anything complex you should always use a strongly typed view model, you get intellisence and its must cleaner for unit testing
View Model:
Public class MyViewModel
{
public List<F> MyListOfFObjects { get; set; }
}
Now when you create your view you can bind it to this view model in the popup or if you don't want to recreate it simply add a reference to it at the top of your view like so:
#model <your project>.<folder view model is in>.<view model name>
for example
#model AdventureWorks.Models.EmployeeViewModel.
In your controller you simply create this view model and pass it to your view such as:
public ActionResult Index()
{
MyViewModel vm = new MyViewModel();
// Initialize your view model
// Get all the F objects from the database and populate the list
return View(vm); // now your view will have the view model
}
Now in the view you can iterate through this view model
#foreach(var fObject in Model)
{
#Html.TextBoxFor(m => m.fId)
#Html.TextBoxFor(m => m.rID)
}
Here is a link to a list of the different #Html helpers that you can use btw
http://qkview.com/techbrij/aspnet-mvc-4
Reference for strongly binded views:
http://www.asp.net/mvc/tutorials/views/dynamic-v-strongly-typed-views