Proper way to populate a ViewModel? - c#

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");
}

Related

How to implement custom LogIn with ASP.NET Core 3.1?

I have a testing database on my local server. In the database, I have a table called "Korisnici" (eng. Users). Using EntityFrameworkCore I generated classes from a database, and here is generated "Korisnici" class:
public partial class Korisnici
{
public Korisnici()
{
BankovniRacuni = new HashSet<BankovniRacuni>();
Dokumenti = new HashSet<Dokumenti>();
ObracuniZarada = new HashSet<ObracuniZarada>();
Poslodavci = new HashSet<Poslodavci>();
PrihodiPoslodavca = new HashSet<PrihodiPoslodavca>();
RashodiPoslodavca = new HashSet<RashodiPoslodavca>();
Takse = new HashSet<Takse>();
Zaposleni = new HashSet<Zaposleni>();
}
public int Id { get; set; }
public string Ime { get; set; }
public string Prezime { get; set; }
[Required]
[Display(Name = "Korisnicko ime")]
public string KorisnickoIme { get; set; }
public string Email { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Lozinka")]
public string Lozinka { get; set; }
public int? TipKorisnika { get; set; }
public virtual TipoviKorisnika TipKorisnikaNavigation { get; set; }
public virtual ICollection<BankovniRacuni> BankovniRacuni { get; set; }
public virtual ICollection<Dokumenti> Dokumenti { get; set; }
public virtual ICollection<ObracuniZarada> ObracuniZarada { get; set; }
public virtual ICollection<Poslodavci> Poslodavci { get; set; }
public virtual ICollection<PrihodiPoslodavca> PrihodiPoslodavca { get; set; }
public virtual ICollection<RashodiPoslodavca> RashodiPoslodavca { get; set; }
public virtual ICollection<Takse> Takse { get; set; }
public virtual ICollection<Zaposleni> Zaposleni { get; set; }
}
This class is used as a model for one View called "Index.cshtml":
#model Korisnici
<img src="/Content/images/LogoFinal.png" />
<div class="row">
<div class="col-md-8">
<section id="loginForm">
#using (Html.BeginForm("Login", "Account"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.KorisnickoIme, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.KorisnickoIme, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.KorisnickoIme, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Lozinka, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Lozinka, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Lozinka, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Uloguj se" class="btn btn-primary" />
</div>
</div>
}
</section>
</div>
</div>
When I click on a submit button, Login action from Controler "AccountControler" is called.
public class AccountController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Login(LoginViewModel model)
{
// Acces database and search for account
var dbContext = new AdministracijaZingDevDBContext();
var korisnik = dbContext.Korisnici
.Where(k => k.KorisnickoIme == model.KorisnickoIme)
.Where(k => k.Lozinka == model.Lozinka)
.FirstOrDefault();
if (korisnik != null)
{
HttpContext.Session.SetString("UserName" , model.KorisnickoIme);
return RedirectToAction("Index", "Main");
}
return RedirectToAction("Index", "Home");
}
}
I have inserted testing data in a database with one record of the Korisnici table.
When I enter correct data into the LogIn form, nothing happens (the user didn't pass login).
just to check, you created migration files and updated the db and so on ?
(This should be in a comment, but I lack the reputation)
also you shouldn't create a new context, but inject it into the constructor of your account controller
( or beter still ... you should inject a repository or better use a unit of work-design and CQRS-design)
Take care and good luck.
so you type ctor tab tab
which would give you
public AccountController(){}
and than you add a parameter in the AccountController-function like
public AccountController(MyContext context){}
right click => quick actions and refactoring => create and assign property MyContext
but you should create at least a repositorypattern and inject something like IKorisniciRepository.
It would be easier if you placed your repo on gitHub so I can test before writing.
Try
public class AccountController : Controller
{
private readonly AdministracijaZingDevDBContext Context {get;}
public AccountController (AdministracijaZingDevDBContext context) {Context = context;}
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Login(LoginViewModel model)
{
// Access database and search for account
var korisnik = Context.Korisnici
.Where(k => k.KorisnickoIme == model.KorisnickoIme)
.Where(k => k.Lozinka == model.Lozinka)
.FirstOrDefault();
if (korisnik != null)
{
HttpContext.Session.SetString("UserName" , model.KorisnickoIme);
return RedirectToAction("Index", "Main");
}
return RedirectToAction("Index", "Home");
}
}

Why does my DropDownList repeat same value?

I am getting values from a ViewModel into a view for form fields. In one of my DropDownList the values are correct but in another the value repeats itself instead of changing. What am I doing wrong?
ViewModel:
namespace FulfillmentPortal.ViewModels
{
public class ViewModel
{
[Required(ErrorMessage = "Please select a carrier")]
public List<Carrier> CarrierList { get; set; }
[Required(ErrorMessage = "Please select a service")]
public List<CarrierService> ServiceList { get; set; }
}
}
Controller:
public class FulfillmentController : Controller
{
private CarrierModel db = new CarrierModel();
// GET: Fulfillment
public ActionResult Index()
{
ViewModel vm = new ViewModel
{
CarrierList = db.Carriers.ToList(),
ServiceList = db.CarrierServices.ToList()
};
return View(vm);
}
}
View:
#model FulfillmentPortal.ViewModels.ViewModel
#{
ViewBag.Title = "index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="panel panel-primary">
<div class="panel-heading">REPORT OPTIONS</div>
<div class="panel-body" style="padding-left:35px;">
<form id="processForm" class="form-horizontal" action="~/Fulfillment/Report" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="sel1">Carrier:</label>
#Html.DropDownListFor(model => model.CarrierList, new SelectList(Model.CarrierList, "CarrierId", "CarrierName"), "Select a Carrier",
new { #class = "form-control", #style = "width:auto; margin-bottom:15px;" })
<label for="sel2">Carrier Services:</label>
#Html.DropDownListFor(model => model.ServiceList, new SelectList(Model.ServiceList, "Code", "WebName"), "Select a Service",
new { #class = "form-control", #style = "width:auto; margin-bottom:15px;" })
</div>
</form>
</div>
</div>
Model:
public partial class CarrierModel : DbContext
{
public CarrierModel()
: base("name=CarrierModel")
{
}
public virtual DbSet<Carrier> Carriers { get; set; }
public virtual DbSet<CarrierService> CarrierServices { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
viewModel.CarrierList.ToList();
viewModel.ServiceList.ToList();
}
return View(viewModel);
}
This my controller now. Now my view is just erroring out. I think I am missing something or misunderstanding something.
DropDownListFor(model => model.ServiceList
This is not how this method is supposed to work, and I suspect this is the answer to your problem.
This lambda is supposed to provide a field that will hold a value that this drop down list outputs. For your case you should have two fields in the model:
public class ViewModel
...
public int CarrierId { get; set; }
public string CarrierServiceCode { get; set; }
These will hold currently selected value, on none if nothing is selected (yet). And they are supposed to be used in that lambda:
DropDownListFor(model => model.CarrierServiceCode
Alternatively you could use DropDownList() method, which does not require a field in the model, and give it a custom name that will be posted with selected value.
Your ViewModel should be as follows:
public class ViewModel
{
[Required(ErrorMessage = "Please select a carrier")]
public int CarrierId {get; set;}
[Required(ErrorMessage = "Please select a service")]
public int ServiceCode {get; set;}
public List<Carrier> CarrierList { get; set; }
public List<CarrierService> ServiceList { get; set; }
}
Then in the view:
<label for="sel1">Carrier:</label>
#Html.DropDownListFor(model => model.CarrierId, new SelectList(Model.CarrierList, "CarrierId", "CarrierName"), "Select a Carrier",
new { #class = "form-control", #style = "width:auto; margin-bottom:15px;" })
<label for="sel2">Carrier Services:</label>
#Html.DropDownListFor(model => model.ServiceCode, new SelectList(Model.ServiceList, "Code", "WebName"), "Select a Service",
new { #class = "form-control", #style = "width:auto; margin-bottom:15px;" })
Then your Index Post method should be as follows:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if(ModelState.IsValid)
{
// do whatever you want with `viewModel.CarrierId` and `viewModel.ServiceCode` here
}
viewModel.CarrierList = db.Carriers.ToList();
viewModel.ServiceList = db.CarrierServices.ToList();
return View(viewModel);
}

How can I take data from two separated column to be shown in DropDownList using .Net MVC 5

I am new student developer in ASP.Net MVC 5. I need to take one data from my two seperated table column in SQL while inserting but I am taking only one value into my DropDownList. How can I do this? There is not properly info or source according to my research. Does anyone help me ?
Model:
public class PersonelModel
{
public int pid { get; set; }
public string pAd { get; set; }
public string pSoyad { get; set; }
public string yonetici { get; set; }
}
My Add Method:
public ActionResult PersonelAdd()
{
/*it does not work
ViewBag.yonetici = new SelectList(db.Personel, "pAd", "pAd" +" "+ "pSoyad");*/
/*it work*/
ViewBag.yonetici = new SelectList(db.Personel, "pAd", "pAd");
return View();
}
[HttpPost]
public ActionResult PersonelAdd(Personel pModel)
{
var personel = new Personel();
personel.pAd = pModel.pAd;
personel.pSoyad = pModel.pSoyad;
personel.yonetici = pModel.yonetici;
/*it works*/
ViewBag.yonetici = new SelectList(db.Personel, "pAd", "pAd", pModel.yonetici
);
// it does not work
// ViewBag.yonetici = new SelectList(db.Personel, "pAd", "pAd"+" "+"pSoyad", pModel.yonetici);
db.Personel.Add(personel);
db.SaveChanges();
return RedirectToAction("Index", "AdminUI");
}
My form:
<div class="form-group">
#Html.LabelFor(model => model.yonetici, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("yonetici", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.yonetici, "", new { #class = "text-danger" })
</div>
</div>
SelectList constructor takes field names (dataValueField and dataTextField) which have to exist in your model class.
You can add new field to describe what do you want to show as Text (note new Description property):
public class PersonelModel
{
public int pid { get; set; }
public string pAd { get; set; }
public string pSoyad { get; set; }
public string yonetici { get; set; }
public string Description
{
get
{
return pAd + " " + pSoyad;
}
}
}
Now you can show pAd + pSoyad as Text Value of SelectList:
ViewBag.yonetici = new SelectList(db.Personel, "pAd", "Description")

How to populate a dropdownlist based on Id from a SQL Database

I am trying to create a drop down list in one of my CRUD screens that are based on a different model in my program. Right now it inputs the values based on ID but I want it to be able to populate the names of instructors in a drop-down list.
I've tried a few different things using the select list but it didn't seem to work.
This is my model:
[Table("Section")]
public class Section
{
[Key]
public int Id { get; set; }
[DisplayName("Section")]
public int? section { get; set; }
public Nullable <int> instructor_id { get; set; }
public int location_id { get; set; }
public int modality_id { get; set; }
public int DOW_id { get; set; }
public int course_id { get; set; }
public virtual DOW DOW { get; set; }
public virtual Instructor Instructor { get; set; }
public virtual Location Location { get; set; }
public virtual Modality Modality { get; set; }
public virtual Course Course { get; set; }
This is my controller:
// GET: Sections/Create
public ActionResult Create()
{
return View();
}
// POST: Sections/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,section,startTime,endTime,startDate,endDate,isTap,isActive,instructor_id,location_id,modality_id,DOW_id,course_id")] Section section)
{
if (ModelState.IsValid)
{
db.Sections.Add(section);
db.SaveChanges();
return RedirectToAction("Index");
}
//ViewBag.instruct = new SelectList(db.Instructors, "Id", "lname", section.instructor_id);
return View(section);
}
And this is my view
<div class="form-group">
#Html.LabelFor(model => model.instructor_id, "instructor_id" , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.instructor_id, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.instructor_id, "", new { #class = "text-danger" })
</div>
</div>
You need to change the Get Method in Controller as follows:
// GET: Sections/Create
public ActionResult Create()
{
ViewBag.Instructors = db.Instructors.ToList();
return View();
}
Your HTML need to be changed as follows:
<div class="form-group">
#Html.LabelFor(model => model.instructor_id, "instructor_id" , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.instructor_id, new SelectList(ViewBag.Instructors, "Id", "Name"), new { #class = "form-control" } )
#Html.ValidationMessageFor(model => model.instructor_id, "", new { #class = "text-danger" })
</div>
</div>
Make sure field names are correct.
Taken form John Peters answers, kindly take a look
Create a data layer that retrieves a list of what you want. Then use EF to get all the states.
//assuming you have a table of states..
var states = db.States();
The states table should be a Unique list of states.
var selectList = new List<SelectListItem>();
foreach(var thing in states){
//if you got everything, thus the ID field for the value...
selectList.Add(new SelectListItem {Text =thing.State, Selected = false, Value = thing.ID);
}
Make sure in your Viewmodel class that selectlist is a public property.....and set to what you did above. You also need to provided a string for the view selection post back.
StatesSelectList = selectList;
public IEnumerable<SelectListItem> StatesSelectList {get;set;}
public string SelectedState {get;set;}
In your view, do this:
#Html.DropDownListFor(p => Model.SelectedState, Model.StatesSelectList)

MVC4 Validation DataAnnotations not working (not validating)

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);
}

Categories

Resources