MVC 3 StackOverflowException w/ #Html.Action() - asp.net-mvc

I've looked over a bunch of other reports of this, but mine seems to be behaving a bit differently. I am returning PartialViewResults for my child actions, so that's not the source of the recursion. Here's a dumbed down version of what I have.
// The Controller
[ChildActionOnly]
public ActionResult _EditBillingInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditBillingInfo(EditBillingInfoViewModel model)
{
// Update billing informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditBillingInfo"
};
return View("EditProfile", profileModel);
}
[ChildActionOnly]
public ActionResult _EditUserInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditUserInfo(EditUserInfoViewModel model)
{
// Update user informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditUserInfo"
};
return View("EditProfile", profileModel);
}
public ActionResult EditProfile(EditProfileViewModel model)
{
if (String.IsNullOrEmpty(model.PartialToLoad))
{
model.PartialToLoad = "_EditUserInfo";
}
return View(model);
}
// EditProfile View
#model UPLEX.Web.ViewModels.EditProfileViewModel
#{
ViewBag.Title = "Edit Profile";
Layout = "~/Views/Shared/_LoggedInLayout.cshtml";
}
<div>
<h2>Edit Profile</h2>
<ul>
<li class="up one"><span>#Ajax.ActionLink("Account Information", "_EditUserInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
<li class="up two"><span>#Ajax.ActionLink("Billing Information", "_EditBillingInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
</ul>
<img alt="Loading Image" id="LoadingImage" style="display: none;" src="../../Content/Images/Misc/ajax-loader.gif" />
<div id="EditProfileDiv">
#Html.Action(Model.PartialToLoad)
</div>
</div>
The partial views are both forms for updating either the user information or billing information.
I debugged through this and found what is happening, but cannot figure out why. When a user browses to EditProfile, it load up with the _EditUserInfo partial and the form is there for editing. When you change some info and submit the form it hangs and you get a StackOverflowException in the EditProfile view on the call to #Html.Action(). What happens is on the initial visit to EditProfile, the #Html.Action calls the HttpGet version of _EditUserInfo. You make some changes to the user info and click submit. Once the information is updated the EditProfile view is returned again, but this time #Html.Action calls the HttpPost version of _EditUserInfo which updates the user information again, returns the EditProfile view again and the #Html.Action calls the HttpPost version of _EditUserInfo... You get where this is going. Why after form submission does it call the post version and not the get version like it did for the initial visit to EditProfile?
Thanks for any help!

I might be getting this a bit wrong, it's been a long day so, but in EditProfile you set PartialToLoad (if it's empty) to "_EditUserInfo", then in _EditUserInfo you set it again to _EditUserInfo, won't this create a loop that behaves as what you are experiencing?

Related

Best practice for rendering data after posting a form in mvc 4.0

Example:
I have a 'Contact Us' view and controller.
My view renders a contact us form as well as the rest of the page containing postal, telephone and email information.
When the form is submitted I want to render the same data, just minus the contact us form and display a 'message sent' instead.
I have a 'Send' method on the controller and can create a 'Send' view with all the data from the contact us view, minus the contact us form and with the 'message sent' string. But obviously having the code now duplicated in two places is far from ideal.
Is there a better way to do this?
I would suggest you to use Ajax.BeginForm instead of using BeginForm. The reason is you don't need to create another action, Ajax.BeginForm will update the display partial view for you.
Below is an example:
Action
[HttpGet]
public ActionResult Contact()
{
return View(new Contact());
}
[HttpPost]
public ActionResult Contact(Contact contact)
{
if (ModelState.IsValid)
{
//
}
return PartialView("_messagePartialView", contact);
}
View
#model Demo.Models.Contact
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="result">
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "result" }))
{
#Html.EditorFor(x => x.Email)
<input type="submit" value="OK" />
}
</div>
Partial View: _messagePartialView
#model Demo.Models.Contact
<h1>
#Model.Email
</h1>
I will assume you have a Contact Model that you use to get the data from user.
And I assume you have the Send Method as follows:
[HttpPost]
public ActionResult Send(Contact contact){
// process your model. ie : send email etc.
TempData["contactData"] = contact;
return RedirectToAction("Sent");
}
public Actionresult Sent(){
return View();
}
In sent view you can use TempData and access Contact model properties.

MVC Parent Child actions page rendering?

I am learning to embed a child action inside a parent action, and render the whole page properly when a form is submitted from the child action.
ParentAction.cshtml--------------------------------------
#model Web1.Models.ParentActionModel
#{ViewBag.Title = "ParentAction";}
<h2>Parent Action</h2>
#Html.ValidationSummary(true, "Please correct parent errors and try again.")
#using (Html.BeginForm()) {
//parent forminput stuff
<input type="submit" value="Parent Button" />
}
#Html.Action("ChildAction","Home") <!-- ChildAction is included here -->
ChildAction.cshtml (included in parent.cshtml) ------------
#model Web1.Models.ChildActionModel
#{ViewBag.Title = "ChildAction";}
<h2>Child Action</h2>
#Html.ValidationSummary(true, "Please correct child errors and try again.")
#using (Html.BeginForm("ChildAction", "Home")) {
//child form input stuff
<input type="submit" value="Child Button" />
}
HomeController.cs-----------------------
public ActionResult ParentAction() {
return View();
}
[HttpPost]
public ActionResult ParentAction(ParentActionModel pmodel) {
//do model update stuff
return View(pmodel);
}
[ChildActionOnly]
public ActionResult ChildAction() {
return PartialView();
}
[HttpPost]
public ActionResult ChildAction(ChildActionModel cmodel) {
//do model update stuff
return PartialView(cmodel); // <---This is wrong, What's the correct way to do it?
}
Now, when I click the "Child Button", I will only get the view of the child action (durrr!), how do I fix it to generate full page parent+children view? It seems like a logic easy enough, but I am stuck on it for hours.
So, if I removed the [ChildActionOnly] in HttpPost Details method,
when I click submit, only the Details.cshtml partialView is returned,
not with the Master.cshtml, which is not what I want, neither.
That's because you should not return a PartialView in this case, but a full View:
[HttpPost]
public virtual ActionResult Details(DetailsModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return RedirectToAction("Success");
}
You might also need to only conditionally render the Details action to avoid infinite loops:
#if (!IsPost)
{
#Html.Action("Details", "Home")
}
Obviously if you want to preserve the original context you were in when you invoked this POST action, you will have to use AJAX and then invoke this POST action with AJAX and replace only the corresponding part of the DOM.

MVC3 One View - Multiple PartialViews - One Model Per PartialView

I'm new to the whole MVC 3 style coding. I'm running into an issue, but first here's how I have my website laid out.
_Layout.cshtml contains
#if (Request.IsAuthenticated)
{
<div class="span2">
#Html.Partial("_NavigationPartial")
</div>
<div class="span10">
#RenderBody()
</div>
}
else
{
#RenderBody()
}
#RenderBody will display my Profile.cshtml file that contains the following:
#{
ViewBag.Title = "My Profile";
}
<div class="row-fluid well">
<div class="page-header">
<h1>
Profile</h1>
</div>
#{
Html.RenderPartial("ChangePersonalInformationPartial");
Html.RenderPartial("ChangePasswordPartial");
}
</div>
As you see, I have two Partials (One to change the Personal Information, the other to Change the Password).
Each one of these Partials uses it's own Model (ChangePersonalInformationModel and ChangePasswordModel).
My problem comes when I click submit on my ChangePasswordPartial, it reloads the _Layout.cshtml page but this time only loads up ChangePasswordPartial.cshtml. I need it to load up Profile.cshtml. But, if I go ahead and change under my AccountController.cs the return View(); to return View("Profile"); I get an error saying:
The model item passed into the dictionary is of type
'PROJECT.Models.ChangePasswordModel', but this dictionary requires a
model item of type 'PROJECT.Models.ChangePersonalInformationModel'.
How can I fix this problem?
Thanks!
Basically you have to make a redirect to the profile action in the ChangePassword action once you saved the password information.
UPDATE:
First you should have a common model say ProfileModel that wraps up the ChangePasswordModel and ChangePersonalInformationModel.
So here are the actions that displays the profile information for viewing and editing.
// this action will returns a views that displays profile info
public ViewResult Profile(string username)
{
ProfileModel model = .. get the profile from database based on username
return View(model);
}
// this action will returns the profile info for editing or adding a new profile
public ViewResult EditProfile(string username)
{
.. if the profile already exists get from database
ProfileModel model =
.. if this is a new profile create an empty model
ProfileModel model = new ProfileModel();
model.ChangePasswordModel = new ChangePasswordModel();
model.ChangePersonalInformationModel = new ChangePersonalInformationModel();
return View(model);
}
Your EditProfile.cshtml will be like this
#model Models.ProfileModel
...
#{
Html.RenderPartial("ChangePersonalInformationPartial",
Model.ChangePersonalInformationModel);
Html.RenderPartial("ChangePasswordPartial", Model.ChangePasswordModel);
}
...
This will be your ChangePassword action
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if(ModelState.IsValid)
{
// save the ChangePasswordModel to database and display the profile info
// or even you can redirect to EditProfile for more editing
return RedirectToAction("Profile");
}
.. there are validation errors so get the complete profile model from database
.. the ChangePasswordModel form will be filled by the details entered in the form
.. and not from the db details this will be taken care by the framework itself.
ProfileModel model =
return View("EditProfile", model);
}

ASP.NET MVC render partial from the POST method

Problem
I have Telerik TabControl and each tab content is a partial view. Everything works smoothly when request is GET:
//
// GET: /ProductVersion/Translations
public ActionResult Translations(Guid id)
{
VersionEditTabViewModel model = CreateTranslationsViewModel(id);
return PartialView("Translations", model);
}
Now the problem is that on some tabs I have a Form that has controls that trigger submit event.
[HttpPost]
public ActionResult Translations(Guid id)
{
FormCollection formCollection = new FormCollection(Request.Form);
string message = string.Empty;
int languageId = int.Parse(formCollection["TranslationsLanguageList"]);
string action = formCollection["TranslationAction"];
if(action == Constants.translation_save)
{
_translationModel.SaveTranslations(formCollection);
message = "Translation information saved";
}
else if (action == Constants.translation_language_changed)
{
/*
PROBLEM: causes whole page to render, not partial
*/
return PartialView("Translations", model);
}
return RedirectToAction( ... updates the complete page not only partial ...);
}
My question is: how to render partial from the POST method? Because now with that source code tab content will be loaded to the WHOLE page, not inside tab.
Solution
I had to put DIV outside of the Ajax.Form and also I had incorrect submit on my DropDownList. What I did was that I created hidden submit button with Id and then I used jQuery to execute it's click event.
For additional reference, please refer to this question on SO:
MVC - Using Ajax to render partial view
This shows a complete implementation of the Ajax.BeginForm with surrounding DIV and inner form controls. You should be able to place this entire setup (DIV + Form + HTML Form Elements) in the Telerik Tab, like this:
<% Html.Telerik().TabStrip()
.Name("TabStrip")
.Items(tabstrip =>
{
tabstrip.Add()
.Text("Your Tab Text")
.Content(() =>
{%>
<div id="containerDiv" align="left">
<% using (Ajax.BeginForm("Example", "Controller/Action", new AjaxOptions { UpdateTargetId = "containerDiv" })){ %>
<%-- Render Partial here -->
<% } %>
</div>
<%});
Hope that helps.
I did my trough ajax form:
using (Ajax.BeginForm("*ActionName*", new { *parameter = ID* }, new AjaxOptions { UpdateTargetId = (*div i will update*), OnSuccess = "*JavaScript that executes on success*", OnComplete = "s*ame as on success*", InsertionMode = InsertionMode.Replace }))
and then i have
return PartialView("*PartialViewName*", model);
in post Action
And it works just fine, on post, action returns partial view and then ajax form replaces the content of the div specified in the UpdateTargetId with InsertionMode.Replace

How do I redirect to the previous action in ASP.NET MVC?

Lets suppose that I have some pages
some.web/articles/details/5
some.web/users/info/bob
some.web/foo/bar/7
that can call a common utility controller like
locale/change/es or authorization/login
How do I get these methods (change, login) to redirect to the previous actions (details, info, bar) while passing the previous parameters to them (5, bob, 7)?
In short: How do I redirect to the page that I just visited after performing an action in another controller?
try:
public ActionResult MyNextAction()
{
return Redirect(Request.UrlReferrer.ToString());
}
alternatively, touching on what darin said, try this:
public ActionResult MyFirstAction()
{
return RedirectToAction("MyNextAction",
new { r = Request.Url.ToString() });
}
then:
public ActionResult MyNextAction()
{
return Redirect(Request.QueryString["r"]);
}
If you want to redirect from a button in the View you could use:
#Html.ActionLink("Back to previous page", null, null, null, new { href = Request.UrlReferrer})
If you are not concerned with unit testing then you can simply write:
return Redirect(ControllerContext.HttpContext.Request.UrlReferrer.ToString());
A suggestion for how to do this such that:
the return url survives a form's POST request (and any failed validations)
the return url is determined from the initial referral url
without using TempData[] or other server-side state
handles direct navigation to the action (by providing a default redirect)
.
public ActionResult Create(string returnUrl)
{
// If no return url supplied, use referrer url.
// Protect against endless loop by checking for empty referrer.
if (String.IsNullOrEmpty(returnUrl)
&& Request.UrlReferrer != null
&& Request.UrlReferrer.ToString().Length > 0)
{
return RedirectToAction("Create",
new { returnUrl = Request.UrlReferrer.ToString() });
}
// Do stuff...
MyEntity entity = GetNewEntity();
return View(entity);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(MyEntity entity, string returnUrl)
{
try
{
// TODO: add create logic here
// If redirect supplied, then do it, otherwise use a default
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
else
return RedirectToAction("Index");
}
catch
{
return View(); // Reshow this view, with errors
}
}
You could use the redirect within the view like this:
<% if (!String.IsNullOrEmpty(Request.QueryString["returnUrl"])) %>
<% { %>
Return
<% } %>
In Mvc using plain html in View Page with java script onclick
<input type="button" value="GO BACK" class="btn btn-primary"
onclick="location.href='#Request.UrlReferrer'" />
This works great. hope helps someone.
#JuanPieterse has already answered using #Html.ActionLink so if possible someone can comment or answer using #Url.Action
I'm using .Net Core 2 MVC , and this one worked for me,
in the controller use
HttpContext.Request.Headers["Referer"];
Pass a returnUrl parameter (url encoded) to the change and login actions and inside redirect to this given returnUrl. Your login action might look something like this:
public ActionResult Login(string returnUrl)
{
// Do something...
return Redirect(returnUrl);
}
You could return to the previous page by using ViewBag.ReturnUrl property.
To dynamically construct the returnUrl in any View, try this:
#{
var formCollection =
new FormCollection
{
new FormCollection(Request.Form),
new FormCollection(Request.QueryString)
};
var parameters = new RouteValueDictionary();
formCollection.AllKeys
.Select(k => new KeyValuePair<string, string>(k, formCollection[k])).ToList()
.ForEach(p => parameters.Add(p.Key, p.Value));
}
<!-- Option #1 -->
#Html.ActionLink("Option #1", "Action", "Controller", parameters, null)
<!-- Option #2 -->
Option #2
<!-- Option #3 -->
Option #3
This also works in Layout Pages, Partial Views and Html Helpers
Related: MVC3 Dynamic Return URL (Same but from within any Controller/Action)
For ASP.NET Core
You can use asp-route-* attribute:
<form asp-action="Login" asp-route-previous="#Model.ReturnUrl">
Other in details example:
Imagine that you have a Vehicle Controller with actions
Index
Details
Edit
and you can edit any vehicle from Index or from Details, so if you clicked edit from index you must return to index after edit
and if you clicked edit from details you must return to details after edit.
//In your viewmodel add the ReturnUrl Property
public class VehicleViewModel
{
..............
..............
public string ReturnUrl {get;set;}
}
Details.cshtml
<a asp-action="Edit" asp-route-previous="Details" asp-route-id="#Model.CarId">Edit</a>
Index.cshtml
<a asp-action="Edit" asp-route-previous="Index" asp-route-id="#item.CarId">Edit</a>
Edit.cshtml
<form asp-action="Edit" asp-route-previous="#Model.ReturnUrl" class="form-horizontal">
<div class="box-footer">
<a asp-action="#Model.ReturnUrl" class="btn btn-default">Back to List</a>
<button type="submit" value="Save" class="btn btn-warning pull-right">Save</button>
</div>
</form>
In your controller:
// GET: Vehicle/Edit/5
public ActionResult Edit(int id,string previous)
{
var model = this.UnitOfWork.CarsRepository.GetAllByCarId(id).FirstOrDefault();
var viewModel = this.Mapper.Map<VehicleViewModel>(model);//if you using automapper
//or by this code if you are not use automapper
var viewModel = new VehicleViewModel();
if (!string.IsNullOrWhiteSpace(previous)
viewModel.ReturnUrl = previous;
else
viewModel.ReturnUrl = "Index";
return View(viewModel);
}
[HttpPost]
public IActionResult Edit(VehicleViewModel model, string previous)
{
if (!string.IsNullOrWhiteSpace(previous))
model.ReturnUrl = previous;
else
model.ReturnUrl = "Index";
.............
.............
return RedirectToAction(model.ReturnUrl);
}

Resources