I'm working in MVC 5. I'm writing a form for users to submit claims to an internal application, and I need to validate the user's claim amount against their current balance when they create a claim. It rejects if the amount is greater than the balance, say $10 vs. $5, and asks them to zero out their balance instead.
I felt it would be best to have the balance be a (non-editable) property, CurrentBalance, on the viewmodel with a custom validation attribute comparing the two values. I render it as a readonly field on the form. When the user picks which program they are submitting to, like their HRA, an ajax request fills in their current balance per which program they selected.
The trouble is that when the viewmodel instance is passed as part of the ValidationContext to my attribute, CurrentBalance always appears as zero. Here's a snip while debugging: https://i.imgur.com/syDEuJ2.png
So no matter what the balance is, validation fails without exception.
I've tried making CurrentBalance an ordinary, non-readonly field on the view and setting the value myself before submitting. I've also tried setting the value in my controller before rendering the view. In either case, it's still zero, as in the image above.
My custom attribute is as follows:
public class ClaimAmountAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var claim = (ClaimCreateViewModel)validationContext.ObjectInstance;
var amount = (decimal)value;
if (claim.CurrentBalance - amount < 0)
{
return new ValidationResult(GetErrorMessage());
}
return ValidationResult.Success;
}
public string GetErrorMessage() => "Amount must be less than balance; please zero out balance instead.";
}
Here are the relevant parts of my view—though again, it didn't seem to make a difference whether the readonly attribute was there or not:
#model ClaimCreateViewModel
<div class="form-group">
#Html.LabelFor(model => model.CurrentBalance, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CurrentBalance, new { htmlAttributes = new { #class = "form-control", #readonly = "readonly" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Amount, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Amount, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Amount, "", new { #class = "text-danger" })
</div>
</div>
Finally, here's a snippet from the viewmodel:
public class ClaimCreateViewModel
{
[Key]
public int TransactionID { get; set; }
// ...
[Required]
[ClaimAmount] //my custom attribute
public decimal Amount { get; set; }
[Display(Name = "Balance")]
public decimal CurrentBalance { get; set; }
// ...
}
Seemingly nothing I do produces the value of CurrentBalance in the ValidationContext. Please help me out.
Well, this is embarrassing. It turns out that the [Bind] attribute on my POST action was missing CurrentBalance. Validation occurs before the action, but that doesn't matter; [Bind] is acting before then, to prevent overposting. I've updated that action and it works now:
public ActionResult Create([Bind(Include = "TransactionID,Amount,CurrentBalance")] ClaimCreateViewModel claim)
{
// ...
}
Related
I have a view that was scaffolded from a model. I need to retrieve the values from the "editfor" helper tags, do a bunch of calculations, then pass back the results (multiple)back to the view. I created a small example to clarify.
public class OpticalcTestViewModel
{
public double OD_Sphere { get; set; }
public double OD_Cylinder { get; set; }
public int Axis { get; set; }
}
Which creates this scaffold:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>OpticalcTestViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.OD_Sphere, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OD_Sphere, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OD_Sphere, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OD_Cylinder, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OD_Cylinder, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OD_Cylinder, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Axis, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Axis, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Axis, "", new { #class = "text-danger" })
</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")
}
This is my controller:
public class OpticalcTestController : Controller
{
// GET: OpticalcTest
public ActionResult Index()
{
return View();
}
}
How do I get the values out of the "editfor" boxes, perform calculations on them, then pass the results of those calculations(multiple results, not just one)back to some labels in the view?
This is such a simple thing in winforms, which is what I usually work with, but I'm at my wits end trying to find an answer to this. It seems like the other 9,000 results I get from searches are always about writing the data (as a whole model) to a database. This will have no database. It's just a form that takes numeric values, does calculations and spits out results.
To be more specific, how would I pull those values into the controller, Add sphere to cylinder, then add cylinder to axis and pass back both results separately to labels (or some other way to view them)?
Thanks,
R
First thing you need to do is add an action to your controller that accepts a parameter of type OpticalcTestViewModel and is tagged with the [HttpPost] attribute:
[HttpPost]
public ActionResult Index(OpticalcTestViewModel model)
{
//perform calculations
return View(model);
}
As you notice above, after you have performed calculations you will need to modify the model variable to add in your new calculations, then you just send it back to the view (return View(model)).
Your form in the view is performing a POST by default. Since you have no actions that are capable of handling a POST request, you will never be able to service those calls. The above code should fix all that.
In either case, I would highly recommend taking some more tutorials on ASP.NET MVC. Microsoft has a couple of decent tutorials, but there are also a lot of free resources online.
You'll need an HttpPost method to accept the model from the view and access the properties bound to the EditorFor helper elements as maccettura wrote.
Then you can perform calculations like so inside the method:
double sphereRadius = model.OD_Sphere / 2; // demo calc
Are the calculation results going to be on the same view? Same model?
Assuming yes, I'd recommend new model properties for the calculated values, then bind those to your view. You can use Razor to show/hide the inputs and calculated values as required.
Example assuming you use the calculation above w/ a new model property:
public class OpticalcTestViewModel
{
public double OD_Sphere { get; set; }
public double OD_Cylinder { get; set; }
public int Axis { get; set; }
public double sphereRadius { get; set; } // new calculated property
}
[HttpPost]
public ActionResult Index(OpticalcTestViewModel model)
{
double sphereRadius = model.OD_Sphere / 2; // demo calc
model.sphereRadius = sphereRadius;
return View(model);
}
Note that if you attempt to edit a model property which has already been bound to the view, it will retain its old value. That's because the value is actually retained in the ModelState, and the default model binder will check there first and use values if they exist. To override this you'll have to clear the ModelState property, but that gets a little messy.
I've got a model field like so:
[Required(ErrorMessage = "Required!")]
[Display(Name = "Some ID")]
[DataType(DataType.Text)]
[RegularExpression(#"SomeRegexHere"]
public string someId { get; set; }
On my view, I have a form for updating the model. Something like this:
<div class="form-group">
#Html.LabelFor(model => model.someId, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.someId, new { htmlAttributes = new { type = "number", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.someId, "", new { #class = "text-danger" })
</div>
Say on this same view I can fill in this field in order to submit it to the database, but I may also fill in some other field to retrieve an entry in the database by this value. However, I'd like to apply the same validations to both fields. Without creating a dummy model attribute, can I display validation errors for both fields?
As I understand form you question, you need
conditional validation. It is better to use third party validation provider for this porpose. I recommend MVC Foolproof Validation. Have a look on this and this as samples for conditional validation in foolproof.
I'm using MVC 5 with Entity Framework Code First.
I have a Class (school class) object that has a List of Students attached to it and I'm attempting to update the class and/or students within the class and what I get when I call SaveChanges() on the context are duplicate records for the students in the class. Basically the old student list gets "orphaned" in the database and a whole new set of students is attached to the edited class in the database.
So instead of 10 corrected student records in the database, I now have 20 student records. The 10 original (uncorrected) and 10 new (corrected) ones. The 10 original ones have the classid foreign key removed so they are no longer part of that class.
Any updates to the Class object are implemented fine without duplicating the Class record.
I've seen one answer that suggests that maybe the context doesn't know that the students are not new items so it adds them... and to grab the students from the database so the context knows about them - but if the students come along when you pull the class object from the database, isn't that the same thing as pulling the directly?
public class Class
{
[Key]
public Guid ClassId { get; set; }
[Display(Name="Class Name")]
public string ClassName { get; set; }
public virtual List<Student> Students { get; set; }
public virtual Teacher Teacher { get; set; }
public Class()
{
ClassId = Guid.NewGuid();
Students = new List<Student>();
}
}
[HttpPost]
public ActionResult Edit(Guid id, FormCollection collection)
{
Class selectedclass = db.Classes.Find(id);
try
{
UpdateModel(selectedclass, collection.ToValueProvider());
db.SaveChanges();
return RedirectToAction("Details", new { id = id });
}
catch
{
return View(selectedclass);
}
}
What in the world am I doing wrong?
The only thing that I can think of to do is to remove all the student records that are attached to that class in the database before I save the changes, but there has to be a better way than that.
db.Students.RemoveRange(db.Classes.Find(id).Students);
So I tried attaching the students in the list to the context and even changing their state to modified, like:
selectedclass.Students.ForEach(s => db.Students.Attach(s));
selectedclass.Students.ForEach(s => db.Entry(s).State = EntityState.Modified);
But no luck, still getting duplicates and orphans.
Tried getting the students directly from the database, but no luck:
var ids = selectedclass.Students.Select(s => s.StudentId).ToList();
selectedclass.Students = db.Students.Where(s => ids.Contains(s.StudentId)).ToList()
HTML for the editor for the student:
#for (int i = 0; i < Model.Students.Count; i++)
{
<div class="col-md-8">
#Html.EditorFor(m => m.Students[i], new { htmlAttributes = new { #class = "form-control" }})
</div>
}
The editor template for student:
<div class="form-group">
#Html.LabelFor(m => m.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
#Html.EditorFor(m => m.FirstName, new { htmlAttributes = new { #class = "form-control" }})
</div>
#Html.LabelFor(m => m.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
#Html.EditorFor(m => m.LastName, new { htmlAttributes = new { #class = "form-control" }})
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
#Html.EditorFor(m => m.UserName, new { htmlAttributes = new { #class = "form-control" } })
</div>
#Html.LabelFor(m => m.Password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-xs-6 col-md-4">
#Html.EditorFor(m => m.Password, new { htmlAttributes = new { #class = "form-control" }})
</div>
</div>
I think what is happening is that you are loading the entity with Class selectedclass = db.Classes.Find(id);, but that does not automatically load the collection of students.
I suspect that what is happening is that since the Students collection is not loaded, most likely UpdateModel simply calls the Students setter, which replaces the collection.
Since the Student objects were not loaded by the context, Entity Framework thinks they are new students, and inserts them accordingly. See Data Points - Why Does Entity Framework Reinsert Existing Objects into My Database?
There are a couple solutions to this. You can try explicitly loading the Students collections before calling UpdateModel. Or you can explicitly tell entity framework that the student records are existing, by explicitly attaching them to the context.
However, in general, I try to avoid dealing with disconnected entities - there are lots of "gotchas" in Entity Framework disconnected entity handling.
Also, be careful when binding directly to your Entity Framework mapped classes, as this can easily lead to overposting/underposting security vulnerabilities. See ASP.NET MVC – Think Before You Bind
Update
I'm beginning to suspect that the issue lies with how the html is specifying the form fields. See Model Binding To A List
Your other option of course, is to not use UpdateModel in this fashion. Instead, just load the entity and "manually" apply the edits to the loaded entity. From a security perspective, this is less likely to cause issues. In which case, I'd probably recommend moving to strongly typed views so that you don't have to hunt through the form collection with dictionary keys.
I am trying to add a new record to a database that I've created. The database is called QUESTIONNAIRES and it has the columns: QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, and Image.
I want the user to specify the QuestionnaireName and provide an Image and I want to generate myself the QuestionnaireUID, UserUID, DateCreated, and Link. So far, this is my View() that represents this creation process:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Hide QuestionnaireUID, UserUID, and Link from user. These fields will be generated instead of assigned by user input.
#Html.HiddenFor(model => model.QuestionnaireUID)
#Html.HiddenFor(model => model.UserUID)
#Html.HiddenFor(model => model.Link)
<div class="form-group"> <!-- Questionnaire name. -->
<h2>Name</h2>
<p> Please provide a name for your decision tree.<p>
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionnaireName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionnaireName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group"> <!-- Questionnaire image. -->
<h2>Image</h2>
<p> Please provide a background image for your decision tree.</p>
<!-- ADD FILE IMAGES & ENCODE IN BINARY. -->
<div class="col-md-10">
#Html.EditorFor(model => model.Image, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Image, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group btn_next"> <!-- Save and continue button. -->
<input type="submit" value="Save and Continue" class="btn">
</div>
}
The Questionnaire Controller methods that are being used are displayed below as well:
// GET: Questionnaires/Create
public ActionResult Create()
{
ViewBag.UserUID = new SelectList(db.Users, "UserUID", "FirstName");
return View();
}
// POST: Questionnaires/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, Image")] QUESTIONNAIRE questionnaire)
{
if (ModelState.IsValid)
{
db.QUESTIONNAIRES.Add(questionnaire);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
As you can see, I've hidden away the three attributes that I want to generate in my View(). I now don't know where I generate and assign these values. Do I do this in my View() or in the Controller method? And what does this assignment look like?
I'd generate those values in the Controller on HttpGet, and I'd use a ViewModel.
Echoing mituw16's comment, using a ViewModel is a good way of keeping everything logically consistent and separated. There's some pretty good in-depth discussions and explanations of ViewModels elsewhere that are worth reading.
Your ViewModel could, for instance, look something (roughly) like this:
public class QuestionnaireViewModel
{
public Guid QuestionnaireUID { get; set; }
public Guid UserUID { get; set; }
public string QuestionnaireName { get; set; }
public DateTime DateCreated { get; set; }
public string Link { get; set; }
public Image Image { get; set; }
}
And it could be passed to the View like this:
[HttpGet]
public ActionResult Create()
{
var vm = new QuestionnaireViewModel();
vm.QuestionnaireUID = Guid.NewGuid();
vm.UserUID = Guid.NewGuid();
return View(vm);
}
When the form is posted, MVC can automatically interpret the incoming data as a QuestionnaireViewModel:
[HttpPost]
public ActionResult Create(QuestionnaireViewModel vm)
{
if (ModelState.IsValid)
{
// map the viewmodel properties onto the domain model object here
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
A couple more points:
In this example, you'll see that it may not even be necessary to include the UID stuff in the ViewModel, as the ViewModel only cares about data gathered from/presented to the user. Further, unless the #Html.HiddenFors have some sort of functional purpose on the view, you might be able to leave them out and generate them on HttpPost.
If you're looking to "create new record with a combination of values being assigned from user input and being generated in ASP.NET MVC 4" (a.k.a. creating a form in MVC), then the more complex your model/viewmodel gets, the more I'd stay away from using ViewBag for these purposes.
Assume I have a page (View) that takes a certain ViewModel:
#model IEnumerable<MyProject.ViewModels.MyViewModel>
In this page, I have a form that posts data through another ViewModel (let's call it a PostModel):
#using (Html.BeginForm("Order", "Order", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Give your order info</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
<label for="Order.Name" class="col-md-2 control-label">Name:</label>
<div class="col-md-10">
#Html.TextBox("Order.Name", null, new { #class = "form-control" })
#Html.ValidationMessage("Order.Name")
</div>
</div>
...
}
This is processed on the controller in an Order HttpPost action method that takes an argument of my PostModel's type.
I can display validation messages in the style I have above. My question is, how (if possible) can I make this strongly typed for my PostModel? Something like:
#Html.TextBox<MyPostModel>(t => t.Order.Name, ...)
#Html.ValidationMessageFor<MyPostModel>(t => t.Order.Name)
Is this at all possible, without changing the ViewModel of the page?
you can simply use a different partial-view for that form and in that partial-view you can specify it to be of any type you want, in this case, as i see in your code example, Order
Lets say you have a model called Order with the following definition
public class Order
{
public string Name { get; set; }
}
and also a partial-view called _MyPostPartialView.cshtml with its definition
#model Order
#using (Html.BeginForm("Order", "Order", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Give your order info</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.Label(m => m.Name, "Name:")
<div class="col-md-10">
#Html.TextBox(m => m.Name, null, new { #class = "form-control" })
#Html.ValidationMessage(m => m.Name)
</div>
</div>
...
}
and you're done!
try import javascript bellow in your view
jquery.validate.min.js
jquery.validate.unobtrusive.min.js
In your form view
#Html.LabelFor(model => model.ClientDocument[0].Number)
#Html.TextBoxFor(model => model.ClientDocument[0].Number, new { #class = "form-control" })
In your controller
[HttpPost]
public ActionResult Create(YourViewModel model){
if (ModelState.IsValid)//to complete validate server side
}
In your ViewModel *Try use DataAnnotation
[Display(Name = "Name of Field")]
[Required(ErrorMessage="your message error")]
public string Name { get; set; }
public DocumentViewModel[] ClientDocument { get; set; }
The simple answer is don't use a different view model for GET vs POST. There's no reason to. Anything you POST should be rendered through the view model used for the GET request. If you are for some reason posting something that was not initially on the view model used for the GET request, then frankly, stop it.