validating the incoming request that depends on other entity's property value? - c#

I know there's data annotation to validate data such as [Required], [Range()] and so on. Not to mention the unobtrusive validation that makes it easier in the client side. Many thanks to them.
But what about if you need to validate the value that depends on the entity's property value? I have this scenarion, say:
My models:
public class Schedule
{
public int Id { get; set; }
public DatetimeOffset StartDate { get; set; }
public DatetimeOffset EndDate { get; set; }
}
Now in a form,
<input type="text" name="StartDate" />
<input type="text" name="EndDate" />
How would you validate that the EndDate should not be less than the StartDate? Is there a built-in attribute in data annotation for that? Or should make a custom one? It would be great if it would make use of the unobstrusive validation by microsoft.
Here's another scenario:
What if you would do validation that depends on the value that is saved in the db? Say,
public class Bag
{
//constructor
public int Capacity { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
public class Item
{
//constructor
public string Description { get; set; }
public virtual ICollection<Bag> Bags { get; set; }
}
That if you would do validation on the Items being added to the Bag but if the user tries to input beyond the limit of the Bag's Capacity, should show the validation error.
Is this possible?
I'm currently using ASP.NET MVC 4. EF5 Code first approach.

The first approach is to implement IValidatableObject:
public class Schedule : IValidatableObject
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (StartDate > EndDate)
{
yield return new ValidationResult("End date must be greater than start date.", new [] { "EndDate"});
}
}
}
It will be executed automatically during model binding on server side.
If you want to validate on client side, you also have options. One of them is remote validation. You can read about it here. To summarize: You have to create constroller and action, that takes validated values and returns if it is valid or not. It can take more than one value. For example ID and username if you want to check uniqueness.
Obviously sending values to server is not necessary in date comparison. You can implement your own validation attribute to handle comparison on client side. Someone tried to implement it here.

Related

How should I deal with a "generic" property in C# ASP.NET MVC?

I am building a dynamic form builder using ASP.NET Core MVC and EF Core.
This is just a hobby so I can change it completely without repercussions.
Users are able to enter a form name and description, set the fields, and create the form.
That form can then be accessed via its ID (just an integer for now), filled in, and submitted.
One of its features is that the form's fields can be assigned an element.
This element can be a text box, radio button group, drop-down list. Basically, the element is the type of control that will accept the input.
My models responsible for the form "schema" itself are set up as follows:
I have a class Form which has a list of type Field.
Field has a property of type Element. Several classes, such as TextBox and RadioButtonGroup, inherit from Element.
These derived classes will have different properties, e.g., a textbox will have a placeholder; a radio button group will not.
I have taken this approach because when I create my form fields, I can just use polymorphism to create an Element of type TextBox and have the mapping be done automatically in the database, like so:
Element textBox = new TextBox() { Name = "Default text box", Placeholder = "Enter a value..." };
I also have just one property in Field which caters for everything.
Don't know if there is a better alternative here.
Here are my models:
public class Form
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? CreatedDate { get; set; }
public List<Field> Fields { get; set; }
}
public class Field
{
public int Id { get; set; }
public string DisplayName { get; set; }
public string? ActualName { get; set; }
public string? DataType { get; set; }
public Element? Element { get; set; }
public bool IsHidden { get; set; } = false;
public Form? Form { get; set; }
}
public class Element
{
public int Id { get; set; }
public string? Name { get; set; }
[ForeignKey("Field")]
public int FieldId { get; set; }
public Field? Field { get; set; }
}
[Table("TextBoxes")]
public class TextBox : Element
{
public string Placeholder { get; set; }
}
Now, this is where I have the problem. Excuse me if my entire design and reasoning is flawed; I am very inexperienced.
When I retrieve a Form to a view (when accessed by ID, for example), EF Core makes it very easy for me to retrieve everything, including the elements belonging to the different fields. The question is: What is the clean way to access the derived class properties?
Currently, in the view, I am using a switch case to check the element type, then downcast the base type to the derived type, and access the properties that way.
I have even managed to do it using reflection before.
Through research, I have found that both of these should be avoided. I have also found something about a visitor pattern; I must admit I have problems understanding how it works and I am not even sure if it applies here.
Currently, this is how I am doing it (ignore the fact that I only have the textbox in the switch in the view):
This is the FormController code for when accessing a created form to submit it.
[HttpGet]
public IActionResult Index(int? id)
{
var submission = new SubmissionViewModel();
submission.Form = _context.Forms.Where(e => e.Id == id).Include(f => f.Fields).ThenInclude(g => g.Element).Include(f => f.EnhFields).FirstOrDefault();
return View(submission);
}
And this is the view code for displaying the form's fields so that you can fill them and eventually submit.
#model WebApplication3.ViewModels.SubmissionViewModel
#using WebApplication3.Models
#{
Layout = "_Layout";
}
<h4>#Model.Form.Name</h4>
<p>#Model.Form.Description</p>
<form asp-controller="Form" asp-action="Submit" class="main-form" method="post">
<input type="hidden" value="#Model.Form.Id" asp-for="#Model.FormId" />
#for (int i = 0; i < Model.Form.Fields.Count; i++)
{
switch (Model.Form.Fields[i].Element.GetType().Name)
{
case "TextBox":
<div>
<label asp-for="#Model.SubmissionFields[Model.Form.Fields[i].ActualName]">#Model.Form.Fields[i].DisplayName</label>
#*<input asp-for="#Model.SubmissionFields[Model.Form.Fields[i].ActualName]" type="text" placeholder="#Model.Form.Fields[i].Element.GetDetails()"/>*#
<input asp-for="#Model.SubmissionFields[Model.Form.Fields[i].ActualName]" type="text" placeholder="#(((TextBox)Model.Form.Fields[i].Element).Placeholder)"/>
</div>
break;
}
}
<button type="submit">Submit</button>
</form>
So, down-casting/reflection make my life easier, but are not good practice.
What is the proper (if any) alternative way to do this?
Sorry if the question does not make sense or is not easily understandable.
I don't use Stack Overflow much but I will try to make it better if necessary.

asp.net MVC 4 simple sorting

I am using ASP.net MVC4 and am trying to accomplish a simple sort , so far I have found how to make the whole database sortable - by using ActionLink buttons in my View (failed to make it work btw...), but what I am trying to accomplish is a permanently sorted database.
My view and controller are both scaffolded at the moment, no changes made to this part. I am trying to make the record with the least TimeRemaining to always show up on top of the list.
Thanks!
My Database:
public class EquipmentDataBase {
public int ID { get; set; }
[DisplayName("Name")]
public string equipment { get; set; }
[DisplayName("Inv.Nr.")]
public string InventoryNumber { get; set; }
[DisplayName("Location")]
public string Location { get; set; }
[Required(ErrorMessage ="Please specify date")]
[DataType(DataType.Date)]
[DisplayName("Next Inspection")]
public DateTime NextInspectionDate { get; set; }
[Required(ErrorMessage ="PleaseSpecifyRegistrationDate")]
[DataType(DataType.Date)]
public DateTime RegistrationDate { get; set; }
public string Responsible { get; set; }
public TimeSpan TimeRemaining
{
get
{
return (NextInspectionDate - DateTime.Today);
}
}
My Controller:
namespace PeriodicInspections.Controllers {
public class EquipmentDBController : Controller
{
private EquipmentDbContext db = new EquipmentDbContext();
// GET: EquipmentDB
public ActionResult Index()
{
return View(db.equipment.ToList());
}
If you want to have it sorted by the same criteria, the best approach is to sort them at database level. You can achieve this by changing the code in the controller as follows:
// GET: EquipmentDB
public ActionResult Index()
{
return View(db.equipment.OrderBy(x => x.NextInspectionDate).ToList());
}
The OrderBy sorts the data by NextInspectionDate. This property is present at database level in contrast to the helper property TimeRemaining that is only available in the .NET code. As regards the sort order, this will make no difference.
Use Linq. instead of
return View(db.equipment.ToList());
use
return View(db.equipment.ToList().OrderBy(e=>e.TimeRemaining );

MVC data annotation on client side conditional

i have a form wherein there is conditional textboxes.i have used mvc dataannotation client side on dropdown change i hide two textboxes data validation error is not fired but in controller i get model error in if (ModelState.IsValid).How can i do condtional handling of data annotation in client side only.I dont want to use fullproof validation or other third party.
i tried removing the data-val-* attributes using jquery still getting error in controller.refer image if i select asset type laptop then sim plan and price is hidden dataannotation dont fire which is correct but get error on controller.
Model:
[Required(ErrorMessage = "Please Enter Make")]
public string Make { get; set; }
[Required(ErrorMessage = "Please Enter Model")]
public string Model { get; set; }
[Required(ErrorMessage = "Please Enter Sim Plan")]
public string SimPlan { get; set; }
[Required(ErrorMessage = "Please Enter Price")]
public decimal? Price { get; set; }
there's a much better way to add conditional validation rules in MVC3. Have your model inherit IValidatableObject and implement the Validate method:
public class Person : IValidatableObject
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsSenior && string.IsNullOrEmpty(Senior.Description))
yield return new ValidationResult("Description must be supplied.");
}
}
see more of a description at http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx
If you trying to manually clear validation error which u have not used in your view than i would suggest you clear them before checking modelstate.
Ex:
[HttpPost]
public ActionResult Register(Model objModel )
{
foreach (string Key in ModelState.Keys)
{
if ((Key.Equals("Email")) || (Key.Equals("Password")))
{
ModelState[Key].Errors.Clear();
}
}
if (ModelState.IsValid)
{
//Do the work
}
}
In the above example i have passed the values in Model and i didn't pass any value for Email and password, so in controller i am clearing those Key which is present in ModelState.

MVC ViewModel, using a VM with some properies marked required, depending if its a GET or POST

I've often found myself fixing validations for the modelstate manually, due to the inconsistency of some fields that are required in a view model during post and get.
Supposing I've got this View Model:
public class RestaurantMenuName_ViewModel
{
public Int64 RestaurantMenuNameId { get; set; }
public Int64 OwnerId{ get; set; }
public string MenuNameCategory { get; set; }
public string CategoryDescription { get; set; }
public bool IsFormSaved { get; set; }
}
During a GET request the controller/Action requires the validation of just the fields, RestaurantMenuNameId and OwnerId. When calling the Action RestaurantMenuName, the query string values are RestaurantMenuNameId and OwnerId. Modelstate validation will be done on:
RestaurantMenuNameId
OwnerId
During a POST request the controller/Action will require the modelstate validation of the fields:
RestaurantMenuNameId
OwnerId
MenuNameCategory
CategoryDescription
This is the inconsistency issue I'm talking about, a solution could be using a ViewModel for Get requests and one for Post, but this could be real a time waster and error prone. Using ViewBag is out of discussion.
Question:
Is there a way to tell MVC that we want some fields [required] for GET and other for POST?
The following is a Pseudo-code of what I'm talking about:
public class RestaurantMenuName_ViewModel
{
[Required: in GET, POST] //<--Pseudo code
public Int64 RestaurantMenuNameId { get; set; }
[Required: in GET, POST] //<--Pseudo code
public Int64 OwnerId { get; set; }
[Required: in POST] //<--Pseudo code
public string MenuNameCategory { get; set; }
[Required: in POST] //<--Pseudo code
public string CategoryDescription { get; set; }
public bool IsFormSaved { get; set; }
}
It's not a very good practice (and confusing in your case) to pass complex objects when you need only few properties. It will be better to pass only the required ids as primitives.
If the case is special and you really need the complex objects, it will be better to create two different view models for every request and decorate the required properties accordingly.
However, you can create your own require validation attribute which will validate properties dependening on the current request.
public class MyRequiredAttribute : ValidationAttribute
{
private string httpVerb;
public MyRequiredAttribute(string httpVerb)
{
this.httpVerb = httpVerb;
}
public override bool IsValid(object value)
{
if(HttpContext.Current.Request.HttpMethod == this.httpVerb)
{
return value != null;
}
return true;
}
}
// Usage
public class MyViewModel
{
[MyRequired("GET")]
public string A { get; set; }
[MyRequired("POST")]
public string B { get; set; }
}
Note: you can use an enumeration to avoid some difficulties (ex. upper case, lower case, misspelling etc.) and also you can override the FormatErrorMessage method to change the default error message and format it properly.

User Defined Class list error "Failed to compare two elements in the array"

I have some code that takes a list made up of custom class objects (called Payments) and sorts them by date, as below:
payments.Sort(delegate(Payments p1, Payments p2) { return p1.GetDate().CompareTo(p2.GetDate()); });
the GetDate() method and the payment class is below:
public class Payments
{
public string Date { get; set; }
public string Payment { get; set; }
public string Reference { get; set; }
public decimal Amount { get; set; }
public DateTime GetDate()
{
return DateTime.Parse(this.Date);
}
}
Once the list is sorted I manually go through each one and compare the date on it to the date on the next one. If they are different then nothing happens, if they are the same then I merge the data in the Payments together into a single new Payment. I then remove the two payments that were being compared and then add in the new one, the list is then resorted and continued until the list is unique by date.
Up until very recently this has been working fine, with no issues. However from today there have been multiple cases of it erroring with the message "Failed to compare two elements in the array".
I have looked around for this but I dont feel that i know enough about what could be causing it to comfortably make changes to my code. Can someone help me understand what would be causing this issue and the best way to fix it?
Thanks
When using Sort, the class of the parameters must implement IComparable interface.
public class Payments : IComparable<Payments>
{
public string Date { get; set; }
public string Payment { get; set; }
public string Reference { get; set; }
public decimal Amount { get; set; }
public int CompareTo(Payments otherPayment)
{
return DateTime.Parse(this.Date).ComapreTo(DateTime.Parse(otherPayment.Date));
}
}

Categories

Resources