I'm trying to make an "advanced search" view, using two partial views and Ajax. I defined a "SearchFilter" entity having as properties all the available search criteria. On submit in the "_Filter" partial view (the OnSuccess AjaxOption), I need to pass it to the "ListResults" action which updates the "_Results" partial view.
The problem is that I always get a null entity as incoming parameter of the ListResults action.
The code is as follows:
AdvancedSearchView.cshtml
#model MyApp.ViewModels.SearchFormViewModel
#{
ViewBag.Title = "Advanced search";
}
<div id="divFilter">
#Html.Partial("_Filter", Model)
</div>
<div id="divResults">
#Html.Partial("_Results", Model.ResultsList)
</div>
_Filter.cshtml
#model MyApp.ViewModels.SearchFormViewModel
<script type="text/javascript">
function getForm(url, divName) {
var obj = new Date();
url = (url.indexOf('?', 0) != -1) ? url + '&uid=' + obj.getTime() : url + '?uid=' + obj.getTime();
$.get(url, function (data) {
$("#" + divName).html(data);
});
}
</script>
#using (Ajax.BeginForm("Search", null, new AjaxOptions
{
UpdateTargetId = "divFilter",
InsertionMode = InsertionMode.Replace,
OnSuccess="getForm('"+Url.Action("ListResults", "Products", new { myFilter = Model.CurrentFilter}) + "','divResults')"
}, new { id = "idSearchForm" }))
{
<fieldset style="width: 800px; line-height: 1.4em;">
<legend>Configure your search filters</legend>
...
</fieldset>
<input type="submit" value="Rechercher" class="submit" style="width: 280px" />
}
Controller
public ActionResult Search()
{
SearchFilter currentFilter = new SearchFilter();
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return View("AdvancedSearchView", new AdvancedSearchFormViewModel(currentFilter, filteredProductsList/* , necessary select lists */));
}
[HttpPost]
public ActionResult Search(AdvancedSearchFormViewModel model)
{
SearchFilter currentFilter = model.CurrentFilter;
// set the necessary select lists
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return PartialView("_Filter", AdvancedSearchFormViewModel(currentFilter, filteredProductsList/* , necessary select lists */));
}
public ActionResult ListResults(SearchFilter myFilter)
{
List<Product> filteredProductsList = repository.FindProducts_Filtered(currentFilter);
return PartialView("_Results", filteredProductsList);
}
The view model
public class AdvancedSearchFormViewModel
{
// Properties
public SearchFilter CurrentFilter { get; set; }
public List<Product> ResultsList { get; set; }
// some SelectLists
// Constructor
public AdvancedSearchFormViewModel()
{}
public AdvancedSearchFormViewModel(SearchFilter pCurrentFilter, List<Product> pResultsList, /* necessary select lists*/)
{
CurrentFilter = pCurrentFilter;
ResultsList = pResultsList;
// the SelectLists
}
}
I have no doubt I do something wrong, but I cannot see what it is.
The generated HTML markup of the BeginForm is like this:
<form action="/Products/Search" data-ajax="true" data-ajax-mode="replace" data-ajax-success="getForm('/Products/ListResults?myFilter=MyApp.Models.SearchFilter&uid=2622ea0e-d7dc-48fa-b65d-519978ee40b3','divResults')" data-ajax-update="#divFilter" id="idSearchForm" method="post">
The reason you are getting a null value for the myFilter argument of the ListResults action is because this is what you are submitting to the server:
/Products/ListResults?myFilter=MyApp.Models.SearchFilter&uid=2622ea0e-d7...
The default modelbinder is trying to turn the string "MyApp.Models.SearchFilter" into an instance of MyApp.Models.SearchFilter, which it cannot do.
I can't see the code for this model object, but you should try sending each of the parameters individually so that the modelbinder is able to construct an instance of the SearchFilter out of its properties:
OnSuccess="getForm('"+Url.Action("ListResults", "Products", new {
Prop1 = Model.CurrentFilter.Prop1,
Prop2 = Model.CurrentFilter.Prop2,
etc...
})...
Update after comment 1
To answer your question about showing parameter values in the URL, this is how HTTP works. If you do not want to show any parameters sent to the server in the URL, then you will need to do an HTTP POST instead of an HTTP GET.
However, any time your operation is idempotent, you should use an HTTP GET. HTTP POST should be reserved for when the input submission should somehow alter the state of the application. In actuality, I disagree with your using [HttpPost] for your public ActionResult Search(AdvancedSearchFormViewModel model) action method. Since all it is doing is returning data to be viewed, it is an idempotent action.
That said, there is nothing preventing you from breaking this guideline and doing an HTTP POST instead of a GET. Personally, I don't see a problem with the URL parameters. You see them all the time on the web. For example, see the URL for this link.
HTTP does not understand complex objects, only text. To send data for complex objects over HTTP, they need to be broken down into their text parts. To send data like this over an HTTP GET, they need to be in the URL.
Related
I'm using the following pattern https://github.com/filamentgroup/Ajax-Include-Pattern
to load partial views through ajax.
View:
#using(Html.BeginUmbracoForm("PostContactInformation", "JoiningSurface", null, new Dictionary<string, object> { { "class", "joinform" } })) {
#Html.AntiForgeryToken()
<div data-append="#Url.Action("RenderJoiningContactInformation", "JoiningSurface", new { ContentId = CurrentPage.Id })"></div>
}
With Action:
public ActionResult RenderContactInformation(int ContentId)
{
var viewModel = ContactViewModel();
viewModel.Content = Umbraco.TypedContent(ContentId);
return PartialView("RenderContactInformation", viewModel);
}
Loads partial view perfectly.
// No need to add partial view i think
Post action works correctly as well:
public ActionResult PostContactInformation(ContactViewModel model)
{
//code here
return RedirectToUmbracoPage(pageid);
}
The problem is, that i need to add model error to CurrentUmbracoPage if it exists in post...
For example:
public ActionResult PostContactInformation(ContactViewModel model)
{
ModelState.AddModelError(string.Empty, "Error occurred");
return CurrentUmbracoPage();
}
In this case i get null values for current model. And this happens only when i use ajax.
If i load action synchronously like that:
#using(Html.BeginUmbracoForm("PostJoiningContactInformation", "JoiningSurface", null, new Dictionary<string, object> { { "class", "joinform" } })) {
#Html.AntiForgeryToken()
#Html.Action("RenderContactInformation", "JoiningSurface", new { ContentId = CurrentPage.Id })
}
everything works like it should.
But i need to use ajax. Is there a correct way to pass values on postback in this case? I know that i can use TempData, but i'm not sure that this is the best approach.
Thanks for your patience
The problem is that Umbraco context is not accessible when you're trying to reach it through ajax call. Those calls are a little bit different.
Check my answer in this thread: Umbraco route definition-ajax form and I suggest to go with WebAPI and UmbracoApiControllers to be able to access those values during the Ajax call.
I need to post the selected item's id from dropdown to post method of same controller and the render the content in same view
PLease help me fast :(
My View
#Html.DropDownListFor(x => x.hotelmodel.SelectedHotelId, Model.hotelmodel.DrpHotelList)
<div>
<p>#Model.percentageoccupied</p>
<p>#Model.Revenue</p>
<p>#Model.UnSold</p>
<div>
MY HttpGetMethod
[HttpGet]
public ActionResult Counter()
{
var personid = ((PersonModel)Session["PersonSession"]).PersonId;
var model = new CounterforModel();
model.hotelmodel.DrpHotelList = iCommonservice.GetHotelListByPersonId(personid);
return View(model);
}
My HttpPostMethod
[HttpPost]
public ActionResult Counter(int id)
{
var result = iCommonservice.LoadCounter(id);
model.percentageoccupied = Convert.ToInt32(result[0].percentageoccupied);
model.Revenue = Convert.ToDecimal(result[0].Revenue);
model.UnSold = Convert.ToInt32(result[0].UnSold);
return View(model);
}
In your POST method pass your View Model as parameter. Then you can use the posted hotelModel.SelectedHotelId for what you need, update the model values and pass your updated model to the View.
public ActionResult Counter(CounterforModel model)
{
var result = iCommonservice.LoadCounter(model.hotelmodel.SelectedHotelId);
model.percentageoccupied = Convert.ToInt32(result[0].percentageoccupied);
model.Revenue = Convert.ToDecimal(result[0].Revenue);
model.UnSold = Convert.ToInt32(result[0].UnSold);
return View(model);
}
If you want, you can use #Ajax.BeginForm() or jQuery .ajax() to make an ajax call to your action. You can look here.
I have created a DropDownList, as described in my last question here
I have been trying to figure out how to get the selected value of the list. I used the answer that was provided but the only thing it returned was {System.Web.Mvc.SelectList}
I debugged it and sure enough the string that was in the "Value" column was {System.Web.Mvc.SelectList}
What am I doing wrong here? I have been miserably failing at MVC and am new at it.
Thank you for the help
Your Action in your controller should look like this:
[HttpPost]
public ActionResult Index(int DropOrgId)
{
System.Diagnostics.Debugger.Break();
return null;
}
The important thing to note is that "DropOrgId" is the same as the string name you passed into #Html.DropDownList("DropOrgID") in your view. This name will store the value of the input from the HTML input control, in this case the
The source will be:
<select id="DropOrgID" name="DropOrgID">...</select>
The id of the input control is how the MVC framework will match up the value of that control to the parameter of the action you are looking for.
Here is a sample app that shows it:
Class
public class Organization
{
public int OrganizationID { get; set; }
public string Name { get; set; }
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var orgs = new List<Organization>();
foreach (var count in Enumerable.Range(1, 10))
{
var newOrg = new Organization();
newOrg.OrganizationID = count;
newOrg.Name = "Organization " + count.ToString();
orgs.Add(newOrg);
}
ViewBag.DropOrgID = new SelectList(orgs, "OrganizationID", "Name", 3);
return View();
}
[HttpPost]
public ActionResult Index(int DropOrgID)
{
//You can check that this DropOrgID contains the newly selected value.
System.Diagnostics.Debugger.Break();
return null;
}
}
Index View
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownList("DropOrgID")
<br />
<br />
<input type="submit" value="Save" />
}
Using Fiddler I can see that the request is not even being made but I can't see why.
Here's the form:
#using (Html.BeginForm("Index", "FileSystemChannelIndex", FormMethod.Post, new {
channelId = #Model.ChannelId }))
{
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ChannelId)
<div class="editor-label">
Select File Source
</div>
<div class="editor-field">
#Html.DropDownListFor(
model => model.SelectedFileSourceValue,
new SelectList(Model.AvailableFilesSources, "Id", "Name"),
new { id = "selectFileSource" })
</div>
<p>
<input class="t-button" type="submit" value="Save" />
</p>
}
The View originally came from:
public ViewResult Create(int channelId)
{
var channel = this.fullUOW.GetFileSystemChannelRepository().All.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId, new FileSystemChannelIndex());
return View("Edit", vm);
}
I've tried adding the "name" attribute to the but that didn't make any difference.
Any ideas?
EDIT: More info for Jim et al...
Domain:
public class FileSystemChannel
{
public int Id {get; set; }
public ICollection<FileSystemChannelIndex> ChannelIndexes { get; set; }
}
public class FileSystemChannelIndex
{
public int Id { get; set; }
public FileSystemChannel ParentChannel { get; set; }
}
Due to a 0...* association, in the UI we have to create a FileSystemChannel first then add a FileSystemChannelIndex to it. So that's why I pass in the channelId to the FileSystemChannelIndex Create View. When submitting the new FileSystemChannelIndex the following action should be called:
[HttpPost]
public ActionResult Index(int channelId, FileSystemChannelIndexViewModel vm)
{
//TODO: get the Channel, add the Index, save to db
return View("Index");
}
So thanks to Mark's comment it's due to a Select failing client side validation. Using IE dev tools to inspect the element:
<select name="SelectedFileSourceValue" class="input-validation-error" id="selectFileSource" data-val-required="The SelectedFileSourceValue field is required." data-val-number="The field SelectedFileSourceValue must be a number." data-val="true">
empo,
further to my comment above:
empo - can you post both public ActionResult Create(////) methods (i.e. HttpPost and HttpGet) into the question as this could highlight if the issue is related to ambiguous method signatures, which i suspect could well be the case as you are posting back the same signature as the HttpGet actionresult
try adding the appropriate HttpPost actionresult along the lines of:
[HttpPost]
public ActionResult Create(FileSystemChannelIndex domainModel)
{
if (!ModelState.IsValid)
{
return View(PopulateEditViewModel(domainModel));
}
_serviceTasks.Insert(domainModel);
_serviceTasks.SaveChanges();
return this.RedirectToAction("Edit", new {id = domainModel.ChannelId});
}
your original HttpGet (which feels 'wierd' to me):
[HttpGet]
public ViewResult Create(int channelId) {
var channel = this.fullUOW.GetFileSystemChannelRepository().All
.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId,
new FileSystemChannelIndex());
return View("Edit", vm);
}
and inside your Edit actionresult, you'd grab the entity based on the passed in id. might work, might not. not sure without a fuller picture of your domain and logic.
obviously, your own plumbing will vary, but this should give an idea of what should be expected.
How can you have Model.Id when you are creating something? Maybe Model.Id is null and because you cannot post
Migrating from textboxe to dropdownlist – Need to send value from a hard-coded dropdownlist to controller
The code below is used in the controller
var list = new SelectList(new[]
{
new{ID="1",Name="20012"},
new{ID="2",Name="20011"},
new{ID="3",Name="20010"},
new {ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"},
},
"ID", "Name", 1);
ViewData["list"] = listYear;
The code below is used in the view
#using (Html.BeginForm()){
<p>
Title: #Html.TextBox("SearchString")
#Html.DropDownList("list",ViewData["list"] as SelectList)
Genre: #Html.DropDownList("Towns", "All")
<input type="submit" value="Filter" /></p>
}
Below is the code which was used for the textbox
if (!String.IsNullOrEmpty(year))
{
car = Cars.Where(s => s.Year.Contains(year));
}
Looks like you are trying to select a value from a selectlist and send that value to the controller. First off, I'd suggest you use a ViewModel instead of magic strings. You should modify your View to accept the new ViewModel and then post the model to your action. It's easy, cleaner and more maintainable.
Here is what your model would look like
public class VehicleYearsViewModel {
public SelectList VehicleYears { get; set; }
public int SelectedYear { get; set; }
public VehicleYearsViewModel() {
VehicleYears = new SelectList(new[]
{
new{ID="1",Name="2012"},
new{ID="2",Name="2011"},
new{ID="3",Name="2010"},
new{ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"}
}
}
}
Your View then would look like so:
#YourAppName.Models.VehicleYearsViewModel
#using (Html.BeginForm()){
#Html.ValidationSummary(true)
#Html.DropDownListFor(model => model.SelectedYear, Model.VehicleYears, "ID", "Name", 1))
<input type="submit" value="OK" />
}
Your controller action would accept the model and can make use of the selected value as an int datatype.
I'm just guessing since your controller action isn't posted but this is pretty much what it would look like:
public class HomeController : Controller {
public ActionResult Index() {
var model = new VehicleYearsViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(VehicleYearsViewModel model) {
if(ModelState.IsValid) {
// you can get selected year like so
int selectedYear = model.SelectedYear;
// ... your code here to do whatever with selectedYear
}
return View(model);
}
}
Hope this helps