this is my first question on stackoverflow, so please be kind if I should be missing something.
I have two forms, each linked to its own action (for data modification) combined in a single view. Currently, I am using partials and a parent model which consists of the two models assigned to the forms.
My main view looks a bit like this:
#model ReportingUploadPortal.Models.ManageUserDataModel
#Html.Partial("_ChangePasswordPartial", Model.PasswordModel)
#Html.Partial("_ChangeEMailPartial",Model.EmailModel)
My parent model is this:
public class ManageUserDataModel
{
public ChangeEmailModel EmailModel { get; set; }
public LocalPasswordModel PasswordModel { get; set; }
}
I get the validation summary information displayed two times, for each of my two partial views (they are very similar). It seems the validation information is tied to the parent model, not to each of the two child models.
#using (Html.BeginForm("ChangeEmail", "Account")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Change your email</legend>
<ol>
<li>
#Html.LabelFor(m => m.EMail)
#Html.TextBoxFor(m => m.EMail)
</li>
<li>
#Html.LabelFor(m => m.ConfirmEMail)
#Html.TextBoxFor(m => m.ConfirmEMail)
</li>
</ol>
<input type="submit" value="Change email" />
</fieldset>
}
How can I get a seperate validation summary for each form/model?
The #Html.ValidationSummary() is just that a summary. It is actually a list of errors which is on the model. So I believe that each model should have it's own summary (not certain about this).
I believe that you are getting duplicate summaries because you have this line: #Html.ValidationSummary() in each form in each partial view. Every time you have this line it will print out the whole summary to the page. You can change the granularity of the summaray with some flags, but there are also alternatives to using the whole summary e.g. #Html.ValidationMessage().
Hope that helps!
Related
I am late in on a project where possibly the most important requirements have been ignored until the 11th hour.
The application allows suppliers to apply to become contractors for a company via a fairly in depth online application form.
The application takes the form of question sections that make up a left sidebar nav and each section has a number of questions.
The requirements that have been ignored up until now are that each applicant who fills out a form online has an application type, e.g. construction, catering etc. and the requirements state that the application is configurable so that only certain sections appear to a certain application type and certain questions within a section should be configurable to appear to a particular application type.
Configuring the sections is easy but configuring the questions is not so easy. At the moment everything is hardcoded into razor views and EditorFor templates. Basically there is one EditorFor per ViewModel which makes up a Section e.g.
#model Models.Enforcement
<div >
<form class="form-horizontal" method="post" action="#Url.Action(nameof(ApplicationController.Enforcements))"
#Html.EditorFor(x => x)
</div>
And the editor for will look something like this:
#model GSCM.Logic.Web.Models.Enforcement
#using System.Web.Mvc.Html
<div>
#Html.HiddenFor(x => x.Id)
<div class="form-group">
#Html.LabelFor(model => model.Name)
<div>
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
My solution to this problem is to first of all be able to add custom attributes to a ViewModel class that tag the property as a configurable Question, e.g.
public class Enforcement
{
[ApplicationQuestion DisplayOrder=1, Partial="DifferentNamedPartial"]
public string Name{get;set;}
}
I then have my own EditorFor that loops through all these tagged Properties:
using System.Web.Mvc.Html;
public static class HtmlHelperExtensions
{
public static MvcHtmlString QuestionSetEditorFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, VMBaseClass model)
{
//PSEUDO CODE
var sectionHtml;
ForEach(var taggedProperty in model)
{
var basedOnProperty = GetFilePathFromProperty(taggedProperty);
sectionHtml += System.Web.Mvc.Html.GetPartial(basedOnProperty)
}
return MvcHtmlString.Create(sectionHtml);
}
}
So basically, I would need to chop up the existing EditorFor ViewModel templates and have a partial for each section, property.
My questions are:
Is this a good idea or is there a better way. Time is a constraint (as always)?
Can I create the html string in the way I have outlined above with the pseduo code that loops rounds the properties and calls out to the partials?
Here you can proceed like
Suppose your partial pages which you want to call in main pages using loops are:
_form-one-partial.cshtml
_form-two-partial.cshtml
_form-three-partial.cshtml
_form-four-partial.cshtml
_form-five-partial.cshtml
One of those partial page content as:
#model PartialFieldViewModel
//---other fileds on the page
<input type="text"/>
//---other fileds on the page
To call the partial on the basis of the loop around the "partial" varible in the main page where partial will be called as:
#model PartialViewModel
foreach (var partial in Model.Parital)
{
#Html.Partial("_form-{0}-partial".FormatWith(partial.PartialType.ToString().ToLower()), partial)
}
Partial view modal class as
public class PartialViewModel
{
//Other properties
public List<PartialFieldViewModel> Partial { get; set; }
}
public class PartialFieldViewModel
{
//Other properties
public Constants.PartialType PartialType{ get; set; }
}
When I post back to the server when tab2 is active viewModel.FirstName is null in the action method. How do you tell the modelbinder that it should take #Html.EditorFor(m => m.FirstName) from tab2 when that tab is active? Everything works fine for tab1.
jQuery hide() and show() are used for switching between tabs.
Controller
[HttpPost]
public ActionResult Index(MyViewModel viewModel)
View
<div id="tab1">
#Html.EditorFor(m => m.FirstName)
</div>
<div id="tab2">
#Html.EditorFor(m => m.FirstName)
</div>
It sounds like you have both FirstName input fields inside the same form.
Something like:
<form>
<div id="tab1">
#Html.EditorFor(m => m.FirstName)
</div>
<div id="tab2">
#Html.EditorFor(m => m.FirstName)
</div>
</form>
When you post this, the form is submitted as:
FirstName=valueFromField01&FirstName=valueFromField02
From the behavior you are describing, it seems that once the model binder sets the FirstName field in your MyViewModel, it ignores the second one (but I'm not really sure about that).
Solutions:
Rather than have a form with different tabs inside of it, have a form inside each tab. This will ensure you only have one FirstName inside a form.
Update the model so that you get both fields. You would need to use an array of string: string[] FirstName.
Update:
Instead of updating your ViewModel, it might be easier to simply add another parameter to your action method so you can get both FirstName values and then figure out which one was actually provided:
public ActionResult Index(MyViewModel viewModel, string [] FirstName).
Then you can have logic in your action method to set the FirstName in your viewModel:
if(!string.IsNullOrEmpty(FirstName[0]))
{
viewModel.FirstName = FirstName[0]
}
else if(!string.IsNullOrEmpty(FirstName[1]))
{
viewModel.FirstName = FirstName[1];
}
You could achieve that disabling the duplicated name form elements. By default, disabled form elemnts will not be passed to the request, and your Controller problably will receive the name corectly from the tab2 when it is shown active.
So. here follows one example
$('#tab1')
.hide()
.find('input[name="FirstName]"')
.prop('disabled',true);
$('#tab2').show()
.find('input[name="FirstName]"')
.prop('disabled',false);
Latter I suggest you should wrap this behaviour inside a function.
Hope it helps.
#model MyMVC.Models.MyMVC.MyModel
#{
ViewBag.Title = "Index";
}
The reason why I ask this question is in MVC we can have more than 1 form, so I would like to have a model for each form. But when I was trying to add another model at the top of the view, it displays an error.
I know we can use ViewModel which has model1 and model2 inside, but it's not the question I am asking.
I just simply want to know is there any way that we can put in 2 models into 1 view.
Update:
For example, I want to have 2 forms in my view, so for each form I'd like to have a separated model.
Update 2
Thank you all for your replies. I now know that MVC only supports one single model on one view. Do you guys consider this a disadvantage of MVC? Why or Why not?(No one has answered it yet.... why?)
Your answers are similar, so I don't even know which one should be set as an answer.
You should use some prtialviews for other models. You have your main view with main model. Inside of that view, you could have some partialviews with their models and their validations.
--------------------------------------Edit:
As others said it is better to use View Models and combine the models with each others. But as you wanted I created a sample project for you on my Github account:
https://github.com/bahman616/ASPNET_MVC_multiple_models_in_a_view_with_partialview.git
Here is the simplified code of that project:
Here are two models:
public partial class Person
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
}
public partial class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
I want to have both create views in one view, so this is the parent view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Person
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Create","Person")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#{Html.RenderAction("_CompanyCreate", "Company");}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And this is the partial view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Company
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("_CompanyCreate","Company")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Company</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Simple answer:
No, you can't have two models.
Details
There is only one way to declare a Model to be used by a view:
#model [namespace].[path].[ViewModelName]
There is no way to specify multiple of these above.
In addition to that, you can only send one object to a view from the Controller, which would be the model:
public ActionResult WhateverAction()
{
var model = new MyViewModel();
return View(model);
}
Theoretically you cant do it because it binds one model to one view. But you can create somethink like a model helper which combines two other models to one
public class model1
{
string var1 {get; set;}
string var2 {get; set;}
}
public class model2
{
string var3 {get; set;}
string var4 {get; set;}
}
public class modelofBoth
{
model1 mod1 {get; set;}
model2 mod2 {get; set;}
}
If you cannot/don't want to modify the model passed to the view then you could pass the additional object in via the ViewBag object. It feels like a hack to me but it works:
Controller:
ViewBag["Model2"] = yourObject
View:
#{var Model2 = ViewBag["Model2"] as yourObjectType;}
Technically, you could write your own view engine (derived from the RazorViewEngine?) using a custom WebViewPage and have it do whatever you want. The view derives from WebViewPage<T> where T is the type of the model for the page. To support multiple models, the view base class would need to have multiple generic types. Using the baked in RazorViewEngine, however, you're limited to a single model type per view and much of the controller framework presumes a single model per view so you'd have to rewrite that, too. There doesn't seem to be much point in that, however, as you could simply have your models be properties of the (single) view model, then use partial views for each of the forms providing the property as the model for the partial.
For more information see http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx
Answer is NO. Thats why ViewModel pattern Exists. because the razor engine is totally relied on the model you passed, the model is used for ModelBinding for the basic CRUD Operations.
You can easily have 1 model per form. For each form, you just have to do something like this:
var model = new MyModel();
#Html.TextBoxFor(m => model.Title)
#Html.ValidationMessageFor(m => model.Title)
The validation works like a charm.
This way, you can keep your model to display information as usual and have 1 model per form to submit data (for example newsletter subscription)
if your models are similar,
Make a new model contains references to all models you want, and use it.
if models are not similar, you can fill new model with similar fields you need from all your models.
but if you wants use totally different models, you should use Partial views.
Suppose I have a simple search form with a textbox. And upon submitting the form I send the contents of the textbox to a stored procedure which returns to me the results. I want the results to be displayed on the same page the form was, except just below it.
Right now I'm doing the following but it's not working out exactly the way I want:
"Index" View of my SearchController
#using (Html.BeginForm("SearchResults", "Search", FormMethod.Post, new { #class = "searchform" }))`{
<fieldset>
<legend>Name</legend>
<div class="editor-label">
#Html.Label("Search")
</div>
<div class="editor-field">
#Html.TextBox("Name")
</div>
<input type="submit" value="Search" class="formbutton" />
</fieldset>
#{ Html.RenderPartial("SearchResults", null);
This is my "SearchResults" View:
#model IEnumerable<MyProject.Models.spSearchName_Result>
<table>
#foreach (var item in Model)
{
<tr>
<td>
#item.Name
</td>
</tr>
}
</table>
This is my Controller:
// GET: /Search/SearchResult
[HttpPost]
public ActionResult SearchResult(FormCollection collection)
{
var result = myentity.spSearchName(collection["Name"]);
return PartialView("SearchResults", result);
}
I can only seem to get the results to display on an entirely new page (not being embedded as a partial view), or I get an error when I load the search page because there are no results (since I hadn't searched yet).
Is there any better way to achieve what I'm trying to do? I feel like I'm going against some of the best practices in MVC.
You could return the results in a ViewData object then only show on the view it if is not null.
Very similar to this question MVC 3 form post and persisting model data
For this instance it looks like you are not passing the results to your partial view. Try this?
#{ Html.RenderPartial("SearchResults", Model.Results);
Since you are not persisting your search information using a model, the search information will be lost when the search form is posted.
Given your design and stated goal, it would be best to convert the form in your Index view into an Ajax form, then your controller can send your partial view back to populate a div below your Ajax form.
counsellorben
I have this field in my model.
class AddUserModel
{
// ....other fields
[Required(ErrorMessage = "Please select at least one role.")]
public string[] Roles { get; set; }
}
In the view this is being rendered as a list of checkboxes:
<div class="editor-field">
#Html.ValidationMessageFor(model => model.Roles)
<ul class="list_roles">
#foreach (string role in ViewBag.PossibleRoles)
{
<li><input type="checkbox" name="Roles" value="#role" />#role</li>
}
</ul>
</div>
How do I get the validation error message to fire if none of the checkboxes are clicked? Will I need to write a custom validator?
I suspect you'll have to write a custom validator (which isn't hard, BTW; the biggest challenge would be to include client-side error checking, but even that's not that hard).
Another option might be to try using a listbox instead. In Html those have multiple selection capability. The listbox validator might let you require at least one value (I don't know for sure, as I haven't had reason to use a validated listbox in my MVC app yet).