Sign in to track progress and bookmarks.
In web development, you frequently need to send users somewhere else — after a form submission, after login, or when a page has moved permanently. ASP.NET Core provides multiple redirect mechanisms, each serving a specific HTTP purpose. Understanding when to use each is essential for building production-grade applications.
Redirect results are ActionResult types that instruct the browser to make a new HTTP request to a different URL. Instead of returning HTML content, the server sends a 3xx HTTP status code with a Location header, causing the browser to automatically navigate to the new URL.
| Method | HTTP Code | Type | Use Case |
|---|---|---|---|
RedirectToAction() | 302 | Temporary | Navigate to another action in your app (most common) |
RedirectToActionPermanent() | 301 | Permanent | SEO migration — old action moved permanently |
RedirectToRoute() | 302 | Temporary | Navigate using route names instead of action names |
Redirect() | 302 | Temporary | Navigate to an external URL or absolute path |
RedirectPermanent() | 301 | Permanent | Old URL permanently moved (SEO) |
RedirectPreserveMethod() | 307 | Temporary | Redirect but keep the original HTTP method (POST stays POST) |
RedirectPermanentPreserveMethod() | 308 | Permanent | Permanent redirect, preserve HTTP method |
LocalRedirect() | 302 | Temporary | Safe redirect — only allows local URLs (prevents open redirect attacks) |
public class OrdersController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> PlaceOrder(OrderViewModel model)
{
if (!ModelState.IsValid)
return View("Checkout", model); // Return view (no redirect)
var orderId = await _orderService.CreateAsync(model);
TempData["Success"] = $"Order #{orderId} placed!";
// RedirectToAction → 302 → Browser makes new GET request
return RedirectToAction(nameof(Confirmation), new { id = orderId });
}
public IActionResult Confirmation(int id)
{
// This is a GET — user can refresh safely without re-submitting
var order = _orderService.GetById(id);
return View(order);
}
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid) return View(model);
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);
if (result.Succeeded)
{
// SECURITY: Use LocalRedirect instead of Redirect
// This prevents attackers from crafting URLs like: /login?returnUrl=https://evil-site.com
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
return LocalRedirect(returnUrl); // Only allows URLs on YOUR domain
return RedirectToAction("Index", "Dashboard");
}
ModelState.AddModelError("", "Invalid login attempt");
return View(model);
}
// Old URL structure: /products/view/42
// New URL structure: /shop/42
// 301 tells search engines to update their index permanently
[Route("products/view/{id:int}")]
public IActionResult OldProductPage(int id)
{
// 301 Permanent Redirect — SEO juice transfers to new URL
return RedirectToActionPermanent("Details", "Shop", new { id });
}
// External URL redirect (old domain to new domain)
[Route("legacy-page")]
public IActionResult LegacyPage()
{
return RedirectPermanent("https://newdomain.com/updated-page");
}
// Define a named route
app.MapControllerRoute(
name: "productDetail",
pattern: "shop/{category}/{slug}",
defaults: new { controller = "Shop", action = "Product" }
);
// Redirect using route name (decoupled from controller/action names)
public IActionResult GoToProduct()
{
return RedirectToRoute("productDetail", new { category = "electronics", slug = "iphone-15" });
// Generates: /shop/electronics/iphone-15
}
Never use Redirect(returnUrl) with user-supplied URLs without validation. Attackers can craft login links like /login?returnUrl=https://phishing-site.com. Always use LocalRedirect() or validate with Url.IsLocalUrl(returnUrl). This is a real OWASP Top 10 vulnerability that has been exploited in major applications.
nameof() instead of magic strings: RedirectToAction(nameof(Index))LocalRedirect() for user-supplied return URLs to prevent open redirect attacksQ: "What is the Post-Redirect-Get pattern and why is it important?"
Architect Answer: "PRG is a web design pattern that prevents duplicate form submissions. After processing a POST request (e.g., placing an order), the server responds with a 302 redirect to a GET endpoint (e.g., order confirmation). If the user hits refresh, the browser re-sends the GET request — not the POST. Without PRG, refreshing after a POST would re-submit the form, potentially creating duplicate orders, double charges, or duplicate database records. In every production application I've built, POST handlers always end with RedirectToAction(), never View()."
Quizzes linked to this course—pass to earn certificates.
On this page
1. WHAT Are Redirect Results? 2. The Complete Redirect Family 3. REAL-TIME PRODUCTION EXAMPLES 4. 301 vs 302 — The Critical SEO Distinction 5. Best Practices 6. Interview Mastery