I have a partial view in which there is a form. I POST this form using the PRG pattern. I am using the AjaxHelper to create my form. I also need this form to work without javascript. The problem is that when model validation fails, it always changes the url to my partial view.
public ActionResult PostForm(PostFormModel postFormModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("SomewhereElse");
}
else
{
if (Request.IsAjaxRequest())
{
return PartialView("_PostForm")
}
else
{
// What do I do here?
}
}
}
Here's what I have tried:
return PartialView("_PostForm", postFormModel);
This just renders the partial view and doesn't contain any of the parent stuff.
return View("Index", new ParentModel() { PostFormModel = postFormModel });
This actually produces the correct result. It displays the parent view, but the URL is that of the partial http://localhost:22485/Controller/PostForm! I feel like this is really close to the solution. What now?
If you want to change url, you should redirect to another action (using PRG pattern). Insert next code instead of '// What do I do here?':
postModelService.Save(postFormModel); //to Session or to DB
return RedirectToAction("Parent");
New action should look like this:
public ActionResult Parent()
{
var postFormModel = postModelService.Load();
return View("Index", new ParentModel() { PostFormModel = postFormModel });
}
Hope it helps.
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.
Is there a way to tell what view a controller action is being called from?
For example, I would like to use "ControllerContext.HttpContext.Request.PhysicalPath" but it returns the path in which the controller action itself is located:
public ActionResult HandleCreateCustomer()
{
// Set up the customer
//..code here to setup the customer
//Check to see of the calling view is the BillingShipping view
if(ControllerContext.HttpContext.Request.PhysicalPath.Equals("~/Order/BillingShipping"))
{
//
return RedirectToAction("OrderReview", "Order", new { id = customerId });
}
else
{
return RedirectToAction("Index", "Home", new { id = customerId });
}
}
If you have a fixed number of locations that it could possibly be called from, you could create an enum where each of the values would correspond to a place where it could have been called from. You'd then just need to pass this enum value into HandleCreateCustomer, and do your condition statement(s) based on that.
At the moment I am using something of the sort:
In the View I am populating a TempData variable using:
#{TempData["ViewPath"] = #Html.ViewVirtualPath()}
The HtmlHelper method ViewVirtualPath() is found in the System.Web.Mvc.Html namespace (as usual) and is as follows and returns a string representing the View's virtual path:
public static string ViewVirtualPath(this HtmlHelper htmlHelper)
{
try{
return ((System.Web.WebPages.WebPageBase)(htmlHelper.ViewDataContainer)).VirtualPath;
}catch(Exception){
return "";
}
}
I will then obviously read the TempData variable in the controller.
I found another way.
In the controller you want to know what page it was called from.
I added the following in my controller
ViewBag.ReturnUrl = Request.UrlReferrer.AbsolutePath;
Then in the View I have a 'Back' button
#(Html.Kendo().Button().Name("ReturnButton")
.Content("Back to List").Events(e => e.Click("onReturn"))
.HtmlAttributes(new { type = "k-button" })
)
Then the javascript for the onReturn handler
function onReturn(e) {
var url = '#(ViewBag.ReturnUrl)';
window.location.href = url;
}
how can i send data between actions with redirectAction??
I am using PRG pattern. And I want to make something like that
[HttpGet]
[ActionName("Success")]
public ActionResult Success(PersonalDataViewModel model)
{
//model ko
if (model == null)
return RedirectToAction("Index", "Account");
//model OK
return View(model);
}
[HttpPost]
[ExportModelStateToTempData]
[ActionName("Success")]
public ActionResult SuccessProcess(PersonalDataViewModel model)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Error");
return RedirectToAction("Index", "Account");
}
//model OK
return RedirectToAction("Success", new PersonalDataViewModel() { BadgeData = this.GetBadgeData });
}
When redirect you can only pass query string values. Not entire complex objects:
return RedirectToAction("Success", new {
prop1 = model.Prop1,
prop2 = model.Prop2,
...
});
This works only with scalar values. So you need to ensure that you include every property that you need in the query string, otherwise it will be lost in the redirect.
Another possibility is to persist your model somewhere on the server (like a database or something) and when redirecting only pass the id which will allow to retrieve the model back:
int id = StoreModel(model);
return RedirectToAction("Success", new { id = id });
and inside the Success action retrieve the model back:
public ActionResult Success(int id)
{
var model = GetModel(id);
...
}
Yet another possibility is to use TempData although personally I don't recommend it:
TempData["model"] = model;
return RedirectToAction("Success");
and inside the Success action fetch it from TempData:
var model = TempData["model"] as PersonalDataViewModel;
You cannot pass data between actions using objects, as Darin mentioned, you can only pass scalar values.
If your data is too large, or does not consist only of scalar values, you should do something like this
[HttpGet]
public ActionResult Success(int? id)
{
if (!(id.HasValue))
return RedirectToAction("Index", "Account");
//id OK
model = LoadModelById(id.Value);
return View(model);
}
And pass that id from RedirectToAction
return RedirectToAction("Success", { id = Model.Id });
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. So you can not pass complex objects like you calling other methods with complex objects.
Your possible solution is to pass an id using with the GET action can build the object again. Some thing like this
[HttpPost]
public ActionResult SuccessProcess(PersonViewModel model)
{
//Some thing is Posted (P)
if(ModelState.IsValid)
{
//Save the data and Redirect (R)
return RedirectToAction("Index",new { id=model.ID});
}
return View(model)
}
public ActionResult Index(int id)
{
//Lets do a GET (G) request like browser requesting for any time with ID
PersonViewModel model=GetPersonFromID(id);
return View(id);
}
}
You can keep data (The complex object) between This Post and GET request using Session also (TempData is internally using session even). But i believe that Takes away the purity of PRG Pattern.
Trying to get my ducks in a row with MVC3 + Razor!
I finally understand the concept of a 'View-Model' to wrap my entity classes and tailor them to a View.
Now I'm assembling a page with partial views representing different elements necessary to the page (such as drop down lists, forms, etc.) each of these will be represented by a 'View-Model' that maps to an entity class and back to my database.
First I am trying to create a partial view representing a component that is a drop-down list of elements in the database, that when selected will render another partial view, etc.
I just can't put together why I can't generate this drop-down list, and once I do how the main 'controller' maps all this together?
In short I'm curious - does each partial view need a controller even if it's based on a strongly typed model?
Breaking it down:
My Entity Model-View Wrapper (getting all the elements available from the database
*Updated* - to a working example now, note I don't think I was asking the right question before, but this will give you an idea of what I was trying to accomplish! Next step is to move all these operations 'off' the controller (and populate them in the models default constructor, for ease of use later).
CharactersListViewModel.cs
Going to move avoid the 'View Model' for now until I get a little more comfortable
Creating a partial view that displays a drop down list with the Characters' ID as a value, and name as the text, create strongly-typed view, partial view
controller for main-page in section:
HistoryController.cs
public class HistoryController : Controller
{
public ActionResult Index()
{
var list = new List<SelectListItem>();
using (var _database = new fff_newEntities())
{
foreach(Character c in (from c in _database.Characters select c)){
list.Add(new SelectListItem(){Text = c.CharacterName, Value = c.Id.ToString()});
}
}
ViewBag.Clients = list;
}
}
//
// GET: /History/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /History/Create
public ActionResult Create()
{
return View();
}
//
// POST: /History/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
//
// GET: /History/Edit/5
public ActionResult Edit(int id)
{
return View();
}
//
// POST: /History/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
public ActionResult Delete(int id)
{
return View();
}
//
// POST: /History/Delete/5
[HttpPost]
public ActionResult Delete(int id, FormCollection collection)
{
try
{
// TODO: Add delete logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The index to display the whole page including the partial component (my drop down list)
index.cshtml:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DropDownListFor(x => x.CurrentCharacterId, (IEnumerable<SelectListItem>)ViewBag.Clients);
On the last line here, #Html.Action(...) where do I actually create the drop-down list?
Sorry if this seems trivial but I can't wrap my head around it and I really want to learn MVC3 + Razor correctly!
A partial view is meant to abstract out some HTML/View Logic so that it can be re-used either in multiple places or for repeating (looping).
Though you can have an action that maps to the partial and if the partial in question does some explicit data access this might be the way to go but if you're just passing down all the data it needs from the controller itself then - no, you don't need a Controller/Action for it.
Since you're doing some explicit data access I would probably make an action for it...
[ChildActionOnly]
public ActionResult Characters()
{
using (var _database = new entities())
{
CharactersViewModel viewModel = new CharactersViewModel();
viewModel.Characters = _database.Characters.ToDictionary(c => c.Id, c => c.CharacterName);
return PartialView(viewModel);
}
}
In your view...
#Html.Action("Characters")
Of course there's nothing wrong with the way you're doing it but I find having it map to an action can make things easier down the road if you ever wanted to retrieve the HTML from this rendered partial view via an ajax request or something of the sort.
Notes:
Try to wrap your entity context object in a using so it can dispose of the connection.
You can use ToDictionary to select your dictionary directly from the query scope.
In my MVC website, I am creating a small forum. For a single post I am rendering my "Single(Post post)" action in my "PostController" like below
<% Html.RenderAction<PostController>(p => p.Single(comment)); %>
Also When a user reply a post I am sending reply as an ajax request to my "CreatePost" action then return "Single" view as result of this action like below
public ActionResult CreatePostForForum(Post post)
{
//Saving post to DB
return View("Single", postViewData);
}
When I do like that only the view is being rendered, Codes in "Single" Actions body isn't beig executed.
What is the best way to do this?
Also I want to return "Single" action result as string in my JsonObject like below
return Json(new{IsSuccess = true; Content= /*HERE I NEED Single actions result*/});
You can use something like this, but be very careful with this. It can actually cause badly traceable errors (for example when you forget to explicitly set view name in Single method).
public ActionResult Single(PostModel model) {
// it is important to explicitly define which view we should use
return View("Single", model);
}
public ActionResult Create(PostModel model) {
// .. save to database ..
return Single(model);
}
Cleaner solution would be to do the same as if it was post from standard form - redirect (XMLHttpRequest will follow it)
For returning ajax views wrapped in json I use following class
public class AjaxViewResult : ViewResult
{
public AjaxViewResult()
{
}
public override void ExecuteResult(ControllerContext context)
{
if (!context.HttpContext.Request.IsAjaxRequest())
{
base.ExecuteResult(context);
return;
}
var response = context.HttpContext.Response;
response.ContentType = "application/json";
using (var writer = new StringWriter())
{
var oldWriter = response.Output;
response.Output = writer;
try
{
base.ExecuteResult(context);
}
finally
{
response.Output = oldWriter;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(new
{
action = "replace",
html = writer.ToString()
}));
}
}
}
It is probably not the best solution, but it works quite well. Note that you will need to manually set View, ViewData.Model, ViewData, MasterName and TempData properties.
My recommendation:
Post your forum reply (and whatever options) via Ajax.
Return your JSONResult, using this method: ASP MVC View Content as JSON to render your content.
In the OnSuccess handler of your ajax call, check if IsSuccess is true. If successful, append the content to the appropriate container using JQuery