I am having a bit of an issue with an MVC action that I am currently implementing. Simply put, I have a table of users with roles assigned to them. The number of roles per user can range from 1-3 and I am not sure how to reflect this in a POST action of MVC when editing them. To get the roles into the view I use a list of class AccountMapping that contains the role name and ID:
public ActionResult EditUser(User user, EditUserModel model)
{
// Omitted model assignments
model.AccountMappings = _reportUsersRepository.GetAccountMapping(user.UserId).ToList();
return View(model);
}
In the View (using Razor):
#foreach (var item in #Model.AccountMappings)
{
<div>
<p>#item.Target_Type.Replace('_', ' ')</p>
<input name="#item.Role_Name" value="#item.Target_Id" type="number" placeholder="Role Id" required />
</div>
}
How would I go about structuring the POST action to take into account these inputs? I am using the same model for the POST action and I am aware that the AccountMapping list would contain no results on postback. I am not sure about using the FormsCollection method because you need to know the name of the keys in order to retrieve the values. Am I missing something obvious?
Many Thanks.
Expanding on my comment, If you use indexing instead of foreach, then it can correctly name and map the individual items in a collection back to the posted input collection. e.g.:
#for (int i = 0; i < Model.AccountMappings.Count(); i++)
{
<div>
#Html.EditorFor(x=>Model.AccountMappings[i])
</div>
}
If you look at the generated HTML you will see the inputs are named uniquely based on their index position.
These will be automatically mapped back into a receiving collection (assuming your postback takes the same object type as your view model that was passed to the view).
Related
.NET Core 3.1 with EF Core, trying to update data loaded into a form, but for some reason it sets the Id = 0 on submit, and I have no idea why. Or, if I explicitly set the id value, the controller gets a null value for all the records. Importantly, it's a list of entities that I'm sending down to the db.
I can create multiple records just fine using the same form. But if I load the form with existing records, and want to make changes, I can't. The Id of each record gets set to zero, if I don't put the Id in for each record. If I do put the Id of each record, the form seems to submit correctly, as I'm looking at Chrome DevTools and see that the values are there, including the Id, but then the controller gets that data, and it's null...
ETA It looks like the 41st row is causing the issue when I do submit the form with the Id included for each row. What I don't know is if that means 41 rows is too many, or there is some issue with the data itself, which means the controller won't pick up any of it...but neither of these make sense, since the form loads the 62 rows just fine.
Here's a simplified version of it, with key pieces included so it makes sense.
My model:
#model List<EvaluationsModel>
My form:
#{
var createOrUpdate = "Update";
if (Model[0].IsCreate)
{
createOrUpdate = "Create";
}
}
<form id="evalForm" asp-action="#createOrUpdate">
#for (var i = 0; i < Model.Count; i++)
{
<textarea asp-for="#Model[i].Comments" rows="5" cols="75"></textarea>
<input asp-for="#Model[i].Id" type="hidden" />
}
<input type="submit" id="submitButton" value="#createOrUpdate" class="btn btn-primary" />
My controller method:
public IActionResult Update(List<EvaluationsModel> evalRows)
{
_dostuffWith(evalRows);
return Redirect(toGet);
}
Sometimes asp-for in the hidden field is not working properly for some reasons, and I usually use value too
<input asp-for="#Model[i].Id" value"#Model[i].Id" type="hidden" />
IMHO, you have to add Id to your model, in case if it is not update, but create
public ViewModel
{
public List<EvaluationsModel> EvaluationsModels {get;set;}
public int Id { get; set;}
{
and add hidden field Id to a view
#model ViewModel
....
<form id="evalForm" asp-action="#createOrUpdate">
<input type="hidden" asp-for="Id" value="#Model.Id" />
#for (var i = 0; i < Model.EvaluationsModels.Count; i++)
action
public IActionResult Update(ViewModel viewModel)
The original problem is that there were checkbox entries that were being doubled, and adding the Id value broke the submission of the form. I hadn't included the checkbox in the original code I submitted because I didn't think that was connected to the issue.
After lots of searching, this is the (final) answer I've come up with:
Upgrade to dotnet core 5.0 or greater.
In Configure Services in Startup.cs, add this line:
services.Configure(options => options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None);
The CheckBoxHiddenInputRenderMode has three options; "none" will prevent the 2nd set of checkboxes from materializing. The other two options will determine where the 2nd set gets generated: either at the end of the form (default), or inline.
I haven't found any documentation showing how to implement this behavior as of this writing.
The problem was twofold:
The tag helper for a boolean input (aka, a checkbox, and not included in my original question) in 3.1 and before, duplicates its entries, so for each <input asp-for="#Model[i].IsCorrect" /> item, I get two entries. (The reason MVC does this is to make sure that something gets submitted, because the HTML5 spec says that if a checkbox isn't checked, don't submit anything at all. MVC make sure that something instead of nothing gets submitted.)
I saw this, but it didn't look strange to me because of some other things I had going on. So the form was sending all the regular entries, plus two of the bool values (instead of just one, like all the others). Adding the id meant I was sending more than the model was expecting.
The controller was only receiving null after getting too many rows/entries, because I was sending the duplicated boolean/checkbox value, and then the Ids. There was no error reporting about this at all. Nothing saying that the form was oversubmitting, or that there were too many entries. This part makes me wonder if there is a bug in the dotnet core framework. I'm not sure if having it fail silently like this is intentional, or was an oversight, if there is a way to check.
But problem solved.
References: the commit for the new options.
I'm binding a list of customers into a customer search results page using the model binding of MVC3, and using Razor to render all the customers in a foreach loop. My question is how then to send back the customer object to the action to save me having to fetch the details again.
Below is my action method signature:
public ActionResult BasketAddCustomer(Customer customer)
The Customer object is quite large, ie. lots of fields
Below is a cut down version of the view which renders each customer and has the button to select each one.
#model WebUI.Models.SearchModel
#foreach (var customer in Model.Customers)
{
<h5>#customer.FirstName #customer.LastName</h5>
<button onclick="window.location.href = '#Url.Action("BasketAddCustomer", "Cust", customer)';">Select customer</button>
}
The problem with this is that the customer that is passed into the action seems to come through as being full of nulls.
The html that is rendered by the #URL.Action is below and looks like a good start but only has some of the customer fields, not all. Is the Customer just too complex for being broken down this way? Is there a better way to do it?
<button onclick="window.location.href =
'/Test/Cust/BasketAddCustomer?BirthDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryEmailFlag=False&PrimaryEmailDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryEmailID=0&PrimaryPhoneFlag=False&PrimaryPhoneDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryPhoneID=0&WifiConnected=False';" >Select customer</button>
As per my knowledge with #url.action we can only send route values and couldn't send the object itself.
Kindly refer this link: https://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.action(v=vs.118).aspx
So for your problem, you can send any unique value to the controller action as a route parameter in url.helper and get the entire object from the database using linq query in action .
Note : if your requirement says that , you should send the object only, then you can put all the controls inside the form in the view and send it as post method.
Hope above information was helpful.
Thanks
Karthik
Is there a way to pass entire object from ASP.NET MVC 5 View to a Controller? This is my situation:
I have a View that displays all rows from a DB table
The view's model is IEnumerable
Each row has a link after it's data that leads to the scaffolded UPDATE view
Is there a way to pass the entire object to the Update controller method so it would initially fill the form inputs with the old data? Something like:
#Html.Action("Update me!", "Update", new { objectFromModelList })
And then in the controller
public ActionResult Update(MyType parameter)
{
return View(parameter);
}
Or something like that. Please help, I am new to this and can't find the answer anywhere.
Your objects could be so big! Query string's has a limitation on how much data you can pass via those based on the browser. You should consider passing a unique id value (of the record) and using which get the entire record from db in your action method and pass that to the view.
#foreach(var item in SomeCollection)
{
<tr>
<td> #Html.Action("Update me!", "Update", new { id = item.Id }) </td>
</tr>
}
and in the action method
public ActionResult Update(int id)
{
var item = GetItemFromId(id);
return View(item);
}
Assuming GetItemFromId method returns the method/view model from the unique id value. Basically you get the entire record using this unique id from your db table/repository.
Assuming that your Update View isn't of type IEnumerable...
You just need to pass the ID of the record that you want to send to the Update view...
Like so:
#Html.Action("Update me!", "Update", new { id = item.ID })
Then your Update action would look like this:
[HttpGet]
public ActionResult Update(int id)
{
var parameter = db/* connection string variable */.TableName.Find(id);
return View(parameter);
}
Then your link should work appropriately.
Hope this helps!
I have searched myself and the best way, aside from passing the ID, that I have found is to store any other variables that you might need into hidden input fields or HTML5 tags. Then you can script a way to handle any button/link click events. This way you can store vital object properties of each record and easily pass them back to a controller. Think Client-side here, Once the data ends up Client-Side, use Client-Side tools to handle and pass it back to server side/controller.
I do something similar with a type of library reservation system that allows users to reserve items on available dates. I pass all available records to the view. Each record has a few fields that I want to hold onto including the ID for users reference. When the user clicks the button, I collect the needed fields.
You could use HTML5 form input fields that are hidden or you could just use JavaScript to collect those values using GetElementByID. An example of this would be to store the ID in the div wrapper. Then have another div hold a sub parameter. You can use Javascript to find the record ID and then get the second div by it's id. Example would be get the id NameRecord from XRecord where X = the ID passed.
I then pass those values to the controller, instantiate a new class/object for the reservation. The new class object also has the item class/object as a property. For example consider the following;
var reservation = new Reservation
{
myKit = new ResourceKit()
};
After that, you can store it in a session if you need to build on it. In my case I am holding it in a session because I allow the user to check availability/dates. These items are a physical resources that gets checked out similar to a library and are transferred via office mail.
If you dont mind the data sitting client-side, you can store it using LocalStorage and JavaScript. This type of data isnt secure at all much like a cookie. One of the ways that I have used this is to set site preferences. Users can select a color scheme and those preferences are stored in LocalStorage. That way when the return to the site those preferences remain. This is a key attribute of LocalStorage and might not be applicable to your needs/circumstances.
I'm trying to send back the contents of the input field as follows.
#model Bike
#using (Html.BeginForm("BikeStore", "Home", FormMethod.Post))
{
<input type="text" value="#Model.Color" />
<input type="submit" value="Save"/>
#Html.ActionLink("Cancel", "Bikes", "Home")
}
The action and the model are declared as follows.
public ActionResult BikeStore(Bike bike)
{
...
return RedirectToAction("Bikes");
}
public partial class Bike
{
[Key] public Guid Id{get; set;}
[Required, StringLength(999)] public string Color { get; set; }
}
I'm hitting the breakpoint in the method BikeStore but bike passed in is empty, i.e. it's not null but all the strings are, the guids are 00..00 etc.
I've tried different variable types. I also tested FormMethod.Get and (not at the same time, of course) adding HttpPost attribute. No luck.
Asp.Net MVC binder system uses the name of the input elements to bind to the appropriate property or parameter. So, change this line:
<input type="text" value="#Model.Color" />
to:
#Html.TextBoxFor(m => m.Color)
This will generate following html(for example):
<input type="text" id="Color" name="Color" value="Black" />
Keep in mind that, you can use another helper which offers you to hard-type the name of the value:
#Html.TextBoxFor("Color")
Or, you can write plain html as you did and add name attribute, but let Asp.Net decide what must be the name of the element.
<input type = "text"
name = "#Html.NameFor(m => m.Color)"
value = "#Model.Color" />
If we want to summarize the result of the answer, then let's write Pros and Cons of each version:
Strongly typed version - These helpers can be used only with strongly typed views. The HTML generated by these helpers is not any different, but we use the strongly typed helper methods in our projects because they reduce the chances of causing an error by mistyping a property name.
Hard-typed version - The string argument is used to search the ViewData, ViewBag, and view model to find a corresponding data item that can beused as the basic for the input element. So, for example, if you call
#Html.TextBox("DataValue"), the MVC Framework tries to find some item of data that corresponds with the key DataValue. The following locations are checked: ViewBag.DataValue and Model.DataValue.
The first value that is found is used to set the value attribute of the generated HTML. (The last check, for #Model.DataValue, works only if the view model for the view contains a property or field called DataValue.)
If we specify a string like DataValue.First.Name, the search becomes more complicated. The MVC Framework will try different arrangements of the dot-separated elements, such as the following:
• ViewBag.DataValue.First.Name
• ViewBag.DataValue["First"].Name
• ViewBag.DataValue["First.Name"]
• ViewBag.DataValue["First"]["Name"]
Also keep in mind that, the first value that is found will be used, terminating
the search.
I am using ASP.NET MVC4 with .NET Framework 4.5. I have a controller action that accepts a model of one type with a property named 'Name' but renders a view using a model of another type. I am still able to use #Html.TextBox("Name") and #Html.ValidationMessage("Name").
I want the textbox to display the sanitized input, that is, the input without leading/trailing/extra spaces the user may have entered. The setter for my model sanitizes the value for me, and I am successfully obtaining the sanitized value using the getter within the controller action. It's just that upon submitting the form, the textbox still displays the unclean input.
Is there some mechanism I am missing? Is the #Html.TextBox(string name) helper looking at the raw request data and not the model? If so, how come the validation message is working?
Update
I have just tried defining a new view model that includes my textbox field so I could hopefully just use the #Html.TextBoxFor helper. Everything is still working as it was after a re-build, I am still not getting sanitized input appearing in the textbox. I still don't know a solution for this.
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult MyAction(MyViewModel model)
{
if (this.ModelState.IsValid)
{
using (var service = new MyService())
{
model.MyResults = service.DoSomething(model.MySanitizedProperty);
}
}
return this.View("MyView", model);
}
Then, in "MyView":
#Html.TextBoxFor(m => m.MySanitizedProperty)
<input type="submit" value="Go" />
#Html.ValidationMessageFor(m => m.MySanitizedProperty)
In the controller, invoking model.MySanitizedProperty returns the sanitized value while the textbox goes on to display the unsanitized data.
It sounds like a problem with the models; make sure you are properly accessing the value from the model you wish to populate it with, i.e., possibly discretely specifying the model "Name" is coming from.
Also, check to see that the setter has a chance to operate on the value - if the controller is activating before the setter function is used, then you'll only get the original input value.
Realize you have to go to the server for the setter to work, possibly you need a async postback or such, and the value reloaded.