MVC navigate back to page with same model - c#

I've built a Search Page with 5 properties to filter on.
When a user clicks on one of the results the detail page is loaded.
Now I want to provide a "Back" button so the user can go back to the Search Page with the original filter.
I was thinking about using TempData to store the filter model.
Tempdata is stored in session for only one call so the session won't be bloated after a while.
Is there a better sollution or do you guys have some suggestions?
Let me know!
Edit:
The search page will make use of ajax calls to page, sort or filter the data.
So all this data will need to be stored if I want to navigate back from the detail page.
Is TempData the best way?

Why not to use query-string for this? E.g. search request is submitted using <form /> element with method attribute set to "get". In this case you can easily restore the form state by just reading from the query-string, the code will be much simpler. Visitors also can easily bookmark the page and return to search results later.
View:
#model SearchResultSet;
<form method="get" action="/search">
<input type="text" name="q" value="#Request.QueryString["q"]" />
<input type="submit" value="Search" />
</form>
#if (Model.Total > 0)
{
<ul>
#foreach (var result in Model.Results)
{
<li>...</li>
}
</ul>
}
Model & controller:
public class SearchResultSet
{
public IList<SearchResult> Results { get; set; }
public long Total { get; set; }
}
public class SearchController : Controller
{
public ActionResult Index(string q = "")
{
return View(GetModel(q));
}
private SearchResultSet GetModel(string searchQuery)
{
// Get search results
}
}
Hope this helps.

Well, TempData retains value for one call, but you can retain the TempData value using TempData.Keep() until your Session expires.
TempData["YourKey"] = "SomeValue";
TempData.Keep("YourKey");
Hope it helps.

Related

Send Selected DropDownList value to HomeController ActionResult

Hi I have a drop down list that is filled in from comma delimited values in the config. This works fine.
What I am trying to do is to send the selected value on button click to a ActionResult in the HomeController.
I created a Model, which is taking a string. When I hit the button I get error:
The view 'TestAction' or its master was not found or no view engine supports the searched locations.
This is what my Controller looks like:
[HttpPost]
[ActionName("TestAction")]
public ActionResult TestAction(SQL_Blocks_App.Models.DropdownList SelectedValue)
{
//System.Diagnostics.Debug.WriteLine(SelectedValue);
return View();
}
This is what my model looks like:
public class DropdownList
{
//
// GET: /DropdownList/
[Display(Name = "Servers")]
public string SelectedValue{ get; set; }
}
and this is what my Index View looks like:
<form id="SelectedValue" action="/Home/TestAction" method="post" style="margin: 0">
<div class="col-lg-5">
#{
ViewBag.Title = "Index";
}
#Html.DropDownList("YourElementName", (IEnumerable<SelectListItem>)ViewBag.DropdownVals, "--Choose Your Value--", new
{
//size = "5",
style = "width: 600px"
})
</div>
<div class="col-lg-5">
<input type="submit" value="Run Query" />
<input id="Button2" type="button" value="Clear" onclick="window.location.reload()" />
</div>
</form>
I want to clarify. My end goal is to use the selected value in a SQL query in the ActionResult and return the results back to the index so I can fill them in a table. ( You don't have to show me how to do the SQL part for now I just would like to see the selected value at least printed in the output.)
Redirect to index action, and pass the parameters along
[HttpPost]
[ActionName("TestAction")]
public ActionResult TestAction(SQL_Blocks_App.Models.DropdownList _selectedValue)
{
//System.Diagnostics.Debug.WriteLine(SelectedValue);
return RedirectToAction("Index", "[Controller]", new {#_selectedValue = _selectedValue });
}
and then your Index method should accept the parameter.
[HttpGet]
public ActionResult Index(SQL_Blocks_App.Models.DropdownList _selectedValue)
{
//use _selectedValue
}
I would recommend using another method other than your index, or make Dropdownlist nullable/set a default for it.
The default framework behavior of return View() is to return a view with the same name as the currently-executing action. Which is TestAction. The error is telling you that no such view was found.
You have a couple of options. You can either create the view, or you can return something else. For example, if you want to redirect back to the Index then you can return a redirect result:
return RedirectToAction("Index");
You could also specify the Index view in the response:
return View("Index");
However, keep in mind that the URL will still be for TestAction and not for Index, which could result in unexpected changes to behavior if you're not aware of this.
Edit: Based on comments on this answer, it sounds like what you actually want is to build a pair of actions which generally operate on the same view. This isn't particularly common for an index view, but is very common for edit views. The only difference is semantics, structurally the concept works anywhere.
Consider two actions:
public ActionResult Index()
{
// just show the page
return View();
}
[HttpPost]
public ActionResult Index(SQL_Blocks_App.Models.DropdownList SelectedValue)
{
// receive data from the page
// perform some operation
// and show the page again
return View();
}
Requests between these two actions would differ only by the HTTP verb (GET or POST), not by the action name on the URL. That name would always be "Index". But when the form on the index view is submitted via POST and has a "SelectedValue", the second action is invoked instead of the first.
In that second action you would perform your database interaction, gather whatever data you needed, and if necessary include a model or some additional data in the response.
You TestAction method is returning to a View. Make sure View TestAction.cshtml exists and is in the Home folder.

Transfer Model Data in View to the Controller

I have a model that I am using in my view that is full of data. This data is then edited in the view. I need to figure out a way to resubmit this data back over to the controller.
Here is what I have so far.
VIEW:
#using (Html.BeginForm("DownloadCSV", "Respondents", FormMethod.Post))
{
#Html.HiddenFor(m => m.FilterSet)
<div class="btn btn-default pull-right" id="dispoCSV" onclick="$('#csvFormSubmit').click()">
<i class="icon-file-alt"></i> Disposition Report
</div>
<input id="csvFormSubmit" type="submit" style="display:none;" />
}
CONTROLLER:
[HttpPost]
public ActionResult DownloadCSV(RespondentsFilterSet model)
{
string csv = "Charlie, Test";
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "DispositionReport.csv");
}
MODEL:
public class RespondentsFilterSet : ColdListFilterSet
{
public List<int> OwningRecruiters { get; set; }
public List<int> RecruitingGroups { get; set; }
public override bool HasAtLeastOneFilter()
{
return base.HasAtLeastOneFilter() || OwningRecruiters.IsNotNullOrEmpty() || RecruitingGroups.IsNotNullOrEmpty();
}
public override ExpressionBase ToExpression()
{
var expr = base.ToExpression();
var expressions = expr == null ? new List<ExpressionBase>() : new List<ExpressionBase> { expr };
if (OwningRecruiters.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitedBy), Values = OwningRecruiters });
}
if (RecruitingGroups.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitingGroupId), Values = RecruitingGroups });
}
return expressions.Count == 0 ? null : BuildAndExpressionFromList(expressions);
}
}
I realize that my controller is not not finalized. I just have displaying some static csv. But I can't figure out why my model from my view is always null when returned to the controller.
Just look at your form. There's not a single input element (except the submit button). You cannot expect to get anything back on the server in this case.
Please read about HTML and how forms work in HTML. In HTML forms you have input fields. Things like text fields, hidden fields, checkboxes, radio buttons, ... - fields that the user interacts with get submitted to the server.
The fact that you have made your HttpPost controller action take some model as parameter doesn't mean at all that this parameter will be initialized. In ASP.NET MVC you have a default model binder. This model binder looks at what gets sent to the server as values when the form is submitted and uses the names of the fields to bind to the corresponding properties. Without input fields in the form, nothing gets sent to the server. Just use the debugging tools built into your web browser to inspect what exactly gets sent to the server.
Contrary to classic ASP.NET WebForms, ASP.NET MVC is stateless. There's no ViewState to remember your model.
So all this rambling is to say that you should read more about HTML forms first and understand the stateless nature of the web before getting into ASP.NET MVC. As far as your particular problem is concerned, well, assuming the user is not supposed to modify any values of the view model in your view throughout some input fields, you could simply include a hidden field containing the id of your model in the form. This id will then be sent to your POST controller action as parameter and you could use it to retrieve your original model from wherever it is stored (I guess a database or something).

Controller methods when submitting an AJAX request

I have an MVC project that has a page with several forms on it. Most of the content on the page is rendered based on an initial form that a user submits. When a user updates information on that page I don't want the entire page to refresh I just need it to submit the form and stay on the page. I've tried to setup the form to submit with AJAX, but I don't know what to make the controller method return to accomplish this.
Form:
<div id="HeatNameDiv" title="Change Heat Name">
#using (Ajax.BeginForm("ChangeHeatName", "Home", new AjaxOptions(){UpdateTargetId = "HeatNameDiv"}))
{
<section>
Heat Name:<input type="text" name="heatName" value="#Html.ValueFor(x => x.heatname)" style ="width:100px"/>
Change to:<input type="text" name="updatedHeat" value="" style="width: 100px" />
<input type="submit" name="ChangeHeatName" value="Change" />
</section>
}
Current Controller:
public ActionResult ChangeHeatName(string heatName, string updatedHeat)
{
string user = User.Identity.Name;
HomeModel H = new HomeModel();
H.ChangeHeatName(heatName, updatedHeat, user);
return Content("Index");
}
It depends on what you want refreshed. Ajax.BeginForm expects an HTML fragment. So you need a partial that represents the heatNameDiv that you want to replace after submit:
public ActionResult ChangeHeatName(string heatName, string updatedHeat)
{
string user = User.Identity.Name;
HomeModel H = new HomeModel();
H.ChangeHeatName(heatName, updatedHeat, user);
return PartialView("NameOfPartialView", H);
}
I agree with Jeremy because Ajax.BeginForm is less flexible. I have tried hard to use it in the past, but the fact that it replaces the target element, instead of the innerHtml of the target, makes it difficult for many scenarios.
With $.ajax you can set url: '#(Url.Action("MyActionReturningAPartialView"))' and in the .success callback function(response) { $('#heatNameDiv').innerHtml(response); }
The action you call still returns a PartialView.

New to ASP.NET MVC, having trouble with render action containing a form

I am used to ASP.NET web forms, and am slowly learning ASP.NET MVC.
My website has a little login form on the homepage. My natural thought is that this login form may be useful in other places, and it is not the primary action of the homepage, so I want to separate it off into a partial view. And because it is related to accounts, I want the login in my AccountController not my HomepageController.
Login form is a pretty basic strongly typed partial view:
#model Models.Account.AccountLogin
<h2>Login Form</h2>
#using (Html.BeginForm("_Login", "Account")) {
#Html.ValidationSummary()
<div>
<span>Email address:</span>
#Html.TextBoxFor(x => x.EmailAddress)
</div>
<div>
<span>Password:</span>
#Html.PasswordFor(x => x.Password)
</div>
<div>
<span>Remember me?</span>
#Html.CheckBoxFor(x => x.RememberMe)
</div>
<input type="submit" value="Log In" />
}
</div>
On the homepage, I have this:
#Html.Action("_Login", "Account")
Finally, in the account controller, this:
[HttpGet]
public PartialViewResult _Login()
{
return PartialView();
}
[HttpPost]
public PartialViewResult _Login(AccountLogin loginDetails)
{
// Do something with this
return PartialView();
}
Now when I load my homepage, it looks OK and contains the form. When I click the Log In button, it takes me to myurl/Account/_Login, which contains the form, but not within the _Layout master page, just basic plain HTML and it doesn't do anything at all when I click Log In.
I am pretty sure that I have just missed some fundamental aspect of what I am supposed to be doing here, can someone please point me in the right direction?
It's because you're returning a partial view, which strips away the master page and just returns the main content. Often actions starting with an underscore are used for partials (e.g. ajaxing in a bit of a page, but not the full page). It sounds like you want a full action, and not a partial, so
[HttpPost]
public ActionResult Login(AccountLogin loginDetails)
{
// Do something with this
return View();
}
The issue here is that you are doing a fullpage postback.
You have two options, really.
Firstly, you can use a full page postback, and then call Html.Partial to display your Partial.
Something like
[HttpGet]
public ActionResult Login()
{
return View();//this typically returns the view found at Account/Index.cshtml
}
And then create a View along the lines of
#{
ViewBag.Title = "Index";
}
<h2>Title</h2>
#Html.Partial("PartialNameGoesHere")
Your partial is then rendered where indicated, but this is done when the page loads (if you look at the generated HTML, it appears exactly as though you had written it inline).
Or you can use jQuery/AJAX to load the partial on demand. Let's say you have a homepage of some description
public ActionResult Home()
{
return View();
}
public ActionResult Login()
{
return PartialView("_Login");
}
Create the view
#{
ViewBag.Title = "Index";
}
<h2>Home</h2>
<div>
<p>Hey welcome to my pretty awesome page!</p>
</div>
Show me the login!
<div id="container">
</div>
You can then load the PartialView into the container div whenever you need it, using some JS.
$(function() {
$('.my-login-link').click(function() {
$.ajax({
url: 'account/login',
success: function(data) {
$('#container').html(data);
}
});
return false;//cancel default action
});
});
In that instance, the page loads as normal without the login part. When the user clicks the link, the Login on the controller Account is called using AJAX/jQuery. This returns the HTML of the PartialView, which you can then add to the page using jQuery in the Success handler.

Passing data from View to Controller

In an ASP.NET MVC application, I'm making logic for Admin to accept or reject new members. I'm showing a list of members and two buttons Accept and Reject, like this:
<% foreach (var mm in (ViewData["pendingmembers"] as List<MyMember>)) %>
<% { %>
<tr><td>Username:<%=mm.UserName %></td><td>
<tr><td>Firstname:<%=mm.FirstName %></td><td>
...etc...
<tr>
<td>
<% using (Html.BeginForm("AcceptPendingUser", "Admin"))
{ %>
<input type="submit" value="Accept" />
<% } %>
</td>
<td>
<% using (Html.BeginForm("RejectPendingUser", "Admin"))
{ %>
<input type="submit" value="Reject" />
<% } %>
</td>
</tr>
<% } %>
So, the list of pending member data is in a list of MyMember-objects. Each MyMember object will be printed out member and two buttons are setup for the admin to either accept or reject a pending member.
Then, in the controller I'm separating the handling of those two input fields/forms, like this:
public ActionResult AcceptPendingUser()
{
// TODO: Add code to save user into DB and send welcome email.
return RedirectToAction("Index");
}
public ActionResult RejectPendingUser()
{
// TODO: Add code to remove user from PendingUsers list and send rejection email.
return RedirectToAction("Index");
}
I would like to directly get the object next to the button the user pressed.
How can I send the MyMember object from the View to the controller?
Or how do I send perhaps a numeric index with button press? Maybe with a hidden field?
The simplest option would probably be a hidden input:
<input type="hidden" value="<%=mm.Key%>" name="key" id="key" />
(name accordingly; in each form)
The two controller would then take an argument called "key" (rename to suit). If you want to parse the object from multiple inputs, you'll need a ModelBinder. Of course, rather than 2*n forms, you might consider either query-string based urls, or use something like jQuery (or some other script helper) to submit the data without needing the forms (if script is available).
Instead of using an HTML button consider using an ActionLink and construct it to include the id of the member being approved. Another alternative would be to have a checkbox (whose value is the id of the member being approved) that the admin can select for each member to be approved and a similar one for reject and one each approve/reject buttons for the entire form.
Answering to myself and other mvc newbies:
I got it finally working with this code:
VIEW:
<%=Html.ActionLink(
"Jump",
"Jump",
new { name=(ViewData["Person"] as Person).Name,
person=ViewData["Person"]},
null) %>
CONTROLLER:
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
Person p = new Person();
p.Name = "Barrack";
p.Age = 35;
ViewData["Person"] = p;
return View();
}
public ActionResult Jump(string name, Person person)
{
return View();
}
Debugging the app in the Jump method gives me nice "Barrack"-string for the name parameter, but Person parameter in null.
I also understand what the kind commenters tried to explain: it's easy to send simple data types like strings and ints to controller, but complex types such as my Person object needs something else.
Basically passing an int is enough for me. The hardest part here was figuring out the right way to set up ActionLink.
Cheers,
Pom

Categories