MVC routing changes url hash when requesting partial view via AJAX - asp.net-mvc

I'm using AJAX calls to request partial views and load their html into a content area on my main Index view. I'm leveraging a hash in to the url to enable browser history support (the same way GMail url browser history works).
Everything is working fine, except after my partial view is returned and loaded, MVC seems to be clearing everything after my url hash symbol which affects the javascript browser history stack.
I have a link on my main view which initiates the request:
<div class="linkButton" data-bind="click:function(){Nav.makeRequest('#/MyController/Profile/2')}">Profile</div>
Here's the javascript that I'm using to request and load the partial views:
var Nav:function(){
var self = this;
$(window).bind("hashchange", self.onHashChange);
makeRequest: function(hash){
window.location.hash = hash;
};
onHashChange: function (e) {
var hash = window.location.hash.substring(1);
var url = 'http://localhost:3333/' + hash.substring(1);
$.get(url, function (data) {
$('#content').html(data);
});
}
}
So, one of my example requests would be for: http://localhost:3333/#/MyController/Profile/2
The request is completed successfully and my Profile view is loaded with the correct model for the id (2) passed to it in the routing and the url in the browser's navigation is what is shown above.
However after the view finishes loading, the browser's url then automatically changes to this: http://localhost:3333/#
This doesn't affect what's currently loaded on the page, but it adds this new url to the browser's history so when I hit the 'back' button it sends the request for the partial profile view again.
The only route I have in my Global.axax is the following:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}",
new { controller = "MyController", action = "Index", id = UrlParameter.Optional }
);
I suspect that the MVC routing engine sees my request for the partial view come in (http://localhost:3333/MyController/Profile/2) and then matches it to the Default route which returns the url for my Index view, which of course is: http://localhost:3333/
I have debugged extensively on the client and the onHashChange event does indeed fire both times, once for the partial view request and then again when the url changes back to localhost:3333/# The call stack doesn't reveal any calls being made client side to cause the url to change back.
Is there a way that I can request and load my partial view using AJAX, and hashes for history support, and have the browser's url not automatically route back to the default route path?

This must what you searching for:
To manipulation with browser history you need to use new method with Html5 support
//use it in ur ajax function to save history
history.pushState({page: 1}, "title 1", "?page=1");
//and to get ur history
window.onpopstate = function(event) {
something like
$.post('url',{page:event}function(event) {
do something
})
}

Related

.NET MVC Cache, Chrome, Actions with Variable Results, and the HTML5 history API

I have a few actions in my .NET MVC Controller that will return a full view on a regular request, but only a partial if the request is an Ajax request.
At the end of the controller action...
if (HttpContext.Request.IsAjaxRequest())
{
return PartialView("_Partial", model);
}
else
{
return View(model);
}
On the same controller, I have set some caching options so that one result should be cached for ajax requests, and one for regular requests.
[OutputCache(Duration = 120, VaryByParam = "*", VaryByHeader="X-Requested-With")]
Finally, I'm controlling the history using history.pushState and the onpopstate event so that if the user clicks the "back" button, only the part of the page that would change gets reloaded.
jQuery(document).ready(function () {
if (history.pushState) {
$(window).on("popstate", function (e) {
FunctionThatUsesAjaxToRewriteThePage();
});
}
$('#SomeForm').on('submit', function (e) {
e.preventDefault();
var url = FunctionToGetUrlToUse();
if (history.pushState) {
history.pushState(null, null, url);
FunctionThatUsesAjaxToRewriteThePage();
}
else {
window.location.href = url;
}
});
});
This works fine, except in Google Chrome. Sometimes, when I navigate to the page by way of the browser's back and forward button, Chrome will inappropriately render the AJAX return value instead of the full page.
So far, this only seems to happen if I use the back/forward buttons to navigate to a different page (either another action on my MVC site, or another site on my domain) and then return to the offending action using the back or forward buttons. Detailed navigation steps:
I load up my site's home page (http://mysite.co).
I click a link on the home page to the controller action that uses all the Ajax/history API magic (http://mysite.co/month/2016/december).
I click a link within that page that triggers an ajax rewrite of the main page content (http://mysite.co/month/2016/january).
I click back (triggers a popstate event, URL changes to http://mysite.co/month/2016/december and AjaxRewrite function gets called).
I click back again (back to http://mysite.co).
I click forward. This returns me to http://mysite.co/month/2016/december, but instead of the full page, I just get the ajax version of the action, which is only a partial view.
Firefox and IE don't have this misbehavior.
Things I've tried to address this:
Specify the output cache location to be the server only. Even after clearing the cache in Chrome, I still get the AJAX version of a page when I ought not.
[OutputCache(Duration = 120, VaryByParam = "*", VaryByHeader="X-Requested-With", Location=OutputCacheLocation.Server)]
Giving more standard/serializable objects to history.pushState
history.pushState({}, '', url);
or
history.pushState({id: "foo"}, '', url);
Adding an arbitrary parameter to the ajax request, per HTML5 History API: JSON displayed when going "back" to another page, and then "forward" again.
$.ajax
({
url: url,
type: 'GET',
traditional: true,
contentType: 'application/json',
data: {
//all my normal URL parameters go here
ajax: "true"
},
//.....
Nevermind! Actually the third thing I tried (adding a dummy parameter to the AJAX requests) did work, but I needed to clear my cache in Chrome before I would see the result.
In short, do this:
$.ajax
({
//...
data: {
//...your data here
ajax: "true" //Add this line or one like it to differentiate AJAX requests
},
And then clear your cache in Chrome (Ctrl+Shift+Del for Windows or Shift+Command+Del on a Mac).

Difference in RedirectToAction and ActionLink causing .post() not to work

I have application that redirect user to Index page of some controller from account controller using RedrirectToAction(), after login.
RedirectToAction("Index", "MyController");
It redirects me to //MyApp/MyController/
I also have navigation on MasterPage view, I use ActionLink:
#Html.ActionLink("Index", "SomeOtherController")
... (other links)
That redirects me to //MyApp/SomeOtherController
Problem is in **/** character on the end of the first route. I have partialView on master page that onClick calls jQuery .post().
function SomeFunction(id) {
$.post("Controller/Action", { id: id },
function () {
... some code
});
}
But when I call that function after redirect from login it trys to access this route:
/MyController/Controller/Action
that doesn't extist. If I change my post call to
$.post("../Controller/Action", ...
it works fine, but then doesn't work for nav links becouse they don't have **/** on the end of route.
What should I do? How to get unique paths from RedirectToAction and ActionLink, with or without **/** on the end?
NOTE:
I can use <a></a> for navigation on master page and enter path with **/** on the end, but I would rather use ActionLink
The key here is that you need MVC to generate your routed URLs for you to pass into your jQuery functions.
1.If your jQuery code is nested within your View, the you can do the following:
function SomeFunction(id) {
$.post('#Url.RouteUrl("Action", "Controller")', { id: id },
function () {
... some code
});
}
2.If your jQuery code is located in an external file (such as myScripts.js), then you will need to somehow pass the MVC generated route to your jQuery function. Since this is not tied to an element directly, you probably would be best served to set this as a hidden element in your view.
View
<input type="hidden" id="jsRoute" value="#Url.RouteUrl("Action", "Controller")"/>
JS
function SomeFunction(id) {
$.post('$("#jsRoute").val()', { id: id },
function () {
... some code
});
}

Long action and waiting for a new view in ASP.NET MVC 3

I'm using ASP.NET MVC3. In the view, I have a link in view that initiates a new request:
#Html.ActionLink ("Link", "LongAction", "Home")
The action "LongAction" takes a long time, and while waiting for the new view I want show an image that simulates loading a whole new view:
public ActionResult LongAction()
{
Threas.Sleep(10000);
return View();
}
You can do something like this:
User Clicks button
Show a loading GIF
POST/GET to a server endpoint
Server endpoint kicks of the long running task.
On the complete event of the ajax request hide the loader.
Notify user
You can look into binding it together with Jquery, or if you want to use something in the mvc framework you can look at the Ajax ActionLink. Either way you can hide/show the loader with javascript.
JQuery Example:
$('#userButton').click(function(){
longRunningTask();
return false;
});
function longRunningTask()
{
$('#loader').show();
$.ajax({
url: 'http://serverendpointaddress.co.uk'
}).done(function(){
//notify the user
}).always(function() {
$('#loader').hide();
});
}

Optionally open a new window in controller Action

In a ASP.Net MVC 2 application I want to do the following: in the action that handles a form post I want to:
Redirect the user to other view in the current browser window
Open a new window showing other info (other view)
That can be done easily setting the target="_blank" attribute in the form element and adding the following jQuery script:
$(function () {
$("form").submit(function () {
window.location = "...";
});
});
The View returned by the action handler will be rendered in the new window where the form is posted to.
But, let's make it a little trickier:
If there are no service layer errors when executing the action, then do the above.
If there's any service layer error when executing the action, then do not open the new window and the view returned by the action must be shown in the same window where the form was in the first place.
E.g.: Imagine that the service layer generates a pdfDocument to show to the user if everything is ok, and that pdf must be shown in a new window.
[HttpPost]
public ActionResult SomeAction(FormCollection form)
{
var serviceMethodParams = ... // convertion from the form data somehow
MemoryStream pdfDocument = null;
if (!serviceLayer.DoSomething(serviceMethodParams, out pdfDocument))
{
// Something went wrong, do not redirect, do not open new window
// Return the same view where error should be displayed
return View(...);
}
// The service method run ok, this must be shown in a new window and the origal window must be redirected somewhere else
return File(pdfDocument.ToArray(), "application/pdf");
}
Note that the original solution work fine when the service returns true, but if the service returns false the view showing the errors is shown in a new window and the original window is redirected somewhere else.
In this situation, the better option would be to simply return a URL specific to that document for the user to point to, and your ajax call, when successful, takes the URL returned from your action method, and then opens a new window itself pointing to that URL. On error, you can display errors or some such - basically, that data is folded in a Json return value.
Outside of an ajax call, the most acceptable pattern to me is to have the view re render, then attach some startup javascript to open the new window pointing to that specific URL.

redirect to an external url on form submit

i have a form which on submit should redirect to an external URL to perform some action with my form data, as well as remain on the home page after successful submission. i used redirection, but this will make my second option possible, but not my first one. Please help..
You have different possibilities here. The first possibility is to set the action attribute of the form directly to the external url and add a returnurl hidden input parameter. When the form is submitted it will POST data to the external url to process and when it finishes processing the external url will use the returnurl parameter to redirect back to your home page.
Another possibility is to call the external url in your POST action using WebClient to send data for processing and return the same view:
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
using (var client = new WebClient())
{
var values = new NameValueCollection
{
{ "param1", model.Property1 },
{ "param2", model.Property2 },
};
// send values for processing to the external url
var result = client.UploadValues("http://externalurl.com", values);
// TODO: analyze result
}
return View(model);
}
You need to manually program for this. For example you can pass a returnUrl parameter (e.g. via the query string) to the second page and that page will be in charge of reading this parameter and perform a redirect of its own.

Resources