In my controller I do have this endpoint:
async Task<FileResult> DownloadSelection(AssignedDatabaseSelection selection)
And my HTML looks like:
#if (Model.AssignedDatabaseSelections.Any())
{
<table>
#foreach (var selection in Model.AssignedDatabaseSelections)
{
<tr>
<td>#selection.DisplayName</td>
<td width="10%">
#Html.ActionLink(Strings.CsvLabel, "DownloadSelection", "Home", selection, null)
</td>
</tr>
}
</table>
}
Now I wanted to add another parameter to my controller method:
async Task<FileResult> DownloadSelection(AssignedDatabaseSelection selection, DownloadFormat format)
And
#if (Model.AssignedDatabaseSelections.Any())
{
<table>
#foreach (var selection in Model.AssignedDatabaseSelections)
{
<tr>
<td>#selection.DisplayName</td>
<td width="10%">
#Html.ActionLink(Strings.CsvLabel, "DownloadSelection", "Home", new {selection = selection, Format = DownloadFormat.CSV}, null)
</td>
<td width="10%">
#Html.ActionLink(Strings.ExcelLabel, "DownloadSelection", "Home", new { selection = selection, Format = DownloadFormat.CSV }, null)
</td>
</tr>
}
</table>
}
When I make an inspect elements I got this:
Excel
Now, the format is filled, but the selection is always null. What am I missing?
In you first example, the 3rd parameter is a complex object (typeof AssignedDatabaseSelections) and the method will correctly serialize each property of your object to a query string. Note that it only works because your object contains only simple properties.
In the second example, your creating a new object containing a complex object. The ActionLink() method (and all methods that generate route/query string values) call the .ToString() method on each property in the object (hence you get the name of the class) and do not do recursion.
If you want to pass back all properties of the AssignedDatabaseSelections object plus another property, you need to generate a new object containing each property of AssignedDatabaseSelections, for example (assuming it contains properties ID and Name)
#Html.ActionLink(Strings.CsvLabel, "DownloadSelection", "Home",
new { ID = selection.ID, Name = selection.Name, ......, Format = DownloadFormat.CSV }, null)
Note that if AssignedDatabaseSelections contains a lot of properties and/or larger values, you risk exceeding the query string limit and throwing an exception. A better approach is just to pass the ID property of the AssignedDatabaseSelection and get the object again in the GET method if you need other properties of it.
#Html.ActionLink(Strings.CsvLabel, "DownloadSelection", "Home",
new { ID = selection.ID, Format = DownloadFormat.CSV }, null)
Here is an example of how to create a link with properties from an object. I am including the code of creating a new Employee within the view but this can/should be in the model:
#{ var emp = new Employee { Name = "Tom", Age = 44 }; }
#Html.ActionLink("Test", "Test2", "Account", new {Name = emp.Name, Age = emp.Age }, null)
Please note how I am passing each property of the Employee into the anonymous object for the route values.
The above link will work with this action. The DefaultModeBinder will take the names from the query string in the link and assign it to the properties of the Employee and pass it to the action below:
public ActionResult Test2(Employee emp)
{
return null;
}
Related
Overview
I'm working on a project where I ingest a CSV file, where each record results in the creation of a model in the back end. After ingestion, I am able to create a list of models, and I can return the list of models back to the view with no issue.
However, I want to do custom validation on each model in the list and return the validation results to the view. In this example, I have custom model validation that checks if the email already exists via a database check. When I am creating a model one at a time through a Form on a view, everything goes well. All the validation is run, and there are no issues.
Problem
The issue is when I programmatically create List<Person>(), and then try to validate each Person model in the list. What ends up happening is all the errors are aggregated and when I return the list to the view and display in table via foreach(var p in Model.PersonList), with #Html.ValidationSummary(), then each table row shows all the errors of all the models, not just the errors that pertain to that PersonModel.
Within a foreach loop, I call TryValidateModel() and follow it with ModelState.Clear(), but this does not help. I've also tried using ModelState.Clear() at the beginning of the foreach loop, again no help.
Simplified code of what I'm doing and where I'm stuck.
Controller code - root of problem is here in the foreach loop:
[HttpPost]
public IActionResult BulkModelCreation(BulkUpload inputCSV)
{
// No issue here, I successfully convert CSV to data table. Each Record represents a model
// If there are 10 records in the CSV, then I will have a list of 10 People
DataTable dt = CSVHelper.CSVToDataTable(inputCSV.InputFile);
List<Person> people = new List<Person>();
if (dt != null)
{
// My issues is located here, I think
// In the foreach loop, I read each row from the DataTable, and create a new Person Model
// I want to validate each PersonModel and then add Person to people list
foreach (DataRow dr in dt.Rows)
{
// Create new person
Person p = new Person();
p.FirstName = dr["FirstName"].ToString();
p.LastName = dr["LastName"].ToString();
// More model attributes
p.Email = dr["Email"].ToString();
// Validate the model, and the validation works
TryValidateModel(p);
people.add(p);
// This is where I am unsure of what to do
//To try and reset for the next PersonModel in the List, but this does not work.
// If any person in the list has a duplicate email, then all items in the list will show
// as having a duplicate email.
ModelState.Clear();
}
}
// Other stuff, create model to be returned that has an attribute that is a List<People>
ModelToReturn ModelReturn = new ModelToReturn();
ModelReturn.Persons = people;
// Obj is returned, and data is populated. No issue here
return View(ModelReturn)
}
Simplified model class:
public class PersonModel
{
[Required]
public string FirstName {get;set;}
[Required]
public string LastName {get;set;}
// More stuff
//Custom Valiation
[EmailValidation]
public string Email {get;set;]
}
Custom validation:
public class EmailValidationAttribute: ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var PersonModel = (PersonModel)validationContext.ObjectInstance;
// Passing in Person Repository service so that I can check the db if email already exists
var service = (IPersonRepository)validationContext.GetService(typeof(IPersonRepository));
bool isDuplicate = service.CheckDuplicate(PersonModel.Email);
if (isDuplicate)
{
return new ValidationResult("Email Already Exists");
}
else
{
return ValidationResult.Success;
}
}
}
Simplified view:
<table class="table">
<thead>
<tr>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
<th scope="col">More Stuff</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
#foreach(var p in Model.Persons)
{
<tr>
<td>
<input asp-for="FirstName" class="form-control" />
</td>
<td>
<input asp-for="LastName" class="form-control" />
</td>
<td>MORE STUFF</td>
<td>
<input asp-for="Email" class="form-control" />
#Html.ValidationSummary()<!-- if one person has a duplicate email, then this will say 'Duplicate Email' for every single record in Model.Persons-->
</td>
</tr>
}
</tbody>
</table>
Note, I have reviewed and tried what was suggested in this post: Validate list of models programmatically in ASP.NET MVC
But that does not work, either for the OP, and myself. Any help with this would be greatly appreciated.
I have a controller named (Term1score) and inside the controller i have two different action method(Index_Test,EditScore).
Now inside the view of my (Index_Test)i have Html action-link that will redirect to (Edit Score)now what i want is it will redirect to the specific Id (Example if(Index_Test) SubjectId=1 then it will go to(EditScore) SubjectID=1,)as u can see in my Controller they both target same SubjectID,any idea on how to fix this? thanks and appreciate your response..
Controller:
public ActionResult Index_Test(int? id)
{
List<Term1Score> score = db.Term1Scores.Where(x => x.SubjectID == id).ToList();
return View(score);
}
[HttpGet]
public ActionResult EditScore(int? id)
{
List<Term1Score> score = db.Term1Scores.Where(x => x.SubjectID== id).ToList();
return View(score);
}
I have tried to put this inside the Index_Test View.
This One Work but it will always go to Id 1 only-How to make this automatic changed?
#Html.ActionLink("Edit", "EditScore", "Term1Score", new { id= "1" }, null
I have tried few but still nothing.
#Html.ActionLink("Edit", "EditScore", "Term1Score", new { id=#Model.id }, null)
#Html.ActionLink("Edit", "EditScore", "Term1Score", new { testId=testId.id }, null)
#Html.ActionLink("Edit", "EditScore", "Term1Score", new { id= "" }, null)
EDIT : As per the comment
i'm targeting one link to edit all now with the code u gave its
looping one by one, and Editscore is a Multi editable table
In this case, You can get the first item and use it's SubjectID
#model List<Term1Score>
#{
var id = Model.FirstOrDefault() != null ? Model.First().SubjectId : (int?) null;
}
#Html.ActionLink("Edit", "EditScore", "Home", new { id = id }, null)
Another option is create a view model which has 2 properties, one for the Id and one for the List of Term1Score and use that to transfer data to your view.
Your Index_Test action method is passing a list of Term1Score objects to the view, So you need to loop through them and render a edit link
The EditScore action method parameter name is id. So make sure when you are building the anchor tag using Html.ActionLink helper, you use id as the route value object key.
So in your Index_Test.cshtml
#model List<Term1Score>
#foreach (var testId in Model)
{
<p>
#Html.ActionLink("Edit", "EditScore", "Home", new { id = testId.Id }, null)
</p>
}
Also , in the EditScore method, you probably want to edit a single record, Currently you are returning the same collection as the previous method. You probably want to get the single record matching that id.
[HttpGet]
public ActionResult EditScore(int id)
{
Term1Score score = db.Term1Scores.FirstOrDefault(x => x.Id== id);
if(score==null)
{
return Content(" This item does not exist"); // or a view with the message
}
return View(score);
}
Now since you are passing a single item, make sure your view is strongly typed to that
#model Term1Score
<h2>#Model.Id</h2>
This question already has answers here:
The model item passed into the dictionary is of type .. but this dictionary requires a model item of type
(7 answers)
Closed 6 years ago.
It seems like this question has been asked and asnwered a lot on this site, but I still cannot seem to get my problem solved. I'm getting the The model item passed into the dictionary is of type 'X', but this dictionary requires a model item of type 'Y' error, even though I'm pretty sure my code is pointing to the right place and receiving the correct viewModel.
View (Index)
#model EveryNationRandburg.ViewModels.AllUsers
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Church Members</h2>
<p>#Html.ActionLink("New Member", "Register", "Account", null, new { #class = "btn btn-primary" })</p>
<table class="table table-bordered table-hover" id="kerkmembers">
<thead>
<tr>
<th>Member</th>
<th>Email</th>
<th>Contact Number</th>
</tr>
</thead>
<tbody>
#foreach (var member in Model.AlleKerkMembers)
{
<tr>
<td>#Html.ActionLink(#member.UserName, "Edit", "KerkMember", new {id = #member.Id}, null)</td>
<td>#member.Email</td>
<td>#member.PhoneNumber</td>
</tr>
}
</tbody>
</table>
#section scripts
{
<script>
$(document).ready(function (){
$("#kerkmembers").DataTable();
})
</script>
}
ViewModel
public class AllUsers
{
public List<ApplicationUser> AlleKerkMembers = new List<ApplicationUser>();
}
Account Controller
public ActionResult Index()
{
var viewModel = new AllUsers
{
AlleKerkMembers = _context.Users.ToList()
};
return View(viewModel);
}
StackTrace
InvalidOperationException: The model item passed into the dictionary is of type 'EveryNationRandburg.ViewModels.AllUsers', but this dictionary requires a model item of type 'EveryNationRandburg.ViewModels.KonnekGroepViewModel'.
What is slightly different for my problem is that whenever I try to send a viewModel to a view, I always get the error saying it is expecting item of type 'Y' (always the same one, no matter what model I declare at the top of my view)
First thing I'd suggest is to attach a debugger to your code and see values you are handling. With debugger you can see call stack which we can't see here.
Where do you use result for return View(viewModel); ?
You don't have listing for your view's constructor.
What is the type of object your view is expecting?
Should be AllUsers since you are passing AllUsers object to it.
You can try using explicit types instead of var to be sure you are passing right object type. Code would be easier to read too.
Please attach your debugger and try to solve it out that way.
Say I have a table that begins like this:
#foreach (var item in Model)
{
<tr>
<td>
#Html.ActionLink(item.Name, "Things", new { someId = item.Id})
</td>
And it takes me to the view for that specific item. Once I am in that separate .cshtml file, how can I reference back to the original "item.Name" string that appeared in this ActionLink?
* SOLUTION EDIT *
The solution ended up looking like this.
The ActionLink:
#Html.ActionLink(item.Name, "Things", new { someId = item.Id, someName = item.Name})
The Action:
public ActionResult Things(Guid? someId, string someName)
...
ViewBag.currentName = someName;
The other View:
<h2>#ViewBag.currentName</h2>
You can add an extra parameter with the name of the item
#Html.ActionLink(item.Name, "Things", new { someId = item.Id, name = item.Name})
You will need to modify your controller and model too for this approach.
I think you don't have to reference to first ActionLink argument. What might interest you is an object which is a Model for this view. When you click the link you will navigate to action where you pass the someId - id of current model object. Then probably you should find an object by its id in your storage. It will have .Name property that was used as ActionLink argument.
However if you just need exactly the same string you've used as a first argument in ActionLink which can be any string you should duplicate it in route values. Your Things action should accept not only someId argument but also this string. After you should pass this string via Model or ViewBag/ViewDictionary to your view.
I have the following code in my CMS 3.0 project
SurveyController.cs
private BuisnessSurveyEntities bsdb = new BuisnessSurveyEntities();
[HttpGet]
public ViewResult BizSurveyCDF()
{
var bquery = from b in bsdb.form_field
where b.ID != null // int
where b.DATID != null // int
where b.CAPTION != null // string
select new {b.ID, b.DATID, b.CAPTION};
ViewData["BZQUESTIONS"] = new SelectList(bquery,"ID","DATID","CAPTION");
return View();
}
form_field.cs model
public virtual string ID {GET; SET;}
public virtual string DATID
{
get{return _dATID;}
set{_dATID = value;}
}
private string _dATID = "\'\'";
BizSurveyCDF.cshtml
#model IEnumberable<CMS.Models.form_field>
<table>
<tr>
<th>
DATID
</th>
<th>
CAPTION
</th>
</tr>
#foreach(var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DATID)
</td>
<td>
#Html.DisplayFor(modelItem => item.CAPTION)
</td>
</tr>
}
</table>
Now my issue is when I run it i get the following error:
Object reference not set to an instance of an object.
and the offending line is
#foreach(var item in Model)
I have gone through the entire table and replaced all NULL values with something and am still receiving this error. So far everything I have read says it is having issues with NULL values but as I already stated I have gotten rid of all null values.
Any help would be GREATLY appreciated.
Thanks
Try something like this, pass the model to the view and don't create a new object in the query, select the full form_field object.
public ViewResult BizSurveyCDF()
{
var bquery = from b in bsdb.form_field
where b.ID != null // int
where b.DATID != null // int
where b.CAPTION != null // string
select b;
//ViewData["BZQUESTIONS"] = new SelectList(bquery,"ID","DATID","CAPTION");
return View(bquery);
}
You are not using ViewData["BZQUESTIONS"] in your view
The reason why you're getting the exception could be that your query isn't returning any value actually.So,make sure that the query contains at least one element before being passed as the SelectList source.For example,try something along these lines:
if(bquery.Any())
{
ViewData["BZQUESTIONS"] = new SelectList(bquery,"ID","DATID","CAPTION");
}
else
{
// Add code here to address there being of no element returned by the query
}
return View();
Make sure the actual code contains IEnumerable not IEnumberable
int is a value type and therefore, is never null. These where clauses are unnecessary.
You are creating an anonymous object, so the model ends up not being of the expected type, which is why it failed when you tried DivHenr's solution. Do not select an anonymous object.
Also, force evaluation of your query and pass it to the model parameter of the view method, (instead of to viewdata).
Your query and action should be
-
return View(
(from b in bsdb.form_field
where b.CAPTION != null
select b)
.ToList() );