Why is the view model passed to my action method null? - asp.net-mvc

I can't seem to pass the form values using a model. I do not want to resort to using individual parameter/FormCollection/Request and then instantiate the model class with the values.
My model
//JcSpaceAccount.cs
namespace JcSpaceEntities
{
public class JcSpaceAccount
{
public string FirstName;
public string LastName;
public string Email;
public DateTime DateOfBirth;
}
}
My View
//Registration.cshtml
#model JcSpaceEntities.JcSpaceAccount
<!DOCTYPE html>
<div class="form-horizontal">
<h4>JcSpaceAccount</h4>
<hr />
#using (Html.BeginForm("Registration", "Registration", FormMethod.Post))
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.LabelFor(m => m.FirstName)
</div>
<div class="col-md-offset-2 col-md-10">
#Html.TextBoxFor(m => m.FirstName)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.LabelFor(m => m.LastName)
</div>
<div class="col-md-offset-2 col-md-10">
#Html.TextBoxFor(model => model.LastName)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.LabelFor(m => m.Email)
</div>
<div class="col-md-offset-2 col-md-10">
#Html.TextBoxFor(model => model.Email)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.LabelFor(m => m.DateOfBirth)
</div>
<div class="col-md-offset-2 col-md-10">
#Html.TextBoxFor(model => model.DateOfBirth)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
</div>
My controller
namespace JcSpace.Areas.Registration.Controllers
{
public class RegistrationController : Controller
{
// GET: Registration/Registration
[HttpGet]
public ActionResult Registration()
{
return View();
}
[HttpPost]
public ActionResult Registration(JcSpaceAccount entity)
{
return View();
}
}
}

You should change your model to:
public class JcSpaceAccount
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime DateOfBirth { get; set; }
}
Problem is default MVC ModelBinder works with properties and you have fields in your model now. So default model binder just can't fill them.
Change your JcSpaceAccount fields to properties and you get your data on post.
And in your Post contoller method you should set your model as #haim770 said:
[HttpPost]
public ActionResult Registration(JcSpaceAccount entity)
{
ViewData.Model = entity; //This line
return View();
}

On your Registration() method decorated with HttpGet.
[HttpGet]
public ActionResult Registration()
{
return View(new JcSpaceAccount());
}

Related

MVC Core. Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions

I am passing in my view this model
public class BlogViewModel
{
public List<CommentModel> commentModels { get; set; }
public BlogPostLayoutModel blogPostLayoutModel { get; set; }
public CommentModel commentModel { get; set; }
public int pageNumber { get; set; }
}
CommentModel class
public class CommentModel
{
public int IDComment { get; set; }
public int IDPost { get; set; }
[Required()]
public string Username { get; set; }
[Required()]
public string Content { get; set; }
public static CommentModel CommentToCommentModel(Comment comment)
{
CommentModel commentModel = new CommentModel();
commentModel.IDComment = comment.IDComment;
commentModel.IDPost = comment.IDPost;
commentModel.Username = comment.Username;
commentModel.Content = comment.Content;
return commentModel;
}
public static List<CommentModel> CommentsToCommentModels(List<Comment> comments)
{
List<CommentModel> commentModels = new List<CommentModel>();
foreach (var comment in comments)
{
CommentModel commentModel = new CommentModel();
commentModel.IDComment = comment.IDComment;
commentModel.IDPost = comment.IDPost;
commentModel.Username = comment.Username;
commentModel.Content = comment.Content;
commentModels.Add(commentModel);
}
return commentModels;
}
}
And my view, where the error is thrown (fifth line)
<form asp-action="AddComment">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
#{ var pageNumber = Model.pageNumber;
#Html.HiddenFor(x => pageNumber)
int IDPost = Model.blogPostLayoutModel.IDPost;
#Html.HiddenFor(x => x.commentModel.IDPost == IDPost)
}
<div class="row">
<div class="form-group col-md-4">
<label asp-for="commentModel.Username" class="control-label"></label>
<input asp-for="commentModel.Username" class="form-control" />
<span asp-validation-for="commentModel.Username" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Comment</label>
<textarea asp-for="commentModel.Content" class="form-control"></textarea>
<span asp-validation-for="commentModel.Content" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</form>
I have searched for the error, but seems like it's mostly people trying to return a value from a method and getting told to pass the value to a local variable and then put that local variable inside the tag helper, but that doesn't seem to be the case here.
And also blogPostLayoutModel.IDPost is defined as
public int IDPost { get; set; }
EDIT: My commentModel was null, so that was the problem.
Working form
<form asp-action="AddComment">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
#{
#Html.HiddenFor(x => Model.pageNumber)
Model.commentModel.IDPost = Model.blogPostLayoutModel.IDPost;
#Html.HiddenFor(x => x.commentModel.IDPost)
}
<div class="row">
<div class="form-group col-md-4">
<label asp-for="commentModel.Username" class="control-label"></label>
<input asp-for="commentModel.Username" class="form-control" />
<span asp-validation-for="commentModel.Username" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Comment</label>
<textarea asp-for="commentModel.Content" class="form-control"></textarea>
<span asp-validation-for="commentModel.Content" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</form>

Asp.net MVC View Textbox Returns Default Value

I can send lat,lon, neighbors and neighborslimit variables to View.Yet, I want change neighborlimit from view. When I post View, MapViewModel's variables are 0, I have tried to ModelState.Clear() but there is no difference, Could you help me about it ? Thanks
MODEL:
public class MapViewModel
{
public double lat;
public double lon;
public List<Point> neighbors;
public Polygon polygon;
public int neighborlimit;
public double[][] polyTable;
}
CONTROLLER:
[HttpGet]
public ActionResult Map()
{
UserAccount user = (UserAccount)UserManager.FindByName(User.Identity.Name);
MapViewModel model = new MapViewModel() { lat = (double)user.address.latitude, lon = (double)user.address.longitude, neighbors = user.getNeighbors(), neighborlimit= (int)user.neighborsLimit };
return View(model);
}
[HttpPost]
public ActionResult Map(MapViewModel model)
{
UserAccount user = (UserAccount)UserManager.FindByName(User.Identity.Name);
user.neighborsLimit = model.neighborlimit;
UserManager.Update(user);
return View(model);
}
VIEW:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(h => h.neighborlimit, new { #class = "form-control" })
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
}
You don't have a property for neighborlimit (just a field). Change it to
public int neighborlimit { get; set; }
which will allow the DefaultModelBinder to set the property when you submit the form
The problem is that you don't have the values in the form that's why when the form is posted the values doesn't exists and the ModelBinder set default values. If the security is not a problem but hidden fields for all values that you want to persist.
Something like this
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(h => h.lat)
/* Now enter hidden fields for all of the properties that you want */
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(h => h.neighborlimit, new { #class = "form-control" })
</div>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
}
UPDATE
As Stephen Muecke said make sure that you use properties not fields

Client validation did not work

In my project used ASP.NET MVC 5. I've used System.ComponentModel.DataAnnotations for validation.
I expect when I don't enter value for a mandatory field,be prevent to continue my action.
But this expectation does't satisfy.
How can I solve this problem?
#model Jahan.Blog.ViewModel.ArticleViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Article</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.UserId)
#*There are some codes*#
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</div>
#*There are some codes*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
ViewModel:
[ModelBinder(typeof(ArticleViewModelBinder))]
public class ArticleViewModel : IArticleViewModel
{
public ArticleViewModel()
{
}
public virtual int Id { get; set; }
[Required]
[StringLength(256)]
public virtual string Title { get; set; }
}
Controller:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Prefix = "")]ArticleViewModel articleViewModel, List<int> availableTags)
{
if (ModelState.IsValid)
{
// There are some codes.
}
return View(articleViewModel);
}
open your web.config and find ClientValidationEnabled in appSettings and check if it's set as true :
<add key="ClientValidationEnabled" value="true" />
According to Fiskeboss's comment, jquery.validate.unobtrusive.js library added.Then it works correctly.

IValidatableObject.Validate does not fire if DataAnnoations add ValidationResult

With a standard ASP.NET MVC controller and view and a model that both implements IValidatableObject and has DataAnnotations, the Validate method never fires if the DataAnnotations generate an exception.
Here's the model...
public class ModelStaticDA : IValidatableObject {
public long Id { get; set; }
[EmailAddress]
public string EmailAddress { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
yield return new ValidationResult("MODEL NOT VALID!")
}
}
Here's the view (client validation is disabled for this demo)...
#model BindingAndValidation.Models.ModelStaticDA
#{
ViewBag.Title = "Create";
HtmlHelper.ClientValidationEnabled = false;
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ModelStaticDA</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.EmailAddress, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
If you post something like "invalid" to EmailAddress, only the DataAnnotation message displays. If you post a valid e-mail address, the message from Validate displays.
Is this the correct behavior? If so, why? If not, what am I doing wrong?
You are doing everything right, that's the behavior. My guess it was designed this way to avoid having to validate again while working with the properties inside the Validate method, you know that when it's called you are working with valid data, and you can do things that require valid data.

Why is my http post returning null from my view model

This is my model:
public class Attribute
{
public string Key { get; set; }
public string Value{ get; set; }
}
I fill it in my GET create
public ActionResult Create()
{
var Attributes = new[]
{
new Attribute {Key = "Name" Value = "" },
new Attribute {Key = "Age" Value = "" },
};
return View(Attributes);
}
My View looks like this:
#model IEnumerable<Customers.ViewModels.Attribute>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
#foreach (var item in Model)
{
<div class="editor-label">
#Html.Label(item.Key)
</div>
<div class="editor-field">
#Html.EditorFor(m => item.Value)
#Html.ValidationMessageFor(m => item.Value)
</div>
}
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Then My Post Create looks like this:
[HttpPost]
public ActionResult Create(IEnumerable<Attribute> attributes)
{
}
but my IEnumerable<Attribute> attributes is null. Any suggestions?
You need to create a editor template for a Attribute and then pass the List<Attribute> model to it.
#Model Attribute
<div class="editor-label">
#Html.LabelFor(m => m.Key)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.Value)
#Html.ValidationMessageFor(m => item.Value)
</div>
In your view use:
<fieldset>
#Html.EditorFor(m > m)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
You need to do this because the foreach doesn't create the correct name for the elements.

Resources