Model not completely binding in POST - c#

Not understanding what I am overseeing here ...
I have a Model which I render in a View.
This is the SubscriptionViewModel:
public Subscription Subscription { get; set; }
public DayOfWeek DayOfWeek { get; set; }
public List<DateTime> SomeDates { get; set; }
In the View I have populated a table with data from the SubscriptionViewModel.
In each table row there is an If/else statement, where in each part I placed a Form so that the data can be posted back to the SubscriptionController, this is a pseudo example:
if (condition)
{
using (Html.BeginForm("Subscribe", "Subscription", FormMethod.Post))
{
#Html.ActionLink("text", "Subscribe", new { #date = item }, new { #class = "btn btn-warning btn-radius" })
}
}
else
{
using (Html.BeginForm("UnSubscribe", "Subscription", FormMethod.Post))
{
#Html.ActionLink("text", "UnSubscribe", new { #date = item }, new { #class = "btn btn-danger btn-radius" })
}
}
This is the SubscriptionContoller method being triggered:
[AllowAnonymous]
public ActionResult Subscribe(SubscriptionViewModel model, string date)
{
return View();
}
The POST is triggered and my Model is not NULL, the date parameter also has the correct value, the only thing which is lost is the parameters from Subscription Entity from my SubscriptionViewModel. They are not NULL but they just don't have their initial values when coming to the View initially...
No clue what I am overseeing, I tried using TempData.Keep() into the View (which I never needed to do before) but that doesn't work either.
Any suggestion is welcome!
Thank you!

This answer assumes you don't want the values for the Subscription to come from the form but rather stay as they were when the values are originally sent to the view which sounds like what you are after.
You need to include the values for the subscription in the form using hidden fields (see example 1). Alternatively you will need to populate the subscription in your post method in the same way that it is in your get method (see example 2).
Here are 2 simple examples demonstrating what I mean
Suppose this is your subscription class
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
}
Example 1
The HiddenField way
The controller
public class SubscriptionController : Controller
{
[AllowAnonymous]
public ActionResult Subscribe()
{
var viewModel = new SubscriptionViewModel
{
Subscription = GetSubscription()
};
return View(viewModel);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Subscribe(SubscriptionViewModel model, string date)
{
return View();
}
private Subscription GetSubscription()
{
return new Subscription
{
Id = 1,
Name = "My Subscription"
};
}
}
The view
#model {The namespace}.SubscriptionViewModel
#using (Html.BeginForm("Subscribe", "Subscription", FormMethod.Post))
{
//Other fields
#Html.HiddenFor(x => x.Subscription.Id)
#Html.HiddenFor(x => x.Subscription.Name)
<input type="submit" value="submit" />
}
Example 2
The get back in the controller way
The controller
public class SubscriptionController : Controller
{
[AllowAnonymous]
public ActionResult Subscribe()
{
var viewModel = new SubscriptionViewModel
{
Subscription = GetSubscription()
};
return View(viewModel);
}
[AllowAnonymous]
[HttpPost]
public ActionResult Subscribe(SubscriptionViewModel model, string date)
{
model.Subscription = GetSubscription();
return View();
}
private Subscription GetSubscription()
{
return new Subscription
{
Id = 1,
Name = "My Subscription"
};
}
}
The view
#model {The namespace}.SubscriptionViewModel
#using (Html.BeginForm("Subscribe", "Subscription", FormMethod.Post))
{
//Other fields
<input type="submit" value="submit" />
}

Related

Pass parameter from a view to a controller

I essentially want to be able to pass a parameter to a view (which works) which will then be able to be passed further to a controller if the user presses a <a> link (which does not work as intended).
My initial view is defined as:
#model IEnumerable<Path.To.MyClass>
// Do stuff....
<a asp-controller="MyController" asp-action="MyView" asp-route-myProducts = "Model">Update</a>
Where my MyView within MyController is defined as:
[HttpGet]
public IActionResult MyView(IEnumerable<MyClass> myProducts)
{
//Insert a breakpoint
}
Essentially I don't get any errors, but within the view in which I have my link myProducts has a bunch of elements. However when I access the breakpoint within MyView myProducts is empty.
Does anyone know what the issue may be?
I've tried to use [HttpPost] instead of [HttpGet] for MyView, but I seemed to have gotten an infinite loop when I ran the program using IIS Express then.
EDIT
I've tried
<input type="button" onclick="location.href='#Url.Action("MyView", "MyController", Model)'">Update</button>
now as well with the same issue (the parameter for the controller is empty without any errors).
EDIT2:
I've also tried making a new class like this:
public class ProductList
{
List<MyClass> products {get;set;}
}
And consequently replaced the MyView parameter to be ProductList myProducts instead of IENumerable<MyClass> myProdcuts. In the #url.Action within the view I've tried to use <input type="button" onclick="location.href='#Url.Action("MyView", "MyController", Model.AsList())'">Update</button> as well. This gives the same issue (i.e. no error, but no elements within ProductList myProducts in my controller).
If you want to pass a list with <a></a>,you can try to serialize the model in view,and then deserialize the data to the model in action.Here is a demo:
Model:
public class ProductList
{
public List<MyClass> products { get; set; }
}
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
}
MyView1.cshtml:
#model Path.To.ProductList
#using Newtonsoft.Json;
<a href=#Url.Action("MyView","Test1",new {myProducts = JsonConvert.SerializeObject(Model) })>Update</a>
Controller:
[HttpGet]
public IActionResult MyView1()
{
ProductList l = new ProductList { products = new List<MyClass> { new MyClass { Id = 1, Name = "class1" }, new MyClass { Id = 2, Name = "class2" }, new MyClass { Id = 3, Name = "class3" } } };
return View(l);
}
[HttpGet]
public IActionResult MyView(string myProducts)
{
ProductList p = JsonConvert.DeserializeObject<ProductList>(myProducts);
return Ok();
}
result:
I will give an example of the general logic of posting information.
In the example I give, I pass a list of products to the controller.
model classes
public class ProductList
{
List<Product> products { get; set; }
}
public class Product
{
public int ID { set; get; }
public string ProductName { set; get; }
}
in controller
[HttpGet]
public IActionResult MyView()
{
var model = new ProductList();
model.products = new List<Product>();
Viewbag.Count = 5;
return View(model);
}
[HttpPost]
public IActionResult MyView(ProductList model)
{
foreach(Product item in model.products)
{
//do somethings
}
Viewbag.Count = 5;
return View(model);
}
in view
#model yournamespace.ProductList
#{
ViewBag.MTitle = "my view";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("MyView", "controllername", FormMethod.Post, new { enctype = "multipart/form-data"})) {
#Html.AntiForgeryToken() #Html.ValidationSummary(true)
#for(var i = 0; i < Viewbag.Count; i++) {
<div class="row">
>#Html.TextBoxFor(model => model[i].ID)
</div>
<div class="row">
#Html.TextBoxFor(model => model[i].ProductName)
</div>
}
<div class="row">
<button class="btn-control">add</button>
</div>
}

Post Dropdown values/keys using FormCollection

I have a form that posts to an action:
public ActionResult Index()
{
CheckDataVM vm = new CheckDataVM();
vm.SerialNumbers = GetAllSerials();
vm.CustomerNames = GetAllCustomers();
vm.DateFrom = DateTime.Now.AddDays(-1);
vm.DateTo = DateTime.Now;
return View(vm);
}
[HttpPost]
public ActionResult Index(CheckDataVM v)
{
CheckDataVM vm = new CheckDataVM();
vm.SerialNumbers = GetAllSerials();
var s = vm.SerialNumbers.First().Text.ToString();
vm.Channels = GetAllChannels(s);
vm.DateFrom = DateTime.Now.AddDays(-1);
vm.DateTo = DateTime.Now;
return View(vm);
}
In my Razor view, I have post:
#using (Html.BeginForm("Index", "CheckData", FormMethod.Post, new { id = "SerialsForm" }))
{
<div class="card-body" style="font-size: small;">
<div class="form-group">
#Html.DropDownListFor(x => x.SelectedSerial, Model.SerialNumbers, new { #class = "form-control form-control-sm" })
<input type="submit" value="Submit" />
</div>
</div>
}
The view model is:
public class CheckDataVM
{
public string CustomersName { get; set; }
public string SelectedSerial { get;set; }
[Display(Name="Select a serial number")]
public IEnumerable<SelectListItem> SerialNumbers { get; set; }
}
The dropdowns work, but when I submit the form the only thing I get back is the object name (SerialNumbers) as the key.
I want to be able to get the selected item from the dropdown list and pass this to the FormCollection in the Httpost of the Index action. For the life of me, I cannot get it to work!
I am expecting to see a key called 'CustomersDdl' and it's value. For example, if I had a dropdown full of countries and I pick England, I am expecting to see a value come back in the FormCollection saying England.
What am I doing wrong?
The value to postback is depending on how you create "SelectListItem", in your case it is in the method "GetAllSerials()"
vm.SerialNumbers = serialNumbers.Select(serial => new SelectListItem
{
Selected = serial.id == vm.SelectedSerial ? true : false,
Text = serial.Name,
Value = serial.Name
}).ToList();

MVC5 : View not submitting correct ViewModel

Minimal Code Structure
I have two ViewModels cmsViewModel and appointments
public partial class cmsViewModel
{
public string rows { get; set; }
public DateTime appt_date_time { get; set; }
public int appt_id { get; set; }
public List<community_meeting_schedule> lst { get; set; }
public cmsViewModel()
{
rows = null;
appt_date_time = DateTime.Today;
lst = null;
appt_id = -1;
}
}
public partial class appointments
{
public int appt_client_id { get; set; }
public int customer_id { get; set; }
[Key]
public int appt_id { get; set; }
public DateTime appt_date_time { get; set; }
public DateTime time_stamp { get; set; }
}
The action method in controller looks like:
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, string Command)
{
return View("CMSchedule", model);
}
The View CMSConfim.cshtml looks like below:
#model Scheduler_MVC.Models.cmsViewModel
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
{
#Html.HiddenFor(model => model.appt_id, new { id = "appt_id" })
#Html.HiddenFor(model => model.appt_client_id, new { id = "appt_client_id" })
#Html.HiddenFor(model => model.appt_date_time)
#Html.HiddenFor(model => model.appt_status)
#Html.HiddenFor(model => model.appt_type)
#Html.HiddenFor(model => model.lst)
<input type="submit" value="Back" id="backBtn" class="btn btn-default" />
<input type="button" value="Submit" id="submitBtn" class="btn btn-default" style="display:inline-block;" />
}
I would like to add that I am able to render correct values in the display fields on the form. The error comes while submitting the form.
Error
Now when I submit the form through Back. I get the following error.
The model in the dictionary is of type 'cmsViewModel' but required is that of type 'appointments'
Please suggest what I might be doing wrong.
Your post view model type is "appointments", but your Html.BeginForm is also routing to an "AppointmentsController"
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
This route is expecting the following
public class AppointmentsController : Controller
{
public Action CMSConfirm(appointments model)
{
}
}
Update your Html.BeginForm if the controller name is wrong
I don't see a match for parameter "Command" either.
Why do you have in your razor sintax cmsViewModel? You expect to submit appointmets but there is cmsVM. Also in your controller you are expecting cmsVM. You need to provide appointmentsVM in razor and also to expect it in your Controller.
[HttpPost]
public ActionResult CMSConfim(appointments model, string Command)
{
return View("CMSchedule", model);
}
Or if you want to get both in controller.
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, appointments appoint, string Command)
{
return View("CMSchedule", model);
}

Adding new data to multiple tables using dropdownlist

Using MVC4 am wanting to implement functionality which will allow a user to add new items to the database.
I've managed to achieve this adding items to a single table, but now I need to display data from multiple tables, then populate the added / selected data to those tables.
I have these 3 tables
Threats
ID
Description
ThreatHasSecurityEvent
ThreatID
SecurityEventID
SecrutiyEvents
ID
Description
And here's my code so far:
ViewModel
public class ThreatWithSecurityEvents
{
public Threat Threat { get; set; }
public SecurityEvent SecurityEvent { get; set; }
public List<int> SecurityEventIds { get; set; }
public ThreatWithSecurityEvents()
{
SecurityEventIds = new List<int>();
}
}
Get Controller
[HttpGet]
public ActionResult AddNewThreat()
{
ThreatWithSecurityEvents ViewModel = new ThreatWithSecurityEvents();
var SecurityEvents = _DBContext.SecurityEvents.Select(x => new SelectListItem()
{
Text = x.Description,
Value = x.ID.ToString()
});
ViewBag.SecurityEventDropdown = SecurityEvents;
return View(ViewModel);
}
View
#model RiskAssesmentApplication.Models.ThreatWithSecurityEvents
#{
ViewBag.Title = "AddNewThreat";
//Layout = "~/Views/Shared/MasterLayout.cshtml";
}
<div style="font-family: Calibri">
<h2>AddNewThreat</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Threat</legend>
#using (Html.BeginForm("Add New Threat", "Threats"))
{
Html.HiddenFor(model => model.SecurityEventIds);
<div class="editor-label">
#Html.LabelFor(model => #Model.Threat.Description, "Threat Description")
</div>
<div class="editor-field">
#Html.EditorFor(model => #Model.Threat.Description)
#Html.ValidationMessageFor(model => #Model.Threat.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => #Model.SecurityEvent.Description, "Associated Security Event")
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SecurityEventIds, ViewBag.SecurityEventDropdown as IEnumerable<SelectListItem>)
</div>
<p>
<input type="submit" value="Add New" />
</p>
}
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</div>
Am unsure how to implement the Post Action Method and a Save Method in the repository.
Previously I could inject a new Threat Object and send it to the edit view doing something like:
Previous Get Method - AddNewThreat
[HttpGet]
public ActionResult AddNewThreat()
{
return View("EditThreat", new Threat());
}
and I would then use the EditThreat Action Method to post back
Previous Post Action - AddNewThreat
[HttpPost]
public ActionResult EditThreat(Threat Threat)
{
if (ModelState.IsValid)
{
repository.SaveThreat(Threat);
TempData["message"] = string.Format("{0} new description has been saved", Threat.Description);
return RedirectToAction("GetThreat", new { ThreatID = Threat.ID });
}
else
{
// something is incorrect!
return View(Threat);
}
}
Previous Save Method - SaveThreat From Repository
public void SaveThreat(Threat Threat)
{
if (Threat.ID == 0)
{
_context.Threats.Add(Threat);
}
else
{
Threat dbEntry = _context.Threats.Find(Threat.ID);
if (dbEntry != null)
{
dbEntry.Description = Threat.Description;
}
}
_context.SaveChanges();
}
That's as far as I have got so far.
I want the user to be able to enter a new threat description and then select a security event or multiple events from a drop down list which will be associated with the new threat.
I realize am going to have to change the post back action method in the controller and the Save method in my repository, but I cant work out how to get both the new Threat description and the existing security events saved back to the database. I've had a search but as of yet haven't found / understood anything.
Any advice/help would be great.
Thanks
You view model should be
public class NewThreatVM
{
public string Description { get; set; } // add validation attributes as required
public List<int> SelectedSecurityEvents { get; set; }
public SelectList SecurityEventList { get; set; } // or IEnumerable<SelectListItem>
}
Side note: The Threat.ID property is not required in a create view, however if your want to use this for editing an existing Threat as well, add property int? ID and use if (model.ID.HasValue) in the POST method to determine if its a new or existing Threat
and the simplified view
#model yourAssembly.NewThreatVM
#Html.BeginForm())
{
#Html.TextBoxFor(m => m.Description)
#Html.ListBoxFor(m => m.SelectedSecurityEvents, Model.SecurityEventList)
<input type="Submit" value="Create" />
}
Side notes: Your view should not include a hidden input for the Security Event ID's (you cannot bind an input to a complex object or collection)
then the controller
public ActionResult Create()
{
NewThreatVM model = new NewThreatVM model();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(NewThreatVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize new data model and map properties from view model
Threat threat = new Threat() { Description = model.Description };
// Save it (which will set its ID property)
_context.Threats.Add(Threat);
_context.SaveChanges();
// Save each selected security event
foreach (int selectedEvent in model.SelectedSecurityEvents)
{
ThreatHasSecurityEvent securityEvent = new ThreatHasSecurityEvent()
{
ThreatID = threat.ID,
SecurityEventID = selectedEvent
};
_context.ThreatHasSecurityEvents.Add(securityEvent);
}
_context.SaveChanges();
return RedirectToAction("GetThreat", new { ThreatID = threat.ID });
}
private void ConfigureViewModel(NewThreatVM model)
{
var securityEvents = _context.SecurityEvents;
model.SecurityEventList = new SelectList(securityEvents, "ID", "Description");
}
I believe the easiest way to achieve this, is "dividing" your form into separated steps.
You have2 entities: Threats, SecurityEventID
Threat has a collection of SecurityEvents
Create a form to add/edit Threats (url: Threats/Add | Threats/Edit/ThreatId)
Create a form to add/delete Events of an existing Threat (url: Threats/AddEvent/ThreatIdHere
Use custom ViewModels instead of the original class to send data to controller. Examples:
public class AddThreatViewModel
{
public string Description { get; set; }
//since it's a add view model, we dont need a ThreatId here
}
[HttpPost]
public ActionResult AddThreat(AddThreatViewModel model)
{
//convert the view model to Threat, add to database
}
public class AddThreatEvent
{
public int ThreatId { get; set; }
public int SecrutiyEventId { get; set; }
}
[HttpPost]
public ActionResult AddThreatEvent(AddThreatEventmodel)
{
//add threat event into existing threat
}

selected Item of DropDownList

How can I get selected Item of my DropDownList?
#using (Html.BeginForm("Doctors", "User", FormMethod.Get))
{
<input name="pageNumber" type="hidden" value="1" /><text>Hospital:</text><br />
#Html.DropDownList("HospitalId", (IEnumerable<SelectListItem>)ViewBag.HospitalList, new { style = "width:90%" }) <br />
<button type="submit" class="btn btn-mini"> Search </button>
}
This question isn't very clear as to when / where you want to get your drop down list item.
I will assume you want to pick it up in the controller for now. What you will need to do is make sure its included in your post to the controller and is using the correct name to be picked up when the form is read server side.
Here is an example:
#Html.DropDownList("SubworkType", "Select Work Item", new { #Id = "SubworkId" , #Name = "SubworkId" })
If You are looking to grab the selected value on the view side you could do something like this:
var workerType = $("#WorkerTypeFilter option:selected").val()
It should simply be accessible as a parameter in your Controller's action method.
public ActionResult Doctors(string pageNumber, string HospitalId)
{
// make use of HospitalId
...
}
I would prefer to avoid dynamic variables like ViewBag/ViewData and stick with strong types.
Use a ViewModel
public class AssignDoctorViewModel
{
//Other Properties also
public IEnumerable<SelectListItem> Hospitals { set;get;}
public int SelectedHospital { set;get;}
}
And in my GET Action method, I would return this with the filled properties
public ActionResult AssignDoctor
{
var vm=new AssignDoctorViewModel();
vm.Hospitals= new[]
{
new SelectListItem { Value = "1", Text = "Florance" },
new SelectListItem { Value = "2", Text = "Spark" },
new SelectListItem { Value = "3", Text = "Henry Ford" },
};
// can replace the above line with loading data from Data access layer.
return View(vm);
}
Now in your view which is strongly typed to our ViewModel class
#model AssignDoctorViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedHospital, Model.Hospitals, "Select..")
<input type="submit" value="save" />
}
Now in your HTTPPOST Action method, you can get the Selected value by accessing the SelectedHospital Property
public ActionResult AssignDoctor(AssignDoctorViewModel model)
{
//check for model.SelectedHospital value
}
Shyju's answer is good, I just want to expand on what Shyju has said with some pointers.
Use a view model per view with just the data that you need, anything not used please remove.
Your domain model could look like this:
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
}
Your view model could look like this:
public class DoctorHospitalViewModel
{
public int HospitalId { get; set; }
public IEnumerable<Hospital> Hospitals { get; set; }
}
Your view could look like this (I put the dropdown in a table for display purposes):
<table>
<tr>
<td class="edit-label">Hospital <span class="required">**</span></td>
<td>
#Html.DropDownListFor(
x => x.HospitalId,
new SelectList(Model.Hospital, "Id", "Name", Model.HospitalId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.HospitalId)
</td>
</tr>
</table>
Your controller might look like this and assuming you are wanting to use this dropdown in a create view:
public class HospitalController : Controller
{
private readonly IHospitalRepository hospitalRepository;
public HospitalController(IHospitalRepository hospitalRepository)
{
// Check that hospitalRepository is not null
this.hospitalRepository = hospitalRepository;
}
public ActionResult Create()
{
DoctorHospitalViewModel viewModel = new DoctorHospitalViewModel
{
Hospitals = hospitalRepository.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult Create(DoctorHospitalViewModel viewModel)
{
// Check that viewModel is not null
if (!ModelState.IsValid)
{
viewModel.Hospitals = hospitalRepository.GetAll();
return View(viewModel);
}
// Do what ever needs to be done
// You can get the selected hospital id like this
int selectedHospitalId = viewModel.HospitalId;
return RedirectToAction("List");
}
}
I hope this helps.

Categories

Resources