ASP.NET MVC: How to display success confirmation message after server-side processing - ajax

I have a requirement to display a message confirming a successful database update in an ASP.NET MVC application. Currently the application only shows messages (using a ValidationSummary helper) when an error occurs. On a successful operation, the application currently redirects to a suitable point in the navigation.
Goals are:
Display the confirmation message in an appropriate way
Minimise user actions required to proceed after reading message
Avoid an extra post / round-trip to display the message
Minimise development effort and risk inserting a message at multiple points in the application
My preference would be some sort of tool-tip type message display near the submit button and then a mechanism for removing the message and proceeding with the existing redirection after success.
That seems to suggest an Ajax call rather than the existing HTTP POST to submit the form. How would I go about this?

I Would use TempData["key"]
This is like ViewData["key"] however the data persists for the next HttpRequest and is disposed automatically by asp.net after this
So you can do this.
Controller Action
[HttpPost]
public ActionResult SomePostAction(SomeViewModel vm)
{
if(ModelState.IsValid) // Is User Input Valid?
{
try
{
CommitData();
TempData["UserMessage"] = new MessageVM() { CssClassName = "alert-sucess", Title = "Success!", Message = "Operation Done." };
return RedirectToAction("Success");
}
catch(Exception e)
{
TempData["UserMessage"] = new MessageVM() { CssClassName = "alert-error", Title = "Error!", Message = "Operation Failed." };
return RedirectToAction("Error");
}
}
return View(vm); // Return View Model with model state errors
}
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
</head>
<body>
#if(TempData["UserMessage"] != null)
{
var message = (MessageVM)TempData["UserMessage"];
<div class="alert #message.CssClassName">
<strong>#message.Title</strong>
#message.Message
</div>
}
#RenderBody()
</body>
</html>
More Info: http://www.devcurry.com/2012/05/what-is-aspnet-mvc-tempdata.html

On a successful operation ,you just store the success message description into ViewBag
like as
ViewBag.successMessage="Success"
then in view check the ViewBag value is null or not? through javascript ,if not null show the message in Div
if('#ViewBag.successMessage'!="")
{
$('#divSuccessMessage').show();
}
else
{
$('#divSuccessMessage').hide();
}
default in page load hide the div

the following links might help you (posting links as it would require better explanation):
http://msdn.microsoft.com/en-us/magazine/ff797575.aspx
http://ofps.oreilly.com/titles/9781449320317/ch_AJAX.html

As others mentioned, TempData is one of the most straight forward options to use. Its main drawback in regular ASP.NET in my opinion is that it uses the session storage in to store its contents. This means that you'll have extra work getting it to function on a web farm, or that you need to turn on sessions in the first place.
TempData is a string based dictionary you can put anything in it, and by default get it out only once on any later request. Before calling RedirectToAction() you set your message and on the next request you check for messages and display them. By retrieving the messages they are automatically deleted at the end of the request.
As an alternative you could use cookies for transporting the message between the two requests. Essentially you could either roll your own solution, or implement a custom ITempDataProvider which transports the contents of TempData via cookies. Given that the messages are short, the performance impact is minimal. Note that you need to properly secure cookies.
I was facing the same problem you did and created a solution for it called FlashMessage. It's available on NuGet. Usage is simple: you simply queue a message before you call RedirectToAction() as follows:
FlashMessage.Confirmation("Your message");
return RedirectToAction("AdminUsers", "Admin");
In your view you include the following statement to render any previously queued messages:
#Html.RenderFlashMessages()

Related

Jade html not updated after a redirect in Express.js

I'm currently having some trouble displaying a flash message in Express.js using Jade's templating engine and connect-flash. I am simply trying to flash an error message when the user tries to add a new User object to my database that already exists. However the flash message is not showing up on my page after calling router.post and redirecting back to the index (code below).
Through various console.logs and debugging, I have found that the data I am posting is indeed posting correctly, and the flash message is being set. What I have found is that on the redirect, all of the correct data is passing to the Jade template, but the variables are not being updated in the file itself. I am now wondering if this is a session related issue, or just something Flash/Jade/Express related that I am completely overlooking?
In the code below I am logging session data as well as setting the flash message to a variable. If the array for the flash message(s) is empty (i.e. on page load), an array is set with a message that says so. If the flash message(s) array contains a flash message, the test array is set with a message that says so.
index.js:
router.get('/', function(req, res, next) {
console.log(req.session);
var testArray;
var errorMessages = req.flash('user-error');
if (errorMessages.length === 0)
testArray = ['errorMessages is empty'];
else
testArray = ['errorMessages contains a message now'];
console.log(errorMessages);
console.log(testArray);
res.render('index', {
message: errorMessages,
tester: testArray,
...other irrelevant vars being passed...
});
});
router.post('/add', function(req, res, next) {
var ajaxData = req.body;
console.log(ajaxData);
User.findOne({name: ajaxData.name}, function(err, user) {
if (err) return console.error(err);
// if User DNE already in DB
if (user === null) {
...new user created and saved here...
}
/*where the important stuff begins*/
else {
console.log("flash message set");
req.flash('user-error', "A user with that name already exists!");
}
// redirect to index
res.redirect('/');
});
});
In my Jade template, I'm again logging errorMessages and testArray to make sure everything is passed to the file correctly (it is) then showing the variables.
index.jade
-console.log(message);
-console.log(tester);
.error-box Error: #{message}
.error-box Error: #{tester}
Initially loading the page, I will get the following HTML output:
<div class="error-box">Error: </div>
<div class="error-box">Error: errorMessages is empty</div>
No surprises here. But when I submit the form with data that sets the error flash message, I get the updated logs from router.get('/') and index.jade with both the correct errorMessages and testArray variables. However my HTML output remains the same:
<div class="error-box">Error: </div>
<div class="error-box">Error: errorMessages is empty</div>
Clearly the variables being passed to Jade are being updated correctly, but it appears that Jade is simply not updating the HTML. With my somewhat limited knowledge of how connect-flash and Jade work, this would lead me to believe that this is a session related issue, however my code in app.js appears to be setup correctly...
var session = require('express-session');
var flash = require('connect-flash');
app.use(session({
secret: 'secret',
cookie: { maxAge: 60000 },
resave: false,
saveUninitialized: false
}));
app.use(flash());
I am relatively new to Express.js, so I feel like there might be something small I am overlooking or don't understand, but I've tried to be as detailed as possible so I'm hoping someone can help me out here!
After more careful inspection, I found that what was really causing the issue was that res.redirect('/') was not running, as I was attempting to use AJAX on the client side to call router.post('/add').
I solved this by simply removing my AJAX request, then going back into my HTML and changing my form's attributes (the form whose data I was sending via AJAX) to include method="POST" and action="/add". This is the proper way to make a SERVER SIDE call to my router.post('/add').
I found that someone was having the same problem here, and this question initially led me to look into the AJAX/Client Side vs. Server Side issue. I found the latter question in a comment from #herbyme on this post.

How do I auto fill field values in a section of a form that is loaded via ajax in Laravel 4?

I have a section of a form that dynamically loads different sets of fields based on the user's selection in a control. I'm using a javascript event handler to detect when the selection changes, and using AJAX (with HTML payload) to pull in the proper set of fields.
I would like to be able to use Laravel's Form::getValueAttribute() method to automatically fill in the form fields' values in both the static and dynamic form parts. However, the partial view that is loaded by my AJAX call does not have the same instance of the Form class as the view with my main Form, so I can't simply call getValueAttribute() in the partial.
My thought is to make the AJAX call a POST, and serialize the necessary data (a subset of Input::old() or the model data depending whether the page is loaded as the result of validation errors, or an UPDATE request) to send along with the POST so that the HTML fragment I get back has the values set properly.
Is this the best way to get what I want? If so, does Laravel have any tools to help with the serialization of form data? If not, what might be a better approach?
I've found an approach I like better. When the view is loaded normally I use AJAX as usual to load the partial. But when the view is loaded for a validation post-back or for editing, I use Laravel's Views' nest method to nest the partial view containing the proper fields directly into the response. The partial then has access to all the Input and error data I need. The user is still able to change the field set as usual but I put up a confirm prompt for them if they have already set some values in a field set they previously selected. If they decide to proceed anyway, the field set is cleared and a new field set is brought in via AJAX as usual.
My code looks something like this:
Controller:
public function newThing() {
if ( Request::session()->has('errors') ) {
// this is a validation post-back
return View::make('thing')
->nest('fields', 'fields_partial');
} else {
// just a normal unfilled form
return View::make('thing');
}
}
public function editThing() {
return View::make('thing')
->nest('fields', 'fields_partial');
}
View: thing.blade.php (just a snip of it)
...
<form>
...
<select id="picker">...</select>
<div class="sub-fields">
{{ isset($fields) ? $fields : '' }}
</div>
...
</form>
...
<script>
$('#picker').change(function() {
// if any .sub-fields inputs have been changed, get confirmation from the user
// if user confirms, do ajax stuff to replace .sub-fields contents with new field set
// otherwise cancel the change
});
</script>

Sammy intercepts a POST that is not one of the added routes

I have an application that uses Sammy for some simple client-side routing.
One of the pages has a "Download Pdf" button, which needs to do a POST to get and download a pdf document (not very resty, I know, but it has to be a POST due to the large amount of data I'm submitting). It does this using the old trick of dynamically creating, populating, and submitting a <form> element.
Everything works fine, except for I can see in the console an error from sammy that my route was not found. Note that this is not a route, or even a verb that Sammy should be handling.
Here is my reduced test case
Sammy(function initializeClientRouting(app) {
app.get('#/', show('#default'));
app.get('#/test', show('#test'));
function show(selector) { return function() {
$('section').slideUp();
$(selector).slideDown();
}; }
}).run('#/');
$('button').click(function() {
var form = $("<form method=post action: 'http://www.google.com'>").hide();
$('<textarea name=q>').text("search text").appendTo(form);
form.appendTo('body').submit().remove();
});
Does anyone know how to prevent this error? Is this a bug in Sammy?
It's a combination of sammy & JQuery behaviour (bug?). When generated dynamically the way you put it, the form tag is being rendered as
<form www.google.com'="" 'http:="" action:="" method="post">
This will try to POST to the current page which probably is something like
http://blah/# or http://blah/#/test
For some reason, Sammy will be triggered because of the hashtag, not finding a POST configured and log an error.
Fiddling with your example, what worked for me was:
var form = $("<form>");
form.attr('method', 'post');
form.attr('action', 'http://www.google.com');
$('<textarea name=q>').text("search text").appendTo(form);
form.appendTo('body').submit().remove();
This seemed to generate the proper HTML and remove the Sammy error.

ViewBag Content not available to Partial Views?

I have a PartialView called TopPanel. This Panel is responsible for displaying error messages generated on any page in my application. To handle this, when any exception occurs on a page, I have it call an "ErrorHandler" action in the TopPanel controller. ErrorHandler updates the ViewBag with the error message and calls the Index action which just returns the partial view(for now, since im testing. I will have it call the Main Controllers Index Action later to display the whole page). My understanding is that calling the Index action will reload the view and the ErrorDiv that I have on TopPanels PartilaView will be able to display the new error message in ViewBag. However, nothing gets displayed and I'm not sure why.
Heres some code -
The ErrorHandler Action -
public ActionResult ErrorHandler(string message)
{
ViewBag.ErrorMsg = message;
return RedirectToAction("Index");
}
I've checked in the debugger, "message" does have a valid value. And ViewBag.ErrorMsg does get populated as well.
Index Action of TopPanel -
public ActionResult Index()
{
return PartialView();
}
TopPanels PartialView contains this lone which displays the error -
<div id="errorMsgBox">#ViewBag.ErrorMsg</div>
Can anyone point out what the issue is?
RedirectToAction is like a browser redirect. It actually sends the redirect code to the browser and the action name is the new URL to request. As such you're actually sending a new request to the server for a new page, which will have its own ViewBag.
Instead of RedirectToAction you might just want to return the View and specify the PartialView name.
Since you are doing a redirect, you are actually going to end up in a completely separate Request/Response which has no knowledge of the previous ViewBag property value.

How do you build a Single Page Interface in ASP.NET MVC?

I want to build a webapplication with a "Single Page Interface", using ASP.NET MVC.
I have searched if this was at least possible and I think the answer is: not by simple means (reading http://msdn.microsoft.com/en-us/magazine/cc507641.aspx#S2 second-last paragraph; that article is from May 2008, though).
I found other examples which implemented this by coding/hacking with jQuery. However, I'm looking for a clean solution, using standard .NET approaches, if possible.
What I want is precisely the same functionality when you create a new "MVC Web Application". However, instead of links to "/Home/About" which reloads the entire page, I want links to "#Home/About" which loads only the new part via AJAX.
The standard approach of calling templates (partial views) with Html.RenderPartial is exactly what I want, only then loading them in through AJAX-requests.
Of course, it might be that I can't use these templates that are rendered by the Master-page for some reason (maybe it's expecting to always be called in a certain context from a certain place or so). But maybe there's another clean solution for how to build your template-pages and fetching them from the Master-page.
Who has a nice solution for implementing such thing, a Single Page Interface?
PS: I'm developing in Visual Web Developer 2008 Express Edition with MVC 1.0 installed, in C#
[edit]
Below I read that working with the templates is possible and that jQuery looks indeed like inevitable, so I tested it.
The following code transforms regular links created by Html.ActionLink into anchor-links (with #) to contain history, and then fetch the page via AJAX and only injecting the html-part I'm interested in (i.e. the partial page inside div#partialView):
$("a").each(function() {
$(this).click(function() {
$("div#partialView").load($(this).attr("href") + " div#partialView");
location.hash = $(this).attr("href");
return false;
});
});
These links also allow for graceful degredation.
But what I have left now, is still fetching the whole page instead of only the partial page. Altering the controller didn't help; it still provided me html of the whole page, with all of these statements:
public ActionResult About()
{
return View();
return View("About");
return PartialView();
return PartialView("About");
}
How could I only return the content of the part I'm interested in (i.e. the contents of Home/About.aspx)?
What I'd like is POSTing a value with AJAX (e.g. "requesttype=ajax") so that my controller knows the page is fetched via AJAX and only returns the partial page; otherwise it will return the whole page (i.e. when you visit /Home/About instead of #Home/About).
Is a good practice to alter Global.asax.cs maybe, to create a new routing schema for AJAX-calls, which will only return partial pages? (I haven't looked into this much, yet.)
[edit2]
Robert Koritnik was right: I also needed an About.ascx page (UserControl) with only the small HTML-content of that page. The first line of About.aspx was linked with the Master-page via MasterPageFile="~/..../Site.master" which caused that all HTML was printed.
But to be able to execute the following in my controller:
public ActionResult About()
{
return Request.IsAjaxRequest() ? (ActionResult)PartialView() : View();
}
I needed to alter the way a PartialView (.ascx file) and a View (.aspx) file was found, otherwise both methods would return the same page (About.aspx, ultimately resulting in an infinite loop).
After putting the following in Global.asax.cs, the correct pages will be returned with PartialView() and View():
protected void Application_Start()
{
foreach (WebFormViewEngine engine in ViewEngines.Engines.Where(c => c is WebFormViewEngine))
{
/* Normal search order:
new string[] { "~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx"
"~/Views/Shared/{0}.ascx"
};
*/
// PartialViews match with .ascx files
engine.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.ascx" };
// Views match with .aspx files
engine.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx" };
}
RegisterRoutes(RouteTable.Routes);
}
Full view vs. Partial view
Seems like you've messed up something. If you create an About.aspx page with all the HTML needed to display the whole page it doesn't really matter if you say
return PartialView('About');
The view still returns all the HTML that's written in it.
You should create a separate About.ascx that will only have the content of the page itself without the header and other stuff that's part of the whole page.
Your original page About.aspx will have something like this in its content (to avoid repeating writing the same content twice):
<%= Html.RenderPartial("About") %>
And you can have two controller actions. One that returns a regular view and one that returns a partial view:
return View("About"); // returns About.aspx that holds the content of the About.ascx as well
return PartialView("About"); // only returns the partial About.ascx
Regarding routes in Global.asax
Instead of writing separate routes for Ajax calls you'd rather write an action filter that will work similar as AcceptVerbsAttribute action filter. This way your requests from the client would stay the same (and thus preventing the user from manually requesting wrong stuff), but depending on the request type the correct controller action will get executed.
Well, you can load Partial View through AJAX request. In example, I'll use jquery to make an ajax call.
These could be the action in controller (named HomeController):
public ActionResult About()
{
//Do some logic...
//AboutView is the name of your partial view
return View("AboutView");
}
JQuery ajax call to place the retured html in place you want:
var resultDiv = $('#contentDIV');
$.ajax({
type: "POST",
url: "/Home/About",
success: function(responseHTML) {
resultDiv.replaceWith(responseHTML);
}
});
[edit-question is updated]
It is possible to do exactly what you want. First controller action can give you back the partial view, so mine "AboutView" could have been something like this:
<table>
<tr>
<th>
Column1Header
</th>
<th>
Column2Header
</th>
</tr>
<tr>
<td>
...
</td>
<td>
...
</td>
</tr>
and this HTML is exactly what are you going to have in responseHTML on success handler in jquery ajax method.
Second, you can distinguish in controller action if the request is an ajax request:
public ActionResult About()
{
//partial AboutView is returned if request is ajax
if (Request.IsAjaxRequest())
return View("AboutView");
else //else it will be the default view (page) for this action: "About"
return View();
}
We've got a site that does exactly this, and you really want to use the jQuery route here--alot easier to implement in the long run. And you can easily make it gracefully degrade for users who don't have javascript enabled--like google.
it isn't all that clear what are you asking for, is it for a complete example or for some specific functionality? You should be able to do this without JQuery for simple scenarios, you can use the Ajax view helpers like the ActionLink method. Also, I don't really understand what is your issue with RenderPartial, but maybe you're looking for something like RenderAction from ASP.NET MVC Futures.
ASP.NET MVC 4 (now in beta) adds support for Single Page Applications in MVC.
http://www.asp.net/single-page-application
UPDATE:
...and they removed it from the MVC 4 RC
UPDATE:
...and it is back with the 2012 Fall update

Resources