Action Results — ViewResult, RedirectResult and All Result Types

Every action method returns an IActionResult — an abstraction over what should be sent as the HTTP response. ASP.NET Core provides a rich set of built-in result types for every common scenario: rendering views, redirecting browsers, returning JSON, sending files, and returning error status codes. Understanding which result type to use in each situation makes your controllers expressive and your HTTP responses semantically correct. Using the wrong result type (returning 200 OK for a not-found resource, or rendering a view instead of redirecting after a form submission) causes confusing user experiences and broken browser behaviour.

Complete Action Result Reference

public class DemoController : Controller
{
    // ── View results ──────────────────────────────────────────────────────
    public IActionResult ShowView()
        => View();                           // → Views/Demo/ShowView.cshtml
    public IActionResult ShowViewWithModel(MyViewModel vm)
        => View(vm);                         // same + passes model
    public IActionResult ShowNamedView(MyViewModel vm)
        => View("CustomViewName", vm);       // → Views/Demo/CustomViewName.cshtml
    public IActionResult ShowPartial()
        => PartialView("_MyPartial", model); // no layout, for AJAX inclusion

    // ── Redirect results ──────────────────────────────────────────────────
    public IActionResult RedirectSame()
        => RedirectToAction(nameof(Index));  // same controller
    public IActionResult RedirectOther()
        => RedirectToAction("Details", "Posts", new { id = 42 }); // other controller
    public IActionResult RedirectNamed()
        => RedirectToRoute("blog", new { year = 2025, slug = "hello" }); // named route
    public IActionResult RedirectPerm()
        => RedirectToActionPermanent(nameof(Index)); // 301 Permanent
    public IActionResult RedirectUrl()
        => Redirect("https://example.com");  // external URL redirect

    // ── Error results ─────────────────────────────────────────────────────
    public IActionResult Return404()  => NotFound();
    public IActionResult Return404M() => NotFound("Post not found");    // with message
    public IActionResult Return400()  => BadRequest();
    public IActionResult Return400M() => BadRequest(ModelState);        // with errors
    public IActionResult Return403()  => Forbid();
    public IActionResult Return401()  => Unauthorized();
    public IActionResult Return409()  => Conflict("Already exists");
    public IActionResult Return500()  => StatusCode(500, "Internal error");

    // ── Data results ──────────────────────────────────────────────────────
    public IActionResult ReturnJson()
        => Json(new { id = 1, title = "Hello" });  // application/json
    public IActionResult ReturnContent()
        => Content("<h1>Hello</h1>", "text/html");  // raw HTML or plain text

    // ── File results ──────────────────────────────────────────────────────
    public IActionResult DownloadPdf(byte[] pdfBytes)
        => File(pdfBytes, "application/pdf", "report.pdf");
    public IActionResult StreamFile(Stream stream)
        => File(stream, "image/jpeg", "photo.jpg");
    public IActionResult PhysicalFile(string path)
        => PhysicalFile(path, "application/pdf", "doc.pdf");
    public IActionResult VirtualFile()
        => File("~/files/guide.pdf", "application/pdf");  // from wwwroot
}
Note: RedirectToAction() returns HTTP 302 (Found — temporary redirect) by default. Use RedirectToActionPermanent() for HTTP 301 (Moved Permanently) when a URL has permanently moved and you want search engines to update their index. 302 is correct for Post-Redirect-Get (PRG) after form submission. 301 is correct for URL restructuring and SEO redirects. Using 301 for PRG causes browsers to cache the redirect and reuse it indefinitely, potentially breaking form re-submission behaviour.
Tip: Use return View(model) (no quotes, no explicit view name) whenever possible — it follows the view location convention and does not need to be updated if the action is renamed. Only use an explicit view name (return View("CustomName", model)) when you need to render a different view from the one named after the action. Similarly, prefer RedirectToAction(nameof(Index)) over RedirectToAction("Index") for compile-time safety.
Warning: Never use Redirect(userProvidedUrl) with URLs that come from user input — this enables open redirect attacks where an attacker crafts a link to your site that redirects to a malicious URL (e.g., /login?returnUrl=https://evil.com). Always validate redirect URLs are local using Url.IsLocalUrl(returnUrl) before redirecting to them. The safe redirect-after-login pattern: if (Url.IsLocalUrl(returnUrl)) return Redirect(returnUrl); else return RedirectToAction(nameof(Index));

Post-Redirect-Get with TempData

// ── Classic PRG pattern — prevents duplicate form submissions ─────────────
// Problem: user submits form → POST action creates record → returns View()
// User refreshes browser → browser re-submits the POST → duplicate record!
//
// Solution: POST → do work → Redirect (302) → GET → return View()
// Browser refresh after redirect just re-runs the GET, not the POST

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreatePostViewModel model)
{
    if (!ModelState.IsValid) return View(model);      // validation failed: stay on form

    await _service.CreateAsync(model.ToRequest());

    TempData["SuccessMessage"] = "Post created successfully!";  // survives the redirect
    return RedirectToAction(nameof(Index));   // POST → Redirect
}

// GET /posts/index — after redirect
public async Task<IActionResult> Index()
{
    var posts = await _service.GetAllAsync();
    // TempData["SuccessMessage"] available in the view once, then cleared automatically
    return View(new PostIndexViewModel(posts));
}

// In Index.cshtml:
// @if (TempData["SuccessMessage"] is string msg) {
//     <div class="alert alert-success">@msg</div>
// }

Common Mistakes

Mistake 1 — Using Redirect() with user-provided URLs (open redirect vulnerability)

❌ Wrong — attacker redirects login users to phishing sites:

return Redirect(returnUrl);  // returnUrl could be "https://phishing.com"!

✅ Correct — validate with Url.IsLocalUrl(returnUrl) before redirecting.

Mistake 2 — Returning View() after a successful POST (double-submit bug)

❌ Wrong — user refreshes browser, browser re-sends POST, duplicate record created.

✅ Correct — always RedirectToAction after a successful POST (Post-Redirect-Get pattern).

🧠 Test Yourself

Why does the Post-Redirect-Get pattern prevent duplicate form submissions?