ASP.NET Core MVC Mastery

RedirectResult

1 Views Updated 5/4/2026

RedirectResult in ASP.NET Core MVC — Mastering Navigation & PRG Pattern

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.

1. WHAT Are Redirect Results?

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.

2. The Complete Redirect Family

MethodHTTP CodeTypeUse Case
RedirectToAction()302TemporaryNavigate to another action in your app (most common)
RedirectToActionPermanent()301PermanentSEO migration — old action moved permanently
RedirectToRoute()302TemporaryNavigate using route names instead of action names
Redirect()302TemporaryNavigate to an external URL or absolute path
RedirectPermanent()301PermanentOld URL permanently moved (SEO)
RedirectPreserveMethod()307TemporaryRedirect but keep the original HTTP method (POST stays POST)
RedirectPermanentPreserveMethod()308PermanentPermanent redirect, preserve HTTP method
LocalRedirect()302TemporarySafe redirect — only allows local URLs (prevents open redirect attacks)

3. REAL-TIME PRODUCTION EXAMPLES

Example 1: Post-Redirect-Get (PRG) — The Most Common Pattern

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

Example 2: Safe Redirect After Login (Preventing Open Redirect Attacks)

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

Example 3: SEO-Friendly Permanent Redirects (URL Migration)

// 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");
}

Example 4: Redirect to Named Routes

// 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
}

4. 301 vs 302 — The Critical SEO Distinction

302 — Temporary Redirect
  • Browser requests the new URL this time only
  • Search engines keep the old URL in their index
  • Use for: PRG pattern, login flows, A/B testing
301 — Permanent Redirect
  • Browser caches the redirect and never requests the old URL again
  • Search engines transfer all SEO ranking to the new URL
  • Use for: URL restructuring, domain migration, slug changes
Security: Open Redirect Vulnerability

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.

5. Best Practices

  • Always use PRG for form submissions to prevent duplicate submissions on browser refresh
  • Use nameof() instead of magic strings: RedirectToAction(nameof(Index))
  • Use LocalRedirect() for user-supplied return URLs to prevent open redirect attacks
  • Use 301 only when you're certain the URL has permanently moved — browsers cache it aggressively
  • Combine with TempData to pass notification messages across the redirect boundary

6. Interview Mastery

Q: "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()."

ASP.NET Core MVC Mastery
1. Core Framework
Introduction to ASP.NET Core MVC
MODULE 1: INTRODUCTION & ENVIRONMENT SETUP
Microsoft Web Stack Overview Evolution of ASP.NET Environment Setup
2. View Engine
Layouts & Partial Views in Razor
MODULE 2: .NET CORE FUNDAMENTALS
Core Concepts Project Structure Startup Flow Middleware Pipeline
MODULE 3: ASP.NET CORE BASICS
Creating Project CLI Commands wwwroot & Static Files
MODULE 4: MVC FUNDAMENTALS
MVC Architecture Dependency Injection (DI) Service Lifetimes
MODULE 5: DATA PASSING TECHNIQUES
ViewData vs ViewBag TempData ViewModel Pattern
MODULE 6: ROUTING
Conventional vs Attribute Routing Custom Constraints
MODULE 7: VIEWS & UI
Razor View Engine Layouts & Sections View Components
MODULE 8: ACTION RESULTS
ViewResult JsonResult RedirectResult
MODULE 9: HTML HELPERS
Form Helpers Custom HTML Helpers
MODULE 10: TAG HELPERS
Built-in Tag Helpers Custom Tag Helpers
MODULE 11: MODEL BINDING
FromQuery vs FromRoute Complex Binding
MODULE 12: VALIDATION
Data Annotations Remote Validation Fluent Validation
MODULE 13: STATE MANAGEMENT
Cookies & Sessions TempData
MODULE 14: FILTERS & SECURITY
Action Filters Authorize Filters Anti-forgery
MODULE 15: ENTITY FRAMEWORK CORE (DEEP DIVE)
DbContext Migrations LINQ Relationships
MODULE 16: DESIGN PATTERNS
Repository Pattern Unit of Work Clean Architecture
MODULE 17: FILE HANDLING
File Upload/Download PDF/Excel Generation
MODULE 18: ADVANCED ASP.NET CORE
Request Lifecycle Bundling & Minification Deployment
MODULE 19: PERFORMANCE & BEST PRACTICES
Caching Strategies Async Programming Secure Coding
MODULE 20: RAZOR PAGES (BONUS)
Razor Pages vs MVC
MODULE 21: REAL-WORLD PROJECTS (🔥 MUST DO)
E-Commerce Web Application Employee Management System