I've read a lot of similar posts about null model but my case is very very simple and still the model on Create action is null. What am I doing wrong???
Here is the situation: One main view, two strongly typed partial views inside, each binded to a public property of the main model. Any help is appreciated.
models:
public class SimpleModel1
{
public IEnumerable<string> SomeStrings1 { get; set; }
}
public class SimpleModel2
{
public IEnumerable<string> SomeStrings2 { get; set; }
}
public class ComplexModel
{
public SimpleModel1 model1 { get; set; }
public SimpleModel2 model2 { get; set; }
public IEnumerable<string> SomeStringsComplex { get; set; }
}
int he controller:
public ActionResult Create()
{
ComplexModel complex = new ComplexModel();
complex.model1 = new SimpleModel1();
complex.model1.SomeStrings1 = new List<string> { "a1", "a2", "a3"};
complex.model2 = new SimpleModel2();
complex.model2.SomeStrings2 = new List<string> { "b1", "b2", "b3" };
complex.SomeStringsComplex = new List<string> { "c1", "c2", "c3" };
return View(complex);
}
[HttpPost]
public ActionResult Create(ComplexModel model)
{
if (ModelState.IsValid)
{
var test = model.SomeStringsComplex;
}
return View();
}
Views:
2 strong partial views -each for model
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<MvcApp1.Models.SimpleModel2>" %>
<fieldset>
<legend>Fields</legend>
<% foreach (string item in Model.SomeStrings2)
{%>
<p>
<label for="Title">Item Title:</label>
<%= Html.TextBox(item,item)%>
</p>
<%
}
%>
</fieldset>
1 main view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcApp1.Models.ComplexModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<% using (Html.BeginForm()) {%>
<fieldset>
<div> Own values
<% foreach (string item in Model.SomeStringsComplex)
{%>
<p>
<label for="Title">Item Title:</label>
<%= Html.TextBox(item,item) %>
</p>
<%
}
%>
</div>
<div>Simple values 1
<%Html.RenderPartial("SimpleModelView1", this.ViewData.Model.model1, new ViewDataDictionary()); %>
</div>
<div>Simple values 2
<%Html.RenderPartial("SimpleModelView2", Model.model2, new ViewDataDictionary()); %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
I'm going to take a wild stab and say your Model validation is failing when posting a ComplexModel back to the server
Your Model validation doesn't have to fail at all. You're not returning anything from inside the if block so you're always returning a View with no Model associated:
if (ModelState.IsValid)
{
var test = model.SomeStringsComplex;
}
return View(); // View is called with no Model data
Judging by your code, that would cause the Create view to be instantiated with no Model. That can be fixed fairly simply:
[HttpPost]
public ActionResult Create(ComplexModel model)
{
if (ModelState.IsValid)
{
var test = model.SomeStringsComplex;
// Do something to Create the object
RedirectToAction("Index");
}
// Model State is invalid, return so the user can correct
return View(model);
}
Aren't you supposed to send something to the view, on that line?
return View();
I think the problem is how you are specifying your text fields - the model binder does not know how to assign them back to the properties that they came from.
You may want to read this Haacked article that describes how to use model binding for enumerable properties:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Finally I found a solution. The data posted to Create method should look s.th. like this:
[HttpPost]
public ActionResult Create(ComplexModel mainModel, SimpleModel nestedModel, SimpleModel2 nested2Model)
{
if (ModelState.IsValid)
{
var main = mainModel;
var nested1 = nestedModel;
var nested2 = nested2Model;
}
return View();
}
The nested parameters are not null when the models are binded to partial views. If used in the main form through main model than the values are in the instance of the main model. I've also changed IEnumerable<string> to IList<string> and then rendered the string values with
for (int i = 0; i < Model.Strings.Count; i++)
{ %>
<%: Html.EditorFor(m => m.Strings[i])%>
<% }
Thanks all
Related
Currently we have a page where you select some parameters and click on a button to load data and display it in a grid, but there is no functionality to display the data on page load (via url parameters) yet. I've added the necessary routing configurations and Action, but I'm having troubles to render the page, it only displays the PartialView without styles.
How can I get the whole page to render and not just the PartialView?
Below is my simplyfied code for the View and Controller.
Views/Planing/Index.cshtml
#model PlaningTool.Web.Models.PlaningViewModel
<div class="row">
<div>
#using (Ajax.BeginForm("GetDataRows",
"Planing",
new AjaxOptions
{
HttpMethod = "Get",
UpdateTargetId = "gridPlaceholder",
LoadingElementId = "loadingIndicator"
}))
{
<!-- some comboboxes to select project and year -->
<input type="submit" value="Load data" />
}
</div>
</div>
<div id="gridPlaceholder">
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/images/loading-image.gif" />
</div>
</div>
Controllers/PlaningController.cs
public partial class PlaningController : Controller
{
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
// return GetDataRows(viewModel);
return RedirectToAction("GetDataRows", viewModel);
}
[RestoreModelStateFromTempData(typeof(PartialViewResult))]
public virtual PartialViewResult GetDataRows(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return PartialView("Shared/_PlaningViewModelRows", vm);
}
[...]
}
I finally found a solution. I'm pretty sure it's not the best way to do this but it works.
If the Model is already set I render the PartialView.
<div id="gridPlaceholder">
#{
if (Model != null)
{
Html.RenderPartial("Shared/_PDataViewModelRows", Model);
}
}
<div id="loadingIndicator" style="display: none;">
<img src="~/Content/kendo/Bootstrap/loading-image.gif"/>
</div>
</div>
And in my Controller I've changed to this, so my ViewModel gets loaded independently and I simply return the same view as I would for Index with the new ViewModel.
public virtual ActionResult Plan(long projectID, int year)
{
var viewModel = new PlaningViewModel
{
ProjectID = projectID,
Year = year
};
return View("Index", LoadViewModel(viewModel));
}
public PlaningViewModel LoadViewModel(PlaningViewModel viewModel)
{
// Load data from database with viewModel.ProjectID
// and viewModel.Year as parameters
[...]
var vm = new PlaningViewModel
{
// Set ViewModel for loaded data
[...]
};
return vm;
}
I am new to MVC/Razor/Web and am hoping to eventually be able to edit a list of items, right now I am just trying to display these items using Html.BeginCollectionItem and it is not working (no error, just does not display my items.) I have listed my code below:
Model:
namespace EditList
{
public class GiftModel
{
public string Name { get; set; }
public double Price { get; set; }
}
}
Controller:
namespace EditList
{
public class GiftController : Controller
{
public ActionResult Index()
{
GiftModel[] initalData = new[]
{
new GiftModel{Name = "Tall Hat", Price = 39.95},
new GiftModel{Name = "Long Cloak", Price = 120.00}
};
return View(initalData);
}
}
}
Main View:
#{
Layout = null;
}
#model IEnumerable<GiftModel>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title></title>
</head>
<body>
<h2>Gift List</h2>
What do you want for your birthday?
#using(Html.BeginForm())
{
<div id="EditorRows">
#foreach (var item in Model)
{
Html.RenderPartial("GiftEditorRow", item);
}
</div>
<input type="submit" value="Finished"/>
}
</body>
</html>
Partial View:
#model GiftModel
<div class="editorRow">
#using(Html.BeginCollectionItem("gifts"))
{
<div>HELLO</div>
Html.DisplayFor(m => m.Name);
Html.TextBoxFor(m => m.Name);
//Html.TextBoxFor(m => m.Price, new { size = 4 });
}
</div>
One thing to note is i did step into my main view and there is data in "items", but the partial view does not display the textboxes ect.. Im new to web code so i'm hoping I am missing something simple (I have looked at many tutorials and maybe i'm just overlooking something)
Let me know if there is any additional info I can provide.
I have the following viewModel
public class ExerciceViewModel
{
public string Code { get; set; }
public string Titre { get; set; }
public int QuestionCourante { get; set; }
}
the following view
#model MonEcoleVirtuelle.ViewModel.ExerciceViewModel
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Test</h2>
#using (Html.BeginForm("test", "exercice", FormMethod.Post))
{
#Model.Code <br />
#Model.Titre<br />
#Model.QuestionCourante<br />
<br />
<br />
Model.Code = "Code Modifie";
<input type="submit" value="Post Moi Ca!" name="fini" />
}
and the following controller methods
[HttpPost]
public ActionResult Test(ViewModel.ExerciceViewModel model)
{
if (ModelState.IsValid)
{
return Content(model.Code);
}
return View(model);
}
[HttpGet]
public ActionResult Test()
{
var vm = new ViewModel.ExerciceViewModel { Code = "code1", Titre = "Mon titre", QuestionCourante = 1 };
return View(vm);
}
When I submit the form, the model passed is empty, all properties are reset, not keeping the original values. What am I missing.
thanks
well, instead of #Model.Code which just display the values, you need some inputs.
So #Html.TextBoxFor(m => m.Code) for example
To manage a collection, you can do something like that :
#for (var i = 0; i < Model.Collection.Count; i++) {
Html.TextBoxFor(m => Model.Collection[i].Property)
}
You have not included any input fields in your view.
The #Model.Code etc only output the value of the field. To be able to post back elements they need to be form elements, like inputs. Use something like
#Html.TextBoxFor(p=>p.Code)
to create input fields that can then be posted back.
For a more complete guide see MSDN at http://msdn.microsoft.com/en-us/library/dd410596(v=vs.100).aspx
I don't understand why ModelState.isValid give me in all the ways. I set something in the email returns true and I pùt empty field, it returns true too. My question ism, what do I have to do to return true when the field is empty and nothing whn I wrote the email?
I have the next view file:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<div style="padding-top:5px;clear:both;"></div>
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Email usuario</legend>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Email) %>
<%: Html.ValidationMessageFor(m => m.Email) %>
</div>
<input type="submit" value="Enviar Email" />
</fieldset>
<% } %>
<div style="padding-top:5px;clear:both;"></div>
</asp:Content>
The Controller is:
//
// GET: /Account/EmailRequest
public ActionResult EmailRequest()
{
return View();
}
[HttpPost]
public ActionResult EmailRequest(string email)
{
if (ModelState.IsValid)
{
// save to db, for instance
return RedirectToAction("AnotherAction");
}
return View();
}
My model class is:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Security;
namespace PortalClient.Models
{
public class EmailRequest
{
[Required(ErrorMessage = "required")]
public string Email { get; set; }
}
}
Change the signature of your post action from string email to EmailRequest model and then check the state. e.g.
[HttpPost]
public ActionResult EmailRequest(EmailRequest model)
{
if (ModelState.IsValid)
{
// save to db, for instance
return RedirectToAction("AnotherAction");
}
return View();
}
You need to bind a view model to your view.
Change your EmailRequest model to something more descriptive like:
public class EmailRequestViewModel
{
[Required(ErrorMessage = "Required")]
public string Email { get; set; }
}
Your get action method would look something like:
public ActionResult EmailRequest()
{
EmailRequestViewModel viewModel = new EmailRequestViewModel();
return View(viewModel);
}
Your post action method:
public ActionResult EmailRequest(EmailRequestViewModel viewModel)
{
// Check for null view model
if (!ModelState.IsValid)
{
return View(viewModel);
}
// Do whatever you need to do
return RedirectToAction("List");
}
And then your view. Please excuse the ASP.NET MVC 4 code, MVC 2 is prehistoric :) This is just part of your view:
#model YourProject.ViewModels.EmailRequestViewModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Email)
#Html.ValidationMessageFor(x => x.Email)
}
I hope this helps.
you need to bind your model with binder first to have ability to chek it by Modelstat.IsValid
public ActionResult EmailRequest()
{
EmailRequest email = new EmailRequest();
TryUpdateModel(email);
if (ModelState.IsValid)
{
// save to db, for instance
return RedirectToAction("AnotherAction");
}
return View();
}
i have two html.actionlinks:
<%= Html.ActionLink("Activate", "ActivateJob", "Management", new { selectedObject = Model.ID }, new { #class = "actions" })%>
|
<%= Html.ActionLink("Deactivate", "DeactivateJob", "Management", new { selectedObject = Model.ID }, new { #class = "actions" })%>
here is part of my table:
foreach (WPM.Logic.Job item in this.Model.Jobs)
{
Model.ID = item.ID;
%>
<tr style="background-color: #FEF0D7">
<td style="border-bottom: solid 1px #f3ad44; width: 80px;" align="center">
<%= i = i + 1 %>
</td>
<td style="border-bottom: solid 1px #f3ad44; width: 120px;">
<input type="checkbox" name="selectedObject" value="<%= Model.ID %>" />
</td>
in page source i have follow results:
<td style="border-bottom: solid 1px #f3ad44; width: 120px;">
<input type="checkbox" name="selectedObject" value="8cdc5c7a-72ba-4883-99b9-272c866c27a9" />
</td>
<td style="border-bottom: solid 1px #f3ad44; width: 120px;">
<input type="checkbox" name="selectedObject" value="fa6b304c-9eee-483f-8208-e2febd077e50" />
</td>
question is: how to get these two checkbox values in HTML.ActionLink selectedObject? I'm getting just a last result in both html.actionlinks but i need value of selected checkbox. i have many of them.
it is a action witch will be called from html.actionlink.
[HttpGet]
public ActionResult ActivateJob(Guid[] selectedObject)
{
foreach (Guid guid in selectedObject)
{
}
return View();
}
[HttpGet]
public ActionResult DeactivateJobs(Guid[] selectedObject)
{
foreach (Guid guid in selectedObject)
{
}
return View();
}
Checkboxes usually go along with HTML forms, not action links. So put them inside a form and use a submit button which will automatically send the checked values to the corresponding controller action. If you want to use links you will need to write javascript code that will subscribe for the click event of the link, extract the values of checkboxes, modify the URL this link is pointing to in order to append those values to the query string which IMHO is too much of a work for something so simple. Of course you can have multiple submit buttons with different names inside a single HTML <form> and in the corresponding controller action you will be able to get the name of the button that was clicked so that you could perform different action.
Also I would strongly recommend you using the HTTP POST or PUT verb for something that is modifying state on the server.
UPDATE:
As requested in the comments section I include an example.
As always you start with a model:
public class JobViewModel
{
public string Guid { get; set; }
public bool Selected { get; set; }
}
public class MyViewModel
{
public IEnumerable<JobViewModel> Jobs { get; set; }
}
then you move on the controller:
public class JobsController: Controller
{
public ActionResult Edit()
{
var model = new MyViewModel
{
// Obviously those will be coming from some data store
// and you could use AutoMapper to map your business entities
// to the corresponding view model
Jobs = new[]
{
new JobViewModel { ID = Guid.NewGuid() },
new JobViewModel { ID = Guid.NewGuid() },
new JobViewModel { ID = Guid.NewGuid() },
}
};
return View(model);
}
[HttpPut]
public ActionResult Update(MyViewModel model, string activate)
{
if (!string.IsNullOrEmpty(activate))
{
// the Activate button was clicked
}
else
{
// the Deactivate button was clicked
}
// TODO: model.Jobs will contain the checked values =>
// do something with them like updating a data store or something
// TODO: return some view or redirect to a success action
return View("Edit", model);
}
}
then you would have a strongly typed view in which you will use editor templates:
<% using (Html.BeginForm("Update", "Jobs")) { %>
<%= Html.HttpMethodOverride(HttpVerbs.Put) %>
<table>
<thead>
<tr>
<th>Foo bar column ...</th>
</tr>
</thead>
<tbody>
<%= Html.EditorFor(x => x.Jobs) %>
</tbody>
</table>
<input type="submit" value="Activate" name="activate" />
<input type="submit" value="Dectivate" name="deactivate" />
<% } %>
and the last part would be the corresponding editor template which will be rendered for each item in the Jobs collection (~/Views/Jobs/EditorTemplates/JobViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.JobViewModel>"
%>
<tr>
<%= Html.HiddenFor(x => x.ID) %>
<%= Html.CheckBoxFor(x => x.Selected) %>
</tr>
Maybe I am complete out of scope. But I have solved my problem of "Simulating a checkbox behavior with an ActionLink" in the following (dirty) way using two ASCII-Characters to visualize my two states:
Index.cshtml:
#Html.ActionLink($"{(HomeController.IsExpertMode ? "☑️" : "⬜")}Expert-Mode", "ToggleExpertMode", "Home")
HomeController.cs:
public class HomeController : Controller
{
...
public bool IsExpertMode { get; private set; }
public ActionResult ToggleExpertMode()
{
IsExpertMode = !IsExpertMode;
return RedirectToAction("Index");
}
...
}
Hopefully this can help somebody searching for a simple solution for that problem - which brought me on this page, too...