The base functionality I wish to achive is that the contents of a table are updated when a dropdownlist item is selected. This will update when the user makes a new selection and retrieve new information from the database and repopulate the table.
It's also worth noting that the DropDownListFor that I want the .change() to work with is not contained within the AjaxForm but appears elsewhere on the page (admittedly in another form)
To achieve this I looked at this question: Rendering partial view dynamically in ASP.Net MVC3 Razor using Ajax call to Action which does a good job of going part the way of what I want to do.
So far, I have a controller method which handles populating a customized viewmodel for the partial view:
[HttpPost]
public ActionResult CompanyBillingBandDetails(int id = 0)
{
var viewModel = new BillingGroupDetailsViewModel();
var billingGroupBillingBands =
_model.GetAllRecordsWhere<BillingGroupBillingBand>(x => x.BillingGroupId == id).ToList();
foreach (var band in billingGroupBillingBands)
{
viewModel.BillingBands.Add(band.BillingBand);
}
return PartialView("BillingGroupDetailsPartial", viewModel);
}
The ViewModel I wish to populate each call:
public class BillingGroupDetailsViewModel
{
public List<BillingBand> BillingBands { get; set; }
}
The strongly typed model I'm using as a model for the partial view
public class BillingBandsObject
{
public int BillingBandId { get; set; }
public int RangeFrom { get; set; }
public int RangeTo { get; set; }
public Decimal Charge { get; set; }
public int BillingTypeId { get; set; }
public bool Delete { get; set; }
}
The partial view it populates and returns:
#model xxx.xxx.DTO.Objects.BillingBandsObject
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeFrom)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeTo)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.Charge)
</td>
</tr>
The Razor code for this section of the page:
<table>
<thead>
<tr>
<th>
Range From
</th>
<th>
Range To
</th>
<th>
Charge
</th>
</tr>
</thead>
<tbody>
#using (Ajax.BeginForm("CompanyBillingBandDetails", new AjaxOptions() { UpdateTargetId = "details", id = "ajaxform" }))
{
<div id="details">
#Html.Action("CompanyBillingBandDetails", new { id = 1 })
</div>
}
</tbody>
</table>
and finally the function I lifted almost straight from Darin's answer:
$(function () {
$('#billinggrouplist').change(function () {
// This event will be triggered when the dropdown list selection changes
// We start by fetching the form element. Note that if you have
// multiple forms on the page it would be better to provide it
// an unique id in the Ajax.BeginForm helper and then use id selector:
var form = $('#ajaxform');
// finally we send the AJAX request:
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#details').html(result);
}
});
});
});
At the moment I have fought through a number of errors, currently I am faced with :
"Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
However, i feel my understanding of the problem as a whole may be lacking.
Any help appreciated!
This error means that there was an exception while rendering your child view. Probably something related to your data, ie. NulLReferenceException.
Just attach your debugger and set to to break when an exception is thrown.
Related
I send from controller to view a list of objects, viewmodel is the object with some properties and pagedList, that need to be presented on page. And by pressing the button, this list need to be exported as file, that is, it need to go back to the controller and be processed there.
Model:
public class ProductsList : ListViewModel<Product>
{
public ProductsList(string prefix) : base(prefix){ }
public ProductsList(PagedList<Product> products)
{
List = products;
}
public int? ProductTypeFilter {get;set; }
public string ProductTypeFilterName {get; set;}
public string FilterBy { get; set; }
}
ListViewModel just contain PagedList.
My controller
[HttpPost]
public FileResult SaveAsFile(PagedList<Product> viewmodel)
{
...
}
And my view
#model MyProject.ViewModels.ProductsList
if (Model.List.Count > 0)
{
<table id="products_table">
<colgroup>
<col class="productType"/>
</colgroup>
<thead>
<tr>
<th >
Product type
</th>
</tr>
</thead>
<tbody>
#{ var i = 0; }
#foreach (var item in Model.List)
{
<tr>
<td onclick="window.location='#Url.Action("Details", new {id = item.Id})'">
<p>
#item.Type
</p>
</td>
}
</tr>
i++;
}
</tbody>
</table>
}
<form asp-action="SaveAsFile" enctype="multipart/form-data" method="post">
#Html.HiddenFor(m => list);
<input type="submit" value="Save as File"/>
</form>
I already have tried add to controller params tags [FromForm], [FromBody] (actually all available tags).
In view tried with hidden field in form, without it just with submit; put form on partial view; other forms: ajax, Html.ActionLink("Save as File", "SaveAsFile", new {Model}).
On debug mod Model.List has 21 items (but it can has more, like 2000 items), but when I press the button, viewmodel is creating newly.
Problem: viewmodel is creating newly and i cannot get back my full viewmodel to controller
I will be grateful for any help :)
You can set your ViewModel data in a Session variable when you send the data to your View from Controller method:
In order to setup your Session, you can follow this S.O answer
Once your Session is setup, then you can put your ViewModel in it like:
HttpContext.Session.SetObjectAsJson("ProductsList", productslist);
And then retrieve it in your POST method like this:
[HttpPost]
public FileResult SaveAsFile(PagedList<Product> viewmodel)
{
//Get your viewmodel here
var list = HttpContext.Session.GetObjectFromJson<ProductsList>("ProductsList");
}
You can also serialize your ViewModel and then send it your Controller method without using form:
Create an ActionLink:
#Html.ActionLink("Submit", "SaveAsFile", "Home", new { jsonModel= Json.Encode(Model.list) }, null)
And your Controller method:
public FileResult SaveAsFile(string jsonModel)
{
var serializer= new DataContractJsonSerializer(typeof(Model.Product));
var yourmodel= (Product)serializer.ReadObject(GenerateStreamFromString(jsonModel));
}
I am relatively new to MVC. I am working on some a project to get a basic understanding of how the MVC architecture works, but am having some issues. I think I have most of it covered but something (small I hope) is missing.
Below is my model, the controller/DataAccess and BusinessLogic class functions I have written, and my method of displaying on the view.
Can someone please glaze through this and hopefully spot what I am either doing wrong or missing? Thank you.
#edit: Also would like to add I have debuggers in my controller func, BL and DA functions and NONE of them are ever even getting hit... I believe this is the issue. Also big thanks to whoever beautified my question.
Model
public class TerminalCommandVM
{
public TerminalCommandVM()
{
TerminalsDDL = new List<SelectListItem>();
TerminalCommandLookupsDDL = new List<SelectListItem>();
}
public TerminalCommand TerminalCommand { get; set; }
public List<TerminalCommand> TerminalCommands { get; set; }
[Display(Name = "Terminal ID")]
public List<SelectListItem> TerminalsDDL { get; set; }
[Display(Name = "Command")]
public List<SelectListItem> TerminalCommandLookupsDDL { get; set; }
}
Controller
//GET: Terminals
public ActionResult GetTerminals()
{
var model = TCBL.GetTerminalDropDowns();
return View(model);
}
Business Logic function
public TerminalCommandVM GetTerminalDropDowns()
{
TerminalCommandVM ternimals = new TerminalCommandVM();
ternimals.TerminalsDDL = TCDA.GetTerminalsDropDown();
return ternimals;
}
Data Access function
public List<SelectListItem> GetTerminalsDropDown()
{
var terminals = DB.TerminalCommand.Select(o =>
new SelectListItem { Text = o.TerminalID, Value = o.TerminalID})
.ToList();
return terminals;
}
View
#using (Html.BeginForm("GetTerminals", "TerminalCommand", FormMethod.Post, new { id = "formTerminalCommand" }))
<div class="row">
<div class="col-md-12" style="overflow-y:scroll">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Terminal</th>
</tr>
</thead>
<tbody>
<tr>
<td>
#Html.DropDownListFor(o => o.TerminalsDDL, Model.TerminalsDDL, new { Class = "form-control" })
</td>
</tr>
<tr>
<td colspan="4">HelpDescription</td>
</tr>
</tbody>
</table>
</div>
</div>
Also going to include my layout for when this page gets hit:
<li class="sidenav-item#(currentPage == "TerminalCommand/Index" ? " active" : "")">
<div>Terminal Commands</div>
</li>
If the break point in your controller method isn't getting hit, then it may be an issue with redirects, routes, url, etc. There's a lot of things you can do to trouble shoot this issue, but nothing concrete.
Do you have any of the built in controllers that come with an empty project (Home, About, etc)? If so, do those work?
Add a constructor method to the controller and set a break point within. If it hits, then the issue may be with action.
If you haven't made any changes to the route config or used the route attribute, the url should be http://localhost:xxxxx/{ControllerName}/GetTerminals
I have a action , ViewModel that shows totaly of product grouped by ProductName. But this doesn't shows me how many in every department.
Let say I have 20 computers in It-department and 10 computers in adminstration department then my code shows my productname which is "Computers".
And Totaly withch is 30 but not How many in it-department and the same for administration.
And the same for Servers or other products.
So I'am trying to use this action to get number of products in every department. I know alrteady departemnt Id's and those department are not populate dynamicaly.
// This is in my HomeController and this is my action trying to get sum of every department
[HttpGet]
public ActionResult GetStatusOfDepartments(int ProductId, int departmentId )
{
var products = context.Inventory.Where(x => x.ProductId == ProductId && x.departmentId == departmentId).FirstOrDefault();
if(products != null)
{
return Content(products.Status.ToString());
}
else
{
return Content("No products");
}
}
And I want to call "GetStatusOfDepartments" action In this ViewModel but this givs me null. Can you please help me what is wrong
to call action in this ViewModel?
#model IEnumerable<ProductsInventory.Models.StatusModel>
<table class="table">
<tr>
<th>
Produkt name
</th>
<th>
It-Department
</th>
<th>
Adminstration
</th>
<th>
TotalProducts
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProductName)
</td>
<td>
// Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 1 })
</td>
<td>
// The same Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 2 })
</td>
<td>
#Html.DisplayFor(modelItem => item.Status)
</td>
</tr>
}
</table>
There are a couple of things that stand out with what you have done so far.
It's very unusual to see an ActionResult returning Content(). That means a controller is providing a raw string to a view, which is not really the point of controllers.
Currently the view has #Html.Action() requests embedded within a loop, which is a big indicator that the view is not being provided with an appropriate model.
The question's title suggests it has more to do with a database query than MVC.
At the moment, a single page load will result in many database queries, at least twice the number of product types. It is best to perform as few database queries as possible as they often have a big impact on the time it takes to load each page.
In my attempt to answer this question it became obvious that there is a relatively complex database query required to create the model. Maybe you have ended up using the approach above as a way to get past that, so I will try and answer this question without a complex database query while still adhering to the MVC design patterns (that should avoid the issues mentioned above)
Create a model
You already have a pretty good idea of what you want to display on the view. It's different enough from the database models that we should create a new model.
public class ProductsInDepartments
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int ITTotal { get; set; }
public int AdminTotal { get; set; }
public int Status { get; set; }
}
This is simply a property for each column in your view. I think you only have two departments and their names aren't mapped in the database. If that's not correct then I would suggest a different model and approach.
Controller
The controller needs to prepare a model. In this case, we will get all of the data, then transform it to the model structure we need:
public enum Departments // Enumerating a database key like this can be helpful if the database itself doesn't describe the numbers in a lookup table or something
{
IT = 1,
Admin = 2
};
[HttpGet]
public ActionResult Status()
{
var Inventory = context.Inventory.ToList(); // Get all records
var ViewModel = new List<Models.ProductsInDepartments>();
foreach (int ProductId in Inventory.Select(e => e.ProductId).Distinct().ToList())
{
ViewModel.Add(new Models.ProductsInDepartments()
{
ProductId = ProductId,
ProductName = Inventory.First(e => e.ProductId == ProductId).ProductName,
AdminTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.Admin),
ITTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.IT),
Status = Inventory.First(e => e.ProductId == ProductId).Status // I'm not sure what you are trying to do with Status, so you might need to change this
});
}
return View(ViewModel);
}
View
Now the view is very straightforward.
#model List<ProductsInventory.Models.ProductsInDepartments>
<table class="table">
<tr>
<th>Product Name</th>
<th>IT Department</th>
<th>Administration</th>
<th>Total Products</th>
</tr>
#foreach (var Item in Model)
{
<tr>
<td>#Model.ProductName</td>
<td>#Model.ITTotal.ToString()</td>
<td>#Model.AdminTotal.ToString()</td>
#if (Model.Status == 0)
{
<td>No Products</td>
}
else
{
<td>#Model.Status</td>
}
</tr>
}
</table>
Again, I'm not sure what you're trying to do with status but for string overrides you can do those in the view like this and it's perfectly fine. The view should handle various aspects of presentation-layer concerns.
In #Html.Action the first parameter should be action name and second parameter should be controller name. You need to change your code to match that.
How do I get the entered values of textboxes to post to my controller with values. Thought of using arrays but have no idea where to start. Textboxes are dynamic due to each have counter number
#{
var counter = 0;
foreach (var addedProduct in Model)
{
counter++;
<tr class="default" data-ng-controller="deleteItemsController">
<td>#addedProduct.name</td>
<td>#addedProduct.price</td>
<td>
<input type="text" id="quantity_#counter" name="quantity_#counter"/>
</td>
</tr>
}
}
<tr>
<td><b>Total:</b>{{#ViewBag.total}}</td>
<td>
#using (Html.BeginForm("Index", "Payment"))
{
//how do i get quantity values//
<input type="submit" value="pay">
}
</td>
my model:
public class ProductVar
{
public int productID { get; set; }
public string name { get; set; }
public int price { get; set; }
public int quantity { get; set; }
public int total { get; set; }
}
I have not implemented method yet, trying to figure out how to post all values first.
You are pretty close. Read this blog post from Phil Haack and things will be clear how your input fields should be named. You will understand how model binding works in ASP.NET MVC as well.
Also you should place your <input> fields inside the HTML <form> or nothing will ever be sent to the server when this form gets submitted.
So start by modifying your view code so that you use strongly typed HTML helpers for generating your input fields. They will take car of properly naming them:
#using (Html.BeginForm("Index", "Payment"))
{
for (var i = 0; i < Model.Count; i++)
{
<tr class="default" data-ng-controller="deleteItemsController">
<td>#Html.DisplayFor(x => x[i].name)</td>
<td>#Html.DisplayFor(x => x[i].price)</td>
<td>
#Html.EditorFor(x => x[i].quantity)
</td>
</tr>
}
<tr>
<td>
<b>Total:</b>
#ViewBag.total
</td>
<td>
<input type="submit" value="pay" />
</td>
</tr>
}
Now you can have your controller action that will be triggered when the form is submitted:
[HttpPost]
public ActionResult Index(ProductVar[] model)
{
...
}
In this example only the quantity field of the model will be sent to the server because that's the only input field present inside the HTML form and so the only values sent to the server. If you want to bind the other values you might include them as hidden fields as well:
#Html.HiddenFor(x => x[i].name)
#Html.HiddenFor(x => x[i].price)
Of course in most cases this is bad practice because the user can manipulate those values. The best approach is to retrieve them from the db in your POST action using the same id that you used to retrieve them in the GET action that you used to render this form.
Here is my model:
public string CustomerNumber { get; set; }
public string ShipMethod { get; set; }
public string ContactPerson { get; set; }
public string ShipToName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3{ get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
Here part of my view:
<table class="table table-condensed">
<thead>
<tr>
<td>Customer Number</td>
<td>Ship Method</td>
<td>Contact Person</td>
<td>Ship to Name</td>
<td>Address 1</td>
<td>Address 2</td>
<td>Address 3</td>
<td>City</td>
<td>State</td>
<td>Zip</td>
</tr>
</thead>
<tbody>
<tr>
<td>#Html.LabelFor(x => x.CustomerNumber, Model.CustomerNumber)</td>
<td>#Html.LabelFor(x => x.ShipMethod, Model.ShipMethod)</td>
<td>#Html.LabelFor(x => x.ContactPerson, Model.ContactPerson)</td>
<td>#Html.LabelFor(x => x.ShipToName, Model.ShipToName)</td>
<td>#Html.LabelFor(x => x.Address1, Model.Address1)</td>
<td>#Html.LabelFor(x => x.Address2, Model.Address2)</td>
<td>#Html.LabelFor(x => x.Address3, Model.Address3)</td>
<td>#Html.LabelFor(x => x.City, Model.City)</td>
<td>#Html.LabelFor(x => x.State, Model.State)</td>
<td>#Html.LabelFor(x => x.ZipCode, Model.ZipCode)</td>
</tr>
</tbody>
</table>
The data gets displayed in the view correctly, but when a post happens on my page, the data comes back null. I need the data in those LabelFors to be sent back to my controller, so I dont have to store it in a session. I thought MVC was supposed to bind to your model automagically with the labelfors. What am I doing wrong?
EDIT
I guess the reason I asked this question, is because I just moved from webforms to MVC, and I am pretty lost without the viewstate. I figured if the values in my model kept posting back to me from the view, I wouldn't have to store my model in a session object. On my page, I need to be able to persist my model during page cycles, so I can store my model data into some sql tables after the user clicks the save button. What are some options to persist your model in MVC?
It only binds input elements inside a form (because the browser posts these). Label's aren't POST'ed.
You can use HiddenFor.. since they are input elements:
#Html.HiddenFor(x => x.City)
..etc. You just can't use labels for sending back to the Controller.
LabelFor:
<label></label> <!-- Not POST'ed by the browser -->
HiddenFor:
<input type="hidden" /> <!-- POST'ed by the browser -->
MVC will work according to HTML standards which means it won't postback a label element.
Use a HiddenFor or TextboxFor with a readonly attribute.
But if you just display the values, why not putting them in a session before sending it to the page?
If you have like a wizard style form where you need to save changes inbetween steps before committing the data to a database for example your best bet would be to save the submitted values in a session which you read out again in the next step in the controller. Otherwise you can put hidden inputs on your form with the values but that is prone to manipulation by the user.
Like Simon and others have explained labels are not posted. A post request should be used when you want to change something. You don't need to store it in session if you're just viewing and editing your model.
In your view, you'll need a link to edit your model:
#Html.ActionLink("edit", "Edit", new {id = Model.CustomerNumber});
In your controller implement your Edit action method:
public ViewResult Edit(int customerNumber) {
var customer = _repository.Customers.FirstOrDefault(c => c.CustomerNumber == customerNumber);
return View(customer);
}
You'll need a view for the Edit action method and in it goes your form to post your update.
#using (Html.BeginForm()) {
<label>Contact Person:</label>
<input name="ContactPerson" value="#Model.ContactPerson" />
// the rest of your form
<input type="submit" value="Save" />
}
Implement the method to handle the post. Make sure to add the HttpPost attribute.
[HttpPost]
public ViewResult Edit(Customer customer) {
if (ModelState.IsValid) {
// Save your customer
return RedirectToAction("Index");
}
else {
return View(customer);
}
}
Hope this helps.