Set an automatic DateTime.Now value for an #Html.HiddenFor - c#

I would like to ask is there any way to set an automatic DateTime.Now value for properties ENTRY_DATE and AUDIT_TIME in the Create() HttpPost method? The form is created and it works fine. If the DateTime is inserted manually. But, it won't work if I set an automatic value and would run into a
"One or more validation Error's"..
This is my model (I don't understand how to make a viewmodel) :
public partial class TRRESPONDENT
{
public TRRESPONDENT()
{
this.TRFOLLOWUPRESPONDENTs = new HashSet<TRFOLLOWUPRESPONDENT>();
}
[Required(ErrorMessage = "Respondent ID is required!")]
[Display(Name = "Respondent ID")]
public string RESPONDENT_ID { get; set; }
[Required(ErrorMessage = "Please select a BINUS Center!")]
[Display(Name = "Binus Center")]
public string BC_ID { get; set; }
[Required(ErrorMessage = "Name cannot be empty!")]
[Display(Name = "Name")]
[StringLength(100,ErrorMessage = "Name length cannot be more than 100 characters!")]
public string FULL_NAME { get; set; }
.... // more properties
[Required(ErrorMessage = "Please pick a City Location!")]
[Display(Name = "City")]
public int CITY_ID { get; set; }
// The following 2 properties need to be set
[Display(Name = "Entry Date")]
public DateTime ENTRY_DATE { get; set; }
public DateTime AUDIT_TIME { get; set; }
....
public virtual LTCITY LTCITY { get; set; }
public virtual LTSOURCERESPONDENT LTSOURCERESPONDENT { get; set; }
public virtual MSBINUSCENTER MSBINUSCENTER { get; set; }
public virtual ICollection<TRFOLLOWUPRESPONDENT> TRFOLLOWUPRESPONDENTs { get; set; }
}
This is my view
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.LabelFor(model => model.RESPONDENT_ID)
#Html.EditorFor(model => model.RESPONDENT_ID)
#Html.ValidationMessageFor(model => model.RESPONDENT_ID)
#Html.LabelFor(model => model.BC_ID, "Binus Center")
#Html.DropDownList("BC_ID", null)
#Html.ValidationMessageFor(model => model.BC_ID)
#Html.LabelFor(model => model.FULL_NAME)
#Html.EditorFor(model => model.FULL_NAME)
#Html.ValidationMessageFor(model => model.FULL_NAME)
.... // more form controls
#Html.LabelFor(model => model.CITY_ID, "City")
#Html.DropDownList("CITY_ID", null)
#Html.ValidationMessageFor(model => model.CITY_ID)
#Html.HiddenFor(model => model.ENTRY_DATE)
#Html.HiddenFor(model => model.AUDIT_TIME)
<input type="submit" value="Create" class="btn btn-default" />
}
This is my controller :
public class RespondentController : Controller
{
private RespondentBINUSEntities db = new RespondentBINUSEntities();
public ActionResult Create()
{
ViewBag.CITY_ID = new SelectList(db.LTCITies, "CITY_ID", "CITY_NAME", 1);
var entry = new Models.TRRESPONDENT
{
ENTRY_DATE = DateTime.Now,
AUDIT_TIME = DateTime.Now,
};
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "RESPONDENT_ID,BC_ID,BINUSIAN_ID,FULL_NAME,EMAIL,PHONE_NUMBER,ADDRESS,CITY_ID,ZIP_CODE,SOURCE_ID,ENTRY_DATE,PACKAGE,AUDIT_USER_NAME,AUDIT_TIME,AUDIT_ACTIVITY")] TRRESPONDENT tRRESPONDENT)
{
if (ModelState.IsValid)
{
db.TRRESPONDENTs.Add(tRRESPONDENT);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CITY_ID = new SelectList(db.LTCITies, "CITY_ID", "CITY_NAME", tRRESPONDENT.CITY_ID);
return View(tRRESPONDENT);
}
}

You have not stated the details of the error message, but no doubt this is because you saving a values of 01/01/0001 to a field which which is DATETIME (which only accepts dates between 01/01/1753 to 12/31/9999) and not DATETIME2.
The reason the values of you dates are 01/01/0001 (which is the default for DateTime) is because you do not pass a model to the view so default values are used. The code in your GET needs to be
public ActionResult Create()
{
ViewBag.CITY_ID = new SelectList(db.LTCITies, "CITY_ID", "CITY_NAME", 1);
var entry = new Models.TRRESPONDENT
{
ENTRY_DATE = DateTime.Now,
AUDIT_TIME = DateTime.Now,
};
return View(entry); // return your model
}
However you should not use your data model in the view, and instead create a view model containing only the properties you need. Values such as ENTRY_DATE should only be set immediately before you save the data model to the database. For information on a creating a view model, refer What is ViewModel in MVC?.
The basic steps for creating a view model are
Create a new folder (say) ViewModels and copy you data model to and
and rename it (say)RespondentVM
Delete all the [Display] attributes from you data model (they are
view specific attributes)
Delete all the properties which the user will not be editing in the
view (e.g ENTRY_DATE and AUDIT_TIME) except the property which
is the objects ID which should be renamed to ID so its
automatically bound assuming your using the default routes (note its
not clear if you even have an ID property - I assume its
RESPONDENT_ID, but that should be an auto-incremented int in the
database -i.e. [Key]public int RespondentId { get; set; }). I also
recommend you rename all your properties to follow naming
conventions - EntryDate, not ENTRY_DATE.
Change all value types to be nullable and add the [Required]
attribute to protect against under-posting attacks (e.g. public int
CITY_ID { get; set; } becomes public int? CityID { get; set; }
Add additional properties for SelectList's etc that you are
currently assigning to ViewBag properties, e.g. public
IEnumerable<SelectListItem> CityList { get; set; }
You controller methods will then be
public ActionResult Create()
{
RespondentVM model = new RespondentVM();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RespondentVM model) // note a [Bind]` attribute is not required
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize an instance of your data model and set its properties based on the view model
TRRESPONDENT respondent = new TRRESPONDENT()
{
FULL_NAME = model.FullName,
CITY_ID = model.CityID,
....
// Set default values
ENTRY_DATE = DateTime.Now,
....
}
db.TRRESPONDENTs.Add(respondent);
db.SaveChanges();
return RedirectToAction("Index");
}
// Common code for populating SelectLists etc - DRY!
private void ConfigureviewModel(RespondentVM model)
{
// Note - delete the 4th parameter of the SelectList constructor
model.CityID = new SelectList(db.LTCITies, "CITY_ID", "CITY_NAME");
}
And a few extra notes on your view code.
You do not need a hidden input for the ID property if its named ID
and your using the default routing
Since you have [Display(Name = "..")] attributes, then in the view
its just #Html.LabelFor(m => m.PropertyName), not
#Html.LabelFor(m => m.PropertyName, "some text")
To generate your dropdownlists, use #Html.DropDownListFor(m =>
m.CityID, Model.CityList, "Please select", new { ... }); - you
current implementation will not give correct 2-way model binding or
client side validation.

Related

Selected item transfer issue, There is no ViewData item of type

I am building ASP.NET MVC project, All other posts about this topic did not help me. i have 2 models, Client and City.
public class Client
{
[Key]
public int Id { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public City City { get; set; }
}
public class City
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And when i want to create a client a have an exception There is no ViewData item of type 'IEnumerable' that has the key 'City'.
This is my get and post method
private readonly ApplicationDbContext _context;
private List<City> _cities;
public ClientsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Clients/Create
public IActionResult Create()
{
if (_context.City != null) _cities = _context.City.ToList();
ViewBag.Cities = new SelectList(_cities, "Id", "Name");
return View();
}
// POST: Clients/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Surname,Name,Patronymic,Telephone,City,Adress,SeriaNumberPassport,IdentificalCode")]
Client client)
{
if (ModelState.IsValid)
{
_context.Add(client);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(client);
}
And View code:
<div class="form-group" mb-3>
<label asp-for="City" class="control-label"></label>
#Html.DropDownListFor(model => model.City, ViewBag.Cities as SelectList, new { #class = "form-select" })
</div>
The data is displayed correctly, but I cannot create a client.
Think that the ModelState.IsValid is false, hence it returns to Create View rather than redirect to Index view (the flow for successful inserting Client).
While for the failure inserting case, you didn't provide the ViewBag.Cities value before returning to Create View (Check the Create method with [HttpPost]).
Talk about why the ModelState.IsValid was false, there is conflict in the type that you are passing CityId which is int type to City property with City type.
Updated:
Recommend creating and using the ViewModel class instead of Model. The main reason is to we can design the class in which the properties are only required for view, create/edit purposes.
Exposing the Model class is unsafe as the users will know how is your database entity looks like.
But the trouble with using ViewModel will be you need to map the property value from ViewModel to Model manually or with reflection. Of course, there are open-source libraries that can automate the mapping such as AutoMapper.
These are the steps you need to do for the fix:
Model
Add for CityId foreign key property.
public class Client
{
[Key]
public int Id { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public int CityId { get; set; } // Add this foreign key property
public City City { get; set; }
}
Note: If you use Entity Framework Code First approach, you need to create migration and update the database via command for this change.
ViewModel
Create ViewModel class with the properties require for Create Client.
public class CreateClientModel
{
public string Surname { get; set; }
public string Name { get; set; }
public int CityId { get; set; }
// Other properties that needed
}
View
2.1. Change the #model to CreateClientModel.
2.2. Use model.CityId instead of model.City. The CityId property is used to pass the selected city's Id.
#model CreateClientModel
<div class="form-group" mb-3>
<label asp-for="CityId" class="control-label"></label>
#Html.DropDownListFor(model => model.CityId, ViewBag.Cities as SelectList, new { #class = "form-select" })
</div>
Controller
3.1. Replace City with CityId in Bind attribute. (To add properties based on CreateClientModel).
3.2. Use CreateClientModel as request body.
3.3. Initialize the ViewBag.Cities value before returning to Create view for the ModelState.IsValid is false case.
public IActionResult Create()
{
if (_context.City != null) _cities = _context.City.ToList();
ViewBag.Cities = new SelectList(_cities, "Id", "Name");
return View(new CreateClientModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Surname,Name,CityId")]
ClientClientModel client)
{
if (ModelState.IsValid)
{
// Perform mapping from CreateClientModel to
_context.Add(new Client
{
Surname = client.Surname,
Name = client.Name,
CityId = client.CityId
// Other properties
});
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// Initialize ViewBag.Cities value
if (_context.City != null) _cities = _context.City.ToList();
ViewBag.Cities = new SelectList(_cities, "Id", "Name");
return View(client);
}
Suggested have a read and follow this tutorial which is similar to your scenario:
Tutorial: Update related data - ASP.NET MVC with EF Core

"There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Skip'

I’m trying to create a team of players, there are 4 players per team so I have 4 select lists that contain and populate the players in the league. When I go to save the team the error:
"There is no ViewData item of type 'IEnumerable' that has the key 'Skip'`
The CreateTeam controller:
public ActionResult CreateLeagueTeam(int? id)
{
int leagueId = id.Value;
League league = db.Leagues.Include(x=>x.Members).First(l=> l.Id == leagueId);
var leagueMemberList = league.Members.ToList();
var leagueMemberSelectListItems = new List<SelectListItem>();
foreach(Member m in leagueMemberList)
{
leagueMemberSelectListItems.Add(new SelectListItem() { Text = m.FirstName + " " + m.LastName, Value = m.Id.ToString() });
}
ViewData["leagueMemberList"] = leagueMemberSelectListItems;
return View();
}
the HTTP POST ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateLeagueTeam([Bind(Include = "skip")] Team team)
{
team.IsBonspiel = false;
team.IsLeague = true;
// team.Name = team.Skip.LastName;
team.YearActive = games.getSeasonYear();
//finds and sets the league ID for the team
int leagueId = (int)Session["leagueId"];
League league = db.Leagues.Find(leagueId);
team.League = league;
if (ModelState.IsValid)
{
db.Teams.Add(team);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(team);
}
Finally the view where the Error is getting thrown:
<div class="form-group">
#Html.Label("Skip", null, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Skip, (IEnumerable<SelectListItem>)ViewBag.leagueMemberList, htmlAttributes: new { #class = "control-label col-md-2" })
</div>
</div>
Also the Team Model:
public class Team: Entity
{
[Required]
public bool IsBonspiel { get; set; }
[Required]
public bool IsLeague { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int YearActive { get; set; }
public int? SkipId { get; set; }
public Member Skip { get; set; }
public string BonspielSkip { get; set; }
public List<BonspielTeamMember> BonspielMembers { get; set; }
public List<Member> Members { get; set; }
public List<Game> Games { get; set; }
public Bonspiel Bonspiel { get; set; }
public League League { get; set; }
}
The are multiple errors with your code.
Skip is a complex object and you cannot bind a <select> to a
complex object. Based on your view, it should be
#Html.DropDownListFor(m => m.SkipId, ...)
You do not have a ViewBag property named leagueMemberList
(change the controller code to ViewBag.leagueMemberList =
leagueMemberSelectListItems; or the view code to
#Html.DropDownListFor(m => m.SkipId,
(IEnumerable<SelectListItem>)ViewData["leagueMemberList"], ...)
You do not reassign the value of ViewData["leagueMemberList"] (or
ViewBag.leagueMemberList) in the POST method if ModelState is
invalid and you return the view, which will result in a different
exception because it will be null (Refer this
question/answer
Your [Bind(Include = "skip")] code in the POST method means that
ModelState will always be invalid because you exclude every
property of the model from binding (except Skip and you dont post
any values for Skip)
You added data to "ViewData" but you try to access from ViewBag.
You should know ViewData is different with ViewBag.
Therefore you try to change this line:
ViewData["leagueMemberList"] = leagueMemberSelectListItems;
to => ViewBag.leagueMemberList = leagueMemberSelectListItems;

Bind ViewModel to Controller on Edit Post Action - (with parameterless constructor) getting null

As a developer asp.net web site I've created project. And Scaffold it for controller creation with DataBase First approach.
There are two EF data model objects: Personal and PersonalDetails.
using System;
using System.Collections.Generic;
public partial class personalDetails
{
public int personID { get; set; }
public Nullable<bool> rehber { get; set; }
//many others, removed for easy to understand
}
public partial class personal
{
public personal()
{
this.passportDetails = new HashSet<passportDetails>();
}
public string name { get; set; }
public string surname { get; set; }
public int ID { get; set; }
public Nullable<short> politicalPartyID { get; set; }
public Nullable<short> familyStatusID { get; set; }
public virtual familyStatus familyStatus { get; set; }
public virtual politicalParties politicalParties { get; set; }
}
For a business logic it's necessary to create viewModel, because on view there are some fields from personal and some fields from personalDetils object. And I added viewModel class.
public class SocialViewModels
{
public personalDetails personalDetails { get; private set; }
public personal personal { get; private set; }
public SelectList marriageStatus { get; private set; }
public SelectList partyList { get; private set; }
public SocialViewModels()
{
}
public SocialViewModels(personalDetails _personalDetails, personal _personal, IEnumerable _marriageStatus, IEnumerable _partyList)
{
personalDetails = _personalDetails;
personal=_personal;
marriageStatus = new SelectList(_marriageStatus, "ID", "familyStatusName", personal.familyStatusID);
partyList = new SelectList(_partyList, "ID", "partyName", personal.politicalPartyID);
}
}
This ViewModel works well for get Details operation. Controller and View below:
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
personalDetails personalDetails = db.personalDetails.Find(id);
SocialViewModels svm = new SocialViewModels(personalDetails, personalDetails.personal, db.familyStatus, db.politicalParties);
if (personalDetails == null)
{
return HttpNotFound();
}
return View(svm);
}
Details view:
#model kadrws.ViewModels.SocialViewModels
#Html.DisplayFor(model => model.personal.familyStatus.familyStatusName)
#Html.DisplayFor(model => model.personal.politicalParties.partyName)
#Html.DisplayFor(model => model.personalDetails.rehber)
<p>
#Html.ActionLink("Redaktə et", "Edit", new { id = Model.personal.ID })
</p>
But problem with Edit post method:
// GET: personalDetails/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
personalDetails personalDetails = db.personalDetails.Find(id);
SocialViewModels svm = new SocialViewModels(personalDetails, personalDetails.personal, db.familyStatus, db.politicalParties);
if (personalDetails == null)
{
return HttpNotFound();
}
return View(svm);
}
// POST: personalDetails/Edit/5
// 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 Edit([Bind(Include = "personID,rehber, partyID, familyStatusID")] SocialViewModels scm )
{
if (ModelState.IsValid)
{
//do
}
ViewBag.personID = new SelectList(db.personal, "ID", "name", scm.personal.ID);
return View(scm);
}
Edit view :
#model kadrws.ViewModels.SocialViewModels
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.personalDetails.personID)
#Html.DropDownList("personal_familyStatusID", Model.marriageStatus)
#Html.DropDownList("personal_politicalPartyID", Model.partyList)
#Html.EditorFor(model => model.personalDetails.rehber)
<input type="submit" value="Save" class="btn btn-default" />
}
Problem is that, SocialViewModels is null.
I also search similar behaviors in Google. Dozen this like problems. Please, help to understand where is problem in this code...
Firstly, properties marriageStatus and partyList will be null because they are typeof SelectList (and you don't generate forms controls for each property of each SelectListItem in the collection - and nor should you). You do however generate <select> elements with names personal_familyStatusID and personal_politicalPartyID but your model does not include properties with those names.
Secondly, properties personal and personalDetails are null because you have include a [Bind] attribute which specifically excludes then (it would need to be Bind(Include = "personal, personalDetails")].
However your misunderstanding what a view model is and how to use it. It is not just a class that holds multiple instances of data models, it should contain only those properties you display/edit in the view. Based on the 4 controls you have included in your form, your view model should be
public class SocialViewModels
{
public int ID { get; set; }
public Nullable<bool> Rehber { get; set; }
public int MarriageStatus { get; set; }
public int Party { get; set; }
public SelectList MarriageStatusList { get; set; }
public SelectList partyList { get; set; }
}
Side note: Do not use private set on your SelectList properties. If you needed to return the view because ModelState was invalid, then you could never repopulate them
Then in the GET method, initialize a new instance of the view model, set its properties from the data models and return it to the view
public ActionResult Details(int? id)
{
....
personalDetails personalDetails = db.personalDetails.Find(id);
....
SocialViewModels model = new SocialViewModels()
{
ID = personalDetails.personID,
Rehber = personalDetails.rehber,
MarriageStatus = personalDetails.personal.familyStatusID,
....
MarriageStatusList = new SelectList(db.familyStatus, "ID", "familyStatusName"),
....
};
return View(model);
}
Side note: Your naming is extremely confusing (one minute its familystatus, then next its marriagestatus) so not sure if I have interpreted it correctly.
Then in the view
#model kadrws.ViewModels.SocialViewModels
#using (Html.BeginForm())
{
..
#Html.HiddenFor(m => m.ID)
#Html.DropDownListFor(m => m.MarriageStatus, Model.MarriageStatusList)
....
#Html.EditorFor(m=> m.Rehber)
<input type="submit" value="Save" class="btn btn-default" />
}
And in the POST method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(SocialViewModels model)
{
if (!ModelState.IsValid)
{
// repopulate your select lists
return View(model);
}
// Get your data models, map the view model properties it
// Save and redirect
}

ViewBag dropdown from aspnetusers table is not saving to database on post

I have a list of items from a dropdown list created from a custom property in the "ASP.NET Identity", "aspnetusers" table named "FullName". My goal is to select the name from the dropdown list and have the fullname and Id saved in my "Application" table creating a one-to-many relationship. The dropdown list is populating with the names, but isn't saving them into my application table.
Model:
public class HomeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
public class Application
{
public int ApplicationId { get; set; }
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
*****Asp.net identity application user class that I have a custom full name property in*****
public virtual ApplicationUser ApplicationUser { get; set; }
}
Controller:
public ActionResult Index()
{
ViewBag.LoanOfficer = new SelectList(db.Users,"Id","FullName");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index([Bind(Include = "Id,FullName,FirstName,LastName")] Application application)
{
if (ModelState.IsValid)
{
ViewBag.LoanOfficer = new SelectList(db.Users, "Id", "FullName", application.ApplicationUser);
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Thanks");
}
return View(application);
}
View:
#Html.DropDownList("LoanOfficer", "Select Loan Officer")
Your model does not have a property named LoanOfficer so it cant be bound. If you inspect Request.Form you will see the ID value of the selected User so you would then need to manually add that to Application before saving it. But you have a lot of issues with your code including the way you are using #Html.DropDownList(), the pointless use of [Bind(Include="...")] (your listing all 3 properties but they are all bound by default anyway, and you have included "FullName" which is not even a property of the model), and your recreating the SelectList if the model is valid (which is pointless since you immediately redirect, but not creating it if you return the view - which means nothing will be shown in the dropdownlist)
Create a view model to represent what you want to display and edit
public class ApplicationVM
{
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
[Required]
[DisplayName("Loan officer")]
public int? LoanOfficer { get; set; }
public SelectList LoanOfficerList { get; set; }
}
Controller
public ActionResult Create()
{
ApplicationVM model = new ApplicationVM();
model.LoanOfficerList = new SelectList(db.Users, "Id", "FullName");
return View();
}
[HttpPost]
public ActionResult Create(ApplicationVM model)
{
if(!ModelState.IsValid)
{
model.LoanOfficerList = new SelectList(db.Users, "Id", "FullName");
return View(model);
}
// Initialize new Application, set properties from the view model, save and redirect
}
View
#model ApplicationVM
#using(Html.BeginForm())
{
....
// controls for FirstName and LastName properties
#Html.LabelFor(m => m.LoanOfficer)
#Html.DropDownListFor(m => m.LoanOfficer, Model.LoanOfficerList, "--Please select--")
#Html.ValidationMessageFor(m => m.LoanOfficer)
<input type="submit" value="Create" />
}

ViewModel nesting and having business logic dependant validation using IValidatableObject

Currently I am presenting a payment form, on this payment form it is toggle-able whether to show the credit card validation. Showing the CVV field is dependent on some configuration dependent on some business logic in a few layers down in the app (in my instance some payment gateways do not have / require CVV).
The form is displaying the following fields:
Card Holder Name
Card Number
Card Type (drop-down of Visa, Amex, Master-Card, etc.)
Expiry Date
CVV dependent on configuration settings found in database, varies
Models (problem with Validate method)
public class IndexViewModel
{
public IEnumerable<CardType> CardTypes { get; set; }
public bool IsCvvEnabled { get; set; }
public PayProcess { get; set; }
}
public class ProcessViewModel : IValidatableObject
{
public string CardHolder { get; set; }
public string CardNumber { get; set; }
public string CardType { get; set; }
public string ExpiryDate { get; set; }
public string Cvv { get; set; }
public IEnumerable<ValidationResult> Validate(
ValidationContext validationContext)
{
// validation of properties CardHolder, Number, Type, etc...
// how do I read this value?
if (IsCvvEnabled)
{
int tempCvv;
if (string.IsNullOrEmpty(Cvv))
{
yield return new ValidationResult(Index.CVVRequired,
new[] { "CVV" });
}
else if (!int.TryParse(Cvv, out tempCvv))
{
yield return new ValidationResult(Index.CVVInvalid,
new[] { "CVV" });
}
}
}
}
View
The view looks as follows:
<section id="aligned">
<h3>#ViewRes.Index.Header</h3>
#Html.ValidationMessageFor(m => m.PayProcess.CreditCardType)
#Html.LabelFor(x => x.PayProcess.CreditCardType,
"Card Type")
#Html.DropDownListFor(m => m.PayProcess.CreditCardType,
Model.CreditCardTypes)
<!-- the other fields ... -->
#if (Model.PayProcess.IsCvvEnabled)
{
#Html.ValidationMessageFor(m => m.PayProcess.Cvv)
#Html.LabelFor(x => x.PayProcess.Cvv,
"Card Verification")
#Html.TextBoxFor(m => m.PayProcess.Cvv)
}
</section>
Controller
public ActionResult Index()
{
var indexViewModel = CreateIndexViewModel(new ProcessViewModel());
return View(model);
}
private PayIndexViewModel CreateIndexViewModel(ProcessViewModel processViewModel)
{
if (!ModelState.IsValid)
{
return View("Index", CreateIndexViewModel(processViewModel));
}
// handle success scenario
}
private IndexViewModel CreateIndexViewModel(ProcessViewModel processViewModel)
{
var isCvvEnabled = _someDependency.Gateway.SupportsCvv;
var cardTypes = _someDependency.Gateway.GetSupportedCardTypes
.Select(x => new SelectListItem {
Text = x.Name,
Value = x.ID });
return new IndexViewModel
{
CardTypes = cardTypes,
IsCvvEnabled = isCvvEnabled,
PayProcess = processViewModel
};
}
The Problem
The ProcessViewModel contains solely the form input values that the user submits, it does not contain IsCvvEnabled as the user is not submitting that value.
How do I correctly perform this validation when the validation needs this contextual information?
If the user isn't submitting the value in the form, then you will have to either manually set it on your post action or you may be able to craft a custom model binder to set the property before it is validated. However for something as trivial as this, I think that is way too much work.
Personally, my preferred way of achieving what you want, is to refactor your validate method to a method that takes in a ModelState and then you call this yourself in your post action:
viewModel.IsCvvEnabled = _someDependency.Gateway.SupportsCvv;
viewModel.Validate(this.ModelState);
if(!ModelState.IsValid)
{
return View(viewModel);
}
You will have to put the IsCvvEnabled property onto your ProcessViewModel.

Categories

Resources