In a web app I'm creating, I have a list of clients. And each client has a sub-list of projects.
This is my Client model:
Public Class ppClient
<Key()>
Public Property ClientID As Integer
Public Property Name As String
Public Overridable Property Projects As ICollection(Of ppProject)
End Class
And here is my Project model:
Public Class ppProject
<Key()>
Public Property ProjectID As Integer
Public Property ClientID As Integer
Public Property ProjectTitle As String
Public Overridable Property Client As ppClient
End Class
The problem I am encountering is that I am trying to manually add a form value when a new project is being created. I have the ClientID saved in ViewData, and so I don't want to ask the user for it. This is my view and form submission:
ModelType MVCnewDB.ppProject
#Using Html.BeginForm()
#Html.ValidationSummary(True)
#* Needs to submit ClientID *#
Html.EditorFor(Function(model) model.ProjectTitle)
Html.ValidationMessageFor(Function(model) model.ProjectTitle)
#<input type="submit" value="Create Project" />
End Using
<div>
#Html.ActionLink("Back", "Index")
</div>
So how do I get the form to submit ClientID as my ViewData value? Can I make a call to Html.HiddenFor and stuff a value into it or something?
Any help would be appreciated.
Ah okay - I've found what I was looking for. I was looking for #Html.Hidden("ClientID", ViewData("key")) which submits the given ViewData value to the form as the ClientID field.
You just need to add a hidden field. I don't know how you would do it in VB, but in C# it would be like this:
#Html.HiddenFor(x => ViewBag.ClientID)
or
#Html.HiddenFor(x => ViewData["ClientID"])
This is how I interpret your question:
You are on a page with a model bound to your ppProject class
You have a ClientID somewhere and you want to know where to put it on the form so that...
You want the form submission to include the ClientID so you can relate the ppProject to a client.
Is this correct?
If so, you have a couple options. If you've included your ClientID in a ViewBag, you can use a hidden field to store the value and submit it. On your action, you can actually specify more than one parameter. The first can be the ppProject class you want the model binder to bind your fields to, and another string parameter with the same name as the hidden field containing your ClientID value. As long as the parameter matches the hidden field name exactly, the model binder will match the two up and you'll get the ClientID.
(In C# because my VB.NET skills are too rusty for it to make sense:)
public ActionResult NewProject(ppProject projectInfo, string ClientID)
{
...
}
The above assumes you keep your view as-is but you add something like this:
<input type="hidden" name="ClientID" value="#ViewBag.ClientID" />
Or possibly (just a guess!):
Html.HiddenFor(Function(model) ViewBag.ClientID)
This assumes you've matched the ViewBag property name to the parameter name on your ActionResult in your controller. (Just replace the ViewBag with whatever you're using to store the ClientID value.)
Related
If I use the MVC designer to create a strongly typed view, it will automatically write code to model bind UI input with model properties. But assume my view is not strongly typed with the model. Assume my model has 5 properties that can be bound, and the UI is sending 10 field inputs.
How does MVC cleverly figure out which field should be bound with which property?
Your inputs should have a name attribute, and those names should match what's in your MVC post action's model properties. Say for example:
Your Model:
public class Person
{
public string Address { get; set; }
}
HTML:
<input type="text" name="Address" /> or #Html.TextBox("Address")
MVC Action:
public ActionResult SubmitPerson(Person p)
{
//p.Address will have the value from the input with the name="Address"
}
MVC Validation Fundamentals (with Entity Framework)
Scenario:
I have a model class as below (autogenerated via Entity Framework EF.x DbContext Generator).
(There is no view model at the moment).
public partial class Activity
{
public int Id { get; set; }
public byte Progress { get; set; }
public decimal ValueInContractCurrency { get; set; }
public System.DateTime ForecastStart { get; set; }
public System.DateTime ForecastEnd { get; set; }
public int DepartmentId { get; set; }
public int OwnerId { get; set; }
public int StageId { get; set; }
public int StatusId { get; set; }
public virtual Department Department { get; set; }
public virtual Owner Owner { get; set; }
public virtual Stage Stage { get; set; }
public virtual Status Status { get; set; }
}
When I submit a blank form on the strongly-typed view, I get these validation messages:
The Progress field is required.
The ValueInContractCurrency field is required.
The ForecastStart field is required.
The ForecastEnd field is required.
i.e. all the fields in the db table.
If I fill these in, and submit again, then the controller gets called. The controller then returns back to the view page due to IsValid being false.
The screen is then redisplayed with these validation messages:
The StageId field is required.
The DepartmentId field is required.
The StatusId field is required.
The OwnerId field is required.
i.e. all the foreign key fields in the db table (these are also all select boxes).
If I fill these in, the form then submits succesfully and is saved to the db.
Questions:
Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only checked by IsValid() even though they are empty and therefore clearly invalid?
How do you make everything get validated in one step (for empty fields), so the user does not have to submit the form twice and all validation messages are shown at once? Do you have to turn off client side validation?
(I tried adding [Required] attributes to the foreign key fields, but that didn't seem to make any difference (presumably they only affect IsValid). I also tried calling Html.EnableClientValidation() but that didn't make any difference either).
4..Lastly, I've seen people using [MetadataType[MetadataType(typeof(...)]] for validation. Why would you do that if you have a viewmodel, or is it only if you don't?
Obviously I'm missing some fundamentals here, so in addition if anyone knows of a detailed tutorial on how exactly the MVC validation process works step-by-step including javascript/controller calls, rather than just another essay on attributes, then I could do with a link to that too :c)
More info for Mystere Man:
Solution setup as follows:
.NET4
MVC3
EF5
EF5.x Db Context Generator
"Add Code Generation Item" used on edmx design surface to associate EF.x Db Context Generator files (.tt files)
Controller looks like this:
// GET: /Activities/Create
public ActionResult Create()
{
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(new List<string>());
ViewBag.StatusId = new SelectList(db.Status.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
return View();
}
// POST: /Activities/Create
[HttpPost]
public ActionResult Create(Activity activity)
{
if (ModelState.IsValid)
{
db.Activities.Add(activity);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(db.Stages, "Id", "Number");
ViewBag.StatusId = new SelectList(db.Status, "Id", "Name");
return View(activity);
}
View is like this:
<!-- this refers to the EF.x DB Context class shown at the top of this post -->
#model RDMS.Activity
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Activity</legend>
<div class="editor-label">
#Html.LabelFor(model => model.StageId, "Stage")
</div>
<div class="editor-field">
#Html.DropDownList("StageId", String.Empty)
#Html.ValidationMessageFor(model => model.StageId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Progress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Progress)
#Html.ValidationMessageFor(model => model.Progress)
</div>
<!-- ETC...-->
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The reason why you get required validation is because the properties are value types (ie they can't be null). Since they can't be null, the framework requires you fill in values for them (otherwise it would have to throw some weird exception).
This problem manifests itself in several ways. I've seen this over and over and over here on Slashdot. I am not sure why so many people fall into this problem, but it's pretty common. Usually this results in a strange exception referring to no default constructor being thrown, but for some reason that did not happen here.
The problem stems from your use of ViewBag and naming the items in ViewBag the same as your model properties. When the page is submitted, the model binder gets confused by similarly named items.
Change these to add List at the end:
ViewBag.DepartmentList = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerList = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractList = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageList = new SelectList(new List<string>());
ViewBag.StatusList = new SelectList(db.Status
.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
And change your view to use the strongly typed versions of DropDownListFor:
#Html.DropDownList(x => x.StageId, ViewBag.StageList, string.Empty)
... and so on
One other item of note. In the example above, I hope you're not using some kind of global data context or worse, a singleton. That would be disastrous and could cause data corruption.
If db is just a member of your controller that you new up in the constructor, that's ok, though not ideal. A better approach is to either create a new context in each action method, wrapped by a using statement (then the connection gets closed and destroyed right away) or implement IDisposable on the controller and call Dispose explicitly.
An even better approach is not doing any of this in your controller, but rather in a business layer, but that can wait until you're further along.
Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
There is a default validation provider in MVC (not EF), checking two things :
the type of the provided value (a string in an int property) => (not sure, but something like) yyy is not valid for field xxx
a "check null" attribute for Value types (it will complain if you let an empty field corresponding to an int property, and would accept an empty field for an int? property). => The xxx field is required
This second behaviour can be desactivated in global.asax (the property name is rather clear) :
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
With client side validation enabled, these validations, and the one related to DataAnnotations (Required, StringLength...) will raise Validation errors on client side, before going to the Controller. It avoids a roundtrip on the server, so it's not useless. But of course, you can't rely on client validation only.
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only
checked by IsValid() even though they are empty and therefore clearly
invalid?
Hmmm, I must admit I ain't got a satisfying answer... So I let this one for a more competent one. They are taken as error in the ModelState.IsValid, because when the ClientSide Validation passed, you then go to ModelBinding (Model binding looks at your POSTed values, looks at the arguments of the corresponding HttpPost method (ActionResult Create for you), and try to bind the POSTed values with these arguments. In your case, the binding sees a Activity activity argument. And he doesn't get anything for StageId (for example) in your POSTed fields. As StageId is not nullable, he puts that as an error in the ModelState dictionary => ModelState is not valid anymore.
But I don't know why it's not catched by Client side validation, even with a Required attribute.
How do you make everything get validated in one step (for empty
fields), so the user does not have to submit the form twice and all
validation messages are shown at once? Do you have to turn off client
side validation?
Well, you'd have to turn off client validation, as you can't trust client validation only. But client validation, as said, is fine to avoid a useless roundtrip to the server.
Lastly, I've seen people using
[MetadataType(typeof(...)]] for validation. Why would you
do that if you have a viewmodel, or is it only if you don't?
It's only if you ain't got a ViewModel, but work on your Model class. It's only usefull when you work with Model First or Database First, as your entity classes are generated (with T4) each time your edmx changes. Then, if you put custom data annotations on your class, you would have to put it back manually after each class (file) generation, which would be stupid. So the [MetadataType(typeof()]] is a way to add annotations on a class, even if the "base class files" are re-generated.
Hope this helps a bit.
By the way, if you're interested in validation, take a look on FluentValidation.
This is a very nice... fluent validation (would you have guessed ?) library.
If the field is not nullable, EF think that it is required.
Cause foreign keys is not nullable, so navigation properties is required too.
To get all validation at once you need to use ViewModel that is transformed in entity mode in controller after validation.
More about attribute validation in mvc, you can read here.
My application writes custom attributes to input controls using Helper classes. And also we are loading the UserControl dynamically so we need to use FormCollection to get the posted values.
Is there a way that we can access attribute values from FormCollection object.
Example:
<input type="text" name="textBox1" value="harsha" customAttr1 = "MyValue" />
My question is how can i access the value of customAttr1 from the above eg from inside the controller.
Thanks for the help in advance..
How is your helper structured? If it's extending HtmlHelper, you can access ViewContext.HttpContext.Request.Form, which is a NameValueCollection; FormCollection is used by the model binder to post values back to an action method. Its not publicly exposed anywhere else.
HTH.
Simple answer is no am afraid, the formCollection only contains the basic Key and Value information.
It might be easier to rehydrate that information once you are in the controller? using some sort of mechanic to identify what you passed in.
An alternative is if you have a list of controls that map to a base type then you could loop through each control.
MVC is a bit magic and can map properties back to a model, even to a list.
If you have a model which has a list of Controls:
public class Control
{
String Value {get; set;}
String Attribute1 {get; set;}
}
public class ControlViewModel
{
IList<Control> Controls {get; set;}
}
then in your view:
for(var i = 0; i<controls.Count;i++)
{
// Obviously this isnt complete right i needs to increment from 0; would be build using your htmlhelpers.
<input id="Controls[i]_Value" name="Controls[i].Value" type="text" value="hello" />
<input id="Controls[i]_Attribute1" name="Controls[i].Attribute1" type="hidden" value="Attribute" />
}
in your httppost action you can then collect the ControlViewModel and the Controls list should populate.
I havent tested this, there are probably plenty of errors, but this should be enough to get started; posts out there that discuss this, if I find any after posting I'll add them.
As Luke already told.. Form Collection is dictionary object and only holds name,value pair.. in order to get that thing into controller you need to pass that custom attribute through ajax.
var form = $("#formid").serialize(),
custom = $("input:text").attr("customAttr1").val();
$.ajax({
type: "POST",
url: "/controller/ProcessData",
data:{collection :form,customAttr: custom },
dataType: "html",
traditional: true
});
in the controller you need to have following syntax:
public ActionResult ProcessData(FormCollection collection ,string customAttr)
{
in case you need to pass multiple custom values you need to post string array from ajax request and make controller signature like:
public ActionResult ProcessData(FormCollection collection ,string[] customArray)
{
Given the following model which has a name, url, and an arbitrary list of keywords (I want the user to add a series of keywords) ...
public class Picture
{
public Picture()
{
keywords = new List<string>();
}
public string name {get;set:}
public string url {get;set;}
public List<string> keywords{get;set;}
}
... and the following action in my controller ...
[HttpPost]
public ActionResult Edit(FormCollection fc)
{
if (ModelState.IsValid)
{
// do stuff
}
return View(ModelManager.Picture);
}
In the FormCollection I have the following field
fc["keywords"] = "keyword1,keyword2,keyword3"
And I then create a Picture object based on the form collection.
However, I would prefer to use a strongly-typed action such as
[HttpPost]
public ActionResult Edit(Picture p)
But in this approach, my p.keywords property is always empty. Is there some way to help the framework recreate my p.keywords property before it hits my controller's action method?
I thought an Editor Template might work here, but I don't think there is a way to model bind a nested IEnumerable view model member. Your fastest bet may be handling it directly with FormCollection and some string parsing magic. Otherwise, if you have to strongly-type this, maybe a custom model binder like this could help if you can control your keyword element id's:
public class PictureKeywordBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
Picture picture = new Picture();
//set name, url, other paramaters here
foreach(var item in Request.Form.Keys)
{
if (item.StartsWith("keyword"))
{
picture.keywords.Add(Request.Form[item]);
}
}
//add any errors to model here
return picture;
}
}
Maybe the keyword id's could be setup in a partial view passed the sub model from your parent view:
<% Html.RenderPartial("PictureKeywords", Model.keywords);
Are your keywords seperate text boxes? If so, create an inputs like this and they will be populated by the model binder.
<input name="keywords[0]" type="text">
<input name="keywords[1]" type="text">
<input name="keywords[2]" type="text">
The way I got around this, is to use a hidden input to store the csv string of items, in your case, keywords.
I then hooked into the form submit event (using jQuery) and appended the inputs to form the csv string, which is then stored in the hidden input. This hidden input was strongly typed to a property on my model.
It's a little clunky, but if you have a dynamic number of possible keywords then this works quite well (except if JS is disabled of course)
In what way you are expecting the user to add more keywords? In the form comma separated values(CSV) or by dynamically adding textboxes?
Based on your requirement, i have two solutions with me.
If you're using the Html.TextBoxFor() type methods, you may well end up with Form controls that have dots in their names, like this:
<input type="text" name="Contact.FirstName" id="Contact_FirstName" />
If you want MVC to map those named fields to parameters in your controller (as opposed to an object parameter or whatever), you have to get the parameter names right. What to do about the dots?
Neither this:
[HttpPost]
public ActionResult FooAction(string firstName)
not this:
[HttpPost]
public ActionResult FooAction(string contact_FirstName)
seem to work.
Edit: Having a suitable object parameter would work (eg see clicktricity's answer), but I'm looking for a way to do it with named value parameters.
I have found another way, a kind of hack because I believe this is misuse of BindAttribute, to associate firstName parameter with Contact.FirstName input element:
[HttpPost]
public ActionResult FooAction([Bind(Prefix="Contact.FirstName")]string firstName)
This for sure works with ASP.NET MVC 1.
Depending on the other form controls, you should be able to have the MVC default model binder construct a Contact object for you. Then the signature of your action method would be:
[HttpPost]
public ActionResult FooAction(Contact contact)
Then the Contact.FirstName (and any other fileds) will be bound correctly
As Clicktricity suggests in comments you may use
[HttpPost]
public ActionResult FooAction(FormCollection form)
{
firstName = form["Contact.FirstName"];
}