I have made a webapp where you search by client id, and then adds orders on that client. The index action method assigns the chosen client to the viewmodel(vm.AllClients). The order table of course has information about the client. In the Insert method i want to use the information about the chosen client, but now vm.AllClients is returning null.
During debugging vm.AllClients is filled with one client object, as it should, during the running of the first method. When the second method is running vm.AllClients is empty.
I have tried to save the search string as a variable and find it in the db(Not a good solution), but the variable is also empty during the running of the second method. Also tried to save the chosen client as a Client object in the viewmodel, still no dice.
AddController
using MainWeb.Models;
public class AddController : Controller
{
OrderEntities db = new OrderEntities();// New instance of db.
ViewModel vm = new ViewModel();//New instance of viewmodel
[HttpPost]
public ActionResult Index(string searchTerm)
{
if (string.IsNullOrEmpty(searchTerm))
{
vm.AllClients = new List<Client>();
}
else
{
vm.AllClients = db.Clients.Where(x =>
x.RefNo.ToString().Equals(searchTerm)).ToList();
foreach (Client client in vm.AllClients)
{
vm.ThisClient = client;//Attempt at a different solution
break;
}
}
return View(vm);
}
public ActionResult InsertOrder(FormCollection form)
{
Order order = new Order();
order.ClientID = vm.AllClients[0].ID;//Here is where the error gets thrown
return RedirectToAction("Index");
}
View
#model MainWeb.Models.ViewModel
<div class="card border-primary mb-3 card-client" style="max-width: 40rem;">
<div class="card-header">Legg til</div>
<div class="card-body">
<div class="editor-label">
<table>
#using (Html.BeginForm("Test", "Add", FormMethod.Post))
{
<tr>
<td>#Html.Label("Velg Prosjekt:")</td>
</tr>
<tr>
<td>
#Html.DropDownList("fromDBProjects", (IEnumerable<SelectListItem>)ViewData["DBProjects"], new { #class = "form-control" })
</td>
</tr>
<tr>
<td>#Html.Label("Velg Produkt:")</td>
</tr>
<tr>
<td>
#Html.DropDownList("fromDBProducts", (IEnumerable<SelectListItem>)ViewData["DBProducts"], new { #class = "form-control" })
</td>
</tr>
<tr>
<td>#Html.Label("Pris:")</td>
</tr>
<tr>
<td><input type="submit" value="Submit" class="btn btn-primary" id="btn-search" /></td>
</tr>
}
</table>
</div>
</div>
</div>
</div>
}
ViewModel
namespace MainWeb.Models
{
public class ViewModel
{
public List<Client> AllClients { get; set; }
public Client ThisClient { get; set; }
}
}
Error:
Object reference not set to an instance of an object
In general I asume you are trying to write an new asp.net web application. But you should consider using asp.net core. This framework is the followup of asp.net mvc and you shouldn't start coding asp.net core mvc instead of the full framework.
That is deprecated and will get replaced by asp.net core mvc
I guess you should do the mvc tutorial from MS first. To get a better understanding how everything works.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/?view=aspnetcore-2.2
But now to your question:
There are a couple of issues in your example:
ViewModel shouldn't be a central class. Create it in your Controller Action, because every webrequest get's a new instance of your controller!
When you hit "InsertOrder" - the controller is created newly for the new request. Which means you get a new instance of the controller and your viewModel is empty again
Your cshtml will never hit your "InsertOrder"
InsertOrder can map your formcollection directly into a class.
Minor: you shouldn't layout your html with a table - See: Why not use tables for layout in HTML?
Your controller should look like this
public class AddController : Controller
{
OrderEntities db = new OrderEntities();// New instance of db.
[HttpPost]
public ActionResult Index(string searchTerm)
{
var vm = new ViewModel();
if (string.IsNullOrEmpty(searchTerm))
{
vm.AllClients = new List<Client>();
}
else
{
vm.AllClients = db.Clients.Where(x =>
x.RefNo.ToString().Equals(searchTerm)).ToList();
foreach (Client client in vm.AllClients)
{
vm.ThisClient = client;//Attempt at a different solution
break;
}
}
return View(vm);
}
[HttpPost]
public ActionResult InsertOrder(ViewModel vm)
{
Order order = new Order();
order.ClientID = vm.AllClients[0].ID;//Here is where the error gets thrown
return RedirectToAction("Index");
}
And your view should set the form to this:
#using (Html.BeginForm("InsertOrder", "Add", FormMethod.Post))
Each request gets a fresh controller instance, so you cannot use the global Viewmodel variable. If you want to communicate between controller actions use ViewData object or simply send the data to the client and get it via FormCollection or your ViewModel class.
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 trying to create a view in my application that performs basic CRUD commands in ASP.NET Core to teach myself some new skills. I am however stuck and would appreciate some assistance please.
I would like to have each "component" of the application sitting in a partial view for maintenance going forward. I initially had my Index view use a declaration of type IEnumerable (for the for each loop):
#model IEnumerable<Project.Web.Models.Sample.SampleModel>
Which worked perfect for returning the list and rendering the page but then when trying to have my Modal window partially loaded into the page and insert data using the "CreateSample" function on the controller it was not picking up the function and failed the insert (no form action found). If I then try to add:
#model Project.Web.Models.Sample.SampleModel
to the CreateModal view page it throws an error and wont even let me render the page, I presume because its being partial loaded the app is seen as having two SampleModel declarations. If I create this page completely separate and not partially loaded with the normal #model declaration it works.
I have the basic setup going so far and have included my code for each below.
Model - SampleModel
public class SampleModel
{
public int Id { get; set; }
public string SampleText { get; set; }
}
Controller - SampleController
public class SampleController : Controller
{
public const string ControllerName = "Sample";
//Open Database Connection
private _DBContext DBDatabase = new _DBContext ();
public ActionResult Index()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView(Model);
}
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView("_CreateModal", Model);
}
Views - Index, View, Create
Index - Calls Partial Views for View and Create
#using Project.Web.Controllers
#model Project.Web.Models.Sample.SampleModel
<!--html stuff here -->
#await Html.PartialAsync("_CreateModal")
<!--more html stuff here -->
#await Html.PartialAsync("_ViewData")
View - Foreach to Loop Records
#model Project.Web.Models.Sample.SampleModel
<table style="width: 100%;" id="example">
<thead>
<tr>
<th>#</th>
<th>Sample Text</th>
<th class="text-center">Status</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
#foreach (var sample in Model)
{
<tr>
<th scope="row">#sample.Id</th>
<td>#sample.SampleText</td>
<td class="text-center">
<div class="badge badge-success">Active</div>
</td>
<td class="text-center">
<div role="group" class="btn-group-sm btn-group">
<button class="btn-shadow btn btn-primary">Edit</button>
<button class="btn-shadow btn btn-primary">Delete</button>
</div>
</td>
</tr>
}
</tbody>
</table>
Create - Insert New Record
#model Project.Web.Models.Sample.SampleModel
<form method="post" asp-action="/SampleModel/CreateSample">
<div class="form-group">
<label for="CreationTime">SampleText</label>
<div>
<input type="text" class="form-control" id="SampleText" name="SampleText" placeholder="SampleText">
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Sign up</button>
</div>
</form>
As per Ammar's comment, you've just copy-pasted the Index Controller's data access. When building a form allowing the user to create a single new item, then the pattern is to typically pre-instantiate an empty model and pass it to the view:
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var model = new SampleModel(); // If Id is a GUID, then you could assign one here
return PartialView("_CreateModal", model);
}
I'm working on a Lexical Analyzer and I want to show all data on a table in MVC. But to simplify code I'll add an example to show what I want to achieve. I have a logic.cs class where the Lexical Analyzer will be receiving the string coming into, and I want to Add items to the List accordingly to the Lexical Analyzer method.
This is my code:
Controller
Repository repo = new Repository();
logic logica = new logic();
public ActionResult Index()
{
var getrepo = repo.GetData();
return View(getrepo.ToList());
}
[HttpPost]
public ActionResult Index(string str) {
logica.Logic_t(str); //I send str parameter to the logic class
var getrepo = repo.GetData();
return View(getrepo.ToList());
Model
Repository.cs
public class Repository
{
public List<data_table> data = new List<data_table>() { };
public List<data_table> GetData() {
return data;
}
}
data_table.cs
public int Line { get; set; }
public string Token { get; set; }
logic.cs
Repository repo = new Repository();
public void Logic_t(string s)
{
int Line = 1;
repo.data.Add(new data_table { Line =Line , Token = " NUMBER" });
}
View
#model IEnumerable<pruebaarray.Models.data_table>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
<textarea rows="10" cols="50" class="textarea" name="str">
</textarea>
<input type="submit" value="send-to-logic" class="btn btn-primary"/>
}
<table class="table-bordered">
<tr>
<th>Line</th>
<th>Token</th>
</tr>
#foreach (var item in Model) {
<tr>
<th>#item.Line</th>
<th>#item.Token</th>
</tr>
}
</table>
And this is my final view:
My code has no Errors, but when I click the submit button, nothing shows in the table. What am I missing? or what could be wrong?
PD: My Lexical Analyzer logic has recursive methods so It will be adding data constantly to the List.
UPDATE: I got this just by setting List to static
Currently, your form does not know which controller or action to target.
Html.BeginForm() has several overloads.
For example:
BeginForm(HtmlHelper, String, String, Object, FormMethod, Object)
Writes an opening tag to the response and sets the action tag
to the specified controller, action, and route values. The form uses
the specified HTTP method and includes the HTML attributes.
Check the overloads here
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 got this problem that I am having a difficulty to solve. I am creating a page where the user will be presented with a list of items (Product Types). Each item will have a dropdown list next to it so that the user can make appropriate selection to create a mapping. After making selection then the user submits the form, and the value will be written to the database.
The problem is that when it is submitted, I am not getting any values back. Specifically, 'Mappings' is empty in the model that is returned by the POST action. The GET action works fine. The following is the essence of what I have written:
Model:
public class ProductTypeMappingViewModel
{
//this is empty in the POST object
public List<ProductTypeMapping> Mappings { get; set; }
public ProductTypeMappingViewModel()
{
Mappings = new List<ProductTypeMapping>();
}
public ProductTypeMappingViewModel(string db)
{
//use this to populate 'Mappings' for GET action
//works fine
}
public void UpdateDB()
{
//to be called on the object
//returned from POST action
foreach(var mapping in Mappings)
{
//Mappings is always empty after POST
//Suppose to add to db
}
}
}
public class ProductTypeMapping
{
public string ProductTypeName { get; set; }
public int SelectedStandardProductTypeKey { get; set; }
public SelectList StandardProductTypes { get; set; }
public ProductTypeMapping()
{
StandardProductTypes = new SelectList(new List<SelectListItem>());
}
public int GetSelectedProductTypeKey() { //return selected key}
public string GetSelectedProductTypeName() { //return selected name}
}
View:
#model CorporateM10.Models.ProductTypeMappingViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<table class="table">
#foreach (var dept in Model.Mappings)
{
<tr>
<td>
#Html.DisplayFor(model => dept.ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
<div>
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
Any insight will be greatly appreciated.
foreach here causes select element in final HTML to have incorrect name attribute. Thus nothing is posted to the server. Replace this with for loop:
<table class="table">
#for (int i=0; i<Model.Mappings.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.Mappings[i].ProductTypeName, new { })
</td>
<td>
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey, model.Mappings[i].StandardProductTypes, "(Select Department)", new { })
</td>
</tr>
}
</table>
As #Andrei said the problem relies on the name attribute.
But to add a little bit to his answer, here's the parameter names in the request that the default model binder expects for your case.
Mappings[0].SelectedStandardProductTypeKey
Mappings[1].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
...
Without any breaks in the numbering, i.e.:
Mappings[0].SelectedStandardProductTypeKey
Mappings[2].SelectedStandardProductTypeKey
Won't work because of the missing Mapping[1]...
When you use the dropdown helper like this:
#Html.DropDownListFor(model => dept.SelectedStandardProductTypeKey, dept.StandardProductTypes, "(Select Department)", new { })
It generates an input with name="SelectedStandardProductTypeKey" (you need it to be Mappings[0].SelectedStandardProductTypeKey)
If you use a for loop and use the dropdown helper like this:
#Html.DropDownListFor(model => model.Mappings[i].SelectedStandardProductTypeKey
You'll get the input with the correct name.
Any parameter in the request for which the model binder cannot find a property in the model, it will ignore, that's why the Mappings property is null in your case.
Here are two great resource that explain all this (and that provide alternative ways to represent collections that might be useful if you can't a the for loop to generate a numbered index without breaks):
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx