I'm looking for a few pointers in updating partial areas in MVC3 with razor engine.
Right now, I'm using jquery for the ajax request.
A little context: I have a list of "Collections" which I wrapped in a partial view, just performing a for-each on a list in the model object of the view in question.
I then have a "add collection" button, with just shows a modal dialog box with the forms for adding a new collection. Upon clicking "add" an ajax request creates the collection in the database, and currently returns a JSON object indicating a success along with the string "Collection Created". In my jquery ajax handler, I examine this json object, to check if the status is "success" and then use jquery to display a growl-like notification containt the "Collection Created" string.
Now my question is: Is there anyway I can update my list of collection in this ajax request? Is there anyway I can throw the partial view, iterating the collections, back with the success json object somehow?
Put very simply: I'd like to update HTML somehow, but still maintain the JSON object, so I can display my notification.
Yes, you can render your partial as a string and wrap it up as JSON. I use this method quite a lot but I've heard people saying it's bad practice. So far I haven't experienced any problems and I've been using it for a couple of years.
I actually use a custom ActionResult which returns JSON. It wraps up multiple views and sends them back to the client as an Array of strings. The "not so straight forward" bit is rendering views as strings from within a controller action. Here's some code to do that:
public static string RenderViewToString(ControllerContext controllerContext, string viewPath, ViewDataDictionary viewData, TempDataDictionary tempData)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext, viewPath);
if (result == null || result.View == null)
throw new Exception("No view found for the following path: " + viewPath);
ViewContext viewContext = new ViewContext(controllerContext, result.View, viewData, tempData, new StringWriter());
HtmlHelper helper = new HtmlHelper(viewContext, new ViewPage());
return helper.Partial(viewPath, viewData).ToHtmlString();
}
You can always make two ajax requests inside a submit handler. Each request would call a different controller action (Create, List).
If that doesn't work for you, have you considered displaying success message inside a partial?
Related
My current situation is as follows.
I have a form that when it gets submitted, passes the model into the controller and it does what it needs to do. At the end it redirects to a confirmation page that also gets passed the same model. All of that works fine, except when I am on the confirmation page, whenever I reload the page it resubmits the form.
I've tried using TempData but when I use that it requires for my model to be serializable and my model uses other models inside it that were made by other people that all need to be serializable which would result in like 15-20 different classes all needing to become serializable which just doesnt seem reasonable.
Here is some of what I am working with:
[HttpPost]
public async Task<ActionResult> SubmitClaim(WarrantyClaim model)
{
... code ...
return BeddingWarrantyConfirmation(model);
}
public ActionResult BeddingWarrantyConfirmation(WarrantyClaim model)
{
return View("BeddingWarrantyConfirmation",model);
}
You could make use of the Post Redirect Get Pattern. You could return the following in SubmitClaim:
return RedirectToAction("BeddingWarrantyConfirmation", "CONTROLLER", model);
For more information, please see https://en.wikipedia.org/wiki/Post/Redirect/Get
This is a common problem in MVC development, you can follow the Post/Redirect/Get strategy to avoid the request.
https://en.wikipedia.org/wiki/Post/Redirect/Get
I'm trying to understand what exact method is generating view? As I've understood when we're sending http- request to a server the following subsequent of actions is executed:
Router is creating ControllerContext class instance
Router is creating a corresponding controller class instance
Router is invoking a corresponding controller's action.
After the action method return an instance actResult of ActionResult the actResult.ExecuteResult(ControllerContext context) is invoked.
Question: Is my understanding of the request processing true? And I don't understand what exactly generate a response text? I'm interested in what method generates the response body?
You can find more about this HERE or HERE.
To address your specific question:
The action returns a result. If this result is a view result the appropriate view engine is selected and then this engine will render the view.
Details from my first link:
View Result
The action method receives user input, prepares the appropriate response data, and then executes the result by returning a result
type. The result type can be ViewResult, RedirectToRouteResult,
RedirectResult, ContentResult, JsonResult, FileResult, and
EmptyResult.
View Engine
The first step in the execution of the View Result involves the selection of the appropriate View Engine to render the View Result. It
is handled by IViewEngine interface of the view engine. By default
Asp.Net MVC uses WebForm and Razor view engines. You can also register
your own custom view engine to your Asp.Net MVC application as shown
below:
protected void Application_Start()
{
//Remove All View Engine including Webform and Razor
ViewEngines.Engines.Clear();
//Register Your Custom View Engine
ViewEngines.Engines.Add(new CustomViewEngine());
//Other code is removed for clarity
}
View
Action method may returns a text string,a binary file or a Json formatted data. The most important Action Result is the ViewResult, which renders and returns an HTML page to the browser by using the current view engine.
One of the View methods on the Controller class (in System.Web.Mvc) allows you pass a model to a different view. However, it does not update the url to the name of the new view because this method works more like Server.Tranfer than Response.Redirect. The point is that this behaviour can be confusing to anyone picking-up MVC. So, after the View method has been called, I would like the url path to be rewritten to reflect the name of the new view. I have tried the following, which does not work, on a class that implements the Controller class:
public ViewResult ViewAsRedirect(string viewName, object model)
{
var baseView = base.View(viewName, model);
ControllerContext.HttpContext.RewritePath(baseView.ViewName);
return baseView;
}
What's the correct code to implement what I have described?
EDIT
Any RedirectToAction method does not allow you to send you model to another action. There is a dirty workaround where you store it in TempData before you exit one action and retrieve it at the beginning of another action. I don't want to use this pattern. This is why I am using View(...) instead.
You simply cannot "update the URL" (i.e. redirect) and return content.
If you want the new URL to show the same content as you anticipated, then you'll need to (temporarily) store the results and include an identifier for the resource that you wish to display on the redirected URL.
There you can pull the resource in from the controller for the redirected URL again, and display it on the appropriate view.
So if you POST your object model to /Foo/Create, you can for example store model in a database, which yields an ID: 42. Then you can redirect to /Foo/View/42, and display it.
If you can explain what you are actually trying to do, a more concrete answer can be given.
RedirectToAction is very intuitive in my opinion... you should use one of the redirecting methods of MVC controller: Redirect(url), RedirectToAction(acion), RedirectToAction(acion, controller), and so on.
example
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Redirect()
{
return this.RedirectToAction("Index");
}
}
EDIT
If your action needs to collect tons of data to pass to the view, you could redirect very early, by detecting the condition of redirection, and loading all the data inside the other action, based on simple route data such as database IDs, or simple strigs or numbers.
== OR ==
You can render javascript code to change the URL in the client side, when the page loads: Modify the URL without reloading the page
This works only in very recent browsers (current date nov/2013).
I'm trying to get the master page changed for all my aspx pages. For some reason I'm unable to detect when this function is called for a ascx page instead. Any help in correting this would be appreciated.
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var action = filterContext.Result as ViewResult;
if (action != null && action.MasterName != "" && Request.IsAjaxRequest())
{
action.MasterName = "Ajax";
}
base.OnActionExecuted(filterContext);
}
If you're still keen on changing the master page based on the fact whether your request is ajax or not - I just accidentally stumbled on exactly the thing you were looking for:
http://devlicio.us/blogs/sergio_pereira/archive/2008/12/05/reusing-views-in-asp-net-mvc-for-ajax-updates.aspx
Basically, instead of overriging the OnActionExecuting method in the BaseController - override the View method! You get exactly the thing you want, with a method that seems specifically designed for it :)
protected override ViewResult View(string viewName, string masterName, object model)
{
return base.View(viewName, Request.IsAjaxRequest() ? "Empty" : masterName, model);
}
So you're saying that MasterPage is empty when you're executing actions to .ascx "pages"?
.ascx's aren't pages, they're UserControls / PartialViews. And as such they don't have master pages. They can be dropped in a mage, or a master page.. But if your request is returning an .ascx, it won't have a master page.. )
UPD:
This is most likely because of the way MVC works - all the 3 parts (M-V-C) are completely independant. Which means that when your code executes inside the controller, we know nothing at all about the view. And the View is the one that selects the master page, not the controller.
Tbh if you're trying to change the way the app looks (change the master page) inside the controller - you're most likely doing something wrong. It was designed with separation of context in the first place, and you're trying to go around it :)
UPD2:
So you're saying that you want to return the full page + master page for regular requests, and just the page without the master (well, clean at least) for ajax requests? You're still trying the wrong approach.
Here's what I've been doing instead:
if (!Request.IsAjaxRequest())
return View(model);
else
return PartialView("PartialName", model);
Exactly the same situation. If I'm loading the url in a browser - it returns the full page, master and all.. If I'm loading it later on, using an ajax call - just load the partial view. Simple and easy. And still adheres to the MVC methodology :)
Also, if you're absolutely keen on preselecting the master name.. just do it like this:
return View("ViewName", "MasterName", model);
I'd like to take data entered in an MVC user form and display it in a different view.
The class has the following private variable:
IList<string> _pagecontent = new List<string>();
The following action accepts a FormCollection object, validates it, and passes it on to the "Preview" view as a List:
[Authorize(Roles = "Admins")]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateContent(FormCollection collection)
{
if (ModelState.IsValid)
{
string PageToInsert = collection["PageToInsert"];
string PageHeader = collection["PageHeader"];
string PageBody = collection["PageBody"];
//validate, excluded...
_pagecontent.Add(PageToInsert);
_pagecontent.Add(PageHeader);
_pagecontent.Add(PageBody);
}
return RedirectToAction("Preview", _pagecontent);
}
The Preview view has the following Page Directive for passing a strongly typed object List:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Template.Master" Inherits="System.Web.Mvc.ViewPage<List<string>>" %>
I would expect to be able to use the Model object to get my data, but alas I cannot. At the following line, I get an error index out of bounds exception, stating the index must be non-negative and less than the size of the collection:
<% if (Model[0].ToString() == "0") { %>
And some strange parameters have been added to the URL, as it resolves to
http://localhost:1894/Admin/Preview?Capacity=4&Count=3
So I have two questions:
When I call RedirectToAction and pass it my List, why is it inaccessible in the view's Model object?
What is the proper way to go about doing what I'm trying to do, namely pass a collection of strings to a view for display there?
Try using TempData. It is like a single-shot session object. You put values you want into TempData, immediately redirect and get them out. There is a good writeup here: http://blogs.teamb.com/craigstuntz/2009/01/23/37947/
Be careful when using TempData. It works great in a single server environment but in a cloud environment it may not work as expected since you cannot guarantee that the request will hit the same machine. This happens because TempData rely on the asp.net session. But if you are using other session manager like SQL or AppFabric Cache it will work fine.
The second parameter to RedirectAction is routeValues, not model.
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
Try using TempData for the model. Its for persisting data between redirects.
The problem with RedirectToAction is it's returning a HTTP 302 and the browser is then on it's own going and doing a brand new HTTP request. You may want to consider using a cookie and/or session object to persist the data between requests.
This is not working because RedirectToAction is actually sending back a Http 302 to the browser. When the browser receives this 302, it does a new request to the server asking for the new page. New request, new temp variables.
You will also face this problem when you try to save/edit/delete something and for some reason you deny it and you have to return the old form again.
So, instead of:
return RedirectToAction("Preview", _pagecontent);
Put the Preview logic in a separate method and just call it:
return PreviewLogic(_pagecontent);
You can also use the TempData[] dic to persist data for the next request like others have said, but then you will not avoid the 302 additional round trip to the server.
It sounds like you're trying to do:
public ActionResult UpdateContent(FormCollection form) {
...
return View("Preview", _pagecontent);
}
Note that a redirection is supposed to be a "clean slate" for the browser (except for things like the auth cookie). You don't get to tell the browser to pass information along to the next request, since the next request should be able to stand on its own. All you get to do is tell the browser what URL to request next. In ASP.NET MVC, when you pass an arguments-object to RedirectToAction, the public properties of that object are appended as query-string parameters to the generated URL.
Can't you just make 2 action results with the same name and mark 1 of them with HttpPost?
public ActionResult UpdateContent(FormCollection preview = null)
{
return View(preview);
}
[HttpPost]
public ActionResult UpdateContent(FormCollection collection = null, bool preview = false)
{
if (preview)
return UpdateContent(collection);
else
return UpdateContent(null);
}
It looks like you are looking for the UpdateModel command:
Check out ScottGu's blog post on the topic:
Improved UpdateModel and TryUpdateModel methods