I have a pretty complex model and a view (below). I only have ONE of my fields as Required (Key Active Mg). When I mark this [Required(ErrorMessage="Key Active Mg is required")] and set the #Html.ValidationMessageFor for that field, my app will let me enter nothing and click Save. Of course, Model.IsValid will return false. It doesn't come back and outline the field in red indicating it's required. Does anyone know why?
My model:
public class RawValues
{
[Key]
public int Pk { get; set; }
public int? FillerFk { get; set; }
[Display(Name = "Filler")]
[ForeignKey("FillerFk")]
public virtual Filler Filler { get; set; }
public int? CapsuleFk { get; set; }
[Display(Name = "Capsule")]
[ForeignKey("CapsuleFk")]
public virtual Capsule Capsule { get; set; }
public int? KeyActiveFk { get; set; }
[Display(Name = "Key Active")]
[ForeignKey("KeyActiveFk")]
public virtual KeyActive KeyActive { get; set; }
[Display(Name="API #1")]
public int? Active1 { get; set; }
[Display(Name = "API #2")]
public int? Active2 { get; set; }
[Display(Name = "API #3")]
public int? Active3 { get; set; }
[Display(Name = "API #4")]
public int? Active4 { get; set; }
[Display(Name = "API #5")]
public int? Active5 { get; set; }
[Display(Name = "Key Active Mg")]
[Required(ErrorMessage="Key Active Mg is required.")]
public int KeyActiveMg { get; set; }
[Display(Name = "E4M")]
public bool E4M { get; set; }
[Display(Name = "K100M")]
public bool K100M { get; set; }
public int TimeReleaseFillerFk { get; set; }
public int NumberCapsules { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
}
My View:
#model CapWorx.QuikCap.Models.RawValues
#* This partial view defines form fields that will appear when creating and editing entities *#
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Capsule, new { #class = "ui-input-text" })
#Html.DropDownListFor(m => m.Capsule.Pk, new SelectList(ViewBag.Capsules, "Pk", "Name", "Pk"))
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Active1)
#Html.EditorFor(model => model.Active1)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Active2)
#Html.EditorFor(model => model.Active2)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Active3)
#Html.EditorFor(model => model.Active3)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Active4)
#Html.EditorFor(model => model.Active4)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Active5)
#Html.EditorFor(model => model.Active5)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.KeyActive, new { #class = "ui-input-text" })
#Html.DropDownListFor(m => m.KeyActive.Pk, new SelectList(ViewBag.KeyActives, "Pk", "Name", "Pk"))
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.KeyActiveMg)
#Html.EditorFor(model => model.KeyActiveMg)
#Html.ValidationMessageFor(model => model.KeyActiveMg)
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.E4M)
#Html.DropDownListFor(x => x.E4M,
new[] {
new SelectListItem() { Text = "Off", Value = "False", Selected = true },
new SelectListItem() { Text = "On", Value = "True" } },
new { data_role = "slider" })
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.K100M)
#Html.DropDownListFor(x => x.K100M,
new[] {
new SelectListItem() { Text = "Off", Value = "False", Selected = true },
new SelectListItem() { Text = "On", Value = "True" } },
new { data_role = "slider" })
</div>
<div data-role="fieldcontain">
#Html.LabelFor(model => model.Filler, new { #class = "ui-input-text" })
#Html.DropDownListFor(m => m.Filler.Pk, new SelectList(ViewBag.Fillers, "Pk", "Name", "Pk"))
#Html.ValidationMessage("FillerName", "Filler is required")
</div>
My Controller:
[HttpPost]
public ActionResult Create(RawValues rawvalues)
{
rawvalues.CreatedBy = User.Identity.Name;
rawvalues.CreatedDate = DateTime.Now;
rawvalues.TimeReleaseFillerFk = Helpers.OperationContext.GetTimeReleaseFillerFk(rawvalues.E4M, rawvalues.K100M);
rawvalues.CapsuleFk = rawvalues.Capsule.Pk;
rawvalues.FillerFk = rawvalues.Filler.Pk;
rawvalues.KeyActiveFk = rawvalues.KeyActive.Pk;
rawvalues.Filler = Helpers.OperationContext.GetFiller(rawvalues.Filler.Pk);
rawvalues.Capsule = Helpers.OperationContext.GetCapsule(rawvalues.Capsule.Pk);
rawvalues.KeyActive = Helpers.OperationContext.GetKeyActive(rawvalues.KeyActive.Pk);
rawvalues.NumberCapsules = 100;
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid) {
rawvaluesRepository.InsertOrUpdate(rawvalues);
rawvaluesRepository.Save();
List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
return View("Results", results);
} else {
ViewBag.Error = "Model State was not valid.";
return View("Error");
}
}
My screenshot:
UPDATE
I've updated my controller code to be the following:
if (ModelState.IsValid) {
rawvaluesRepository.InsertOrUpdate(rawvalues);
rawvaluesRepository.Save();
List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
return View("Results", results);
} else {
ViewBag.Capsules = Helpers.OperationContext.GetCapsules();
ViewBag.Fillers = Helpers.OperationContext.GetFillers();
ViewBag.KeyActives = Helpers.OperationContext.GetKeyActives();
return View();
}
This resolves my issue. I needed to return the same View to display the errors on the screen. With DataAnnotations Validation, the form does in fact hit the HttpPost method of Create, and if there are errors (validation errors) the ModelState.IsValid will return false, in which case I need to return the same view. See screenshot below!
Looks like you are returning another view (Error) if ModelState.IsValid is false. You should return the posted viewmodel to the same create view.
public ActionResult Create(RawValues model)
{
if(ModelState.IsValid)
{
//everything is good. Lets save and redirect
}
return View(model);
}
My guess is that you are not including 'jquery.unobtrusive-ajax.min.js' and/or 'jquery.validate.unobtrusive.min.js'. I believe these are required to interpret the metadata added by DataAnnotations and supply the validation methods your looking for.
When your model is invalid, you don't actually return it with it's validation errors. Then, you do this:
#Html.ValidationMessageFor(model => model.KeyActiveMg)
but there is no Validation Message for anything. You returned a brand new view with a blank Model. Instead, you should return your invalid model to the view, like such:
return View(rawvalues);
UPDATE
Your controller should look like this:
public ActionResult Create()
{
return View(new RawValues());
}
[HttpPost]
public ActionResult Create(RawValues model)
{
if (ModelState.IsValid)
{
// Do stuff
}
else
{
return View(model);
}
}
Your view seems fine, so it should work from here.
If you're talking about client side validation, you need to reference the necessary JS files. They should be already preconfigured in your Bundle config. You just need to add to your view:
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryui")
#Scripts.Render("~/bundles/jqueryval")
Just to make sure, your BundleConfig class that sits in the App_Start folder should have these:
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
I think you need this
[HttpPost]
public ActionResultCreate(RawValues model)
{
....
if (!ModelState.IsValid)
{
ModelState.AddModelError("KeyActiveMg", "KeyActiveMg field is required");
var model = new GetModel();
return View("Results", model);
}
rawvaluesRepository.InsertOrUpdate(rawvalues);
rawvaluesRepository.Save();
List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
return View("Results", results);
}
Related
Model
League and LeagueDivision are two model classes
public class League
{
public int Id { get; set; }
public string League1 { get; set; }
public string Icon { get; set; }
public virtual ICollection<LeagueDivision> LeagueDivisions { get; set; }
}
public class LeagueDivision
{
public int Id { get; set; }
public Nullable<int> LeagueId { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public virtual League League { get; set; }
}
public class ViewModelForHostBooster
{
[Required(ErrorMessage = "Please enter price")]
[Display(Name = "Price")]
public decimal Price { get; set; }
[Required(ErrorMessage = "Please select a league")]
[Display(Name = "League")]
public int? SelectedLeague { get; set; }
[Required(ErrorMessage = "Please select a league division")]
[Display(Name = "League Division")]
public int? SelectedLeagueDivision { get; set; }
public SelectList LeagueList { get; set; }
public SelectList LeagueDivisionList { get; set; }
}
Controller
In IndexDropdown action I am just populating view with model and validating if
the model is validated then populate the view otherwise return the view. In FetchLeagueDivision action I am selecting Id and Name properties of model class based on passed argument ID.
Can anybody guide me why a WebHttpException is happening when I run this piece of code? Here is a link of exception Http Exception Image
public class DropDownController : Controller
{
[HttpGet]
public ActionResult IndexDropDown()
{
ViewModelForHostBooster model = new ViewModelForHostBooster();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult IndexDropDown(ViewModelForHostBooster model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// save and redirect
return RedirectToAction("Somewhere");
}
private void ConfigureViewModel(ViewModelForHostBooster model)
{
HostBoostersDBEntities db = new HostBoostersDBEntities();
var leagues = db.Leagues.Select(x => new { Value = x.Id, Text = x.League1 }).ToList();
model.LeagueList = new SelectList(leagues, "Id", "League1");
if (model.SelectedLeague.HasValue)
{
IEnumerable<LeagueDivision> leaguedivisions = db.LeagueDivisions.Where(l => l.LeagueId == model.SelectedLeague.Value);
model.LeagueDivisionList = new SelectList(leaguedivisions, "Id", "Name");
}
else
{
model.LeagueDivisionList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
}
View
#model HostBooster.Models.ViewModelForHostBooster
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.Price)
#Html.TextBoxFor(m => m.Price)
#Html.ValidationMessageFor(m => m.Price)
</div>
<div>
#Html.LabelFor(m => m.SelectedLeague)
exception is occurring here #Html.DropDownListFor(m => m.SelectedLeague, Model.LeagueList)
#Html.DropDownListFor(m => m.SelectedLeague, Model.LeagueList)
#Html.ValidationMessageFor(m => m.SelectedLeague)
</div>
<div>
#Html.LabelFor(m => m.SelectedLeagueDivision)
#Html.DropDownListFor(m => m.SelectedLeagueDivision, Model.LeagueDivisionList)
#Html.ValidationMessageFor(m => m.SelectedLeagueDivision)
</div>
<input type="submit" value="save" />
}
In view Model.LeagueList is null. It should not be null.
if edit view code like this(for example), Works well:
<body>
#using (Html.BeginForm())
{
List<SelectListItem> listItems = new List<SelectListItem>();
listItems.Add(new SelectListItem
{
Text = "Example1",
Value = "Example1"
});
listItems.Add(new SelectListItem
{
Text = "Example2",
Value = "Example2",
Selected = true
});
listItems.Add(new SelectListItem
{
Text = "Example3",
Value = "Example3"
});
<div>
#Html.LabelFor(m => m.Price)
#Html.TextBoxFor(m => m.Price)
#Html.ValidationMessageFor(m => m.Price)
</div>
<div>
#Html.LabelFor(m => m.SelectedLeague)
#Html.DropDownListFor(m => m.SelectedLeague, listItems)
#Html.ValidationMessageFor(m => m.SelectedLeague)
</div>
<div>
#Html.LabelFor(m => m.SelectedLeagueDivision)
#Html.DropDownListFor(m => m.SelectedLeagueDivision, Model.LeagueDivisionList)
#Html.ValidationMessageFor(m => m.SelectedLeagueDivision)
</div>
<input type="submit" value="save" />
}
I'm working on a webapp for work, and I'm using standard CRUD style interactions. However, there are certain fields that I do not want the users updating, so I removed them from the view. However, if I don't explicitly set these fields, they're cleared when the model is updated in the database.
I'm concerned with what the proper method of populating the fields for my ViewModels is.
The rough idea I came up with was something like this:
My view model:
public class EditSoftwareTrackingViewModel
{
public EditSoftwareTrackingViewModel(SoftwareTracking model)
{
Id = model.Id;
SoftwareId = model.SoftwareId;
ComputerId = model.ComputerId;
SoftwareActionId = model.SoftwareActionId;
LastModified = model.LastModified;
Computer = model.Computer;
Software = model.Software;
SoftwareAction = model.SoftwareAction;
}
public int Id { get; set; }
[DisplayName("Software")]
public int SoftwareId { get; set; }
[DisplayName("Computer")]
public int ComputerId { get; set; }
[DisplayName("Software Action")]
public int SoftwareActionId { get; set; }
[DisplayName("Last Modified")]
public DateTime? LastModified { get; set; }
public virtual Computer Computer { get; set; }
public virtual Software Software { get; set; }
public virtual SoftwareAction SoftwareAction { get; set; }
}
My main model
[Table("asset.SoftwareTracking")]
public partial class SoftwareTracking
{
public int Id { get; set; }
[DisplayName("Software")]
public int SoftwareId { get; set; }
[DisplayName("Computer")]
public int ComputerId { get; set; }
[DisplayName("Date Entered")]
public DateTime? EnteredDate { get; set; }
[DisplayName("Software Action")]
public int SoftwareActionId { get; set; }
[DisplayName("Last Modified")]
public DateTime? LastModified { get; set; }
public virtual Computer Computer { get; set; }
public virtual Software Software { get; set; }
public virtual SoftwareAction SoftwareAction { get; set; }
}
And my controller using the view model
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
EditSoftwareTrackingViewModel softwaretracking = new EditSoftwareTrackingViewModel(db.SoftwareTrackings.Find(id));
if (softwaretracking == null)
{
return HttpNotFound();
}
GeneratePageData(softwaretracking.Software.Id);
return View(softwaretracking);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditSoftwareTrackingViewModel softwaretracking)
{
if (ModelState.IsValid)
{
softwaretracking.LastModified = DateTime.Now;
var softwareTrack = db.SoftwareTrackings.Find(softwaretracking.Id);
softwareTrack = new SoftwareTracking
{
Computer = softwaretracking.Computer,
ComputerId = softwaretracking.ComputerId,
LastModified = softwaretracking.LastModified,
Software = softwaretracking.Software,
SoftwareAction = softwaretracking.SoftwareAction,
SoftwareActionId = softwaretracking.SoftwareActionId,
SoftwareId = softwaretracking.SoftwareId,
EnteredDate = softwareTrack.EnteredDate
};
db.Entry(softwareTrack).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
GeneratePageData(softwaretracking.Software.Id);
return View(softwaretracking);
}
Is there a better alternative? Or should I continue to create my view models in this manner?
EDIT
My business logic and view
private void GeneratePageData(int? id = null)
{
ViewBag.Computers = new SelectList(db.Computers, "Id", "ComputerName");
ViewBag.SoftwareActions = new SelectList(db.SoftwareActions, "Id", "ActionPerformed");
var usedSoft = (from softTrack in db.SoftwareTrackings
where (softTrack.SoftwareActionId != 3)
select softTrack.Software);
var softwareList = (from soft in db.Softwares
where (
((from softTrack in db.SoftwareTrackings
where (softTrack.SoftwareActionId != 3 && softTrack.SoftwareId == soft.Id)
select softTrack.Software).Count() < soft.KeyQuantity)
&& !(soft.AssetStatusId == 4 || soft.AssetStatusId == 5)
|| soft.Id == id)
select soft).ToList();
ViewBag.SoftwareList = softwareList.Select(t => new SelectListItem
{
Text = t.SoftwareIdNameFull,
Value = t.Id.ToString()
});
}
And my view
#model Lighthouse_Asset_Manager.Models.EditSoftwareTrackingViewModel
#{
ViewBag.Title = "Edit Software Install";
Layout = "";
}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel">Edit Software Install</h4>
</div>
<div class="modal-body">
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "computerForm" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id)
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.SoftwareId, "Software", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SoftwareId", (IEnumerable<SelectListItem>)ViewBag.SoftwareList, "-- Select --", new
{
#style = "width:100%",
#class = "select2"
})
#Html.ValidationMessageFor(model => model.SoftwareId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ComputerId, "Computer", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("ComputerId", (IEnumerable<SelectListItem>)ViewBag.Computers, "-- Select --", new
{
#style = "width:100%",
#class = "select2"
})
#Html.ValidationMessageFor(model => model.ComputerId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SoftwareActionId, "Action", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SoftwareActionId", (IEnumerable<SelectListItem>)ViewBag.SoftwareActions, "-- Select --", new
{
#style = "width:100%",
#class = "form-control"
})
#Html.ValidationMessageFor(model => model.SoftwareActionId)
</div>
</div>
<div class="form-actions no-color">
<button type="submit" class="btn btn-primary btn-sm"><i class="fa fa-floppy-o"></i> Edit Install Record</button>
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
</div>
</div>
}
</div>
You approach of using a view model is a good one. The answers to this question explains some of the benefits including preventing over-posting attacks, using view specific display and validation attributes and including view specific properties such as SelectLists. Tools such as automapper can make it easy to map between you data and view models and reduce the code in the controller. A few changes I would suggest to your view model. The LastModified, Computer, Software and SoftwareAction properties are not required (you not binding to these), and I would include the SelectList properties in the model rather than ViewBag
View model
public class EditSoftwareTrackingViewModel
{
public int Id { get; set; }
[Display(Name="Software")]
public int SoftwareId { get; set; }
[Display(Name="Computer")]
public int ComputerId { get; set; }
[Display(Name="Software Action")]
public int SoftwareActionId { get; set; }
public SelectList Computers { get; set; }
public SelectList SoftwareActions{ get; set; }
public SelectList SoftwareList{ get; set; }
}
Then change the GeneratePageData() method to accept the view model
private void GeneratePageData(EditSoftwareTrackingViewModel model)
{
model.Computers = new SelectList(db.Computers, "Id", "ComputerName");
....
and in the view (always preferable to use the strongly typed helpers)
#Html.DropDownListFor(m => m.SoftwareId, Model.SoftwareList, "-- Select --", new { #class = "select2" })
A few other things to note.
You should use the [Display(Name="..")] attribute (not
[DisplayName(..)])
When you set the LastModified property, you should consider using
UCT time.
The hidden input for the Id property is not required in the view
(assuming your using the default {controller}/{action}/{id} route
mapping) - its added to the route values and will be bound anyway
Unless you specifically want an id attribute for the form, you can
just use #using(Html.BeginForm()) {
You do not need the second parameter in LabelFor() - it can be just
Html.LabelFor(m => m.SoftwareId, new { #class = "control-label
col-md-2" }) since you have specified it in the [Display]
attribute
Finally, if you want to simplify your view further, you could consider custom EditorTemplates or html helpers as indicated in this answer which would allow you to replace
<div class="form-group">
#Html.LabelFor(model => model.SoftwareId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SoftwareId, Model.SoftwareList, "-- Select --", new { #class = "select2" })
#Html.ValidationMessageFor(model => model.SoftwareId)
</div>
</div>
with (custom EditorTemplate)
#Html.EditorFor(m => m.SoftwareId, "BootstrapSelect", Model.SoftwareList)
or (custom HtmlHelper)
#Html.BootstrapDropDownFor(m => m.SoftwareId, Model.SoftwareList)
You should use the AutoMapper to make the mapping between Model and ViewModel cleaner. Use this code to create the mapper first.
Mapper.CreateMap<SoftwareTracking, EditSoftwareTrackingViewModel>();
Mapper.CreateMap<EditSoftwareTrackingViewModel, SoftwareTracking>();
When you want to create a viewmodel from model, do this:
public ActionResult Edit(int? id)
{
SoftwareTracking tracking = db.SoftwareTrackings.Find(id);
EditSoftwareTrackingViewModel viewmodel =
Mapper.Map<SoftwareTracking, EditSoftwareTrackingViewModel>(tracking);
return View(viewmodel);
}
When you want to populate the info from the viewmodel back to the model, do this
public ActionResult Edit(EditSoftwareTrackingViewModel vm)
{
if (ModelState.IsValid)
{
vm.LastModified = DateTime.Now;
var softwareTrack = db.SoftwareTrackings.Find(softwaretracking.Id);
softwareTrack =
Mapper.Map<EditSoftwareTrackingViewModel, SoftwareTracking>(vm, softwareTrack);
db.Entry(softwareTrack).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
To patch update your model without loading the object from Db. Try Attach:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditSoftwareTrackingViewModel softwaretracking)
{
if (ModelState.IsValid)
{
var softwareTrack = new SoftwareTracking
{
Computer = softwaretracking.Computer,
ComputerId = softwaretracking.ComputerId,
LastModified = softwaretracking.LastModified,
Software = softwaretracking.Software,
SoftwareAction = softwaretracking.SoftwareAction,
SoftwareActionId = softwaretracking.SoftwareActionId,
SoftwareId = softwaretracking.SoftwareId,
EnteredDate = softwareTrack.EnteredDate
};
db.SoftwareTrackings.Attach(softwareTrack);
db.Entry(softwareTrack).Property(a => a.Computer).IsModified = true;
db.Entry(softwareTrack).Property(a => a.ComputerId).IsModified = true;
db.Entry(softwareTrack).Property(a => a.LastModified).IsModified = true;
db.Entry(softwareTrack).Property(a => a.Computer).IsModified = true;
db.Entry(softwareTrack).Property(a => a.Software).IsModified = true;
db.Entry(softwareTrack).Property(a => a.SoftwareAction).IsModified = true;
db.Entry(softwareTrack).Property(a => a.SoftwareActionId).IsModified = true;
db.Entry(softwareTrack).Property(a => a.SoftwareId).IsModified = true;
db.SaveChanges();
return RedirectToAction("Index");
}
GeneratePageData(softwaretracking.Software.Id);
return View(softwaretracking);
}
Regarding the second question about whether to use ViewModel or just use the Model directly. This is really a matter of opinion, each approach has its pros and cons. I don't have strong opinion about this, i just want to point out these pros and cons for your consideration:
Using the model directly saves us from creating the viewModel, resulting in smaller source code and avoiding mapping logic but it would mix concerns. Because you use the same Model for your domain logic and for communcating with the client, any changes to the model may propagate up to the client if we don't take that into account.
Using the viewModel is a good way for separation of concerns but it would require more effort and mapping logic (maybe slow down the performance a bit). To apply ViewModel efficiently, I suggest using a mapper: https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
This is the Model Class
[Table("CURRENCY")]
public class CurrencyClass : ICurrency
{
private Int32 mCURRENCY_ID = default(Int32);
[Key]
public virtual Int32 CURRENCY_ID
{
get { return mCURRENCY_ID; }
set { mCURRENCY_ID = value; }
}
private string mCURRENCY_NAME = default(string);
public virtual string CURRENCY_NAME
{
get { return mCURRENCY_NAME;}
set { mCURRENCY_NAME = value;}
}
private string mCURRENCY_DESC = default(string);
public virtual string CURRENCY_DESC
{
get { return mCURRENCY_DESC; }
set { mCURRENCY_DESC = value; }
}
private string mCURRENCY_SYMBOLE = default(string);
public virtual string CURRENCY_SYMBOLE
{
get { return mCURRENCY_SYMBOLE; }
set { mCURRENCY_SYMBOLE = value; }
}
private Int32 mcreated_by = default(Int32);
public virtual Int32 created_by
{
get { return mcreated_by; }
set { mcreated_by = value; }
}
private DateTime mcreated_date = default(DateTime);
public virtual DateTime created_date
{
get { return mcreated_date; }
set { mcreated_date = value; }
}
private Int32 mmodified_by = default(Int32);
public virtual Int32 modified_by
{
get { return mmodified_by; }
set { mmodified_by = value; }
}
private DateTime mmodified_date = default(DateTime);
public virtual DateTime modified_date
{
get { return mmodified_date; }
set { mmodified_date = value; }
}
}
This is the ViewModel
public class CurrencyViewModel
{
[Key]
public Int32 CURRENCY_Id { get; set; }
[Required(ErrorMessage="Currency Name is required")]
public string CURRENCY_NAME { get; set; }
[Required(ErrorMessage="Currency Description is required")]
public string CURRENCY_DESC { get; set; }
[Required(ErrorMessage = "Currency Symbole is Required")]
public string CURRENCY_SYMBOLE { get; set; }
}
This is the Action
[HttpPost]
[ActionName("Create")]
public ActionResult Create(CurrencyViewModel vm)
{
if (!ModelState.IsValid)
{
return View("Create");
}
obj.CURRENCY_NAME = vm.CURRENCY_NAME;
obj.CURRENCY_DESC = vm.CURRENCY_DESC;
obj.CURRENCY_SYMBOLE = vm.CURRENCY_SYMBOLE;
obj.created_by = 1;
obj.created_date = DateTime.Now;
obj.modified_by = 1;
obj.modified_date = DateTime.Now;
db.Currencies.Add(obj);
db.SaveChanges();
return RedirectToAction("Index");
}
My entities:
public class Meal
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required(ErrorMessage = "Proszę podać nazwę posiłku")]
public string Name { get; set; }
[Required(ErrorMessage = "Proszę podać ilość białka")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Protein { get; set; }
[Required(ErrorMessage = "Proszę podać ilość węglowodanów")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Carbohydrates { get; set; }
[Required(ErrorMessage = "Proszę podać ilość tłuszczy")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Fat { get; set; }
[Required(ErrorMessage = "Proszę podać ilość kalorii")]
[Range(0.00, double.MaxValue, ErrorMessage = "Proszę podać dodatnią ilość.")]
public double Calories { get; set; }
}
public class EatenMeal
{
public int Id { get; set; }
public virtual Meal Meal { get; set; }
public virtual MealType MealType { get; set; }
public double Serving { get; set; }
public string Username { get; set; }
public DateTime Date { get; set; }
}
public class MealType
{
public int Id { get; set; }
public string Name { get; set; }
}
In MealController's view MealList which displays meals from datebase. And there is a button "Add" which refers to action AddEatenMeal in EatenMealController.
public ActionResult AddEatenMeal(int id)
{
var meal = mealRepository.GetMeal(id);
EatenMeal eatenMeal = new EatenMeal() { Meal = meal, Username = User.Identity.Name };
return View(eatenMeal);
}
[HttpPost]
public ActionResult AddEatenMeal(EatenMeal eatenMeal)
{
if(ModelState.IsValid)
{
eatenMealRepository.AddEatenMeal(eatenMeal);
RedirectToAction("Index", "Home");
}
return RedirectToAction("Index", "Home");
}
I am creating there object EatenMeal and partially initializing this object. Then I am passing this object to View to further initializing.
#model Domain.Entities.EatenMeal
#{
ViewBag.Title = "Dodawanie posiłku do dziennika";
}
#using (Html.BeginForm("AddEatenMeal","EatenMeal", FormMethod.Post, new {#class = "form"}))
{
#Html.HiddenFor(x => x.Meal.Name)
#Html.HiddenFor(x => x.Username)
#Html.HiddenFor(x => x.Meal.Calories)
#Html.HiddenFor(x => x.Meal.Carbohydrates)
#Html.HiddenFor(x => x.Meal.Fat)
#Html.HiddenFor(x => x..Meal.Protein)
#Html.HiddenFor(x => x.Meal.Id)
#Html.HiddenFor(x=>x.Username)
<div class="form-group">
#Html.Label("Nazwa posiłku")
#Html.Label(Model.Meal.Name, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.Label("Porcja (g)")
#Html.TextBoxFor(x => x.Serving, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.Label("Typ posiłku")
#Html.DropDownListFor(x=>x.MealType)????
</div>
<div class="form-group">
#Html.Label("Data spożycia")
#Html.TextBoxFor(x => x.Date, new { #class = "form-control", #id="date-eaten", #Value=DateTime.Today.ToShortDateString()})
</div>
<input type="submit" class="btn btn-info" value="Dodaj" />
}
Now I have a question. Is it correct to hiding fields? I don't know how I can save data from first controller to second in other way.
And is a second question. How I can make DropDownListFor for property MealTye in EatenMeal?
Rather than sending and receiving a whole lot of unused data across the wire and opening yourself to over posting attack, create a view model that represents what you want to display and edit. See What is a view model in MVC?
View model
public class EatenMealVM
{
public int MealID { get; set; }
[Display(Name="Nazwa posiłku")]
public string MealName { get; set; }
[Display(Name = "Typ posiłku")]
[Required(ErrorMessage = "Please select a meal")]
public int? MealTypeID { get; set; }
[Display(Name = "Porcja (g)")]
public double Serving { get; set; } // is this really double?
[Display(Name = "Data spożycia")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
public SelectList MealTypeList { get; set; }
}
Controller
public ActionResult AddEatenMeal(int id)
{
var meal = mealRepository.GetMeal(id);
var mealTypes = // get the list of meal types from the database
EatenMealVM model = new EatenMealVM()
{
MealID = meal.Id,
MealName = meal.Name,
MealTypeList = new SelectList(mealTypes, "ID", "Name")
};
return View(model);
}
View
#model EatenMealVM
....
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.MealID)
#Html.DisplayNameFor(m => m.MealName)
#Html.DisplayFor(m => m.MealName)
#Html.LabelFor(m => m.MealTypeID)
#Html.DropDownListFor(m => m.MealTypeID, Model.MealTypeList, "--Please select--")
#Html.ValidationMessageFor(m => m.MealTypeID)
#Html.LabelFor(m => m.Serving)
#Html.TextBoxFor(m => m.Serving, new { #class = "form-control")
#Html.ValidationMessageFor(m => m.Serving)
#Html.LabelFor(m => m.Date)
#Html.TextBoxFor(m => m.Date)
#Html.ValidationMessageFor(m => m.Date, new { #class = "form-control" })
<input type="submit" class="btn btn-info" value="Dodaj" />
}
Post method
[HttpPost]
public ActionResult AddEatenMeal(EatenMealVM model)
{
if (!ModelState.IsValid)
{
var mealTypes = // get the list of meal types from the database
model.MealTypeList = new SelectList(mealTypes, "ID", "Name");
return View(model);
}
// Initialize new EatenMeal class
// Map properties from view model (including setting user name)
// Save and redirect
}
Note also the use of [Display] attribute and #Html.LabelFor(). Currently you not creating 'real' labels (they are not associated with the corresponding control)
I have edit page and controller that creates new model object and fills some data from db into this object then send a model object to view. When I click the submit button, some fields in this object have been cleared.
For example:
Before:
user_id
name
birth_date
username
password
id_role
email
After (Fields that are not null or empty):
name
username
birth_date
The model:
public partial class Users
{
public Users()
{
this.Albums = new HashSet<Albums>();
this.History = new HashSet<History>();
this.Country = new HashSet<Country>();
this.Artists = new HashSet<Artists>();
this.SelectedCountries = new List<string>();
}
[DisplayName("User ID")]
public System.Guid user_id { get; set; }
[DisplayName("Name")]
public string name { get; set; }
[DisplayName("Birth date")]
public Nullable<System.DateTime> birth_date { get; set; }
[DisplayName("Username")]
public string username { get; set; }
[DisplayName("Password")]
public string password { get; set; }
[DisplayName("Rights")]
public System.Guid id_role { get; set; }
[DisplayName("User Email")]
public string email { get; set; }
public bool isRemember { get; set; }
public virtual ICollection<Albums> Albums { get; set; }
public virtual ICollection<History> History { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<Country> Country { get; set; }
public virtual ICollection<Artists> Artists { get; set; }
public virtual List<string> SelectedCountries { get; set; }
}
Edit method:
public ActionResult Edit()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
var userName = HttpContext.User.Identity.Name;
var user = db.Users.Where(x => x.username == userName).FirstOrDefault();
ViewBag.Countries = new MultiSelectList(db.Country, "id_country", "name", user.SelectedCountries);
return View(user);
}
return HttpNotFound();
}
Edit method for handling post request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Users users)
{
if (ModelState.IsValid)
{
foreach (var country in users.SelectedCountries)
{
var dbCountry = db.Country.Find(new Guid(country));
if (dbCountry != null)
users.Country.Add(dbCountry);
}
db.Entry(users).State = System.Data.Entity.EntityState.Modified;
//There handle of string array goes
db.SaveChanges();
return RedirectToAction("Index");
}
return View(users);
}
View:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Users</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>
<div class="editor-label">
#Html.LabelFor(model => model.birth_date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.birth_date)
#Html.ValidationMessageFor(model => model.birth_date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.username)
#Html.ValidationMessageFor(model => model.username)
</div>
<div class="editor-label">
#Html.Label("Country")
</div>
<div class="editor-field">
#Html.DropDownList("SelectedCountries", (ViewBag.Countries as MultiSelectList), new { multiple = "multiple", #class = "chosen", style = "width: 350px;"})
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Thanks in advance :)
You will only receive values that are in your form. Http is stateless..
What you need to do.. is create a ViewModel. That ViewModel is the subset of properties from your domain model that are displayed in the view. Like this:
public class UserViewModel {
public string Name { get; set; }
public string Username { get; set; }
public DateTime? DateofBirth { get; set; }
}
Use this model in your view. Then, in your controller.. get the user and update the appropriate fields:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel viewModel) {
var user = db.Users.Where(x => x.username == viewModel.Username).FirstOrDefault();
user.Name = viewModel.Name;
user.Username = viewModel.Username;
// .. etc.
db.SaveChanges();
}
If you are worried about all of the manual mapping involved in this, there exists frameworks to help you with that:
Automapper
ValueInjector
You are heading down a very very daunting path if you start adding hidden fields into your view. Its a maintenance nightmare and very error prone.
The post operation only collects the values you have in the form.
If you want the other values to proceed in your controllers post-method, you can for example, add hidden fields.
#Html.HiddenFor(x => x.HiddenPostBack)
I'm using EF Code First and here is my class which references a Professor object.
public class Mark
{
public int MarkId { get; set; }
[Required(ErrorMessage = "Please provide Grade")]
[Display(Name = "Grade Value")]
public int MarkValue { get; set; }
[Required(ErrorMessage = "Please provide grade type")]
[Display(Name = "Grade Type")]
public string Type { get; set; }
[Required(ErrorMessage = "Please provide Subject")]
[Display(Name = "Subject")]
public virtual Subject Subject { get; set; }
public virtual User User { get; set; }
public virtual Professor Professor { get; set; }
}
In my view I have a dropdown to select a professor who put the mark.
I implemented it like this:
//controller
public MarksController() : this(new MarkRepository())
{
IProfessorRepository professorsRepo = new ProfessorRepository();
List<Professor> professors = professorsRepo.All.ToList<Professor>();
List<SelectListItem> items = new List<SelectListItem>();
foreach (Professor p in professors)
{
items.Add(new SelectListItem
{
Text = String.Concat(p.FirstName, " ", p.LastName), Value = p.ProfessorId.ToString()
});
}
items.Add(new SelectListItem
{
Text = "Please select Professor", Value = "0", Selected = true
});
ViewBag.Professors = items;
}
//view create or edit
<div class="editor-label">
#Html.LabelFor(model => model.MarkValue)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MarkValue)
#Html.ValidationMessageFor(model => model.MarkValue)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Type)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Type)
#Html.ValidationMessageFor(model => model.Type)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Professor)
</div>
<div class="editor-field">
#Html.DropDownList("Professors");
</div>
Yes, I know that there is is a metod in Controller called Create which appeals repository method InsertOrUpdate.
[HttpPost]
public ActionResult Create(Mark mark)
{
if (ModelState.IsValid) {
markRepository.InsertOrUpdate(mark);
markRepository.Save();
return RedirectToAction("Index");
} else {
return View();
}
}
public void InsertOrUpdate(Mark mark)
{
if (mark.MarkId == default(int)) {
// New entity
context.Marks.Add(mark);
} else {
// Existing entity
context.Entry(mark).State = EntityState.Modified;
}
}
Everything seems logic, but how can I save a newly created Mark object with the professor selected? How does ASP.NET arranges the object which is passed to Create method on Controller? How should I match the selected professor with the mark I am creating?
Take a look at this, you need a ProfessorId in the model.
Model binding dropdown selected value
public class Mark
{
// this will hold the selected value
public string ProfessorID { get; set; }
public IEnumerable<SelectListItem> Professors
{
get
{
return Proffessors
.Select(x => new SelectListItem {
Value = x.ProfessorId.ToString(),
Text = String.Concat(p.FirstName, " ", p.LastName)
});
}
}
public IEnumerable<Professor> Proffessors
{
get
{
IProfessorRepository professorsRepo = new ProfessorRepository();
return professorsRepo.All;
}
}
//the rest of your code
}
in the view do
#Html.DropDownListFor(
x => x.ProfessorId,
new SelectList(Model.Professors, "Value", "Text")
)