asp.net mvc Html helper hidden field behaving strangely - c#

The behavior of the HtmlHelper.Hidden eludes me.
This is from a template inside Views/Shared/DisplayTemplates/Test.cshtml. Idea was to output a delete button next to the element in the list. So the view accepts a model of IEnumerable and then uses #Html.EditorForModel() to output each test item.
So if i put this into Test.cshtml:
<input type="hidden" name="Name" value="#Model.Name"/>
#Html.Hidden("Name2", Model.Name)
That yields this:
<input type="hidden" name="Name" value="test"/>
<input id="RoleList_12__Name2" name="RoleList[12].Name2" type="hidden" value="test" />
Why does the name of the Html helper render something different from what i tell it to? I assume that this is intended, but i don't understand why.
Update
How would i go ahead and retrieve RoleList[12].Name in my controller?
My delete function accepts:
[HttpPost]
public ActionResult DeleteRole(Roles.Test model)
{
}
How can that one accept a RoleList[12] name item? It always returns null if i try.

This is happening because your view accepts IEnumerable of some model type. You basically have a list of objects passed to the view and since they all have a property named Name the binder is trying to distinguish between different instances by giving them names like model[index]property.
If you're trying to delete an instance you should have an Html.ActionLink link that invokes some action of your controller which takes the unique ID of the instance you're trying to delete. Something like this in your view:
#foreach (var item in model)
{
#Html.ActionLink("linkText", "DeleteRole", "controllerName", new {id = item.RoleId})
}
and then your controller should have an action like this:
public ActionResult DeleteRole(int id)
{
// Logic to delete role based on provided role ID.
return View();
}

It's so that the (default) model binding will work; i.e. when posted the field will be resolved to RoleList[12].Name in the Controller that takes a parameter equivalent to the model class.
If your controller action doesn't reference the model class the data will silently be discarded, so that you don't have to post back entire data - but that which is posted back will be realized for you.
You can override the default model binding; see The Features and Foibles of ASP.NET MVC Model Binding
The problem with your delete is that it's at the wrong level; either you bind something to the instance of the enumerable - wrapped up in its own form (ok for small amounts of data) or you decide to have an #ActionLink into which the ID of the enumerable is passed. I generally do the second, something like:
#Html.ActionLink("Del", "DeleteRole", "Controller", new { Id = item.Id}, new { #class = "btn"})
Of course the above doesn't need a [Post] action so that'd need changing on your controller.
The above would sit in your display or edit template as appropriate.

Related

Specifying single method as target for multiple controller URL's

There are lots of questions asking about this logic in reverse, however, I can't find an answer for my issue. I'm new to MVC so might not have the terminology specified correctly.
I have a View containing a form, where a user can request a product. The same View is used regardless of the product, but specific fields are shown in the form relating to that particular product, e.g.
public class RequestController : Controller
{
// This bit works fine and displays the appropriate form in the view
public ActionResult MyProduct(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return new HttpNotFoundResult();
}
ProductRequest qd = new ProductRequest();
switch (id)
{
case "Beer":
qd.RequestType = Models.RequestType.Beer;
break;
case "Coffee":
qd.RequestType = Models.RequestType.Coffee;
break;
case "Soda":
qd.RequestType = Models.RequestType.Soda;
break;
}
return View("Index", qd);
}
// Need to get all forms rendered by the above to post here...
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyProduct(ProductRequest qd)
{
if (!ModelState.IsValid)
{
return View(qd);
}
else
{
// To do ...
}
}
}
When the form is rendered in the view, using...
#using (Html.BeginForm())
...the rendered HTML shows a different form target URL for each product type, e.g:
<form action="/Request/MyProduct/Beer" method="post">
Can I make the form action property use the same controller/method regardless of the product type? e.g.
<form action="/Request/MyProduct" method="post">
I assume there are different ways to achieve this given the flexibility MVC seems to offer, but I'm looking for best practice please as a learning experience.
The Html.BeginForm() call accepts various parameters, which can declare the controller/method you wish the form to use.
#using (Html.BeginForm("MyProduct", "Request", FormMethod.Post, new { enctype = "multipart/form-data"}))
Should produce:
<form action="/Request/MyProduct" enctype="multipart/form-data" method="post">
What added value gives you the Html.BeginForm helper over writting the html yourself, apart from preventing you to easily control your action url?
Personally, I have banned most Html helpers usages, there are just getting in my way instead of helping me.
Write your html code yourself, that is frequently even more concise than using html helpers (as illustrated in this answer).
<form action"#Url.Action("MyProduct", "Request")" method="post">
#* Add [ValidateAntiForgeryToken, HttpPost] attributes to target action *#
#Html.AntiForgeryToken()
...
</form>
(And even better, use UrlHeper extensions to define and centralize your URIs, instead of calling Url.Action inside your views or controllers.)
For completeness, I should mention that most input fields html helper extensions (those suffixed with For) have the added value of exploiting data-annotations of your view-models. But I consider those annotations as bad practice, because they handle subjects which are not the view-model responsibility in strict MVC pattern. (Display labels? Should be controlled by the view. Validation rules? Should be controlled by the controller. Action argument and/or parameter model? No reason to be bound to the view-model names.)

MVC NotMapped attribute EditorFor returning null value in Edit.cshtml but not Create.cshtml

This question relates to a Visual Studio 2013, Asp.net MVC-5.2.2 project:
I have a NotMapped string attribute in my class:
[NotMapped]
public string myNotMappedAttribute{ get; set; }
I use this attribute, which is not mapped in the original table of the Controller, to fill a column in a different table that has a foreign key relationship with the Controller's table under consideration. Again, this idea works perfectly in Create, but not edit.
I have an MVC EditorFor box that works in the Create.cshtml (razor) as follows:
<div class="editor-label">
#Html.LabelFor(model => model.myNotMappedAttribute)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.myNotMappedAttribute)
#Html.ValidationMessageFor(model => model.myNotMappedAttribute)
</div>
However, the exact same razor code returns null when used in the Edit.cshtml file.
In the Edit post-back method of the controller, this attribute (and only this attribute) is null the other attributes have the correct values. I even moved this attribute so it wouldn't be the last attribute, and one after it was correct.
Any ideas as to why this would work in Create and not Edit?
Turns out that a new default project that I used had some bind attributes in the controller's post-back Edit method:
public ActionResult Edit([Bind(Include = "myAttribute_1,myAttribute_2,myAttribute_3")] MyModel myModel)
One can just omit this and it will work:
[HttpPost]
public ActionResult Edit(MyModel myModel)
Presumably, adding this attribute to the bind list will also work, though I didn't check that personally myself.
NOTE: I really didn't know the answer to this question, but pondering the mystery, I eventually saw those attributes. I didn't even think the method worth putting in the original question as I was fairly certain that it would be the Razor code that was a problem. Also, I was originally using a dropdown that wasn't working, and decided to replace it with a textbox for simplicity; that turned out to be a wise decision, as one can imagine the number of red-herrings in debugging the textbox!

Weird result when sending and then retrieving text from view through controller and model to view

I have a problem with a mvc4 project.
I try sending some text from a view to a controller which then add it in a model and then I try to retrieve the text in another view.
The first views code looks like this:
#{
Html.BeginForm("Result", "Search", "test");
}
<div>
</div>
The controller code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TheLibrary.Models;
namespace TheLibrary.Controllers
{
public class SearchController : Controller
{
public ActionResult Result(string text)
{
var searchCriteria = new Search { searchCriteria = text };
ViewBag.Search = searchCriteria;
ViewData["ViewSearch"] = searchCriteria;
TempData["TempSearch"] = searchCriteria;
return View();
}
}
}
My models code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TheLibrary.Models
{
public class Search
{
public string searchCriteria { get; set; }
}
}
The second views code:
#Model TheLibrary.Models.Search
#{
var text = (ViewBag.Search as TheLibrary.Models.Search).searchCriteria;
}
<div>
#text
</div>
The result I get is "TheLibrary.Models.Search" rather than the expected "test".
What am I doing wrong?
With all due respect, you really need to work through some MVC tutorials. I'm not usually a fan of Microsoft documentation (especially their insipid 'quickstarts') but their MVC tutorials are actually quite good. That said, let me clear some things up for you.
Firstly, the whole point of creating a view model (Search in this case) is so that you don't have to use ViewBag or ViewData, to give you a guaranteed way of accessing your model's data. ViewBag and ViewData are virtually the same thing. The main point here though is the idea to move away from these in favour of using strongly-typed views.
So, that begs the question, what is a strongly-typed view? Put simply, a strongly-typed view is a view that requires a specific type to be passed to it. You specify a strongly-typed view by using the #model directive at the very top of the view. In your case, this would be:
#model TheLibrary.Models.Search
Note: The lowercase m. Do not confuse this with #Model which is a way of accessing your strongly-typed model.
Being as we're telling our view exactly what type we're going to pass to it, we can use #Model to access the properties of the model, like so:
<div>
#Model.searchCriteria
</div>
(Please also note that guidelines suggest using Pascal Case for properties.)
Now, let's clear up the issue with your controller. Firstly, as we're already using our model, as discussed above, there is no point in using ViewBag or ViewData. TempData serves an altogether different purpose and has no bearing on your question. (See this answer for when you might want to use it.) In that sense, it is also not needed. Lastly, you would simply pass the model to the view itself:
public ActionResult Result(string text)
{
var model = new Search { searchCriteria = text };
return View(model);
}
Now, your complete view would look like this:
#model TheLibrary.Models.Search
<div>
#Model.searchCriteria
</div>
However, as stated at the start of this answer, I really believe you should look into a few tutorials because there are things here that you're still not aware of (such as passing searchCriteria directly to your view without any kind of validation).
Update per comments
Your view wants to look something like this:
#using (Html.BeginForm("Result", "Search", FormMethod.Get))
{
#Html.TextBox("text")
<input type="submit" value='Search' />
}
There are a couple of things to note.
Firstly, the FormMethod.Get is ensuring this form's data is sent via GET instead of POST. This is what puts text into the query string in your result's URL. So it makes it look something like:
http://somewebsite.com/Search/Result?text=test
This is good because it means if the user refreshes the page, they're not asked to resubmit the form and it also acts in the same way every time the page is accessed from that URL.
Secondly, #Html.TextBox("text"). The string you supply to the TextBox() method is the name that is given to that textbox, and it is also the name that is used to lookup the data in that textbox. That means it has to match the name of the parameter in your Result action (or part of a model, but let's keep things simple for now), so this part is important for it to work correctly.
So the idea here is that for you to get the correct data for your searchCriteria in your Result view, using your example, is that the user would type test into the textbox. They'd then click the Search button which would get MVC to call the Result action. When doing so, it would use what's called 'model binding' to match the value of the text textbox to the text parameter in your action. That means when we get here:
public ActionResult Result(string text)
{
// ...
}
text will have the value of whatever someone has typed into the textbox. You then pass that to your model which is then passed to the view.
#Model should not be capitalized. Then you can just write:
#model TheLibrary.Models.Search
<div>
#Model.SearchCriteria
</div>
To be clear, the first #model should not be capitalized when you are declaring the type. After that you capitalize it to reference it.
Also I am not sure why you are using the ViewBag and not just passing in the model to the view.
return View(search);
Also I don't recommend this naming scheme:
var searchCriteria = new Search{ searchCriteria = text };
becuase then you have write searchCriteria.searchCriteria. Instead call it what it is:
var search = new Search...

MVC Model Binding - Maintaining values during a partial edit

I have a model class that goes like
public class Mod
{
public string StaticProp1 {get; set;}
public string StaticProp2 {get; set;}
public string EditableProp1 {get; set;}
}
I want a view in which I can edit EditableProp1 but where StaticProp1, StaticProp2 are displayed but not editable.
I created a strongly-typed view :
#model Mod
#using (Html.BeginForm())
{
<p>#Model.StaticProp1</p>
<p>#Model.StaticProp2</p>
#Html.TextBoxFor(m => m.EditableProp1)
<input type="submit" value="Save" />
}
In my Controller, when I deal with the action I find the EditableProp1 fine.
[HttpPost]
public ActionResult Edit(Mod model, FormCollection collection)
{
string editableProp = model.EditableProp1; //This works fine
string staticProp1 = model.StaticProp1; //Missing
return View(model);
}
This causes a problem if I post back for some reason as staticProp1 will now be null and the view won't be able to display it.
I know that I can add
#Html.HiddenFor(m => m.StaticProp1)
#Html.HiddenFor(m => m.StaticProp2)
to my view and that it will work fine, but I am wondering if there is another better way.
My values are already on the form (<p>#Model.StaticProp1</p>). Is there a way to bind the model to un-editable tags like that? Is there an HTML helper that does something like this?
By the way, if it isn't obvious, I am just starting out with MVC so if I am completely missing the point please let me know!
Every property of a model you want to persist has to be in the form (in an editor or hidden field). You can use, as you propose, Html.HiddenFor() for this. If you want to avoid overloading your view with hidden fields, you could store only the id of an entity in the hidden field and fetch the rest of the data based on the id in the Post action. Or use Html.TextBoxFor() with a readonly attribute, see this question for more information about it (I like the approach in the second answer as well).
I think the question relates more to model binding and how it works. If you don't want to use hidden field here (which I think fits your scenario), you can custom Model Bind by inheriting a class from:
DefaultModelBinder

How to retrieve all values for all the inputs on MVC page

I have an MVC 3 project, that I am starting, using c# and Razor. I have a page, that has about 20 input fields that will be used. I create my ViewModel to pass the data to the View to create the page. I am confused as to, how I get the values of the fields, when the user submits the form.
Does my Controller have to have a parameter for every input field on my page? Is there any way to have the Controller get all the data on the page and then I can parse through it? The parameter list would be huge.
You can just use the same model that you passed to your view as the parameter in your subsequent action.
An example:
//This is your initial HTTP GET request.
public ActionResult SomeAction() {
MyViewModel model;
model = new MyViewModel();
//Populate the good stuff here.
return View(model);
}
//Here is your HTTP POST request; notice both actions use the same model.
[HttpPost]
public ActionResult SomeAction(MyViewModel model) {
//Do something with the data in the model object.
}
The model object in the second method will automatically be populated from the data included in the HTTP request (the technical name is "Model Binding").
In your controller's action, expect to receive the same "model" you passed back to the view. This will works if you generated your 'input controls' properly (by using Html.TextBoxFor() or by setting the Name attribute to the same name of your model's properties).
public ActionResult MyAction(MyViewModel model)
{
...
}
Note MVC will use a ModelBinder to figure out how to create and fill the properties of the object your action is expected based on the fields submitted from the user.
If you want to capture all inputs from the user you can make your action to receive an object of type FormCollection:
public ActionResult MyAction(FormCollection values)
{
...
}
Please create an mvc action in your controller taking the model as parameter
Like this:
[HttpPost] or [HttpGet]
public ActionResult Employee(EmployeeModel employee)
{
// now you will have all the input inside you model properties
//Model binding is doen autoamtically for you
}

Categories

Resources