Populating A Select List Dynamically MVC - asp.net-mvc

I am attempting to create a cascading dropdown. My Controller looks like this to initialize the view..
public ActionResult Create()
{
var model = new RoundDetailViewModel();
model.AvailableFacilities = new SelectList(db.Facilities, "FacilityId", "Facility_Name");
model.AvailableCourses = new SelectList(Enumerable.Empty<Course>(), "CourseId", "Course_Name");
model.AvailableTeeTypes= new SelectList(Enumerable.Empty<TeeType>(), "TeeTypeId", "Name");
return View(model);
}
This will populate the first dropdown, and also create 2 empty dropdown as expected.
Now on a selection of the first dropdown I want to call an Action in my controller to populate the second dropdown. This is where I am a little foggy on the code in the action to populate the second dropdown. I want to use something like this to trigger calling the action..
$("#ddlFacility").change(function () {
var selectedFacility = $(this).val();
if (selectedFacility != null && selectedFacility != '') {
$.getJSON("#Url.Action("GetCourse")", { facility: selectedFacility }, function (courses) {
var coursesSelect = $('#ddlCourse');
coursesSelect.empty();
$.each(courses, function (index, course) {
coursesSelect.append($('<option/>', {
value: course.value,
text: course.text
}));
});
});
}
});
public ActionResult Courses(int facilityId)
{
//WHAT GOES HERE TO POPULATE SELECT LIST??
}

Need to return JsonResult and allow Get (if that is what you decide to do) Alternatively you need to make it a POST and do a POST to it.
Something like
public JsonResult GetCourses(int id)
{
var Courses= db.Courses.Where(a=>facilityId==id).ToList();
SelectList list = new SelectList(Courses,"Text", "Value");
return Json(list , JsonRequestBehavior.AllowGet);
}

Related

.Net 6 Razor View Not Updating after Post

I am passing a complex model to a Razor View. The data in the model is used in a for-each loop in the view to display a list of products that are for sale in bootstrap cards. There are no forms on the view.
On the left side of the page are list elements that contain nested list elements. These represent product categories. When the user selects a category, I pass the product category to an action method in the controller as an integer. This will build a new model based on the selected category. The new model is then passed back to the view.
At this point, I want to display the categories that are in the category that the user selected. But, the view is not updating even though the model is now different.
I have been reading about this issue, and one forum post I read suggested that this is by design. I read somewhere that in order to achieve what I need to achieve, I have to clear the model state. But I cannot clear the model state because I am not passing the model back to the controller, and you can only pass the model back to the controller by reconstructing the model to be passed in an ajax call for example. I spent all day yesterday trying to get ajax to work and every time I executed the post, the model was populated in the JavaScript function and null or empty in the controller.
Is there another way to force the view to update upon sending a new model to the view? Does anyone have any suggestions?
<script type="text/javascript">
function AddSubCategory(elem) {
event.stopPropagation();
var e = document.querySelectorAll(".products");
e.forEach(box => {
box.remove();
});
var categoryId= $(elem).data('sub-id');
console.log(`Id: ${categoryId}`);
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
}
if (request != null) {
var url = `/Products/GetProductsByCategory?categoryId=${categoryId}`;
request.open("POST", url, false);
};
//request.onreadystatechange = function () {
// if (request.readyState == 4 && request.status == 200) {
// }
//};
request.send();
// model = JSON.stringify(model);
// console.log(model);
// $.ajax({
// type: "Post",
// url: '#Url.Action("GetProductsByCategory", "Products")',
// data: JSON.stringify({"categoryId": categoryId}),
// contentType: 'charset=utf-8;application/json',
// cache: false,
// complete: function (data) {
// }
//});
}
// Load all products
public async Task<IActionResult> Index()
{
ReadCartIDFromCookie();
string SubDomain = GetSubDomain(HttpContext);
var allProducts = await _productService.GetAllProductsWithImagesAsync(SubDomain);
var productCategoryLookup = await _productService.GetAllProductCategoryLookupAsync();
ViewBag.host = SubDomain;
ViewBag.productCategoryLookup = productCategoryLookup;
return View("Index", allProducts);
}
//Load filtered list of products
[HttpPost]
public async Task<IActionResult> GetProductsByCategory(int categoryId = -1)
{
ReadCartIDFromCookie();
string SubDomain = GetSubDomain(HttpContext);
var allProducts = await _productService.GetAllProductsWithImagesAsync(SubDomain, categoryId);
var productCategoryLookup = await _productService.GetAllProductCategoryLookupAsync();
ViewBag.host = SubDomain;
ViewBag.productCategoryLookup = productCategoryLookup;
return View("Index", allProducts);
}
I was able to resolve my issue thanks to this post: https://stackoverflow.com/a/66277911/1561777
I did have a bit of trouble trying to figure it out because it was not completely clear, but I could tell it was what I needed to do. I ended up utilizing a partial view for the display of my products. Here is my final code.
//My index.cshtml razor view
<div class="col" id="product"></div>//partial view will be loaded onto this div
//Javascript function that gets the chosen filter category Id
#section Scripts
{
<script type="text/javascript">
function AddSubCategory(elem) {
event.stopPropagation();
//get the id
var categoryId= $(elem).data('sub-id');
console.log(`Id: ${categoryId}`);
//call controller Products, controller action GetProductsByCategory and pass the categoryId
//The partial view will be returned and load onto the div id #product
$('#product').load(`/Products/GetProductsByCategory?categoryId=${categoryId}`);
}
//when the index view first loads, load all products (i.e. category -1)
$('#product').load(`/Products/GetProductsByCategory`);
</script>
}
public async Task<IActionResult> GetProductsByCategory(int categoryId = -1)
{
ReadCartIDFromCookie();
string SubDomain = GetSubDomain(HttpContext);
var allProducts = await _productService.GetAllProductsWithImagesAsync(SubDomain, categoryId);
var productCategoryLookup = await _productService.GetAllProductCategoryLookupAsync();
ViewBag.host = SubDomain;
ViewBag.productCategoryLookup = productCategoryLookup;
//notice that I am using PartialView method
//Returning View was including the full website (i.e. header and footer).
//_Products is my partial view. allProducts is the
//view model that is being passed to the partial view.
return PartialView("_Products", allProducts);
}
Now, everytime I select a new category, the partial view is reloaded. Also, when first loading the view, all products are displayed as expected.

Reference DropDownList selected value from enclosing Form

I'm just getting started with MVC5 (from WebForms), and dropdownlist bindings are giving me some fits.
I'd like to get this working using a GET request back to the page, with a selected value parameter. I'm hopeful that I can specify the route arguments in the form itself, so I'd like to reference the DDL's SelectedValue.
<p>
#using (Html.BeginForm("Index", "Profile", FormMethod.Get, new { id = WHATDOIPUTHERE} )) {
#Html.AntiForgeryToken()
#Html.DropDownList("ApplicationID", new SelectList(ViewBag.ApplicationList, "ApplicationID", "ApplicationName", ViewBag.SelectedApplicationId), new {onchange = "this.form.submit();"})
}
</p>
I can make it work with a POST form, but that requires a second controller method so I end up with
public ActionResult Index(long? id) {
ConfigManager config = new ConfigManager();
//handle application. default to the first application returned if none is supplied.
ViewBag.ApplicationList = config.GetApplications().ToList();
if (id != null) {
ViewBag.SelectedApplicationId = (long)id;
}
else {
ViewBag.SelectedApplicationId = ViewBag.ApplicationList[0].ApplicationID; //just a safe default, if no param provided.
}
//handle profile list.
List<ProfileViewModel> ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId) select new ProfileViewModel(p)).ToList();
return View(ps);
}
//POST: Profile
//read the form post result, and recall Index, passing in the ID.
[HttpPost]
public ActionResult index(FormCollection collection) {
return RedirectToAction("Index", "Profile", new {id = collection["ApplicationId"]});
}
It would be really nice to get rid of the POST method, since this View only ever lists child entities.
What do you think?
You can update your GET action method parameter name to be same as your dropdown name.
I also made some small changes to avoid possible null reference exceptions.
public ActionResult Index(long? ApplicationID) {
var config = new ConfigManager();
var applicationList = config.GetApplications().ToList();
ViewBag.ApplicationList = applicationList ;
if (ApplicationID!= null) {
ViewBag.SelectedApplicationId = ApplicationID.Value;
}
else
{
if(applicationList.Any())
{
ViewBag.SelectedApplicationId = applicationList[0].ApplicationID;
}
}
var ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId)
select new ProfileViewModel(p)).ToList();
return View(ps);
}

ASP.NET MVC Master Detail Entry Form

I’m trying to implement an order entry form using ASP.NET MVC but facing a lot of difficulties. All the samples that I found are related to viewing master detail forms, and none for adding or editing.
Suppose I have two tables: Order and OrderLines that are related to each others with one-to-many relationship. In the main view I had a “New” button when clicked it should show a new order view composed of the order fields, a grid that shows the order lines, and a “Save” button that when clicked will persist the whole order along with its lines into a database. The grid should have three buttons: “Add Line”, “Edit Line”, and “Delete Line”. When the “Add Line” is clicked a new view should be shown that allows the user to add the line to the order view grid lines –at this stage the database is not affected-. When the user clicks “Edit Line” a view will be shown that allows the user to edit the selected line and when done update the order grid lines.
The most difficult problems are:
How to pass the order and its lines collection between the order view and the order line views?
How to update the order view based on changes in the order line view?
And how to persist changes between views without the database being involved?
Is there a concrete example that shows how to implement this using MVC?
Your help and feedback is appreciated.
Pleas have a look at my blog post on creating master detail form in asp.net mvc. it also contains demo project that you can download
Unlike WebForms, ASP.NET MVC does not try to hide the stateless nature of HTTP. To work with a complex object across multiple forms you have a couple of options:
Save the object on the server with each change so that the updated object is available using only an id
Use jquery to populate the order line form and save details to the main form
I usually go with the client side option myself, with the main form having hidden fields for the data that will be edited in the subform. You may find the server side option easier though - if you really don't want to involve the database you can keep your partially updated object in the session.
Step 1: Create view model
public class OrderVM
{
public string OrderNo { get; set; }
public DateTime OrderDate { get; set; }
public string Description { get; set; }
public List<OrderDetail> OrderDetails {get;set;}
}
Step 2: Add javascript for add order lines
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
$(function () {
$('#orderDate').datepicker({
dateFormat : 'mm-dd-yy'
});
});
$(document).ready(function () {
var orderItems = [];
//Add button click function
$('#add').click(function () {
//Check validation of order item
var isValidItem = true;
if ($('#itemName').val().trim() == '') {
isValidItem = false;
$('#itemName').siblings('span.error').css('visibility', 'visible');
}
else {
$('#itemName').siblings('span.error').css('visibility', 'hidden');
}
if (!($('#quantity').val().trim() != '' && !isNaN($('#quantity').val().trim()))) {
isValidItem = false;
$('#quantity').siblings('span.error').css('visibility', 'visible');
}
else {
$('#quantity').siblings('span.error').css('visibility', 'hidden');
}
if (!($('#rate').val().trim() != '' && !isNaN($('#rate').val().trim()))) {
isValidItem = false;
$('#rate').siblings('span.error').css('visibility', 'visible');
}
else {
$('#rate').siblings('span.error').css('visibility', 'hidden');
}
//Add item to list if valid
if (isValidItem) {
orderItems.push({
ItemName: $('#itemName').val().trim(),
Quantity: parseInt($('#quantity').val().trim()),
Rate: parseFloat($('#rate').val().trim()),
TotalAmount: parseInt($('#quantity').val().trim()) * parseFloat($('#rate').val().trim())
});
//Clear fields
$('#itemName').val('').focus();
$('#quantity,#rate').val('');
}
//populate order items
GeneratedItemsTable();
});
//Save button click function
$('#submit').click(function () {
//validation of order
var isAllValid = true;
if (orderItems.length == 0) {
$('#orderItems').html('<span style="color:red;">Please add order items</span>');
isAllValid = false;
}
if ($('#orderNo').val().trim() == '') {
$('#orderNo').siblings('span.error').css('visibility', 'visible');
isAllValid = false;
}
else {
$('#orderNo').siblings('span.error').css('visibility', 'hidden');
}
if ($('#orderDate').val().trim() == '') {
$('#orderDate').siblings('span.error').css('visibility', 'visible');
isAllValid = false;
}
else {
$('#orderDate').siblings('span.error').css('visibility', 'hidden');
}
//Save if valid
if (isAllValid) {
var data = {
OrderNo: $('#orderNo').val().trim(),
OrderDate: $('#orderDate').val().trim(),
//Sorry forgot to add Description Field
Description : $('#description').val().trim(),
OrderDetails : orderItems
}
$(this).val('Please wait...');
$.ajax({
url: '/Home/SaveOrder',
type: "POST",
data: JSON.stringify(data),
dataType: "JSON",
contentType: "application/json",
success: function (d) {
//check is successfully save to database
if (d.status == true) {
//will send status from server side
alert('Successfully done.');
//clear form
orderItems = [];
$('#orderNo').val('');
$('#orderDate').val('');
$('#orderItems').empty();
}
else {
alert('Failed');
}
$('#submit').val('Save');
},
error: function () {
alert('Error. Please try again.');
$('#submit').val('Save');
}
});
}
});
//function for show added items in table
function GeneratedItemsTable() {
if (orderItems.length > 0) {
var $table = $('<table/>');
$table.append('<thead><tr><th>Item</th><th>Quantity</th><th>Rate</th><th>Total</th></tr></thead>');
var $tbody = $('<tbody/>');
$.each(orderItems, function (i, val) {
var $row = $('<tr/>');
$row.append($('<td/>').html(val.ItemName));
$row.append($('<td/>').html(val.Quantity));
$row.append($('<td/>').html(val.Rate));
$row.append($('<td/>').html(val.TotalAmount));
$tbody.append($row);
});
$table.append($tbody);
$('#orderItems').html($table);
}
}
});
</script>
Step 3: Create an action for save data
[HttpPost]
public JsonResult SaveOrder(OrderVM O)
{
bool status = false;
if (ModelState.IsValid)
{
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
Order order = new Order { OrderNo = O.OrderNo, OrderDate = O.OrderDate, Description = O.Description };
foreach (var i in O.OrderDetails)
{
//
// i.TotalAmount =
order.OrderDetails.Add(i);
}
dc.Orders.Add(order);
dc.SaveChanges();
status = true;
}
}
else
{
status = false;
}
return new JsonResult { Data = new { status = status} };
}
you can download source code and video tutorial
You could try Telericks free MVC grid control...
http://demos.telerik.com/aspnet-mvc/grid/hierarchyserverside
Just off the top of my head (a kind of brain dump)...
You could have a main grid part of the form. This would be full view loaded from an action (either with an order number or not depending on loading an existing one or not).
When clicking an event (new or edit) this could open a partial view in a "lightbox" style. This would then pass back a json object back to the main form.
The passed json object would then be rendered using templating to the bottom of the table (for a new one) or update an existing record. This could also be saved back to the server in the same ajax call. Or just update the client side and need the user to click a save button.
An isDirty flag will be needed so any changes set it to true and the when the browser tries to leave or close etc. then you can prompt the user to save or not.
Hope this helps.
edit
Not tried this but could be interesting with the none db aspect of your question click
Step 3: Create an action for save data.
[HttpPost]
public JsonResult SaveOrder(OrderVM O)
{
bool status = false;
if (ModelState.IsValid)
{
using (ManageMobileStoreWebContext dc = new ManageMobileStoreWebContext())
{
//Random rnd = new Random();
//OrderID = rnd.Next(),
Order order = new Order { OrderNo = O.OrderNo, OrderDate = O.OrderDate, Description = O.Description };
foreach (var i in O.OrderDetails)
{
if(order.OrderDetails == null)
{
order.OrderDetails = new List<OrderDetail>();
}
// i.TotalAmount =
order.OrderDetails.Add(i);
//dc.OrderDetails.Add(i);
}
dc.Orders.Add(order);
dc.SaveChanges();
status = true;
}
}
else
{
status = false;
}
return new JsonResult { Data = new { status = status } };
}

Rewriting the URL for a Controllers Action Method

I have a controller called Person and it has a post method called NameSearch.
This method returns RedirectToAction("Index"), or View("SearchResults"), or View("Details").
The url i get for all 3 possibilities are http://mysite.com/Person/NameSearch.
How would i change this to rewrite the urls to http://mysite.com/Person/Index for RedirectToAction("Index"), http://mysite.com/Person/SearchResults for View("SearchResults"), and http://mysite.com/Person/Details for View("Details").
Thanks in advance
I'm assuming your NameSearch function evaluates the result of a query and returns these results based on:
Is the query valid? If not, return to index.
Is there 0 or >1 persons in the result, if so send to Search Results
If there is exactly 1 person in the result, send to Details.
So, more of less your controller would look like:
public class PersonController
{
public ActionResult NameSearch(string name)
{
// Manage query?
if (string.IsNullOrEmpty(name))
return RedirectToAction("Index");
var result = GetResult(name);
var person = result.SingleOrDefault();
if (person == null)
return RedirectToAction("SearchResults", new { name });
return RedirectToAction("Details", new { id = person.Id });
}
public ActionResult SearchResults(string name)
{
var model = // Create model...
return View(model);
}
public ActionResult Details(int id)
{
var model= // Create model...
return View(model);
}
}
So, you would probably need to define routes such that:
routes.MapRoute(
"SearchResults",
"Person/SearchResults/{name}",
new { controller = "Person", action = "SearchResults" });
routes.MapRoute(
"Details",
"Person/Details/{id}",
new { controller = "Person", action = "Details" });
The Index action result will be handled by the default {controller}/{action}/{id} route.
That push you in the right direction?

ASP.NET MVC: Server Validation & Keeping URL paramters when returning the view

I currently have the following code for the POST to edit a customer note.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditNote(Note note)
{
if (ValidateNote(note))
{
_customerRepository.Save(note);
return RedirectToAction("Notes", "Customers", new { id = note.CustomerID.ToString() });
}
else
{
var _customer = _customerRepository.GetCustomer(new Customer() { CustomerID = Convert.ToInt32(note.CustomerID) });
var _notePriorities = _customerRepository.GetNotePriorities(new Paging(), new NotePriority() { NotePriorityActive = true });
IEnumerable<SelectListItem> _selectNotePriorities = from c in _notePriorities
select new SelectListItem
{
Text = c.NotePriorityName,
Value = c.NotePriorityID.ToString()
};
var viewState = new GenericViewState
{
Customer = _customer,
SelectNotePriorities = _selectNotePriorities
};
return View(viewState);
}
}
If Validation fails, I want it to render the EditNote view again but preserve the url parameters (NoteID and CustomerID) for something like this: "http://localhost:63137/Customers/EditNote/?NoteID=7&CustomerID=28"
Any ideas on how to accomplish this?
Thanks!
This action is hit by using a post. Wouldn't you want the params to come through as part of the form rather than in the url?
If you do want it, I suppose you could do a RedirectToAction to the edit GET action which contains the noteId and customerId. This would effectively make your action look like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditNote(Note note)
{
if (ValidateNote(note))
{
_customerRepository.Save(note);
return RedirectToAction("Notes", "Customers", new { id = note.CustomerID.ToString() });
}
//It's failed, so do a redirect to action. The EditNote action here would point to the original edit note url.
return RedirectToAction("EditNote", "Customers", new { id = note.CustomerID.ToString() });
}
The benefit of this is that you've removed the need to duplicate your code that gets the customer, notes and wotnot. The downside (although I can't see where it does it here) is that you're not returning validation failures.

Resources