Redirect to an ASP.NET MVC page problem after POST request from Silverlight - asp.net-mvc

In an ASP.NET MVC application I have a CartController with this AddToCart action:
public RedirectToRouteResult AddToCart(Cart cart, decimal productId,
string returnUrl)
{
Product product = productsRepository.Products
.FirstOrDefault(p => p.prodID == productId);
cart.AddItem(product);
return RedirectToAction("Index", new { returnUrl });
}
When a user submits a POST request ("Add to Cart" button) to this action from a plain ASP.NET MVC view, everything goes well: the Product is added to the Cart and the user is automatically redirected to a Cart/Index page.
If the product is submitted from a Silverlight app (which is inside an ASP.NET MVC view) it is successfully added to the Cart as well, but the there is no redirection in this case.
What is the problem? Maybe it is due to the fact that all requests from a Silverlight are asynchronous (if I'm not mistaken), and the request from a general ASP.NET MVC view is synchronous by nature? How it can affect the redirection?
In any case, how this problem could be solved?
Edited (added):
My code for sending a post request from a Silverlight app:
//first build a "paramstring" in the format "productId=126504" and then post it using this
WebClient wc = new WebClient();
wc.Headers["Content-type"] = "application/x-www-form-urlencoded";
wc.UploadStringAsync(new Uri("http://localhost:10930/Cart/AddToCart"), "POST", paramstring, "http://localhost:10930/Products");

The WebClient you are using to send the POST request will automatically follow the redirects performed on the server and return the HTML and everything ends in the success callback. If you want to redirect the user browser to this page you shouldn't use WebClient. You need javascript to submit a <form>. Silverlight allows you to execute javascript, so you could use it to dynamically generate and submit a form, or if the form already exists in the DOM set the values of the input fields and submit it.
Here's an example of how you could do this. Add the following javascript function to the same page hosting the Silverlight application:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
function addToCart(productId, returnUrl) {
var form = $(document.createElement('form'))
.attr('action', '/products/addtocart')
.attr('method', 'post')
.append(
$(document.createElement('input'))
.attr('type', 'hidden')
.attr('name', 'productId')
.val(productId)
)
.append(
$(document.createElement('input'))
.attr('type', 'hidden')
.attr('name', 'returnUrl')
.val(returnUrl)
);
$('body').append(form);
form.submit();
}
</script>
And then inside your Silverlight application whenever you decide to invoke the POST action:
HtmlPage.Window.Invoke("addToCart", "123", "http://example.com/someReturnUrl");
You may add other parameters if necessary.

Related

.NET Core MVC/Azure AD - 302 Found when making an ajax request

I am using Azure AD along with asp.net core mvc. The following code is the same with a default MVC project generated with Work or School Accounts authentication.
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Everything works just fine for the most time. The app is basically a notepad. A user logs in and adds notes/tasks. Everything after logging in is done using ajax requests. After some time the app stops working because there is a need for authentication again. Note that if I refresh the page everything is working again.
Am I doing this right? Am I missing something or this kind of use case is not supported.
Should I just create a javascript function that will auto refresh the page after some time?
Should I just create a javascript function that will auto refresh the page after some time?
You could try to create a hidden iframe in all the templates used by the Web App to make automatic calls to a MVC controller method that forces a call to renew the authentication data on a regular basis.
This is achieved very easily by configuring an automatic javascript process in the front-end executed in a loop on a regular basis of 45'. This value could be configured or extracted from a configuration file too. The only key condition is that it must be less than one hour.
Here is the simplified example code related to MVC Controller:
/* Action method, inside "Account" controller class, to force renewal of user authentication session */
public void ForceSignIn()
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
And here is the simplified example HTML and javascript code employed to call silently in a hidden iframe to MVC Controller:
<iframe id="renewSession" hidden></iframe>
<script>
setInterval( function ()
{ #if (Request.IsAuthenticated) {
<text>
var renewUrl = "/Account/ForceSignIn";
var element = document.getElementById("renewSession");
element.src = renewUrl;
</text>
}
},
1000*60*45
);
</script>
For more details, you could refer to this article which get the similar situation with you.
I found a simple solution by accident. My goal was to hit a check endpoint every minute and If I get a 302 status code I would redirect the user to the authentication page.
public IActionResult Check()
{
return Ok(new { });
}
I left the developer tools open and noticed that every 30 mins I get a bigger response.
And this actually refreshes the cookie and as a result there is no need to redirect the user.
So to sum up someone needs to do this check every 40-50 minutes because the expiration is set to ~1 hour by default.

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();
});
}

Redirect after POST doesn't change URL

I'm using the "Redirect After Post" (http://en.wikipedia.org/wiki/Post/Redirect/Get) pattern to solve the problems with refreshing that it solves, but I'm not seeing the URL change after the POST and subsequent GET.
Here is my setup:
I have a form with some pretty extensive client-side validation, then submit.
#using (Html.BeginForm("AddItem", "Order", FormMethod.Post, new { Id = "addItemForm" }))
{
// form stuff
}
Client-side validation:
$('#addToOrder').click(function () {
// do a bunch of validation stuff.
}
if (criteriaMet) {
$('#addItemForm').submit();
}
"AddItem" controller:
public class OrderController {
[HttpPost]
public ActionResult AddItem(long? orderId, long menuItemId)
{
if (oneConditionIsTrue)
{
return RedirectToRoute("NamedRoute1", new { RouteValueDictionary values });
}
else
{
return RedirectToRoute("NamedRoute2", new { RouteValueDictionary values });
}
}
public class NamedRouteController
{
public ActionResult NamedRouteAction
{
// do some stuff
if (mobile)
{
return View("MobileView", model);
}
else
{
return View("RegularView", model);
}
}
After redirecting from the POST action (AddItem), I can step things through the GET action to the return (either one). I would expect the URL in the browser after all of this to be http://mydomain.com/NamedRoute/NamedRouteAction but it's http://mydomain.com/Order/AddItem. Why is this? Shouldn't the RedirectToRoute change the URL?
What am I missing?
I suspect that the controller action is somehow invoked with an AJAX request. For example this could happen if you are using jQuery Mobile or something. Or maybe there's some other script that you have written doing this - it hijacks the form submission and sends an AJAX request instead. And since it is an AJAX request, you cannot possibly expect that the url in the client browser would ever change - that's the whole point of AJAX - stay on the same page.
This could be very easily verified by using a javascript debugging tool such as FireBug. Simply look at the Network tab and see if the POST request was an AJAX request. In the Net tab find the request and see if there's the following request header:
X-Requested-With: XMLHttpRequest
jQuery appends this HTTP header to all AJAX requests it sends.
So basically if you expect a redirect to happen after a POST request you shouldn't use AJAX to submit your form. Or to be more precise: the redirect happens on the server side (once again you will be able to see it in FireBug - the 302 status code) and then the XMLHttpRequest simply follows this redirect but the client browser will not change its current location.

How to get OAuth working on MVC4 mobile

I have been struggling to get OAuth to work on my MVC4 mobile application.
I basically
Created an MVC4 Mobile application
Created an MVC4 Web application
Copied the AccountController, AccountModel, InitializeSimpleMembershipAttribute and all account views into my MVC4 Mobile application
Enabled Google and Facebook OAuth providers
I also ensured RoleManager was initialized according to simplemembershipprovider-not-working, although I do not think that was important. ( I needed it for some role-based security I was testing)
Disabled ajax ( I think) by setting data-ajax="false":
using (Html.BeginForm("ExternalLogin", "Account",new RouteValueDictionary { { "ReturnUrl", ViewBag.ReturnUrl } }, FormMethod.Post, new Dictionary<string, object> { { "data-ajax", false } })) (This did not seem to have any effect on the page - so I may be doing something wrong here...)
I can get to the LogIn view /Account/Login, and when I click the google button, the debugger breaks into public ActionResult ExternalLogin(string provider, string returnUrl)
However - the public ActionResult ExternalLoginCallback(string returnUrl) is never hit.
Visually, I get the jquery mobile "page loading" animation - then I get "Error Loading Page"
I have two questions:
How can I get more information when I try to figure out what happens?
How can I get OAuth working on my MVC4 mobile site?
BTW: Both sites target .Net4.0
OK - so I figured out the answer - the cuplrit was indeed the ajax in jQuery mobile.
I modified my _layout.cshtml so that it can render a custom script after loading jQuery, but before loading jQuery mobile:
#Scripts.Render("~/bundles/jquery","~/scripts/RemoveHashFromWindowLocation")
#RenderSection("beforeJqueryMobile", required: false);
#Scripts.Render( "~/bundles/jquerymobile")
#RenderSection("scripts", required: false)
Then I modified my Login.cshtml so that it contains the section:
#section BeforeJqueryMobile {
#Scripts.Render("~/scripts/disable-ajax.js")
}
And finally, I added the following to my scripts folder:
disable-ajax.js:
$(document).bind("mobileinit", function () {
$.mobile.ajaxEnabled = false;
});
RemoveHashFromWindowLocation.js: (Thanks to Facebook Oauth Login With Jquery Mobile #_=_)
if (window.location.hash == "#_=_")
window.location.hash = "";
Voila - OAuth working with jQuery mobile. :-)
The above answer from espenalb does work if your site loads directly to the login page, however if you have say a home page and then and a link to the login page(which uses the same layout), you will get an error loading page when you click one of the social login buttons.
To fix this add
<script>
$('#login-link').live("click", function () {
$.mobile.ajaxEnabled = false;
});
</script>
to the bottom of your mobile layout page, this will attach an event handler that will disable ajax to the login link.

ASP.NET MVC multiple forms, staying on same page

I have forms located in multiple areas in my layout page (not nested).
I have a partial view which performs a post to controller action.
What action result do I return in that post to keep the user on the current page?
Is jquery/ajax my only option? I would rather a solution that didn't depend on javascript, maybe even a solution that degrades nicely.
You can use the Request.Referrer property to see what page the user has come from and then just use that to redirect them back there.
This does introduce other issues, e.g. losing ModelState, so you'll have to design for that. Also note that some users can block sending referrer information in their requests to the server - so the Referrer property can be null.
I would recommend using AJAX and then falling back on this.
You just need to do a RedirectToAction("") back to your main view.
To post a form without submitting the whole page, which refreshes the browser, you need to use Ajax/jQuery. The degraded solution is to submit the whole page like you would with a normal form.
Here's how I do it with jQuery.
Html:
<div id="RequestButtonDiv">
<button id="RequestButton" name="Request" type="button">Request</button>
</div>
This calls AddToCart on my Request controller when the RequestButton button is clicked. The response is placed inside the RequestButtonDiv element.
<script type="text/javascript">
$(document).ready(function () {
$('#RequestButton').click(function (event) {
$('#RequestButton').text('Processing...');
$('#RequestButton').attr('disabled', true);
submitRequest();
});
});
function submitRequest() {
$.ajax({
url: '<%: Url.Action("AddToCart", "Request", new { id = Model.RowId, randomId = new Random().Next(1, 999999) } ) %>',
success: function (response) {
// update status element
$('#RequestButtonDiv').html(response);
}
});
}
</script>
Controller action:
public ActionResult AddToCart(int id)
{
var user = AccountController.GetUserFromSession();
user.RequestCart.AddAsset(id);
return View("~/Views/Assets/Details_AddToCart.ascx");
}
The controller returns a partial view. You could also return Content("some stuff") instead.
Holler if you have questions or need more detail.

Resources