I am new to MVC and I have been struggling with this problem for a few days now.
When I am posting a form back to the server, the values are always null. I have tried using the model itself, using a collection/list, and the last approach I have tried was using a ViewModel.
The Goal I'm trying to achieve is to mark attendance of events that users are signed up for. I am grabbing the correct Attend info and sending them to the view. I will select the check boxes to update the boolean value Attend.Attended. During debugging I'll put a break point at the beginning of the Post action, and the model, collection/list, ViewModel has been null everytime.
Models:
public class Attend
{
[Key]
public int AttendID { get; set; }
public virtual UserProfile User { get; set; }
public virtual Event Event { get; set; }
public Boolean SignedUp { get; set; }
public Boolean Attended {get; set; }
}
public class Event
{
[Key]
public long EventID { get; set; }
[Required]
[DisplayName("When is this event?")]
public DateTime DateScheduled { get; set; }
public DateTime DateCreated { get; set; }
[DisplayName("Event Category")]
public String Category { get; set; }
[Required]
[DisplayName("Location")]
public String Location { get; set; }
public string Comments { get; set; }
[Required]
[DisplayName("Event Name")]
public string EventName { get; set; }
[Required]
[DisplayName("Event Description")]
public string EventDescription { get; set; }
public virtual ICollection<Attend> Attends { get; set; }
}
Controller:
//
// GET: /Event/Attendance
[HttpGet]
public ActionResult Attendance(long id)
{
try
{
var model = new AttendanceViewModel();
if (db == null)
return HttpNotFound();
if (Request.UrlReferrer != null && Request.UrlReferrer.AbsoluteUri != null)
ViewBag.ReferrerUrl = Request.UrlReferrer.AbsoluteUri;
else
ViewBag.ReferrerUrl = Url.Action("Index");
model.Attending = db.Attends.ToList();
ViewBag.myID = id;
return View(model);
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
return HttpNotFound();
}
}
//
// POST: /Event/Attendance
[HttpPost]
public ActionResult Attendance(AttendanceViewModel Attending, long id)
{
//POST ACTION...
}
View:
model CottagesOfHope.ViewModels.AttendanceViewModel
#{
ViewBag.Title = "Attendance";
}
<h2>Mark Attendance</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Attendance</legend>
<table>
<thead>
<tr>
<th>Name</th>
<th>Attendance</th>
</tr>
</thead>
<tbody>
#foreach (var j in Model.Attending)
{
if (j.Event.EventID == ViewBag.myId)
{
<tr>
<td>#j.User.FirstName #j.User.LastName</td>
<td>#Html.EditorFor(model => j.Attended)</td>
</tr>
}
}
</tbody>
</table>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
}
ViewModel :
public class AttendanceViewModel
{
public virtual List<Attend> Attending { get; set; }
}
Like I said before, this was the last approach I took trying to bind the data correctly. Any help would be greatly appreciated.
Thanks in advance!
Looks like you're not passing any of the required params to the BeginForm method, try this:
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post)) {...}
instead of this:
#using (Html.BeginForm()) {...}
Where "ControllerName" is the name of the controller and "ActionName" is the name of the controller action. See more here.
Without specifying params, the resulting html would look like this:
<form action="/" method="post"></form>
But when you do specify params, the html will look like this:
<form action="/ControllerName/ActionName" method="post"></form>
So there were actually two problems:
I was filtering the list in the View when I should've filtered it in the Controller.
I read that foreach loops sometimes do not work correctly over lists, and it is recommended to use a for loop and index the List on each iteration.
Updated Controller:
[HttpPost]
public ActionResult Attendance(ViewModels.AttendanceViewModel a)
{
try
{
foreach (var j in a.Attending)
{
//Needed to filter by EventID here
Attend attends = db.Attends.Where(e => e.AttendID == j.AttendID).Single();
attends.Attended = j.Attended;
}
db.SaveChanges();
return RedirectToAction("Index", "Event");
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
return HttpNotFound();
}
}
Update View:
#model CottagesOfHope.ViewModels.AttendanceViewModel
#{
ViewBag.Title = "Attendance";
}
<h2>Mark Attendance</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Attendance</legend>
<table>
<thead>
<tr>
<th>Name</th>
<th>Attendance</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Attending.Count(); i++)
{
<tr>
<td>#Model.Attending[i].User.FirstName
#Model.Attending[i].User.LastName</td>
<td>#Html.CheckBoxFor(model => Model.Attending[i].Attended)</td>
#Html.HiddenFor(model => Model.Attending[i].AttendID)
</tr>
}
</tbody>
</table>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
}
Related
I have some probles with using Post methods in controllers ASP.NET Core MVC. Maybe i using wrong architecture.
I have 2 Models from DB.
public class RecipeTable
{
public int Id { get; set; }
public string MetrologyRecipe { get; set; }
public string MetrologyTool { get; set; }
//other properties
}
public class ParamTable
{
public int AupId { get; set; }
public string ParamName{ get; set; }
public string RecipeName { get; set; }
public int? ParamOrderAuto { get; set; }
//other properties
}
And box for them. Because one entry in RecipeTable is associated with several entres from ParamTable.
public class FullModel
{
public List<ParamTable> ParamRows ;
public RecipeTable RecipeRow { set; get; }
public FullModel()
{
ParamRows = new List<ParamTable> ();
}
}
For [Get]"Edit" method this is work great.
[HttpGet]
public async Task<IActionResult> Edit(int? id, string recipeName)
{
var fullModel = new FullModel();
if (id == null) return NotFound();
fullModel.RecipeRow = await
_context.RecipeTable.SingleOrDefaultAsync(m => m.Id == id);
foreach (var row in _context.ParamTable)
if (recipeName == row.RecipeName)
fullModel.ParamRows.Add(row);
if (fullModel.RecipeRow.MetrologyRecipe == null) return NotFound();
return View(fullModel);
}
But for [Post]"Edit" this is does not work, of course.
Only Recipe part updated. I dont understand how post method get data from View. How work with this complicated models, when you can't change database and can't
specify connection directly in database designer.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, FullModel fullModel)
{
if (id != fullModel.RecipeRow.Id) return NotFound();
if (ModelState.IsValid)
{
try
{
//**Here fullModel.ParamRows.Count = 0**
_context.Update(fullModel.RecipeRow);
await _context.SaveChangesAsync();
foreach (var elem in fullModel.ParamRows)
{
_context.Update(elem);
await _context.SaveChangesAsync();
}
}
catch (DbUpdateConcurrencyException)
{
if (!RecipeTableExists(fullModel.RecipeRow.Id))
return NotFound();
throw;
}
return RedirectToAction(nameof(Index));
}
return View(fullModel);
View part look like this:
#model FullModel
#{
ViewData["Title"] = "Edit";
}
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<table class="table" style="margin-left: -50px">
<thead>
<tr>
<th>Order</th>
<th>Param</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.ParamRows.Count; i++)
{
<tr>
<td>
<div class="form-group">
<input asp-for="#Model.ParamRows[i].ParamOrderAuto" type="text" class="form-control" />
</div>
</td>
<td>
<div class="form-group">
<input asp-for="#Model.ParamRows[i].ParamName" class="form-control" />
</div>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
You can't use a foreach to iterate over the ParamRows. In order for the modelbinder to be able to bind this back to a list on post, the fields need to have names like ParamRows[N].ParamName. Using a foreach, you'll end up with names like row.ParamName instead, which the modelbinder will not recognize and will simply discard.
Instead, use a standard for loop:
#for (var i = 0; i < Model.ParamRows.Count; i++)
{
...
<input asp-for="ParamRows[i].ParamName" />
...
}
Alternatively, you can create an editor template for a ParamTable, i.e. ~/Views/Shared/EditorTemplates/ParamTable.cshtml, and put all the HTML (fields, labels, etc.) for editing a ParamTable instance in that. Then, instead of iterating over the items in ParamRows, you can simply do:
#Html.EditorFor(m => m.ParamRows)
Razor will automatically iterate over each item and render you editor template for each one, giving each field a correct name for binding.
You have a small problem with the FullModel, need to add get; set; methods. Just correct like this:
public class FullModel
{
public List<ParamTable> ParamRows { get; set; };
public RecipeTable RecipeRow { set; get; }
...
In my ASP.NET MVC Core app, from an action method shown below, I'm passing Blogs data and its related data from Posts table to a view as return View(await _context.Blogs.Include(p => p.Posts).ToListAsync()); Since I'm passing data from two tables, I need to use a ViewModel shown below. Question: How can I use ViewModel to pass the related data from my Controller Action method
Test() to view shown below?
In the code below I'm getting the obvious error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[ASP_Core_Blogs.Models.Blog]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IList'1[ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel]'.
Model:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
Controller:
[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
}
ViewModel:
public class BlogsWithRelatedPostsViewModel
{
public int BlogID { get; set; }
public int PostID { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
}
View:
#model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
<div class="row">
<div class="col-md-12">
<form asp-controller="DbRelated" asp-action="EnterGrantNumbers" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
#for (int t = 0; t < Model.Count; t++)
{
<tr>
<td><input type="hidden" asp-for="#Model[t].BlogID" /></td>
<td><input type="hidden" asp-for="#Model[t].PostID" /></td>
<td>
<input type="text" asp-for="#Model[t].Url" style="border:0;" readonly /> <!--Not using /*Html.DisplayFor(modelItem => Model[t].Url)*/ since it does not submit stateName on Post. Not using <label asp-for=.....> since Bootstrap bold the text of <label> tag-->
</td>
<td>
<input asp-for="#Model[t].Title" />
</td>
<td>
<input asp-for="#Model[t].Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
You need to project your query using your BlogsWithRelatedPostsViewModel class:
return View( _context.Blogs
.Include(p => p.Posts)
.SelectMany(e=> e.Posts.Select(p=> new BlogsWithRelatedPostsViewModel
{
BlogId= e.BlogId,
PostId=p.PostId,
Url=e.Url,
...
})
.ToList());
SelectMany extension method allows you flatten each projection from e.Posts into one sequence, so at the end you will get a List<BlogsWithRelatedPostsViewModel>
On top of Octavioccl's, answer there is a nice little extension method I have been using (I don't know of the author to this but if anyone else knows, I will happily update my answer to give credit). This way, you don't have to write out each property.
public static T Cast<T>(this object myobj)
{
var target = typeof(T);
var x = Activator.CreateInstance(target, false);
var d = from source in target.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
var memberInfos = d as MemberInfo[] ?? d.ToArray();
var members = memberInfos.Where(memberInfo => memberInfos.Select(c => c.Name)
.ToList().Contains(memberInfo.Name)).ToList();
foreach (var memberInfo in members)
{
var propertyInfo = typeof(T).GetProperty(memberInfo.Name);
if (myobj.GetType().GetProperty(memberInfo.Name) == null) continue;
var value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
propertyInfo.SetValue(x, value, null);
}
return (T)x;
}
Usage:
var ViewModelList = ModelList.Select(model => model.Cast<ViewModel>()).ToList();
There is also a well supported framework built for this specific problem. Called AutoMapper (http://automapper.org/).
For passing data from Action to view as ViewModel. Create a new instance of your View Model first and assign value to each propery by calling your context query(whatever your Linq query is) and return the list of view as your View model variable.
var blogWithRelatedPost = new BolblogWithRelatedPost();
// your logic here for assigning value to property or LINQ query
return View(blogWithRelatedPost);
Commodity Report View :
#model PMEX.CSR.Models.ReportModel
#{
ViewBag.Title = "Commodity Report";
}
<div>
<table>
<tr>
<td>
#{
if (Model != null)
{
#Html.ActionLink("Download PDF Report", "DownloadReportPDF", Model);
}
}
</td>
</tr>
</table>
</div>
Report Controller cs File :
public ActionResult DownloadReportPDF(ReportModel model)
{
// to do some stuff
return View("Commodity");
}
Report Model
public class ReportModel
{
public string testValue { get; set; }
public DataTable dt { get; set; }
public LikeFilterModel LikeFilterModelObj { get; set; }
// [Required]
// public string SearchText { get; set; }
public GridModels GridDataModel { get; set; }
/// <summary>
/// Represents that datagrid has rows in it.
/// </summary>
public bool isValue { get; set; }
}
My model when i received on the view
Model which i am receiving on Controller through actionlink.
As you can see everything i receiving on controller is null. Please tell me what going wrong here ?
I want to pass the same model to the controller which i received on View.
Html.ActionLink is expecting to have routeValues instead of your object Model. Check the reference here.
...
You can't pass data to the controller in this manner, any data that comes from a view should be POSTed or passed along in the query string e.g.
if (Model != null)
{
#using (Html.BeginForm("DownloadPDFReport", FormMethod.Post))
{
#Html.HiddenFor(x => x.Property1)
#Html.HiddenFor(x => x.Property2)
...
<input type="submit" value="Download PDF Report" />
}
}
I have the following Model:
public class ContractPlain
{
public int Id { get; set; }
public Guid ContractGuid { get; set; }
public int SenderId { get; set; }
public int RecvId { get; set; }
public int ContractType { get; set; }
public string ContractStatus { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime CreditEnd { get; set; }
}
public class Contrtacts
{
List<ContractPlain> listOutput;
public void Build(List<ContractPlain> listInput)
{
listOutput = new List<ContractPlain>();
}
public List<ContractPlain> GetContracts()
{
return listOutput;
}
internal void Build(List<contract> currentContracts)
{
throw new NotImplementedException();
}
}
As you can see, I defined a whole collection.
Why?
I need to render the data in table for user, because there are several rows which belongs to the exact/unique user (e.g. 20-30 shop items are referred to the single client).
So, I'm getting data from the DB using ADO.NET Entity. The binding question to the model instance in Controller is done and I don't have issues with that, I do with the rendering question only.
I think, it could be used with the #for stuff, but didn't know, how it would be better using especially my custom model.
So, how can I render the data in View using my model?
Thanks!
See the view below. You simply foreach over your collection and display the Contracts.
Controller:
public class ContactsController : Controller
{
public ActionResult Index()
{
var model = // your model
return View(model);
}
}
View:
<table class="grid">
<tr>
<th>Foo</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td class="left"><%: item.Foo %></td>
</tr>
<% } %>
</table>
Razor:
#model IEnumerable<ContractPlain>
<table class="grid">
<tr>
<th>Foo</th>
</tr>
#foreach (var item in Model) {
<tr>
<td class="left"><#item.Foo></td>
</tr>
#}
</table>
If your action returns a List of contracts you can do the following in the view:
#model IEnumerable<ContractPlain>
#foreach(ContractPlain contract in Model)
{
<ul>
<li>#contract.ContractGuid</li>
<li>#contract.SenderId</li>
<li>#contract.ContractStatus</li>
<li>#contract.CreditEnd</li>
</ul>
}
I have two model and I want to show in one view. So I'm using
#Html.Partial
This is my first Model.
public partial class graduandModel :BaseNopEntityModel
{
public graduandModel()
{
this.AvailableCeremony = new List<SelectListItem>();
}
public string first_name { get; set; }
public string middle_name { get; set; }
public string last_name { get; set; }
public int student_id { get; set; }
public int ceremony_id { get; set; }
public DateTime ceremony_date { get; set; }
public int graduand_id { get; set; }
public IList<SelectListItem> AvailableCeremony { get; set; }
public graduandDegreeModel graduandDegreeGroup { get; set; }
}
This is my second Model.
public class graduandDegreeModel
{
public graduandDegreeModel()
{
this.AvailableDegree = new List<SelectListItem>();
}
public string degree_id { get; set; }
public int graduand_id { get; set; }
public string degree_name { get; set; }
public IList<SelectListItem> AvailableDegree { get; set; }
}
This is mu controller
public ActionResult CheckData(int ceremony_id, string first_name, string middle_name, string last_name)
{
graduandModel model = new graduandModel();
graduandDegreeModel model_1 = new graduandDegreeModel();
var graduandList = _graduandService.GetGraduandByStudent(ceremony_id, first_name, middle_name, last_name);
if (graduandList.Count != 0)
{
model.ceremony_id = ceremony_id;
model.first_name = first_name;
model.middle_name = middle_name;
model.last_name = last_name;
// var degreeList = "";
foreach (var c in graduandList)
{
var degreeList = _graduandDegreeService.getAllDegreeIdBtGraduandId(c.graduand_id);
foreach (var d in degreeList)
{
model_1.AvailableDegree.Add(new SelectListItem() { Text = d.Degree.degree_name, Value = d.degree_id });
}
}
}
return View(model);
}
This is my views
#{
Layout = "~/Views/Shared/_ColumnsThree.cshtml";
}
#model graduandModel
#using Nop.Web.Models.Hire;
#using Nop.Web.Framework;
#using Telerik.Web.Mvc.UI;
#using System.Linq;
#using (Html.BeginForm())
{
<table >
<tr>
<td >
Ceremony :
</td>
<td>
Ceremony at #Model.ceremony_date
</td>
</tr>
<tr>
<td >
Name :
</td>
<td >
#Model.first_name #Model.middle_name #Model.last_name
</td>
</tr>
</table>
<div>
#Html.Partial("_DegreeDetailsByGraduand", Model.graduandDegreeGroup)
</div>
}
This is my Partial view
#{
Layout = "~/Views/Shared/_ColumnsThree.cshtml";
}
#model graduandDegreeModel
#using Nop.Web.Models.Hire;
#using Nop.Web.Framework;
#using Telerik.Web.Mvc.UI;
#using System.Linq;
<table >
<tr>
<td >
AAAAAA
</td>
<td>
#Html.DropDownListFor(model => model.degree_id, Model.AvailableDegree)
#* #Html.ValidationMessageFor(model => model.ceremony_id)*#
</td>
</tr>
</table>
there is error
The model item passed into the dictionary is of type 'Nop.Web.Models.Hire.graduandModel', but this dictionary requires a model item of type 'Nop.Web.Models.Hire.graduandDegreeModel'.
How can I slove it???
You didn't create an instance for graduandModel's graduandDegreeGroup property. So this line:
#Html.Partial("_DegreeDetailsByGraduand", Model.graduandDegreeGroup)
will throw an exception like you said. Simply because the second parameter is NULL.
You can try to modify graduandModel's constructor as below:
public graduandModel()
{
this.AvailableCeremony = new List<SelectListItem>();
this.graduandDegreeGroup = new graduandDegreeModel();
}
The exception should be gone.
You may also find this link helpful: ASP.NET MVC renderpartial, model item passed into the dictionary is of type
Another option for you may be to create a new view model which combines the two models above into one. That way it has properties for all of the data you require for this view. Then you don't need to specify a model in your call to the partial view, it will automatically use the parent's model. Alternatively, you may not need to separate the view into partials at all with the use of a combined model. It is not uncommon to have a unique view model for each different view. In some applications, it can be rare that two different views require the same data.
The combined view model:
public class CheckDataViewModel
{
public CheckDataViewModel ()
{
this.AvailableCeremony = new List<SelectListItem>();
this.AvailableDegree = new List<SelectListItem>();
}
public string first_name { get; set; }
public string middle_name { get; set; }
public string last_name { get; set; }
public int student_id { get; set; }
public int ceremony_id { get; set; }
public DateTime ceremony_date { get; set; }
public int graduand_id { get; set; }
public IList<SelectListItem> AvailableCeremony { get; set; }
public graduandDegreeModel graduandDegreeGroup { get; set; }
public string degree_id { get; set; }
public string degree_name { get; set; }
public IList<SelectListItem> AvailableDegree { get; set; }
}
The combined view:
#{
Layout = "~/Views/Shared/_ColumnsThree.cshtml";
}
#model CheckDataViewModel
#using Nop.Web.Models.Hire;
#using Nop.Web.Framework;
#using Telerik.Web.Mvc.UI;
#using System.Linq;
#using (Html.BeginForm())
{
<table >
<tr>
<td >
Ceremony :
</td>
<td>
Ceremony at #Model.ceremony_date
</td>
</tr>
<tr>
<td >
Name :
</td>
<td >
#Model.first_name #Model.middle_name #Model.last_name
</td>
</tr>
</table>
<div>
<table >
<tr>
<td >
AAAAAA
</td>
<td>
#Html.DropDownListFor(model => model.degree_id, Model.AvailableDegree)
#* #Html.ValidationMessageFor(model => model.ceremony_id)*#
</td>
</tr>
</table>
</div>
}