I have a simple for with at text field where the user enters the address of a RSS feed. I wont act if the field is blank, this is my markup:
<%=Html.ValidationSummary() %>
<table>
<tr>
<td>
Feed Url:
</td>
<td>
<%=Html.TextBox("url", null, new {#style="width:300px"}) %>
</td>
</tr></table>
My controller is also very simple:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddFeed(FormCollection collection)
{
string url = collection.Get("url");
string roles = collection.Get("Roles");
if (string.IsNullOrEmpty(url))
{
ModelState.AddModelError("url", "Please provide a propre feed url");
}
if (string.IsNullOrEmpty(roles))
{
ModelState.AddModelError("Roles", "Please select a valid role");
}
if (ModelState.IsValid)
{
Session["url"] = url;
Session["Roles"] = roles;
return RedirectToAction("ValidateFeed");
}
else
{
return View();
}
}
When it fails it reloads the view and makes an exception in the line where it renders my textbox, saying that there has been a null pointer exception. This really botheres me, it should be so simple... but still im struggling
/H4mm3r
Edit Please disregard the Roles element its a drop down i have, but removed it from markup for simplicity
You probably can't use null when using the HTML helper. Try this in your form instead:
<%= Html.TextBox("url", String.Empty, new {#style="width:300px"}) %>
I haven't tested that, but that's the first thing I would try, given that error message.
I think you cant use the ModelSate like that if you didn't do something like:
UpdateModel(collection);
or
TryUpdateModel(collection);
or by Parameter Binding
public ActionResult AddFeed(YourModelType collection)
Because the Modelstate is not associated with any Model.
As the MSDN reference says:
ModelState Class
Encapsulates the state of model
binding to a property of an
action-method argument, or to the
argument itself.
Related
I am using MVC 4, ASP.NET. I am having an issue of passing checkbox values to my post method in the controller. It returns as null value.
Here's part of my view (Get works and properly fills data):
#using (Html.BeginForm())
{
<fieldset>
<table>
<tr>
#{
int cnt = 0;
List<LearningEnterprises.ViewModel.AssignedVendor> techs = ViewBag.Tech;
foreach (var tech in techs)
{
if (cnt++ % 3 == 0)
{
#:</tr><tr>
}
#:<td>
<input type="checkbox"
name="selectedTechs"
value="#tech.TechID"
#(Html.Raw(tech.Assigned ? "checked=\"checked\"" : "")) />
#tech.TechID #: #tech.Title
#:</td>
}
#:</tr>
}
</table>
<p>
<input type="submit" value="Save" id="save" />
</p>
</fieldset>
}
here's my post:
[Authorize(Roles = "Admin")]
[HttpPost, ValidateInput(false)]
public ActionResult EditVendor(int? id, string[] selectedTech)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var vendor = db.Vendors
.Include(i => i.VendorType)
.Include(i=>i.Stocks)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(vendor, "",
new string[] { "CompanyName", "PhoneNumber", "Address", "ProvinceState", "City", "Country", "PostalCode", "Email", "numberofBooths", "comments", "electric", "internet", "tonicEquipment", "VendorTypeID" }))
{
try
{
UpdateVendorTech(selectedTech, vendor);
db.Entry(vendor).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateAssignedTechData(vendor);
PopulateDropDownLists(vendor.VendorTypeID);
return View(vendor);
}
string[] selectedTech is displaying a null value in debug. It is supposed to equal the id value of checkboxes selected in the view.
The first thing I can see is that your names mismatch: you have string[] selectedTech in your controller but name = "selectedTechs" in your view, then the ASP.NET binder being unable to find any values, it sets your array to null.
I also note you don't have any post URL in your Html.BeginForm() but if you said you could check the value in debug I suppose you are doing it an other way...
I expect that user3426870 is correct and that the problem is to do with the name on the EditVendor method signature (selectedTech) not matching the name of checkbox on the view (selectedTechs). I suspect that the DefaultModelBinder will not be able to match the parameters to bind the values to.
Additionally, the TryUpdateModel does not seem to include any properties defined in the view that will be passed in your post request so I suspect that this will not work.
May I suggest some other improvements (which you could research) that you could make:
The controller method is quite large. You should look at using
services/repositories to perform any business logic and database
calls to reduce the number of actions the controller is performing.
This will help to make those smaller parts more testable.
If the id on the signature should not be null (as you return a bad request if it is) then you should not make it a nullable int? parameter. You will then no longer need to perform that check.
Also, you may wish to add validation to ensure that the id is greater than 0 or that the list of selectedTech has a count that is greater than 0. Such validation can be performed using data annotations or fluent validation and then a simple check performed in the code such as:
if (ModelState.IsValid) { ... }
to ensure that the parameters passed in pass your defined validation.
You should use a view model for the view which holds exactly the
information that you need. You can then map between this and your
lower layer business classes using mapping classes (or automapper).
Using a view model is the preferred way and avoids use of ViewBag
properties. You can also use some of the HTML helper methods to
write out some of those view model properties.
Hope these ideas help.
I am using MVC3, Razor and C#.
I have implemented a simple and robust inline editing solution for a grid.
Basically I use Razor to build my form which encloses the grid, and then the row that matches the item id gets opened up as the editable row which is coded as a partial View.
The Grid View (part):
#using (Html.BeginForm("Edit", "GridTest"))
{
<table>
<tr>
<th>col1</th>
<th>Col2</th>
</tr>
#{
foreach (var item in Model)
{
<tr>
#{
if ((Model.ItemId == item.Id))
{
Html.RenderPartial("_EditRow", item);
}
else
{
Html.RenderPartial("_DisplayRow", item);
}
}
</tr>
}
</table
}
EditRow.cshtml
#Html.TextBoxFor(p=>p.Name)
Save
Cancel
#Html.HiddenFor(p=>p.Id)
DisplayRow.cshtml
<td>
#Model.Name
</td>
<td>
#Html.ActionLink("Edit", "Edit", "GridTest", new {id = Model.Id}, null)
</td>
GridTest/Edit Action
public ActionResult Edit(int id)
{
var myRecord = db.Orders.First(p => p.Id == id);
return View("Index",myRecord);
}
GridTest/Edit Post Action
[HttpPost]
public ActionResult Edit(Order myRecord, string btn="")
{
if (btn == "Save")
{
if (ModelState.IsValid)
{
Order myCurrentRecord = db.Order.First(p => p.Id == myRecord.Id);
myCurrentRecord.Name = myRecord.Name;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myRecord);
}
else
{
return RedirectToAction("Index");
}
The above code shows the design. It works greats and is simple. However it causes a postback of the complete page, and I would like to stop this "flashiness". So I suspect I need to somehow tweak the above code such that the "EditRow" posts inline, without refreshing the entire page. I suspect I am looking at using Ajax?
So how can the above code be simply upgraded to prevent complete page refresh, but rather row refresh?
Many thanks in advance.
At its simplest, you can probably just capture the submit event of the form and serialize that form to an AJAX POST. Something like this:
$('form').submit(function () {
$.post('#Url.Action("Edit", "GridTest")', $('form').serialize())
.done(function(data) {
// AJAX call is complete
// do something on the page?
});
return false;
});
This will use the same controller action in the same way that the form does, just via AJAX. The controller action will also respond the same way, which probably isn't what you want with an AJAX request. You might instead want to return some JSON data to indicate success, error conditions, results of server-side processing, etc. Something as simple as this can just indicate success from the controller action:
return Json(true);
You can, of course, return any structured data by passing it to Json(), which will serialize it to JSON data as the data value in the JavaScript done handler.
Edit: If you want to replace a piece of the client-side content wholesale with a partial view, you can still return that partial view in the AJAX request. The controller action can do something like this:
return PartialView("_DisplayRow", myRecord);
(Assuming myRecord is the type that is bound to that partial view.)
Then you'd have something for the done handler in the client-side code. Maybe something like this:
$('tr.someClass').html(data);
The idea here is that the tr element which is the "edit row" should be uniquely identified in some way (I'm using a class in my selector at the moment, but you can use whatever works for you), and its contents should be replaced with the contents of the partial view being returned from the server-side code.
I need to pass multiple data ( probably 2 Html.DropDownList's selected values ) to MVC controller action method from MVC View ( .aspx). I think it would be from somehow Html.Hidden form , but how?
I am unable to get the selected value from Html.DropDownList and pass it as Html.Hidden("paramName", MvcStringSelectedValue) to controller's action.
My Code is :
based on<br />
<%: Html.DropDownList("Semester")%>
<%= Html.Hidden("strSemesterToBaseOn",returnedValueFromAbove)%>
<%: Html.ValidationSummary(true) %>
<input type="submit" value="Clone" />
<% } %>
<br/><br/>
Do I need to write the input tag of "submitt" 2 times or just only once?
Edit ( EXTRA CODE )
Controller's action method :
[HttpPost]
public ActionResult CloneSemesterData(string strSemesterToOrganize, string strSemesterToBaseOn)
{
.............................................................
..............................
}
HERE ( another Controller's method ) IS THE DROP DOWN LIST Filled with Semester values
public ActionResult DepartmentAdministration()
{
// Get list of semesters
var lr = new ListRepository();
ViewData["Semester"] = new SelectList(lr.ListSemester(3)); //this ListSemester(3) will generate the list with 3 strings ( e.g "WS 2012", "SS2010")
return View();
}
My View code in .aspx file is :
//this executes when radioButton ="Clone" is selected
<% using (Html.BeginForm("CloneSemesterData", "CourseNeededHours"))
{%>
<%= Html.DropDownList("Semester")%> // this is First drop down list box , from which selected value , I want to transfer as 1st parameter of controller's action method
<%: Html.ValidationSummary(true) %>
based On
<%= Html.DropDownList("Semester")%> //this is Second drop down list box, from which selected value, I want to transfer as 2nd parameter of controller's action method.
<input type="submit" value="Clone" />
<% } %>
ERROR:
Now, after fixing using Edit 2 : it is giving red lines under
as it is somehow not recognizing the ViewData["SemesterList"]...
"System.Web.Mvc.HtmlHelper does not contain a definition for 'DropDownList' and the best extension method overloaded 'System.Web.Mvc.Html.SelectExtensions.DropDownList(System.Web.Mvc.HtmlHelper, string,System.Collections.Generic.IEnumerable') has some invalid arguments".
Hope now it will clear, still ambiguity , do let me know then.
Regards
Usman
I am not really sure what you're asking here. You don't need any kind of hidden field to post the selected values of a dropdown. Your Dropdownlist code is invalid to begin with.
Typically you have something like this:
<%= Html.DropDownList("SemesterToOrganize", GetSemesterToOrganize()) %>
<%= Html.DropDownList("SemesterToBaseOn", GetSemesterToBaseOn()) %>
And in your controller:
[HttpPost]
public ActionResult MyAction(string SemesterToOrganize, string SemesterToBaseOn) {
// your code.
}
EDIT:
Based on what you've told us. You are relying on the behavior of MVC of populating the DropDownList because you are adding your list to the ViewData with the same name as your dropdownlist. This won't work for you. You will have to populate each dropdown list seperately.
In your controller, do something like this:
public ActionResult MyAction ()
{
ViewData["SemesterList"] = // list of semesters
return View();
}
Then, in your view you have:
<%= Html.DropDownList("SemesterToOrganize", ViewData["SemesterList"]) %>
<%= Html.DropDownList("SemesterToBaseOn", ViewData["SemesterList"]) %>
then your post method
[HttpPost]
public ActionResult MyAction(string SemesterToOrganize, string SemesterToBaseOn) {
// your code.
}
If you want to continue to argue that you can do it your way, then you won't solve your problem. Each dropdown must have it's own unique id, otherwise it will not post correctly. The only way to solve this problem is to give each it's own unique id. That breaks the behavior of the drop down automatically getting the data, so you MUST specify the list of data explicitly.
So stop arguing that this is an unimportant part of the problem. It's not. It's key to the problem.
EDIT2:
Based on your code above:
<%= Html.DropDownList("strSemesterToOrganize", (SelectList)ViewData["Semester"]) %>
<%= Html.DropDownList("strSemesterToBaseOn", (SelectList)ViewData["Semester"]) %>
That's all you need
If you had just given us this, and didn't argue, this would been solved a lot easier.
// Try this. Change names and put in the appropriate namespace.
//Your view
#model MvcApplication2.Models.CloneSemesterDataViewModel
#Html.LabelFor(x => x.SemesterToOrganize)
#Html.DropDownListFor(x => x.SemesterToOrganize, Model.ListofSemestersToOrganize)
--------
#Html.LabelFor(x => x.SemesterToBaseOn)
#Html.DropDownListFor(x => x.SemesterToBaseOn, Model.ListofSemestersToBaseOn)
//view model
namespace MvcApplication2.Models
{
public class CloneSemesterDataViewModel
{
public string SemesterToOrganize { get; set; }
public IEnumerable<SelectListItem> ListofSemestersToOrganize
{
get
{
return new List<SelectListItem> { new SelectListItem { Text = "SS2012" , Value = "SS2012"} };
}
}
public string SemesterToBaseOn { get; set; }
public IEnumerable<SelectListItem> ListofSemestersToBaseOn
{
get
{
return new List<SelectListItem> { new SelectListItem { Text = "SS2012", Value = "SS2012" } };
}
}
}
}
----------
Controller.
[HttpPost]
public ActionResult CloneSemesterData(CloneSemesterDataViewModel viewModel)
{
//viewModel.SemesterToBaseOn
//viewModel.SemesterToOrganize
}
// This should do the trick.
ASP.NET MVC Model Binding is still new to me and I'm trying to understand exactly how it works. Right now, I appear to be having problems with a feature of Html.Textbox()
Specifically, I have a View where I set Html.Textbox to a value both in the "Get" and the "Post". It sets fine in the "Get", but after the user submits a value during the "Post", I have the class change one of the values internally based on the other value submitted.
(I'm basically validating one value based on the other... I'm not sure if this is the right way to do this...)
Tracing through, I can see that the value has actually changed as expected both in the Model and in the View, but when it displays on my screen after the "Post", the value does not display as it was changed. Instead it is what it was set to originally.
Here's my simplified example:
The View shows a:
Drop-down with items from a SelectList (pre-selected as "Other")
a Read-only Text Box (with a pre-loaded value of 0)
Submit Button
User should pick a new value from the Drop-Down and click submit. The "Post" method in the controller picks up the new value from the Drop-Down and changes the Value in the Read-only text-box and re-displays.
(Yes, I'll eventually be doing this with JQuery, too...)
Here's my sample Model class:
public class SampleSubmission
{
public string Name { get; set; }
public int Volume { get; set; }
public readonly SortedList<string, int> NameVolumeList = new SortedList<string, int>();
// Standard Constructor
public SampleSubmission()
{
NameVolumeList.Add("Sample1", 10);
NameVolumeList.Add("Sample2", 20);
NameVolumeList.Add("Sample3", 50);
NameVolumeList.Add("Other", 0);
this.Name = NameVolumeList.Keys[0];
this.Volume = NameVolumeList[Name];
}
// Copy Constructor
public SampleSubmission(SampleSubmission samSub) : this()
{
this.Name = samSub.Name;
this.Volume = NameVolumeList[Name];
}
}
Here's the Controller:
public class SampleSubmissionController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
SampleSubmission sampleSub = new SampleSubmission();
return View(sampleSub);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SampleSubmission sampleSub)
{
SampleSubmission samSub = new SampleSubmission(sampleSub);
return View(samSub);
}
}
Here's the View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcModelBindTest.Models.SampleSubmission>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%= Html.DropDownList("Name", new SelectList(Model.NameVolumeList.Keys.ToList())) %>
<%= Html.TextBox("Volume",Model.Volume) %>
<input type="submit" name="pick" id="pick" value="Pick" /> <% } %>
</asp:Content>
Any ideas as to why the new value does not display?
EDIT:
In order to fix the problem, I read the link given by "jfar" and made a 1-line change.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SampleSubmission sampleSub)
{
SampleSubmission samSub = new SampleSubmission(sampleSub);
// Reset Model Value
ModelState.SetModelValue("Volume", new ValueProviderResult(
samSub.Volume, "", System.Globalization.CultureInfo.CurrentCulture));
return View(samSub);
}
This definitely works. Unfortunately, this feels like a gross hack to me. What if I had to update the values of multiple fields? There must be a better (simpler?) way of doing this.
EDIT2: Found my answer. See below...
From: How to clear textboxes defined with MVC HTML helpers
"The HTMLHelper's first look at the
ModelState and ViewData to see if any
values match their key and then
finally use whatever value you provide
them.
If you need to reset the textboxe's
value you also need to clear the
ModelState entry with the matching
key. Another alternative is
redirecting to the same page instead
of simply rendering a view via
javascript or with MVC.
I figured out the answer to my own question when I stumbled upon another variable that needed to be reset. As I was looking at the data structure, I realized what I wanted was the pristine state where there were no Keys in the ModelState.
ModelState.Remove(key);
Where "key" is the value you're trying to reset.
Another simple workaround is instead of
<%= Html.TextBox("Volume",Model.Volume) %>
Use HTML input tag
<input id="Volume" name ="Volume" value="#Model.Volume" />
Once again I'm confronted with a "This shouldn't be this ?*!# hard" situation.
Problem: I want to use a form in MVC for creation of an object. One of the elements of the object is a set of limited choices - a perfect candidate for a drop down list.
But if I use a SelectList in my model, and a drop down list in my View, and then try to post the Model back to my Create method, I get the error "Missing Method Exception:No Parameterless constructor for this object". Exploring the MVC source code, it appears that in order to bind to a model, the Binder has to be able to create it first, and it can't create a SelectList because there is no default constructor for it.
Here's the simplified code:
For the model:
public class DemoCreateViewModel
{
public SelectList Choice { get; set; }
}
For the controller:
//
// GET: /Demo/Create
public ActionResult Create()
{
DemoCreateViewModel data = new DemoCreateViewModel();
data.Choice = new SelectList(new string[] { "Choice1", "Choice2", "Choice3" });
ViewData.Model = data;
return View();
}
//
// POST: /Demo/Create
[HttpPost]
public ActionResult Create(DemoCreateViewModel form)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
And for the View:
<fieldset>
<legend>Fields</legend>
<%= Html.LabelFor(model => model.Choice) %>
<%= Html.DropDownListFor(model => model.Choice, Model.Choice) %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
Now, I know I can MAKE this work by dropping back 10 yards and punting: bypass model binding and drop back to the FormCollection and validate and bind all the fields myself, but there's got to be a simpler way. I mean, this is about as simple a requirement as it gets. Is there a way to make this work within the MVC ModelBinding architecture? If so, what is it? And if not, how come?
Edit: Well, I have egg on my face, but maybe this will help someone else. I did some more experimenting and found a simple solution that seems to work.
Provide a simple value (string or integer, depending on what your select list value type is), and name that as the model element that you bind to. Then provide a second element as the select list of choices, and name it something else. So my model became:
public class DemoCreateViewModel
{
public string Choice { get; set; }
public SelectList Choices { get; set; }
}
And then the DropDownListFor statement in the View becomes:
<%= Html.DropDownListFor(model => model.Choice, Model.Choices) %>
When I do this, the submit button correctly binds the choice made in the form to the string Choice, and submits the model back to the second Create method.
Here is one approach:
#Html.DropDownListFor(model => model.Choice,
ViewBag.Choices as SelectList,
"-- Select an option--",
new { #class = "editor-textbox" })
Notice that I use ViewBag to contain my SelectList. This way when you post back, the client doesn't send the entire select list up to the server as part of the model.
In your controller code, you just need to set the view bag:
ViewBag.Choices = new SelectList(....
Consider creating a different view model for your post action without the SelectList property:
public class DemoCreateViewModelForUpdate
{
public string Choice { get; set; }
}
Then you can always map from the DemoCreateViewModelPost instance to an DemoCreateViewModel instance if the model state is invalid and you want to re-show the view. I tend to prefer everything needed by the view to be in my display view model class, so using a separate update only view model let's me keep things slim and trim for the trip back to the server.
In your view, you'd do:
#Html.DropDownListFor(m => m.Choice, Model.Choices)
as in the previous answer, so no unnecessary data would round trip.