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; }
}
Related
I'm working on an ASP.NET MVC 5 application and the project owner is concerned about "under-posting" issues caused by validating non-nullable types (as mentioned in http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html and http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api).
I created a test case to replicate this issue in ASP.NET MVC 5 but without luck.
Model:
public class ContactModel
{
[Required]
public Int32 data1 { get; set; }
public Int32 data2 { get; set; }
}
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
</div>
</div>
<div>
#Html.LabelFor(model => model.data2)
<div>
#Html.EditorFor(model => model.data2)
</div>
</div>
Controller:
public ActionResult Index(Models.ContactModel contact)
{
if (ModelState.IsValid)
{
Response.Write("modelstate is valid<br>");
return View();
}
else
{
Response.Write("modelstate is invalid<br>");
return View();
}
}
It seems that when data1 and data2 are null in the post, their values in the model (contact) will be 0. However, ModelState.IsValid will also be false (instead of true as shown in the two articles).
What I have:
What the second article showed:
I couldn't find any information regarding changes on how model validation works in ASP.NET MVC, so I'm guessing I did something wrong with my test case. Any thought and suggestion are appreciated.
The reason your ModelState is false is because the post is providing form values from each property in your model. Essentially the Model binding system is checking the validity of both data1 and data2 fields as you have #Html.EditorFor helpers explicitly written for both properties in your view (so no underposting is actually going on).
I did successfully replicate the under-posting concerns from the articles. Simply remove one of the EditorFor helpers in your view, so you're actually underposting. With both helpers present, there's no underposting going on. So the view looks like this now (note I added the validation helper for both properties to get feedback in the view on what's going on):
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data2)
</div>
</div>
Make sure to leave the #Html.EditorFor helper completely off for the data2 property. Now fill in zero in the form field (you'll only one form field in the view of course now), and post to your action.
ModelState will come back as true in this scenario, even though only one form field is being posted. Not a good result if someone does underpost! So here's the (slightly modified) original model class where underposting issues will occur in the case a form field is left off of your form (note the Required attributes don't make any difference in this situation as both properties are value types):
//You could add the Required attribute or not, doesn't matter at this point.
//The concern here is that the Modelstate will still come back as Valid
//in the case of a form field being left off of your form (or someone underposts).
//So to replicate underposting issues, make sure to comment or delete
//at least one Html.EditorFor helper in the view.
//[Required] Underposting will occur regardless if this is marked required or not,
//so be careful if someone does underpost your form.
public Int32 data1 { get; set; }
//[Required]
public Int32 data2 { get; set; }
Now the solution if you want to solve the underposting issue:
Simply mark both properties as required and make them nullable as mentioned in the articles you provided, like so:
[Required]
public Int32? data1 { get; set; }
[Required]
public Int32? data2 { get; set; }
Now when the view is posted with a missing #Html.EditorFor helper or a missing form field, the ModelState Validation will come back as false, and you're protected from underposting issues.
I have my program with the following model:
public class PlayToy {
public int PlayToyID {get;set;}
public string Name {get;set;}
public bool Available {get;set;}
}
In my controller I have:
Viewbag.PlayToyID = new SelectList(db.PlayToys, "PlayToyID", "Name");
Here is the View
<div class="editor-label">
#Html.LabelFor(model => model.PlayToy
</div>
<div class="editor-field">
#Html.DropDownList("PlayToyID", "Choose a Toy")
#Html.ValidationMessageFor(model => model.PlayToyID)
</div>
This works fine for showing dropdown list of all the Toys on the list, but I would like
to only show the the Items that are 'Available' to play with.
I am new to MVC, so I don't know AJAX or any other Scripting languages to integrate into MVC. So I was wondering how to do this with using just MVC, and would the code to filter the list go in the controller or the view?
Use LINQ to filter your list using the Where() operator
Viewbag.PlayToyID = newSelectList(db.PlayToys.Where(x=>x.Available).ToList(), "PlayToyID", "Name");
I recommend reading up a little on LINQ and how you can apply it to Lists and use in things like Entity Framework. It is an extremely powerful tool in the .NET toolkit for filtering, ordering, grouping, sorting, etc.
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!
#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.
I have a main view which contains a partial view. The model of partial view has 1 property called "HttpPostedFileBase file", together with other properties
However when the main-view get posted, all the other properties in that model get correct value, but the "HttpPostedFileBase file" is null. I already set the name of to be the same as parameter. Also even Request.Files give me 0 number of files.
What have I done wrong?
P.S. My main-view actually has 2 partial views. One PV has the same model as main-view. The 2nd one is what I mentioned above. The model contains a list of objects and HttpPostedFileBase file. Code like this:
public class MyPartialViewModel
{
public List<MyObject> objInfos { get; set; }
public ICollection<HttpPostedFileBase> file { get; set; }
}
And in the PV I looply use #Html.EditFor(model=>model.objInfos[i]) to bind it to a template.
So in main-view post method, I could get "objInfos" list & all ojects' value correct. But just NULL for "file".
Try adding enctype = "multipart/form-data" as one of the htmlAttributes in the #Html.BeginForm() helper.
EDIT: To not make a visual full postback:
AJAX FILE Upload in MVC3 using iFrame
In this right moment I'm using this approach, actually implementing this as we write.
I've just come across the same problem, and have worked around it by placing an extra parameter in the ActionResult to take the file upload parameter.
[Authorize]
[HttpPost]
public ActionResult MyActionResult(MyViewModel viewModel, HttpPostedFileBase myFileToUpload)
{
myViewModel.PartialViewModel.MyFileToUpload = myFileToUpload;
//manually bind MyFileToUpload;
//for some reason, because it's in the Partial View and it's of type HttpPostedFileBase, it won't post.
return View(viewModel);
}
Just for context:
My PartialViewModel contains the code:
public HttpPostedFileBase MyFileToUpload{ get; set; }
and PartialView the code:
<div class="editor-item">
<div class="editor-label">
#Html.LabelFor(model => model.MyFileToUpload)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MyFileToUpload)
</div>
</div>
Finally, I have an EditorTemplate Views/Shared/EditorTemplates/HttpPostedFileBase.cshtml:
#model HttpPostedFileBase
<input type="file" name="#ViewData.ModelMetadata.PropertyName" />
It's a workaround, not a solution, but does enable you to get the file uploaded while leaving the HttpPostedFileBase in the PartialViewModel.