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>
}
Related
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" />
}
I have one model that combines more than model always is sent to controller as empty model
Model
namespace Test.Models
{
public class Compination
{
public firstmodel fmodel{ get; set; }
public secondmodel smodel{ get; set; }
}
}
View
#model Test.Models.Compination
#using (Html.BeginForm("Edit", "Home", FormMethod.Post)){
<div>
#Html.LabelFor(model => model.firstmodel.Name, "Name")
#Html.EditorFor(model => model.firstmodel.Name)
</div>
//other fields
}
Controller
public ActionResult Edit(int? id){
firstmodel FM= db.firstmodel.Find(id);
ClassA object = methodA(id);
secondmodel SM = object.secondmodel;
var Compination= new Compinati
on{ firstmodel= FM, secondmodel = S};
return View(Compination);
}
By this way name for example is empty
public ActionResult Edit(Compination comp){
var name= comp.firstmodel.Name;
//other code }
But if i have assigned name for each item in each model it's pass the values correctly like this
#Html.EditorFor(model => model.firstmodel.Name, new { htmlAttributes = new { #Name = "Name" })
then in controller
public ActionResult Edit(string name){
// Now name isn't empty
//other code }
I do not want to call each item separately I want the whole model
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
}
I've got two models.
Model A for example contains basic get set for atrributes (i.e)
[Required]
[StringLength(100)]
[Display(Name = "Comment: ")]
public string Comment { get; set; }
public bool IsSuccess { get; set; }
Model two uses attributes in modal a, i.e
public ModelA Deposit { get; set; }
public ModelA Withdrawal { get; set; }
public ModelA Transfer { get; set; }
In my View all I am doing is using 3 forms in one page with very similar fields.
For same reason Withdrawal and transfer work with no problems. The code is almost identical in all 3
However when I am doing deposit, the ActionResult in the post is not been passed on the model.
public ActionResult Index()
{
SetViewBagAccounts();
var model = new ModelB {};
return View(model);
}
[HttpGet]
public ActionResult Deposit()
{
return View();
}
[HttpPost]
public ActionResult Deposit(ModelB model)
{
int acct = Convert.ToInt32(Request.Form["Accounts"]);
var comment = model.Deposit.Comment;// This causes **NullReferenceException**
}
The error I am getting in http post is NullReferenceExcption.
When I debug, the model being passed to the action method, Deposit, is all empty.
BELOW IS A SAMPLE OF THE VIEW
#model Login.Models.ModelB
#{
ViewBag.Title = "ATM";
}
<h2>ATM</h2>
<div id="leftpanel" style="position:absolute;left:0;width:33%;">
#using (Html.BeginForm("Deposit", "ATM", FormMethod.Post, new {}))
{
<div>
<fieldset>
<legend>Deposit Money</legend>
<div>#Html.LabelFor(u=>u.Deposit.AccountNumber1)</div>
<div>#Html.DropDownList("Accounts", "-- Select Account --")
</div>
<div>#Html.LabelFor(u=>u.Deposit.Amount)</div>
<div>#Html.TextBoxFor(u=>u.Deposit,new {style = "width:150px"})
#Html.ValidationMessageFor(u=>u.Deposit.Amount)
</div>
<div>#Html.LabelFor(u=>u.Deposit.Comment)</div>
<div>#Html.TextAreaFor(u=>u.Deposit.Comment,new {style = "width:250px"})
#Html.ValidationMessageFor(u=>u.Deposit.Comment)
</div>
<input type="submit" value ="Submit" style="width:31%;"/>
<input type="reset" value ="Clear" style="width:31%;"/>
</fieldset>
</div>
}
The div for deposit is pretty much copied again for withdrawal and transfer, only changed bit is :
#using (Html.BeginForm("Withdrawal", "ATM", FormMethod.Post, new {}))
#using (Html.BeginForm("Transfer", "ATM", FormMethod.Post, new {}))
I have NameModel and RegisterModel and SuperClass classes as below: -
Case 1: - Using SuperClass
public class SuperClass
{
public RegisterModel Register{ get; set; }
public NameModel NameInfo { get; set; }
}
public class NameModel
{
[Required]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
public class RegisterModel
{
public NameModel NameInfo{ get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set;}
}
MyNamePartial strongly typed View is as follows :-
#model MyNamespace.Models.NameModel
#{
Layout = null;
}
{
#Html.TextBoxFor(m=>m.FirstName,new { #id="firstName"} )
#Html.TextBoxFor(m=>m.MiddleName,new { #id="middleName"} )
#Html.TextBoxFor(m=>m.LastName,new { #id="lastName"} )
}
My Registration View is strongly typed of Register Model as follows :-
#model MyNamespace.Models.SuperClass
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Partial("NameModel",Model.NameInfo)
#Html.TextBoxFor(m=>m.Register.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Register.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
</div>
}
Above approach gives object reference error.
Case 2: - Using HTML.Action and no SuperClass
Tried using #Html.Action("MyNamePartialView")instead of #Html.Partial("NameModel",Model.NameInfo),Then I use Controller Action method as below
My Registration View is strongly typed of Register Model as follows :-
#model MyNamespace.Models.RegisterModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Action("MyNamePartialView")
#Html.TextBoxFor(m=>m.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
</div>
}
Register Controller :-
public ActionResult MyNamePartialView()
{
return PartialView("MyNamePartial", new NameModel());
}
[HttpPost]
[AllowAnonymous]
public ActionResult Register(RegisterrModel model)
{
#ViewBag.sideMenuHeader = "Create an Account";
if (ModelState.IsValid)
{
//Perform Something
return View();
}
return View();
}
The above case doesnt bind the values entered on form. it sets null for NameModel.
I don't want to use EditorFor as i have to supply html and custom attributes to helpers.The binding for the partial view fails.it gives me object reference error in Registration view. How can i use such strongly typed Partial views with such a such Model class hierarchy as explained above ?
The simplest way to do this is to use a child action
#model MyNamespace.Models.Register.SuperModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Action("MyNamePartialView")
</div>
#Html.TextBoxFor(m=>m.Register.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Register.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
}
make your action post accept 2 classes
public ActionResult Register(RegisterModel RegisterInfo, NameModel NameInfo)