I have a view model as shown below
public class SampleViewModel
{
public Model1 Property1 { get; set; }
}
public class Model1
{
[Required]
public string Name{get; set;}
}
In View We have
#Html.DisplayFor(m=>m.Model1.Name)
#Html.ValidationMessageFor(m=>m.Model1.Name)
In Controller we have
public ActionResult Index()
{
Model1 a= new Model1();
SampleViewModel s= new SampleViewModel();
s.Property1=a;
return View(s);
}
When Name is null ModelState is not getting populated with model errors even though I have Required attribute for Name. Where am I doing wrong?
To make this work you should at least create some <input> tag. For example to use the Html.TextBoxFor():
#Html.ValidationSummary(true)
#Html.DisplayFor(m => m.Property1.Name)
#Html.ValidationMessageFor(m => m.Property1.Name)
<p>#Html.TextBoxFor(m => m.Property1.Name)</p>
<br />
<input type="submit" value="Submit " />
The problem can be discovered if you look at the HTML generated by the MVC. The #Html.TextBoxFor(m => m.Property1.Name) is rendered to the following markup:
<input data-val="true" data-val-required="The Name field is required." id="Property1_Name" name="Property1.Name" type="text" value="" />
With the data-val-required attribute the posted page is validated correctly and the ModelState.IsValid returns false when no data entered.
Or you can use:
#Html.HiddenFor(m => m.Property1.Name)
Similar problem is described here: Required string attribute with null value gives IsValid=true in ASP.NET Core 2 Razor Page
Related
I'm developing a web MVC application with net core 2.2.
I have the following classes:
public class A
{
public IList<B> Bs { get; set; }
}
public class B
{
public string Id { get; set; }
public string Name { get; set; }
}
The following view:
#model A
#for (int i = 0; i < Model.Bs.Count; i++)
{
<partial name="_BsPatialView" for="Bs[i]" />
}
And the following partial view (_BsPatialView.cshtml):
<input type='hidden' asp-for="#Model.Id" />
<input asp-for="#Model.Name" />
Until here, everything it-s been generated fine. An example of the created inputs in the partial view is:
<input type="hidden" id="Bs_3__Id" name="Bs[3].Id" />
<input type="text" id="Bs_3__Name" name="Bs[3].Name" />
With the elements name and ids the model binder in the controller can properly bind everything.
The problem is when I try to return the partial view from the controller. What I do is:
public IActionResult AddBElement(A a)
{
a.Bs.Add(new B() { Id = Guid.NewGuid() });
return PartialView("_BsPatialView", a.Bs.Last());
}
The resulting html is:
<input type="hidden" id="Id" name="Id" />
<input type="text" id="Name" name="Name" />
So then, when I submit a form in which these inputs are, the model binding fails.
So, how should I return the partial view from the controller to fix this? Is there any equivalent to the partial's tag helper for attribute to use on the controller?
Model binding uses field names to map them to Model properties. Now because your name does not contain any information about the parent class, A, model binder does not know how to bind them.
So in other words, model binder would know how to bind this input:
<input type="hidden" id="Bs_3__Id" name="A.Bs[3].Id" />
But not this input:
<input type="hidden" id="Bs_3__Id" name="Bs[3].Id" />
One solution would be pass the prefix, A to the partial view: See this answer
A better solution would be to use EditorTemplate instead of Partial View, which would generate correct name for your input field. To use EditorTemplate Rename your partial view to B.cshtml (this should be your class name) and put it under /Views/Shared/EditorTemplates folder... then you can use it like this:
#Html.EditorFor(m => m.Bs)
Check this tutorial for more info about Editor Templates
Also check this question, which is very similar to yours
I am trying to pass hidden field value from view to controller by doing the following
#Html.HiddenFor(model => model.Articles.ArticleId)
and also tried
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
On both instances the value of ArticleId is 0 but when i use TextboxFor i can see the correct ArticleId, please help
Here it is
View
#model ArticlesCommentsViewModel
....
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
<div class="row">
<div class="col-xs-10 col-md-10 col-sm-10">
<div class="form-group">
#Html.LabelFor(model => model.Comments.Comment, new { #class = "control-label" })
#Html.TextAreaFor(m => m.Comments.Comment, new { #class = "ckeditor" })
#Html.ValidationMessageFor(m => m.Comments.Comment, null, new { #class = "text-danger"})
</div>
</div>
</div>
<div class="row">
#*#Html.HiddenFor(model => model.Articles.ArticleId)*#
<input type="hidden" id="ArticleId" name="ArticleId" value="#Model.Articles.ArticleId" />
</div>
<div class="row">
<div class="col-xs-4 col-md-4 col-sm-4">
<div class="form-group">
<input type="submit" value="Post Comment" class="btn btn-primary" />
</div>
</div>
</div>
}
Controller
// POST: Comments/Create
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.ArticleId,
CommentByUserId = User.Identity.GetUserId()
};
}
Model
public class CommentsViewModel
{
[Required(ErrorMessage = "Comment is required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Comment")]
[AllowHtml]
public string Comment { get; set; }
public int ArticleId { get; set; }
}
ViewModel
public class ArticlesCommentsViewModel
{
public Articles Articles { get; set; }
public CommentsViewModel Comments { get; set; }
}
The model in the view is ArticlesCommentsViewModel so therefore the parameter in your POST method must match. Your use of
#Html.HiddenFor(model => model.Articles.ArticleId)
is correct, but you need to change the method to
[HttpPost]
public ActionResult Create(ArticlesCommentsViewModel model)
and the model will be correctly bound.
As a side note, your ArticlesCommentsViewModel should not contain data models, and instead should contain only those properties you need in the view. If typeof Articles contains properties with validation attributes, ModelState would be invalid because your not posting all properties of Article.
However, since CommentsViewModel already contains a property for ArticleId, then you could just use
#Html.HiddenFor(model => model.Comments.ArticleId)
and in the POST method
[HttpPost]
public ActionResult Create([Bind(Prefix="Comments")]CommentsViewModel model)
to effectively strip the "Comments" prefix
In your controller, you need to pass the hidden value with the model,
for example, if you have a userId as a hidden value, in your Page you add:
#Html.HiddenFor(x => x.UserId)
In your model of course you would already have UserId as well.
In your controller, you need the model as a parameter.
public async Task<ActionResult> ControllerMethod(YourViewmodel model) { model.UserId //this should be your HiddenValue
I guess your model have another class called Articles inside CommentsViewModel.Change your controller function for accessing the ArticleId accordingly.
[HttpPost]
public ActionResult Create(CommentsViewModel comments)//, int ArticleId)
{
var comment = new Comments
{
Comment = Server.HtmlEncode(comments.Comment),
ArticleId = comments.Articles.ArticleId, // Since you are using model.Articles.ArticleId in view
CommentByUserId = User.Identity.GetUserId()
};
}
In my case, I didn't put the hidden input in the form section, but out of form, so it's not send to backend. Make sure put hidden input inside the form.
Also make sure name attribute is specified on the hidden field. Element's "id" is often used on client side but "name" on server side.
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining"
name="selectedTraining" />
In my case, I was passing a couple of fields back and forth between controllers and views. So I made use of hidden fields in the views.
Here's part of the view. Note a controller had set "selectedTraining" and "selectedTrainingType" in the ViewBag to pass to the view. So I want these values available to pass on to a controller. On the hidden tag, the critical thing is set to the "name" attribute. "id" won't do it for you.
#using (Html.BeginForm("Index", "ComplianceDashboard"))
{
<input type="hidden" value="#ViewBag.selectedTraining" id="selectedTraining" name="selectedTraining" />
<input type="hidden" value="#ViewBag.selectedTrainingType" id="selectedTrainingType" name="selectedTrainingType" />
if (System.Web.HttpContext.Current.Session["Dashboard"] != null)
{
// Show Export to Excel button only if there are search results
<input type="submit" id="toexcel" name="btnExcel" value="Export To Excel" class="fright" />
}
<div id="mainDiv" class="table">
#Html.Grid(Model).Columns(columns =>
Then back on the controller:
// POST: Dashboard (Index)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string excel)
{
string selectedTraining, selectedTrainingType;
selectedTraining = Request["selectedTraining"];
selectedTrainingType = Request["selectedTrainingType"];
Or can put the requests as parameters to the method: public ActionResult Index(string excel, string selectedTraining, string selectedTrainingType)
SCENARIO:
First of all, sorry for my english.
What I'm trying to do is posting trough form-POST the following object:
public class AppConfigViewModelInput
{
public string Setting { get; set; }
public string Value { get; set; }
}
to the following method:
[HttpPost]
public ActionResult Index(List<AppConfigViewModelInput> listOfAppConfigToUpdate)
{ ... }
But this input-object is constructed by only two properties of the view-object that I use to show the data on my razor page:
public class AppConfigViewModel : AppConfigViewModelInput
{
public string Description { get; set; }
public string ConfigType { get; set; }
public int ViewOrderInWebAdmin { get; set; }
public string ViewSpecialBackgroundColor { get; set; }
}
I was reading a lot of questions and blogs (check out SO References in the question). Finally I could get the following code for my razor page (I only post the form-code section):
#model List<PGWebAdmin.Models.AppConfigViewModel>
#{
var itemCnt = 0;
}
#foreach (var item in Model)
{
itemCnt++;
<input type="hidden" name="AppConfigViewModelInput.Index" value="#itemCnt" />
<input type="text" class="input-sm form-control" value="#item.Value" name="AppConfigViewModelInput[#itemCnt].Value"/>
<input type="text" name="AppConfigViewModelInput[#itemCnt].Setting" value="#item.Setting"/>
}
and the form is created by:
#using (Html.BeginForm("Index", "AppConfig",
FormMethod.Post, new { #class = "navbar-form navbar-right", role = "search" }))
{
QUESTION:
I could send the data, I'm checking with the dev tool the following information:
that is posted to the method, and the method is hit, but the value of the parameter is null:
I tested and corrected and tried several ways to do this but this is the far away I could get, and I can't understand what's happening.
I'm doing something wrong? Why I'm still getting null?
Any help will be preciated.
Thanks!
REFERENCES:
MVC post a list of complex objects
How can I post a list of items in MVC
Posting to a list<modeltype> MVC3
You need the change the name of the parameter to match what you are sending in the "name" field.
ie change your post controller:
[HttpPost]
public ActionResult Index(List<AppConfigViewModelInput> AppConfigViewModelInput)
{ ... }
Try this? It's maybe not the best answer but it should work for your purposes.
[HttpPost]
public ActionResult Index(AppConfigViewModelInput[] listOfAppConfigToUpdate)
{ ... }
And the html like this ..
#model List<PGWebAdmin.Models.AppConfigViewModel>
#{
var itemCnt = 0;
}
#foreach (var item in Model)
{
itemCnt++;
<input type="text" class="input-sm form-control" value="#item.Value" name="listOfAppConfigToUpdate[#itemCnt].Value"/>
<input type="text" name="listOfAppConfigToUpdate[#itemCnt].Setting" value="#item.Setting"/>
}
I removed the top input of index.. i don't see where it fits in. You can convert the array to a list inside your Index method.
I dont see the reason you send the AppConfigModelInput.Index, i think that might be your problem. The message you send should not contain data that is not part of the model
Your input file names are wrong ! Since your HttpPost action expects a collection of AppConfigViewModel. You don't really need the AppConfigViewModelInput. prefix for your input field names. For model binding to work, Your input field names should be like
<input type="hidden" name="[0].Index" value="" />
<input type="hidden" name="[1].Index" value=" />
Also make sure your form elements are in a form tag.
The below should work.
#model List<PGWebAdmin.Models.AppConfigViewModel>
#{
var itemCnt = 0;
}
#using (Html.BeginForm())
{
foreach (var item in Model)
{
<input type="hidden" name="[#itemCnt].Index" value="#itemCnt" />
<input type="text" value="#item.Value" name="AppConfigViewModelInput[#itemCnt].Value"/>
<input type="text" name="[#itemCnt].Setting" value="#item.Setting"/>
itemCnt++;
}
<input type="submit" value="Save" class="btn btn-default" />
}
I'm programming in C# ASP.NET MVC4 (Razor engine). I have a need to create a partial view and reuse it in multiple places. The problem is that the view is a form, and in some cases I will need to use it with a ViewModel. My question is how the model binding will work because in a ViewModel it will be a property of a property. Example:
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class OfficeViewModel
{
public PersonModel Person { get; set; }
// other properties
}
The partial view for the PersonModel would be:
#model SomeNameSpace.PersonModel
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
When this view is rendered it would look like this:
<input type="textbox" id="FirstName" name="FirstName" />
<input type="textbox" id="LastName" name="LastName" />
Now I want to use this same view with my OfficeViewModel. In that case I would do this in my Office view:
#{ Html.RenderPartial("Person", Model.Person); }
When this partial view is rendered it will be rendered as shown above. If I were to NOT reuse that view, my Office view will be like this:
#model SomeNameSpace.OfficeViewModel
#Html.TextBoxFor(m => m.Person.FirstName)
#Html.TextBoxFor(m => m.Person.LastName)
That would be rendered as:
<input type="textbox" id="Person_FirstName" name="Person.FirstName" />
<input type="textbox" id="Person_LastName" name="Person.LastName" />
Notice how the name attribute has the Person prefix property. So if I use the RenderPartial option and pass in the Model.Person, will the model binder know where to bind the FirstName and LastName in the OfficeViewModel?
If the model binder is smart enough to check for properties of properties, what happens when I have a ManagerModel and EmployeeModel in the OfficeViewModel and they both have properties named FirstName and LastName?
I hope I have been clear, and thanks in advance.
Unfortunately, Html.RenderPartial does not carry over the information necessary to come up with the correct field names in this situation.
If you want to reuse a partial view in this way, look into using Editor Templates with Html.EditorFor instead. Like the other *For helpers, EditorFor takes a lambda expression which allows it to carry over the name of the property being passed in to the template.
#Gerald is right. Default RenderPartial won't figure it out. Though you can write a custom helper that will take care of that issue:
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = name
}
};
return helper.Partial(partialViewName, model, viewData);
}
Use it in the View like that:
#Html.PartialFor(x => x.Person, "Person")
Found the answer here: https://stackoverflow.com/a/4807655/78739
Basically I need to set the ViewData.TemplateInfo.HtmlFieldPrefix.
So I added a property to the PersonModel and called it HtmlFieldPrefix. Example:
public class PersonModel
{
public string HtmlFieldPrefix { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Then in my partial view I do this:
#model SomeNameSpace.PersonModel
#{
ViewData.TemplateInfo.HtmlFieldPrefix = Model.HtmlFieldPrefix;
}
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
In my controller action I can do this:
model.HtmlFieldPrefix = "Person";
return View("_Person", model);
Now my rendered view will look like this:
<input type="textbox" id="Person_FirstName" name="Person.FirstName" />
<input type="textbox" id="Person_LastName" name="Person.LastName" />
If I leave the model.HtmlFieldPrefix as null, the view will render as:
<input type="textbox" id="FirstName" name="FirstName" />
<input type="textbox" id="LastName" name="LastName" />
So this fixes my issue, and allows me to use the partial view with ViewModels.
I have a class that looks like this:
public class UserListVM
{
public SearchModel SearchModel { get; set; }
public PagedList<User> Users { get; set; }
}
public class SearchModel
{
public string Text { get; set; }
/* other properties */
}
I send UserListVM to my view but the action accepts SearchModel:
public ActionResult Search(SearchModel filter)
{
UserListVM model = new UserListVM();
model.Users = userService.GetUsers(filter);
model.SearchModel = filter;
return View(model);
}
My view is:
#model UserListVM
<form>
#Html.TextBoxFor(m => Model.SearchModel.Text)
</form>
But this generates:
<input id="SearchModel_Text" name="SearchModel.Text" type="text" value="">
Which sends UserListVM to the action instead of SearchModel. How can I get it to generate this:
<input id="Text" name="Text" type="text" value="">
#Html.TextBoxFor(m => m.SearchModel.Text, new { id = "Text" })
Utilize the overloaded TextBoxFor() method that takes a second object parameter (called htmlAttributes). Here you can specify HTML attributes to apply to the DOM element you are currently utilizing (in this case, your input element).
Edit: I believe your lambda expression is wrong. Change:
#Html.TextBoxFor(m => Model.SearchModel.Text)
To
#Html.TextBoxFor(m => m.SearchModel.Text)
// htmlAttributes omitted to show the issue
Edit Edit: it turns out that even with a specified name attribute, it will be rendered according to what the form is requiring for a POST to the necessary field(s).
Edit Edit Edit: Try to be explicit with FormExtensions.BeginForm():
#using (Html.BeginForm("Search", "YourController", FormMethod.Post, null))
{
#Html.TextBoxFor(m => m.SearchModel.Text)
}
Use this as a substite of your <form /> element.
Create a partial view for your SearchModel, and call it using Html.Partial. Then, from within that partial view, do all of the EditorFor/TextBoxFor Extensions
Your view - UserList.cshtml:
#model UserListVM
#using (Html.BeginForm())
{
#Html.Partial("Search", Model.SearchModel)
}
Your view - Search.cshtml:
#model SearchModel
#Html.TextAreaFor(m => m.Text)
Assumming there is more the the View than you have shown, why not just have your Search method take the UserListVM model. It will just contain a null reference to the users, so there is no extra data sent in the post.
Try doing it manually like this:
#Html.TextBox("Text", Model.SearchModel.Text)