Null Value Passing From View To Controller - c#

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" />
}
}

Related

View Model and Post action to Nested Classes in ASP.NET Framework

I have been working on a mock website for a car dealership using MVC in .NET Framework. I am using razor pages to make a simple page to display existing models, and add a new model of car to the database, but no matter what syntax I use I can not seem to get my post data to properly map to the Model class when it is returned to my controller. Here is my VM:
public class ModelVM
{
public List<Make> Makes { get; set; }
public List<Model> Models { get; set; }
public Model Model { get; set; }
}
The list of makes is to populate a dropdown for adding. The list of models is used to populate a table of all Models. Here are the classes for each.
public class Make
{
public int MakeId { get; set; }
public string MakeName { get; set; }
public string AddedByEmail { get; set; }
[DataType(DataType.Date)]
public DateTime DateAdded { get; set; }
}
public class Model
{
public int ModelId { get; set; }
public Make Make { get; set; }
public string ModelName { get; set; }
public string AddedByEmail { get; set; }
public DateTime DateAdded { get; set; }
}
Controller:
[Route("admin/Models")]
public ActionResult Models()
{
var vm = new ModelVM();
var _morepo = RepoFactory.GetModelRepo();
var _marepo = RepoFactory.GetMakeRepo();
vm.Makes = _marepo.GetMakes();
vm.Models = _morepo.GetModels();
vm.Model = new Model();
vm.Model.Make = new Make();
return View(vm);
}
public ActionResult AddModels(Model model)
{
var mrepo = RepoFactory.GetModelRepo();
mrepo.AddModel(model);
return RedirectToAction("Models", "Admin");
}
Here is my page view. Everything works on it, except for the post function.
#model GuildCars.UI.Models.ModelVM
#{
ViewBag.Title = "Models";
}
<h2>Models</h2>
#using (Html.BeginForm("AddModels", "Admin", FormMethod.Post, new { #class = "row form-control" }))
{
<label class="form-label col-1" for="SpecialName">New Model:</label>
<input class="col-3" id="ModelName" name="ModelName" type="text" required />
<label class="form-label col-1" for="MakeId">Make:</label>
<select id="#Model.Model.Make.MakeId" name="#Model.Model.Make.MakeId" class="col-4">
#foreach (var m in Model.Makes)
{
<option value="#m.MakeId">#m.MakeName</option>
}
</select>
<button class="btn btn-primary col-1" type="submit">Save</button>
}
<div class="col-4">
<table class="table">
<tr>
<th>Make</th>
<th>Date Added</th>
<th>User</th>
</tr>
#foreach (var m in Model.Models)
{
<tr>
<td>#m.Make.MakeName</td>
<td>#m.ModelName</td>
<td>#m.DateAdded.ToString("MM/dd/yyyy")</td>
<td>#m.AddedByEmail</td>
</tr>
}
</table>
</div>
I have gone though 100 iterations of changing the name and Id, but none of them work. The MakeName is returned since it is a simple type, but the Make class within the model returns null. My goal is to get the Model being returned to the post controller to be populated with the Make selected in the dropdown. I have been programming for less than one year, so I am unsure if this is just a simple syntax error, or if there is a bigger concept that I am missing somewhere.
you have to fix an action input parameter, since your view uses ModelVm as a model, post action should use the same
public ActionResult AddModels(ModelVM model)
if you want to submit data you have to fix the view to use asp-for for the inplut controls
#{
var items=Model.Makes.Select( m=> new SelectListItem (m.MakeId.ToString(), m.MakeName) ).Tolist();
}
....
<input class="col-3" asp-for ="#Model.Model.ModelName" type="text" required />
<label class="form-label col-1" for="MakeId">Make:</label>
<select asp-for="#Model.Model.Make.MakeId" asp-items="#items" class="col-4">
</select>
to use this helper your Views Folder should contain _ViewImports.cshtml file with this code
#using TestCore.WebApp
#using TestCore.WebApp.Models
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
but if you use old mvc you can replace view with this
#Html.TextBoxFor(model => model.Model.ModelName, new { #class = "form-control", #required = "required"} )
#Html.LabelFor(m => m.Model.Make.MakeId)
#Html.DropDownListFor(model => model.Model.Make.MakeId, #items, "select", new { #class = "form-control" })
and IMHO you have to add MakeId to your model class, then your view model will be easier to understand
public class Model
{
public int ModelId { get; set; }
public string ModelName { get; set; }
public string AddedByEmail { get; set; }
public DateTime DateAdded { get; set; }
public int MakeId { get; set; }
public virtual Make Make { get; set; }
}

Entity Framework Eager Loading - pass data to ViewModel

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);

MVC5 : View not submitting correct ViewModel

Minimal Code Structure
I have two ViewModels cmsViewModel and appointments
public partial class cmsViewModel
{
public string rows { get; set; }
public DateTime appt_date_time { get; set; }
public int appt_id { get; set; }
public List<community_meeting_schedule> lst { get; set; }
public cmsViewModel()
{
rows = null;
appt_date_time = DateTime.Today;
lst = null;
appt_id = -1;
}
}
public partial class appointments
{
public int appt_client_id { get; set; }
public int customer_id { get; set; }
[Key]
public int appt_id { get; set; }
public DateTime appt_date_time { get; set; }
public DateTime time_stamp { get; set; }
}
The action method in controller looks like:
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, string Command)
{
return View("CMSchedule", model);
}
The View CMSConfim.cshtml looks like below:
#model Scheduler_MVC.Models.cmsViewModel
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
{
#Html.HiddenFor(model => model.appt_id, new { id = "appt_id" })
#Html.HiddenFor(model => model.appt_client_id, new { id = "appt_client_id" })
#Html.HiddenFor(model => model.appt_date_time)
#Html.HiddenFor(model => model.appt_status)
#Html.HiddenFor(model => model.appt_type)
#Html.HiddenFor(model => model.lst)
<input type="submit" value="Back" id="backBtn" class="btn btn-default" />
<input type="button" value="Submit" id="submitBtn" class="btn btn-default" style="display:inline-block;" />
}
I would like to add that I am able to render correct values in the display fields on the form. The error comes while submitting the form.
Error
Now when I submit the form through Back. I get the following error.
The model in the dictionary is of type 'cmsViewModel' but required is that of type 'appointments'
Please suggest what I might be doing wrong.
Your post view model type is "appointments", but your Html.BeginForm is also routing to an "AppointmentsController"
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
This route is expecting the following
public class AppointmentsController : Controller
{
public Action CMSConfirm(appointments model)
{
}
}
Update your Html.BeginForm if the controller name is wrong
I don't see a match for parameter "Command" either.
Why do you have in your razor sintax cmsViewModel? You expect to submit appointmets but there is cmsVM. Also in your controller you are expecting cmsVM. You need to provide appointmentsVM in razor and also to expect it in your Controller.
[HttpPost]
public ActionResult CMSConfim(appointments model, string Command)
{
return View("CMSchedule", model);
}
Or if you want to get both in controller.
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, appointments appoint, string Command)
{
return View("CMSchedule", model);
}

ViewModel is Null on HTTPPost

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>
}

Asp.net MVC3 Compare Attribute : Error when comparing against a nested property

Getting an error when using MVC3 Compare attribute against a nested property.
Sample code is as follows:
Model and View Model :
public class Data
{
public string Input { get; set; }
}
public class DataVM
{
public Data Data { get; set; }
[Compare("Data.Input")]
public string ConfirmInput { get; set; }
}
Controller :
public ActionResult Data() {
return View(new DataVM());
}
[HttpPost]
public ActionResult Data(FormCollection fc) {
DataVM vm = new DataVM();
TryUpdateModel(vm, fc);
if (ModelState.IsValid){
return Content("Success!!!");
}
return View(vm);
}
View:
#model myth.Models.ViewModels.DataVM
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Data.Input)
#Html.ValidationMessageFor(m => m.Data.Input)
<br />
#Html.EditorFor(m => m.ConfirmInput)
#Html.ValidationMessageFor(m => m.ConfirmInput)
<br />
<input type="submit" value="Save" />
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
If I use [Compare("Input")], client side validation fails.
If I use [Compare("Data.Input")], client side validation works but server side fails.
In class CompareAttribute.cs, method
protected override ValidationResult IsValid(...) { .. },
fails to find Data.Input Property.
What is the correct way to use Compare for Nested Property comparison?
Change your view model and map back to your entity later:
public class DataVM
{
public string Input { get; set; }
[Compare("Input")]
public string ConfirmInput { get; set; }
}

Categories

Resources