MVC Parent Child actions page rendering? - asp.net-mvc

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.

Related

ASP.NET MVC 5: passing POST data between views

I'd like to ask a very simple question from a rookie. I want to pass data from view Alpha.cshtml, into controller HomeController.cs with action Beta(), and then display this data in view Beta.cshtml.
Here's my Alpha.cshtml:
#using (Html.BeginForm("Beta", "Home", null, FormMethod.Post, null))
{
#Html.TextBox("data")
<input type="submit" value="Submit" />
}
Here's my Beta.cshtml:
<p>The submitted value is: #ViewBag.Data</p>
And here's my Beta() action:
public ActionResult Beta()
{
ViewBag.Data = ???
return View();
}
What do I put in place of the ???
Thanks!
When the form is submitted, all form input fields will be posted to the server. ModelBinding will take care of reading the posted values and supplying them to your action method in the list of parameters.
[HttpPost]
public ActionResult Beta(string data)
{
ViewBag.Data = data
return View();
}

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.

How to get main controller and action names from partial view action

I have a controller:
public class LanguageController : Controller
{
[HttpGet]
public ActionResult Index()
{
// populate viewModel from database
return PartialView(viewModel)
}
[HttpPost]
public ActionResult Index(string language)
{
LanguageCookie.Write(Response, language);
return RedirectToAction(ACTION, CONTROLLER, new {culture = language});
}
}
and its partial view:
#model MyModel
#using (Html.BeginForm("Index", "Language"))
{
#Html.DropDownList(
Model.SelectedLanguageShortName,
Model.AllLanguages
)
<input type="submit" value="Select" />
}
which I render in _Layout.cshtml:
<div>
#Html.Action("Index", "Language")
</div>
Please let me know how can I get ACTION/CONTROLLER names of main (not partial) controller, from my LanguageController was called. I need this information on the postback where I set cookie and want to redirect user on the same page but with prefered language.
I have found this example:
var rd = ControllerContext.ParentActionViewContext.RouteData;
var currentAction = rd.GetRequiredString("action");
var currentController = rd.GetRequiredString("controller");
But ControllerContext.ParentActionViewContext is null in the postback. I am able to get what I need in the view but it is ugly:
#Html.Hidden("Controller", HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString());
#Html.Hidden("Action", HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString());
How to get the same information in the controller scope?
When Index(string language) is processed ParentActionViewContext is null because this is another request to server and it doesn't know anything about previous request that invoked child action.
Instead of storing control and action in hidden field you can store the whole address and invoke Redirect:
#model MyModel
#using (Html.BeginForm("Index", "Language", new { redirectUrl = Request.Url }))
{
#Html.DropDownList(
Model.SelectedLanguageShortName,
Model.AllLanguages
)
<input type="submit" value="Select" />
}
and then
[HttpPost]
public ActionResult Index(string language, string redirectTo)
{
LanguageCookie.Write(Response, language);
return Redirect(redirectTo);
}
Another way is to save CONTROLER and ACTION in TempData, but in this way you can have problem if somebody open multiple pages of your site.
Third solution is to invoke that method with Ajax and when response arrive reload the page with javascript.

Model change in post action not visible in Html.TextBoxFor?

This must be something very obvious but for me it looks very strange. I have simple controller, model with one property, and view which displays value of property and renders editor for that property. When I click the button, form is posted and exclamation mark is appened to property. This exclamation mark is visible in my view but only in p tag, not in input tag rendered by Html.TextBoxFor().
Why Html.TextBoxFor() ignores that I updated my model in post action?
Is there any way to change this behavior of Html.TextBoxFor()?
View
#model ModelChangeInPostActionNotVisible.Models.IndexModel
#using (Html.BeginForm())
{
<p>#Model.MyProperty</p>
#Html.TextBoxFor(m => m.MyProperty)
<input type="submit" />
}
Model
namespace ModelChangeInPostActionNotVisible.Models
{
public class IndexModel
{
public string MyProperty { get; set; }
}
}
Controller
namespace ModelChangeInPostActionNotVisible.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new IndexModel { MyProperty = "hi" });
}
[HttpPost]
public ActionResult Index(IndexModel model)
{
model.MyProperty += "!";
return View(model);
}
}
}
HTML after clicking on submit button
<form action="/" method="post"> <p>hi!</p>
<input id="MyProperty" name="MyProperty" type="text" value="hi" /> <input type="submit" />
</form>
This is by design.
The helper methods are using the ModelState, thus if the response of your request is using the same Model, it will display the value that was posted.
This is to allow you to render the same view in the situation where the validation would have failed.
To make sure you display the new information add : ModelState.Clear(); before you return.
Read more here : http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx
namespace ModelChangeInPostActionNotVisible.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new IndexModel { MyProperty = "hi" });
}
[HttpPost]
public ActionResult Index(IndexModel model)
{
model.MyProperty += "!";
ModelState.Clear();
return View(model);
}
}
}
Yan Brunet is absolutely correct that the variable needs to be removed from the ModelState in order to be modified in the controller. You don't have to clear the entire ModelState, though. You could do the following to remove just the variable to want to modify:
ModelState.Remove("MyProperty");
This would be useful in case you wanted to retain other values which the user had entered.

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