Html.Actionlink passing null values to controller - asp.net-mvc

I have this method in my controller called "DirectorySearchController"
public ActionResult PersonDetails(FoundPerson person) //for some reason person is null here
{
DirectoryViewModel viewModel = new DirectoryViewModel();
viewModel.person = person;
return View(viewModel);
}
When I pass some parameters to it from the view using Html.Actionlink it returns a null value
<ul data-role="listview">
#if (ViewBag.Message == "NO RESULTS FOUND")
{
<li>#ViewBag.Message</li>
}
else
{
foreach (var employee in Model)
{
<li>
<div class="ui-grid-b">
<div class="ui-block-a" style="width:20%; vertical-align:middle"><img src="#employee.pictureURL" width="40px" height="40px"/></div>
<div class="ui-block-b" style="width:80%; vertical-align:middle">#Html.ActionLink(employee.name, "PersonDetails", "DirectorySearch", new { person = employee}, null)</div>
</div>
</li>
}
}
</ul>
But the funny thing is that when I pass the parameter without using the "new" keyword it passes the correct value. However, the problem is I need to pass multiple parameters so I need to use the "new" keyword.
<div class="ui-block-b" style="width:80%; vertical-align:middle">#Html.ActionLink(employee.name, "PersonDetails", "DirectorySearch", employee, null)</div>

I think you are not using the correct overload of ActionLink. Try with either of these
The first one should mostly work according to your case:
<%=Html.ActionLink(employee.Name, "PersonalDetails", "DirectorySearch", new { person = employee }, null)%>
<%=Html.ActionLink(employee.Name, "PersonalDetails", new {person = employee})%>
Just a recommendation here. Ideally I'd use an Input submit to post the data to the Controller. You may either use a Model class OR FormCollection in the controller side to retrieve the values you entered in the View.

You should not pass model to the controller action like new { person = employee } in Html.ActionLink. Because what happens is, when you generate the action link the object you pass is converted into RouteValueDictionary which will be passed to the UrlHelper to append the route dictionary values to the link.
When you pass an anonymous object like in the first case the RouteValueDictionary stores a single parameter with name person and to set the value it sees that you have passed an object, it can't serialize the complete instance and set it to the single property so all it does is set the type name of the model Employee as the value to the person. So you will see the generated link as http://someserver/DirectorySearch/PersonDetails?person=Models.Employee
If you pass an instance like you did in the second case then it iterate all the properties and create key/value pairs, while the keys are the property names and the values are the property values, finally they all appended to the link as querystrings. In the case the generated url will be http://someserver/DirectorySearch/PersonDetails?Property1=Value1&Property2=Value2
Correct
#Html.ActionLink(employee.name, "PersonDetails", "DirectorySearch", employee, null)
Wrong
#Html.ActionLink(employee.name, "PersonDetails", "DirectorySearch", new {person = employee }, null)
You can use anonymous object to pass route parameters to the action link but at the time you should not pass reference types but built-in types like integer, string..
Ex.
#Html.ActionLink(employee.name, "PersonDetails", "DirectorySearch",
new { Id = 23, Name = "Mark" }, null)

Related

Routing with multiple parameters in ASP.NET MVC

I'm new to ASP.NET and I'm struggling to understand how routing works. In my project I've managed to create routing for; login, logout, create new user and delete user.
I've created cards containing dates and a stretched-link with the purpose to act as a booking-table (click on a card to book the said time).
View code:
When I click on the link I want to pass forward the user as a string and the time as DateTime(or string). If I replace the url.action argument "TimeSlot.ToString()" with null my routing "works", but of course with the exception that only the user is passed forward to my controller.
#model MyProject.Models.BookingSchedule
...
<div class="row" style="padding-top: 50px; border:solid">
#{ foreach (var TimeSlot in Model.GetAllAvailableTimes())
{
<div class="col-sm-1" style="padding:10px">
<div class="card text-md-center">
<a class="stretched-link" href="#Url.Action("BookTime","Booking",new { user = Model.UserName }, TimeSlot.ToString())">#TimeSlot.ToString()</a>
</div>
</div>
}
}
</div>
Controller:
So far I've just created a mockup code for my controller, its only purpose is to reload my view and redisplay available times together with the time I've booked. For now, I just want to see if my routing passes all parameters to my controller (which it doesn't):
public ActionResult BookTime(string user, string Time)
{
return View("BookingPage", bookingSchedule(user));
}
Routing:
routes.MapRoute(
"BookTime",
"Booking/{Action}/{user}/{time}",
new { controller = "Booking", action = "BookTime", user = UrlParameter.Optional, time = UrlParameter.Optional }
);
When I run my code I get the following error:
How do I create a routing with two or more parameters and what are the key aspects that I need to keep in mind?
I've tried to get knowledge from the Microsoft-docs but as I've not yet managed to overcome this issue I'm hoping someone here could explain it.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing
If I set my href to "href="#Url.Action("BookTime","Booking",new { user = Model.UserName }, TimeSlot.ToString())" then I get the error message above.
If I set href to href="#Url.Action("BookTime","Booking",new { user = Model.UserName }, null)" it will route correctly but of course it doesn't pass my parameters (see photo below):
(in the picture/code above I changed the name of the controller to BookingPage as is the name of my viewpage. I did this to rule out any routing issues)
just add an attribute route
[Route("~/Booking/BookTime/{user?}/{time?}",
public ActionResult BookTime(string user, string Time)
{
return View("BookingPage", bookingSchedule(user));
}
and fix your link
href= ( "Book Time",
"BookTime",
"Booking",
new { user = Model.UserName, time = TimeSlot.ToString() },
null )

Passing a dropdown selected value to a contorller

I'm playing around in an MVC application and am trying to figure out something that seems pretty straight forward.
I have a Index.cshtml file in my Views/Home/ folder that is pretty simple (below)
Index view
...
<div>
Search
#Html.DropDownList("selection", MyProject.Util.Lists.GetMyList(), "Select One")
#Html.ActionLink("Search", "Index", "Search", new { st = xxx }, null)
</div>
...
I also have a Search controller that needs to take a "st" value and looks like this
public class SearchController : Controller
{
// GET: Search
public ActionResult Index(string st)
{
ApplicationDbContext db = new ApplicationDbContext();
List<Report> filteredReports = db.Reports.Where(r => r.Tag == st).ToList();
return View(filteredReports);
}
}
What I'm not sure of how do is grab what ever value is selected from the drop down and add that to my Html.ActionLink where I just have 'xxx' now.
Clicking on the link usuallly does a new GET request to the href attribute value of the link. It will not send any data from your form.
You need to use javascript and hijack the click event on the link and append the selected option value from the SELECT element as querystring to that and send it.
So give an id to the link which you can use later to wireup the click event
#Html.ActionLink("Search", "Index", "Search", null, new {#id="search"})
And the javascript
$(function(){
$("#search").click(function(e){
e.preventDefault();
window.location.href=$(this).attr("href")+"?st="+$("#selection").val();
});
});
Another option is to use the form submit. Wrap your select element inside a form and have a submt button which sends the form to the action method. This method does not need javascript. But your select input element name should match with your parameter name.
#using(Html.BeginForm("Index","Search",FormMethod.Get))
{
<div>
Search
#Html.DropDownList("st", MyProject.Util.Lists.GetMyList(), "Select One")
<input type="submit" value="Search" />
</div>
}
If you do not prefer to have button, but need the link element, you can use javascript to submit the form (like we did in the first approach). With this approach you do not need to manually append the querystring.
You have to call a AJAX POST call to your controller which store selected value, and then when your action link event fire get stored value from there.
AJAX:
$.ajax(
{
url:your url,
data:{"st":dropdown selected value},
type:"post"
});
Controller:
public ActionResult Index()
{
string st=TempData["st"].ToString();
ApplicationDbContext db = new ApplicationDbContext();
List<Report> filteredReports = db.Reports.Where(r => r.Tag == st).ToList();
return View(filteredReports);
}
public void SetSt(string st)
{
TempData["st"] = st;
}

MVC3 Razor Passing Model from View to Controller

I have a MVC3 application using Entity Framework as the Model layer.
In the EmployeeController, I have:
public ActionResult GetEmployeeEdit(String id)
{
// Get the desired Employee
var model = GetEmployees().FirstOrDefault(o=>o.EFolderid == id);
return View("EmployeeEdit", model);
}
private IQueryable<Employee> GetEmployees()
{
// Returns IQueryable<Employee>
return _employeeService.GetTable();
}
In EmployeeEdit I have:
#model Metastorm.Domain.Models.Employee
#{
ViewBag.Title = "Employee Edit";
}
#using (Html.BeginForm("SaveEmployee", "Employee", FormMethod.Get, Model))
{
<fieldset>
<legend>Edit Employee</legend>
<br />
#Html.Label("firstName", "First Name: ")
#Html.EditorFor(o => #Model.NameFirst)
<br />
#Html.Label("lastName", "Last Name: ")
#Html.EditorFor(o => #Model.NameLast)
</fieldset>
<br />
<input class="button" id="submit" type="submit" value = "Save Employee" />
}
Now back to the EmployeeController:
[HttpGet]
public ActionResult SaveEmployee(Employee employee)
{
if (ModelState.IsValid)
{
// Get the Employee Model again from Entity, Update and save
// Unfortunately, the employee object's FolderId value is null
}
// Just getting a model to satisfy the function
var model = GetEmployees().FirstOrDefault();
return View("EmployeeEdit", model);
}
The problem I'm having is that all properties on the employee object are null, except for Employee.NameFirst and Employee.NameLast, which happen to be the properties that were exposed in the View with Html.EditorFor.
In summary, I get an Employee model object, which is fully hydrated. I pass this model from the Controller to the view. In the view, selected fields are allowed to be updated. The Employee model is then passed back to the Controller where updates are persisted.
My question is how do I keep the Employee model that was originally passed from the Controller to the View intact. In other words, I want to have the model
Try to use this code:
[HttpGet]
public ActionResult SaveEmployee([Bind(Include = "NameFirst,NameLast,EFolderid")] Employee employee)
{
if (ModelState.IsValid)
{
// Do your staff
// The employee object's FolderId value will be not null
}
// Just getting a model to satisfy the function
var model = GetEmployees().FirstOrDefault();
return View("EmployeeEdit", model);
}
This will force the data members "NameFirst, NameLast, EFolderid" of the object employee to be persistent. You need to declare in the include statement all the members that you want to be preserved during the Http request.
You should also add this code in your view, inside the Form, or the EFolderid data will be lost:
#Html.HiddenFor(o=> #Model.EFolderid)
Add an "HiddenFor" declaration foreach member that you want to preserve.
To obtain model properties in a Post action, you need include all the properties inside the form.
It is necessary for a correct model serialization.
I went a different route. I created a view model, then us AutoMapper to map the Employee object's properties to the view model. I pass the view model to the view, make changes, then pass the view model back to the Action. Then I can then user AutoMapper to map the view model back to a new instance of the Employee Object, then persist the changes.

How do I append a variable to a string in an ActionLink?

I keep getting a Compilation Error and can't find matching overloaded method. I've tried a couple ways (variable, variable.toString). Below is the latest try.
When I click on the day (ex: 2) on the calendar the ActionLink should send the querystring: "Index?day=2".
#{ string dayAsString = startCount.ToString();}
<div><span>#Html.ActionLink(#startCount.ToString, "Index?day=" + dayAsString , "Event")</span></div>
Do this
<div>
<span>
#Html.ActionLink(startCount.ToString(), "Index", new { day = startCount })
</span>
</div>
The last parameter creates an anonymous object with the property day and value startCount. ActionLink knows to convert that into a querystring using the property name and the property value.
More details here http://msdn.microsoft.com/en-us/library/dd492936.aspx
Edit:
If you want to target a specific controller, do this
#Html.ActionLink(startCount.ToString(), "Index", new { controller = "Event", day = startCount })
You can also do this
#Html.ActionLink(startCount.ToString(), "Index", "Event", new { day = startCount }, null)
but I don't like passing null as a parameter.
Here's a list of all the overloads: http://msdn.microsoft.com/en-us/library/dd505040.aspx
You can also just cycle in the intellisense.
This should work
#Html.ActionLink(#startCount.ToString,"Index","Yourcontroller",new { day=#startCount.ToString()} , null)
replace Yourcontroller with your controller name

DropDownListFor in EditorTemplate not selecting value

I have an editor template for a custom object. Inside that editor template I use a couple of DropDownListFor helpers. In each of them I specify a unique model property (with the pre-selected value) and the select list containing all the select options.
Example:
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I know that the option values are being populated (from viewing source) and that my Model is passed in with the correct ID value (DocumentCategoryType).
When the view is rendered, there is no selected item in my dropdown and therefore it defaults to the first (non-selected) value.
Does anyone have any ideas?
Thanks.
We also solved the solution by populating a new SelectList that has the appropriate SelectListItem selected, but created this extension method to keep the call to DropDownListFor a little cleaner:
public static SelectList MakeSelection(this SelectList list, object selection)
{
return new SelectList(list.Items, list.DataValueField, list.DataTextField, selection);
}
Then your DropDownListFor call becomes:
<%= Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList.MakeSelection(Model.DocumentCategoryType)) %>
Looking through the ASP.NET MVC 2 source code reveals some solutions to this problem. Essentially, any SelectListItem in the SelectList passed in the helper extension method that has the Selected property set to true does not have any bearing over the <option> element rendered with the selected attribute applied for the item.
The selected attribute on <option> elements is determined by
1) checking that the helper extension method was passed a SelectList. If this is null, the framework will look in the ViewData for a value corresponding to the key that is the view model property for which you wish to render the drop down list for. If the value is a SelectList, this will be used to render the <select> including taking any selected values, so long as the model state for the model property is null.
2) If a SelectList has been passed in the helper extension method and the model state for the model property is null, the framework will look in the ViewData for a default value, using the model property name as the key. The value in view data is converted to a string and any items in the SelectList passed to the helper extension method that have a value (if no value is set, then the Text will be checked) that matches the default value will have the Selected property set to true which in turn will render an <option> with the attribute selected="selected".
Putting this together, there are two plausible options that I can see to have an option selected and use the strongly typed DropDownListFor:
Using the following view model
public class CategoriesViewModel
{
public string SelectedCategory { get; private set ; }
public ICollection<string> Categories { get; private set; }
public CategoriesViewModel(string selectedCategory, ICollection<string> categories)
{
SelectedCategory = selectedCategory;
Categories = categories;
}
}
Option 1
Set a value in the ViewData in the controller rendering your view keyed against the property name of the collection used to render the dropdown
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
this.ViewData["Categories"] = selectedCategory;
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.Categories, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
Option 2
Render the dropdown using the name of the property of the selected item(s)
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.SelectedCategory, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
It is confirmed as a bug # aspnet.codeplex.com
and only behaves like this for strongly typed views.
Workaround: populate your SelectList in the view code
like
<%= Html.DropDown("DocumentCategoryType", new SelectList(Model.Categories,"id","Name",Model.SelectedCategory")) =>
Yuck. I ended up solving it like this. I hope this gets fixed for RTM.
<%if(Model!=null){ %>
<%= Html.DropDownListFor(m => m.DocumentCategoryType, new SelectList(Model.DocumentCategoryTypeList,"Value","Text", Model.DocumentCategoryType))%>
<%}else{%>
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
<%}%>
Make sure you have a value assigned to m.DocumentCategoryType when you send it to the view.
Generally this value will get reset when you do a post back so you just need to specify the value
when returning to your view.
When creating a drop down list you need to pass it two values. 1. This is where you will store the selected value 2. Is the actual List
Example
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I made the mistake of setting the select list item Selected value to True. This won't do anything. Instead just assign a value to m.DocumentCategoryType in your controller and this will actually do the selection for you.
Here's another good solution if the source for your drop down list is an IEnumerable instead of a SelectList:
public static SelectList MakeSelection(this IEnumerable<SelectListItem> list, object selection, string dataValueField = "Value", string dataTextField = "Text")
{
return new SelectList(list, dataValueField, dataTextField, selection);
}
Model.DocumentCategoryTypeList
This is probably your problem. On the SelectListItems, do you set the value to the .ToString() output?
var list = new List<SelectListItem>()
{
new SelectListItem()
{
Value = Category.Book.ToString(),
Text = "Book"
},
new SelectListItem()
{
Value = Category.BitsAndPieces.ToString(),
Text = "Bits And Pieces" },
new SelectListItem()
{
Value = Category.Envelope.ToString(),
Text = "Envelopes" }
};
Works for me after doing that. It just needs to be able to match the value from the object
I managed to solve the same problem by saying the folling:
new SelectList(sections.Select(s => new { Text = s.SectionName, Value = s.SectionID.ToString() }), "Value", "Text")
This trick is converting to the value to a string. I know this has been mentioned in previous answers but i find my solution a little cleaner :). Hope this helps.
Copied na pasted from my project:
<%= Html.DropDownListFor(m => m.Profession.Profession_id, new SelectList(Model.Professions, "Profession_id", "Profession_title"),"-- Profession --")%>
Model that is passed:
...
public Profession Profession { get; set; }
public IList<Profession> Professions { get; set; }
...
Html generated:
<select id="Profession_Profession_id" name="Profession.Profession_id">
<option value="">-- Profesion --</option>
<option value="4">Informatico</option>
<option selected="selected" value="5">Administracion</option>
</select>
Works for me. I have this on the form and the only disadvantage is that if model is not valid and I return the model back to the view I have to reload the list of Professions.
obj.Professions = ProfileService.GetProfessions();
return View(obj);
I also had this problem with a field ProgramName. Turns out we used ViewBag.ProgramName in the BaseController and Layout.cshtml, and this was for a different purpose. Since the value in ViewBag.ProgramName was not found in the dropdownlist, no value was selected even though the SelectListItem.Selected was true for one item in the list. We just changed the ViewBag to use a different key and the problem was resolved.
Here is a drop-in DropDownListFor replacement that varies only slightly from the original MVC source.
Example:
<%=Html.FixedDropDownListFor(m => m.DocumentCategoryType,
Model.DocumentCategoryTypeList) %>
I was worried about the performance of making so many copies of my selectList, so instead, I added the selectedvalue as a custom attribute, then used jquery to actually perform the item select:
#Html.DropDownListFor(item => item.AttendeeID, attendeeChoices, String.Empty, new { setselectedvalue = Model.AttendeeID })
........
jQuery("select[setselectedvalue]").each(function () { e = jQuery(this); e.val(e.attr("setselectedvalue")); });

Resources