I am trying to send 2 variables to my controller with a Html.Actionlink, but everytime i do this, i get a NULL value, instead of the value i am trying to send.
This is my Html.Actionlink:
<ul>
#{ Spot selectedSpot = (Spot)Session["SelectedSpot"];}
#foreach (Spot spot in (List<Spot>)Session["AllSpots"])
{
if (selectedSpot != null)
{
if (selectedSpot.Id == spot.Id)
{
<li>#Html.ActionLink(spot.ToString(), "SetSpot", "EmsUser", new { selectedSpot = spot, user = Model }, new { #class = "selected"})</li>
}
else
{
<li>#Html.ActionLink(spot.ToString(), "SetSpot", "EmsUser", new { selectedSpot = spot, user = Model }, null)</li>
}
}
else
{
<li>#Html.ActionLink(spot.ToString(), "SetSpot", "EmsUser", new { selectedSpot = spot, user = Model }, null)</li>
}
}
</ul>
This is my controller:
public ActionResult SetSpot(Spot selectedSpot, User user)
{
Session["SelectedSpot"] = selectedSpot;
user.SetId(0);
return View("MakeReservation", user);
}
EDIT:
This is what the actionlink expects from me. "EmsUser" is the controller name and "SetSpot" is the action name
I figured out what the issue is. The route arguements specified in the ActionLink are posted as query string parameters to controller action. You can't add instance type in the route arguements. If you wish to pass the objects, you will have to do some work arounds. Please see below my suggestions:
Method 1:
Pass all the fields of the class individually in the route arguments. For e.g. lets say the classes are-
public class Model1
{
public int MyProperty { get; set; }
public string MyProperty1 { get; set; }
}
public class Model2
{
public int MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
Your ActionLink should be:
#Html.ActionLink("Text", "SetSpot", "EmsUser",
new {
MyProperty = 1,
MyProperty1 = "Some Text",
MyProperty2 = 2,
MyProperty3 = "Some Text"
}, null)
Method 2:
Use ajax call as shown in this link
Related
I'm trying to present a table with a list of rows from the database, as well as a small form option for the user to add an additional db row. I made two Models in order to both hold the table rows and capture the form. I'm not having problems with the table model, just the form model.
So I'm confident the problem is in my Controller; I am new to MVC and this is the first time I've seen two models in the same VM... I've been going back and forth making small changes to things, but I keep getting a Null Reference Error in my Controller.
VM:
namespace User.ViewModel
{
public class UploadDocumentTemplatesViewModel
{
public string TemplateName { get; set; }
public string Comments { get; set; }
}
public class TemplateModel
{
public UploadDocumentTemplatesViewModel NewTemplate { get; set; }
public List<UploadDocumentTemplatesViewModel> Templates { get; set; }
}
}
View (UploadDocumentTemplates.cshtml):
#using (Html.BeginForm("AddNewTemplate", "User", FormMethod.Post, new { enctype = "multipart/form-data", id = "NewTemplateForm"}))
{
#Html.AntiForgeryToken()
<div>
<input type="text" class="form-control" rows="5" id="TemplateName" value="#Model.NewTemplate.TemplateName">
<label for="comments">Comments:</label>
<textarea class="form-control" rows="5" id="comments" name="#Model.NewTemplate.Comments"></textarea>
<button id="AddTemplate" onclick="AddNewTemplate()" name="Add Template">Add Template</button>
</div>
}
<table>
<tbody>
#{ foreach (var template in Model.Templates)
{
<tr id="tr_#template.DocumentTemplateId">
<td>#template.TemplateName</td>
<td>#template.Comments </td>
<td>#template.IsActive </td>
}
<script>
function AddNewTemplate() {
$("#NewTemplateForm").submit();
}
</script>
Controller:
public class UserController : Controller
{
public ActionResult UploadDocumentTemplates()
{
var model = entity.DocumentTemplates.Select(x => new UploadDocumentTemplatesViewModel()
{
DocumentTemplateId = x.DocumentTemplateId,
TemplateName = x.TemplateName,
Comments = x.Comments,
});
var _obj = new TemplateModel();
var templatelist = _entities.GetTemplates();
_obj.Templates = new List<UploadDocumentTemplatesViewModel>();
foreach (var template in templatelist)
{
_obj.Templates.Add(new UploadDocumentTemplatesViewModel()
{
TemplateName = template.TemplateName,
Comments = template.Comments,
});
}
_obj.NewTemplate = new UploadDocumentTemplatesViewModel();
return View(_obj);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddNewTemplate(TemplateModel vModel)
{
var template = new DocumentTemplates();
template.TemplateName = vModel.NewTemplate.TemplateName;
template.Comments = vModel.NewTemplate.Comments;
entity.DocumentTemplates.Add(template);
entity.SaveChanges();
return View("~/User/UploadDocumentTemplates.cshtml", vModel);
}
}
I hit a Null Reference at this line in my Post method in the Controller:
template.TemplateName = vModel.NewTemplate.TemplateName;
The vModel.NewTemplate.TemplateName is null. Can anyone help me with this?
Thank you so much!
SOLUTION:
The NRE did get fixed with Andy's help (see answer). My further issue was fixed after I populated the new VM with the fields I was using in my form.
namespace Example.Areas.ViewModel
{
public class UploadDocumentTemplatesViewModel
{
public long DocumentTemplateId { get; set; }
public string TemplateName { get; set; }
public string Comments { get; set; }
}
public class TemplateModel
{
public string TemplateName { get; set; }
public string Comments { get; set; }
public HttpPostedFileBase TemplateFile { get; set; }
public List<UploadDocumentTemplatesViewModel> Templates { get; set; }
}
}
In the controller I populated the TemplateModel and sent that to the View:
public ActionResult UploadDocumentTemplates()
{
var templateList = Repository.GetTemplates();
var _obj = new TemplateModel
{
Templates = templateList.Select(x => new UploadDocumentTemplatesViewModel()
{
DocumentTemplateId = x.DocumentTemplateId,
TemplateName = x.TemplateName,
Comments = x.Comments
}).ToList()
};
return View(_obj);
}
Then I put #using Example.Areas.ViewModel.TemplateModel on my View and used Model.Templates for the table, and Model.TemplateName and Model.Comments in the form.
Add a breakpoint in the line where you hit the Null Reference and check if vModel.NewTemplate has a valid instance.
You can add a constructor in TemplateModel class to create a new instance of UploadDocumentTemplatesViewModel()
namespace User.ViewModel
{
public class UploadDocumentTemplatesViewModel
{
public string TemplateName { get; set; }
public string Comments { get; set; }
}
public class TemplateModel
{
public TemplateModel()
{
NewTemplate = new UploadDocumentTemplatesViewModel();
}
public UploadDocumentTemplatesViewModel NewTemplate { get; set; }
public List<UploadDocumentTemplatesViewModel> Templates { get; set; }
}
}
OR
create an instance after instantiating template:
var template = new DocumentTemplates();
template.NewTemplate = new UploadDocumentTemplatesViewModel();
I got a two DropDownList's in View. When i try pass those parameters, method in controller called but parameters equals a null.
When i check in browser (F12-network) i watch parameters - they are sended but in method still nulls
P.S.
I try change type of parameters on List or Location and JobTitle or CommonEntity, but its doesn't work
Controller:
public class HelloController: Controller
{
[HttpGet]
public IActionResult Index()
{
var locations = new List<Location>()
{
new Location()
{
Id = 0,
Title = "Russia"
},
new Location()
{
Id = 1,
Title = "Canada"
}
};
ViewBag.Location = locations;
var jobs = new List<JobTitle>()
{
new JobsTitle()
{
Id = 0,
Title = "Manager"
} ,
new JobsTitle()
{
Id = 1,
Title = "Programmer"
}
};
ViewBag.JobTitle = new SelectList(jobs, "Title", "Title");
return View();
}
[HttpPost]
public string Find(string answer1, string answer2)
{
return "Fine";
}
View:
#using Stargate.Core.Models.CoreEntities
#model CommonEntity
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.Location.Title, new SelectList(ViewBag.Location, "Title", "Title"))
#Html.DropDownListFor(m => m.JobTitle.Title, new SelectList(ViewBag.JobTitle, "Title", "Title"))
<button type="submit">Find</button>
}
Models:
public class CommonEntity
{
public Location Location { get; set; }
public JobTitle JobTitle { get; set; }
}
public class JobTitle
{
public long Id { get; set; }
public string Title { get; set; }
}
public class Location
{
public long Id { get; set; }
public string Title { get; set; }
}
Because the parameter names you accept are answer1, answer2, you should have a matching name in your view to make it possible to bind successfully.
You can modify your front-end code as follows(DropDownListForto DropDownList):
#model CommonEntity
#using (Html.BeginForm("Find", "Hello"))
{
#Html.DropDownList("answer1", new SelectList(ViewBag.Location, "Title", "Title"))
#Html.DropDownList("answer2", new SelectList(ViewBag.JobTitle, "Title", "Title"))
<button type="submit">Find</button>
}
Your Controller:
public class HelloController : Controller
{
[HttpGet]
public IActionResult Index()
{
var locations = new List<Location>()
{
new Location()
{
Id = 0,
Title = "Russia"
},
new Location()
{
Id = 1,
Title = "Canada"
}
};
ViewBag.Location = locations;
var jobs = new List<JobTitle>()
{
new JobTitle()
{
Id = 0,
Title = "Manager"
} ,
new JobTitle()
{
Id = 1,
Title = "Programmer"
}
};
ViewBag.JobTitle = jobs;
return View();
}
[HttpPost]
public string Find(string answer1,string answer2)
{
return "Fine";
}
}
Class:
public class CommonEntity
{
public Location Location { get; set; }
public JobTitle JobTitle { get; set; }
}
public class JobTitle
{
public long Id { get; set; }
public string Title { get; set; }
}
public class Location
{
public long Id { get; set; }
public string Title { get; set; }
}
Result:
you are doing things wrongly,
you should correct your cshtml so that when submitting the form, it will target your Find Action,
#using (Html.BeginForm("Find", "Hello"))
In your Find Action you should provide in input args resolvable by the DefaultModelBinder, since you don't have a ViewModel to intercept the response, I would suggest that you recieve a FormCollection and you can access your values from there.
[HttpPost]
public string Find(FormCollection form)
{
return "Fine";
}
Try updating parameters as below. Please refer Model Binding in ASP.NET Core for more details.
[HttpPost]
public string Find(Location Location, JobTitle JobTitle)
{
return "Fine";
}
Or you can try with parameter of CommonEntity like below.
[HttpPost]
public string Find(CommonEntity commonEntity)
{
var locationTitle = commonEntity.Location.Title;
var jobTitle = commonEntity.JobTitle.Title;
return "Fine";
}
I have a model class named categories that retrieves data from database :
public class HomeController : Controller
{
private StoreContext db;
public HomeController()
{
db = new StoreContext();
}
public ActionResult Categories()
{
return View(db.Categories.ToList());
}
}
I want to use DropDownList helper method to display them in the view and I want all the categories inside it to be clickable, say, when you click them it has to adress you to the specified url belongs to the clicked category. Is there a way to make this happen with DropDownList helper? if yes then how?
You can do this but you have to use Jquery . If you ask how?
For example:
My sample entity
public class Category
{
public int Id { get; set; }
public string Url{ get; set; }
public string Name { get; set; }
}
My action:
public IActionResult Categories()
{
var list = new List<Category>();
for (int i = 0; i < 10; i++)
{
list.Add(new Category(){Id = i, Url = "https://stackoverflow.com", Name = "stackoverflow" });
}
var selectList = list.Select(x => new SelectListItem() {Value = Url, Text = x.Name})
.ToList();
return View(selectList);
}
in my View:
#Html.DropDownList("url",Model, "Choose a URL", new { id = "url_list" })
and then using jquery you could subscribe for the change event of this dropdownlist and navigate to the corresponding url:
$(function() {
$('#url_list').change(function() {
var url = $(this).val();
if (url != null && url != '') {
window.location.href = url;
}
});
});
I wish to utilize the ASP.NET MVC convention to parse POSTed form submission to a model by ActionResult MyAction(MyModel submitted). MyModel includes a property of the type that I defined -UsState-.
submitted.UsState as the result of the action returns null.
Is there any way to get the submitted to be set to proper value?
I have the following view with a form in a MyForm.cshtml
...
#using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, ...))
{
#Html.DropDownList("States",
null,
new { #id = "state", #class = "form-control", #placeholder = "State"})
}
...
with the controller
public class MyController : Controller
{
public ActionResult MyForm()
{
ViewBag.States = GetStatesList();
}
public ActionResult MyAction(MyModel info) //info.State is set to null on POST
{
return View();
}
private static List<SelectListItem> GetStatesList()
{
var states = new List<SelectListItem>();
states.Add(new SelectListItem { Value = "", Selected = true, Text = "State" });
foreach (var s in UsStates.ToList())
{
var state = new SelectListItem { Value = s.Abbreviation, Text = s.Name, Disabled = !s.Available };
states.Add(state);
}
return states;
}
}
with the model
public class MyModel
{
public UsState States { get; set; } //Do I need proper setter for it to work?
}
public static class UsStates //Should I get the UsStates in a different way?
{
private static List<UsState> states = new List<UsState> {
new UsState("AL", "Alabama"),
//...
}
public static List<UsState> ToList()
{ return states; }
}
public class UsState
{
public UsState(string ab, string name, bool available = true)
{
Name = name;
Abbreviation = ab;
Available = available;
}
}
Change you MyModel to
public class MyModel
{
public string SelectedState {get;set; }
public IEnumerable<SelectListItem> States { get; set; }
}
modify your view to
#using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, ...))
{
#Html.DropDownListFor(m => m.SelectedState, Model.States, "State",
new { #class = "form-control" })
}
I changed this to a DropDownListFor, because it will automatically generate the id and name attributes for you. It will also create an element at the top "State" so that you don't have to pre-pend it to your list.
I removed your placeholder because placeholders are not supported with select elements. As well as not to be used as replacements for labels.
Per W3C
The placeholder attribute specifies a short hint that describes the
expected value of an input field (e.g. a sample value or a short
description of the expected format).
Split your controller actions to a GET/POST pair
public class MyController : Controller
{
//Get
public ActionResult MyAction()
{
return View(new MyModel { States = GetStateList() });
}
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
model.States = GetStateList();
return View(model);
}
//do whatever you are going to do with the posted information.
return RedirectToAction("<some view>"); //redirect after a post usually to Index.
}
private static IEnumerable<SelectListItem> GetStatesList()
{
var states = new List<SelectListItem>();
foreach (var s in UsStates.ToList())
{
var state = new SelectListItem { Value = s.Abbreviation, Text = s.Name, Disabled = !s.Available };
states.Add(state);
}
return states;
}
}
After looking into the POSTed data, I realized that the submitted form data -States=AL-type is string, which would fail to be set as UsState type.
I changed the model to the following:
public class MyModel
{
public string States { get; set; }
}
I get the string value of the submitted form, at least. Not sure if this is given - if that's the case, I suppose I should use a separate ViewModel in order to get States as a string, and convert that into MyModel where I cast string States into UsState States.
I have the following form:
#model Teesa.Models.SearchModel
#using (Html.BeginForm("Index", "Search", FormMethod.Get, new { id = "SearchForm" }))
{
<div class="top-menu-search-buttons-div">
#if (!MvcHtmlString.IsNullOrEmpty(Html.ValidationMessageFor(m => m.SearchText)))
{
<style type="text/css">
.top-menu-search-text
{
border: solid 1px red;
}
</style>
}
#Html.TextBoxFor(q => q.SearchText, new { #class = "top-menu-search-text", id = "SearchText", name = "SearchText" })
#Html.HiddenFor(q=>q.Page)
<input type="submit" value="search" class="top-menu-search-submit-button" />
</div>
<div id="top-menu-search-info" class="top-menu-search-info-div">
Please Select one :
<hr style="background-color: #ccc; height: 1px;" />
<div class="top-menu-search-info-checkbox-div">
#Html.CheckBoxFor(q => q.SearchInBooks, new { id = "SearchInBooks", name = "SearchInBooks" })
<label for="SearchInBooks">Books</label>
</div>
<div class="top-menu-search-info-checkbox-div">
#Html.CheckBoxFor(q => q.SearchInAuthors, new { id = "SearchInAuthors" })
<label for="SearchInAuthors">Authors</label>
</div>
<div class="top-menu-search-info-checkbox-div">
#Html.CheckBoxFor(q => q.SearchInTags, new { id = "SearchInTags" })
<label for="SearchInTags">Tags</label>
</div>
}
and the following Controller and Models :
namespace Teesa.Models
{
public class SearchModel
{
[Required(ErrorMessage = "*")]
public string SearchText { get; set; }
public bool SearchInTags { get; set; }
public bool SearchInAuthors { get; set; }
public bool SearchInBooks { get; set; }
public int Page { get; set; }
public List<SearchBookModel> Result { get; set; }
public List<SimilarBookModel> LatestBooks { get; set; }
}
public class SearchBookModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Summary { get; set; }
public List<Tags> Tags { get; set; }
public string StatusName { get; set; }
public string SubjectName { get; set; }
public string ThumbnailImagePath { get; set; }
public string BookRate { get; set; }
public string RegistrationDate { get; set; }
public int NumberOfVisit { get; set; }
}
}
[HttpGet]
public ActionResult Index(SearchModel model)
{
FillSearchModel(model);
if (ModelState.IsValid)
{
string page = model.Page;
DatabaseInteract databaseInteract = new DatabaseInteract();
model.Result = new List<SearchBookModel>();
List<Book> allBooks = databaseInteract.GetAllBooks();
List<Book> result = new List<Book>();
#region
if (model.SearchInTags)
{
var temp = (from item in allBooks
from tagItem in item.Tags
where tagItem.Name.Contains(model.SearchText)
select item).ToList();
result.AddRange(temp);
}
if (model.SearchInBooks)
{
var temp = (from item in allBooks
where item.عنوان.Contains(model.SearchText)
select item).ToList();
result.AddRange(temp);
}
if (model.SearchInAuthors)
{
var temp = (from item in allBooks
where item.Author.Contains(model.SearchText)
select item).ToList();
result.AddRange(temp);
}
#endregion
#region Paging
string itemsPerPage = databaseInteract.GetItemsPerPage();
int ItemInPage = int.Parse(itemsPerPage);
var pagingParams = Helpers.SetPagerParameters(page, ItemInPage, result);
ViewBag.AllPagesCount = pagingParams.AllPagesCount;
ViewBag.CurrentPageNumber = pagingParams.CurrentPageNumber;
ViewBag.CountOfAllItems = pagingParams.CountOfAllItems.ToMoneyFormat().ToPersianNumber();
result = pagingParams.ListData as List<Book> ?? result;
#endregion
foreach (var item in result)
{
var bookRate = (item.BookRate == null || item.BookRate.Count == 0)
? 0.0
: item.BookRate.Average(q => q.Rate);
model.Result.Add(new SearchBookModel
{
Author = item.Author,
Id = item.Id,
.
.
.
});
}
}
else
{
model.Result = new List<SearchBookModel>();
}
return View(model);
}
When I submit the form I see the following query strings(Notice the duplicate names) :
http://localhost:2817/Search?SearchText=book&Page=2&SearchInBooks=true&SearchInBooks=false&SearchInAuthors=true&SearchInAuthors=false&SearchInTags=true&SearchInTags=false
But it has to be something like this :
http://localhost:2817/Search?SearchText=book&Page=2&SearchInBooks=true&SearchInAuthors=true&SearchInTags=true
How can I fix it ?
Thanks
Html.Checkbox (and the related For... methods) generate a hidden input for false, and the checkbox for true. This is to ensure that model binding works consistently when binding.
If you must get rid of "false" items resulting from the hidden inputs, you'll need to construct the checkbox inputs yourself (using HTML and not the helper).
<input type="checkbox" id="SearchInBooks" name="SearchInBooks">
Why dont your create a Post Method with a matching name to the Get method. This will ensure that the code is much easier to debug. As you will not have a huge function to go through trying to find problems like this.
I cannot find a where your getting the duplicate url query strings from though.
This will also allow you to bind your results back to the model.
If you want the model binding to happen successfully then you have to go with this way because that is the nature of the Html.CheckBox/Html.CheckBoxFor methods they will render a hidden field as well.
I would suggest rather go with POST to make your life easy. If you still want to use GET then you have to use checkbox elements directly but you have to take care of the model binding issues. Not all the browsers returns "true" when the checkbox is checked for ex. firefox passes "on" so the default model binder throws an error.
Other alternate options is you can go for custom model binder or you can submit the form using jquery by listening to the submit event.