Section 5 of 6

URL Generation

🎯 What You'll Learn

  • Why generate URLs programmatically
  • Url.Action() and Url.RouteUrl()
  • CreatedAtAction() for APIs
  • LinkGenerator service
  • Named routes
  • Best practices

Why Generate URLs?

URL generation creates URLs programmatically instead of hardcoding them.

Benefits

  • Maintainability: Change routes in one place
  • Type safety: Compile-time checking
  • Consistency: URLs always match routes
  • Refactoring: Rename actions without breaking links
Hardcoded vs Generated C#
// ❌ Hardcoded (brittle)
return Created("/api/products/123", product);

// ✅ Generated (maintainable)
return CreatedAtAction(nameof(GetProduct), new { id = 123 }, product);

Url.Action() - MVC

Url.Action() generates URLs for MVC controller actions.

Basic Usage C#
// Current controller
var url = Url.Action("Details");
// → /Products/Details

// Different controller
var url = Url.Action("Index", "Home");
// → /Home/Index

// With route values
var url = Url.Action("Details", "Products", new { id = 123 });
// → /Products/Details/123

// Absolute URL
var url = Url.Action("Details", "Products", new { id = 123 }, Request.Scheme);
// → https://example.com/Products/Details/123

In Razor Views

Views/Products/Index.cshtml HTML
<a href="@Url.Action("Details", "Products", new { id = 123 })">
    View Product
</a>

<!-- Or use asp-* tag helpers -->
<a asp-controller="Products" asp-action="Details" asp-route-id="123">
    View Product
</a>

CreatedAtAction() - APIs

CreatedAtAction() returns 201 Created with a Location header.

API Controller C#
[HttpPost]
public IActionResult CreateProduct([FromBody] ProductDto dto)
{
    var product = new Product { Id = 123, Name = dto.Name };
    
    // Generate URL to GetProduct action
    return CreatedAtAction(
        nameof(GetProduct),        // Action name
        new { id = product.Id },  // Route values
        product);                  // Response body
}

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
    return Ok(new Product { Id = id });
}

// Response:
// HTTP/1.1 201 Created
// Location: /api/products/123
// { "id": 123, "name": "Laptop" }

CreatedAtRoute()

Using Named Routes C#
[HttpGet("{id}", Name = "GetProduct")]
public IActionResult GetProduct(int id) { }

[HttpPost]
public IActionResult CreateProduct([FromBody] ProductDto dto)
{
    var product = new Product { Id = 123 };
    
    return CreatedAtRoute(
        "GetProduct",           // Route name
        new { id = product.Id },
        product);
}

LinkGenerator Service

LinkGenerator generates URLs outside of controllers (services, middleware, etc.).

Using LinkGenerator C#
public class EmailService
{
    private readonly LinkGenerator _linkGenerator;

    public EmailService(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public string GenerateConfirmationEmail(int orderId)
    {
        // Generate URL by action
        var url = _linkGenerator.GetUriByAction(
            "Details",
            "Orders",
            new { id = orderId },
            "https",
            new HostString("inventrackapp.com"));
        
        return $"View your order: {url}";
    }
}

LinkGenerator Methods

LinkGenerator Methods C#
// By action
var url = _linkGenerator.GetPathByAction("Details", "Products", new { id = 123 });
// → /Products/Details/123

// By route name
var url = _linkGenerator.GetPathByName("GetProduct", new { id = 123 });
// → /api/products/123

// Absolute URI
var url = _linkGenerator.GetUriByAction(
    "Details", "Products", new { id = 123 },
    "https", new HostString("example.com"));
// → https://example.com/Products/Details/123

Named Routes

Named routes provide a stable identifier for URL generation.

Attribute Routing C#
[HttpGet("{id}", Name = "GetProduct")]
public IActionResult GetProduct(int id) { }

// Generate URL by name
var url = Url.RouteUrl("GetProduct", new { id = 123 });
Conventional Routing C#
app.MapControllerRoute(
    name: "product-details",
    pattern: "products/{id}",
    defaults: new { controller = "Products", action = "Details" });

// Generate URL by name
var url = Url.RouteUrl("product-details", new { id = 123 });

Redirect to Action

Redirects C#
// Redirect to action in same controller
return RedirectToAction("Index");

// Redirect to action in different controller
return RedirectToAction("Details", "Products", new { id = 123 });

// Redirect to named route
return RedirectToRoute("GetProduct", new { id = 123 });

Complete InvenTrack Example

Controllers/ProductsController.cs C#
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    // Named route for URL generation
    [HttpGet("{id}", Name = "GetProduct")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        
        if (product == null)
            return NotFound();
        
        return Ok(product);
    }

    // CreatedAtAction with URL generation
    [HttpPost]
    public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
    {
        var product = await _productService.CreateAsync(dto);
        
        // Generate URL to GetProduct action
        return CreatedAtAction(
            nameof(GetProduct),
            new { id = product.Id },
            product);
        
        // Response:
        // HTTP/1.1 201 Created
        // Location: /api/products/123
    }

    // CreatedAtRoute with named route
    [HttpPost("batch")]
    public async Task<IActionResult> CreateBatch([FromBody] List<CreateProductDto> dtos)
    {
        var products = await _productService.CreateBatchAsync(dtos);
        
        // Generate URLs for each product
        var result = products.Select(p => new
        {
            product = p,
            url = Url.RouteUrl("GetProduct", new { id = p.Id })
        });
        
        return Ok(result);
    }
}

Email Service with LinkGenerator

Services/EmailService.cs C#
public class EmailService
{
    private readonly LinkGenerator _linkGenerator;

    public EmailService(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public string GenerateOrderConfirmation(int orderId)
    {
        // Generate absolute URL for email
        var orderUrl = _linkGenerator.GetUriByName(
            "GetOrder",
            new { id = orderId },
            "https",
            new HostString("inventrackapp.com"));
        
        return $@"
            Thank you for your order!
            View your order: {orderUrl}
        ";
    }
}

Best Practices

  • Use nameof(): nameof(GetProduct) for type safety
  • Named routes: Use for stable API contracts
  • CreatedAtAction(): Always use for POST endpoints
  • LinkGenerator: Use in services/middleware
  • Avoid hardcoding: Never hardcode URLs
  • Absolute URLs: Use for emails, external links
  • Relative URLs: Use for internal navigation

Key Takeaways

  • URL generation: Create URLs programmatically
  • Url.Action(): Generate URLs in MVC controllers
  • CreatedAtAction(): Return 201 with Location header
  • CreatedAtRoute(): Use named routes
  • LinkGenerator: Generate URLs in services
  • Named routes: Stable identifiers for URL generation
  • RedirectToAction(): Redirect to another action
  • Type safety: Use nameof()
🎯 Next Steps

You now understand URL generation! In the final section, we'll explore Areas—how to organize large applications into logical sections with their own controllers, views, and routes.