Knowledge Guardian

Tag: routing

Mastering .NET Core Route Parameters

Route parameters are extensively used in the industry and is essential for you to understand before designing any website or web API.


Route parameters in .NET Core play a pivotal role in handling dynamic parts of URLs, allowing developers to create flexible and efficient routing configurations. In this comprehensive guide, we’ll explore various aspects of route parameters, including examples, best practices, and solutions to common engineering challenges.

Understanding Route Parameters in .NET Core:

What are Route Parameters?

Route parameters in .NET Core MVC are placeholders in route templates that capture dynamic parts of the URL. They enable the extraction of data from the URL and pass it to controllers or actions for further processing.

Route Parameter Syntax:

Define route parameters within curly braces {} in route templates:

endpoints.MapControllerRoute(
    name: "details",
    pattern: "products/{id}",
    defaults: new { controller = "Products", action = "Details" });

Here, {id} acts as a route parameter capturing the product ID from the URL.

Examples and Usage of Route Parameters:

Basic Route Parameter Usage:

public IActionResult Details(int id)
{
    // Fetch product details based on the 'id' route parameter
    // ...
}

Optional Route Parameters:

Make route parameters optional by appending a ? symbol:

endpoints.MapControllerRoute(
    name: "optional",
    pattern: "users/{id?}",
    defaults: new { controller = "Users", action = "Details" });

Multiple Route Parameters:

Handle multiple route parameters in a single route:

endpoints.MapControllerRoute(
    name: "multiple",
    pattern: "categories/{category}/{subcategory}",
    defaults: new { controller = "Categories", action = "Details" });

Tips, Tricks, and Best Practices:

1. Route Parameter Constraints:

Apply constraints to route parameters for validation:

endpoints.MapControllerRoute(
    name: "constraint",
    pattern: "orders/{id:int}",
    defaults: new { controller = "Orders", action = "Details" });

2. Parameter Naming Conventions:

Maintain consistent and descriptive parameter names for better readability and understanding.

3. Route Parameter Defaults:

Use default values for route parameters to handle missing values gracefully:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "page/{pageNumber:int}",
    defaults: new { controller = "Page", action = "Index", pageNumber = 1 });

4. Handling Route Parameter Mismatch:

Implement error handling mechanisms to manage unexpected route parameter types or missing values.

5. Versioning APIs with Route Parameters:

Leverage route parameters to version APIs by including version identifiers in URLs.

Common Problems and Solutions:

1. Parameter Ambiguity:

Avoid ambiguous route parameters by using distinct route templates or constraints.

2. Route Parameter Order:

Ensure correct order of route parameters in templates to prevent misinterpretation.

3. Route Parameter Caching:

Be cautious when caching responses with route parameters to avoid caching issues due to dynamic data.

Conclusion:

Route parameters in .NET Core MVC offer immense flexibility in handling dynamic URLs and extracting relevant data for processing. By following best practices, applying constraints, and anticipating potential issues, engineers can effectively leverage route parameters to create robust and scalable applications.

Mastering route parameters is crucial for optimizing routing configurations and enhancing the overall user experience in .NET Core applications.


Deep Dive into Conventional Routing in .NET Core 6 MVC Application

Detailed technical blog post on conventional routing in .NET Core 6 MVC application:


Routing is a fundamental aspect of ASP.NET Core MVC, enabling developers to define URL patterns and direct incoming HTTP requests to the appropriate controllers and actions. In this comprehensive guide, we’ll explore conventional routing, a powerful feature that allows for structured and predictable URL mappings within .NET Core 6 MVC applications.

Understanding Conventional Routing:

What is Conventional Routing?

Conventional routing in ASP.NET Core MVC follows a convention-based approach to define URL patterns and map them to controller actions. It relies on a set of predefined rules to match incoming requests with specific controllers and their actions.

Route Templates:

Route templates define URL patterns that are matched against incoming requests. They consist of placeholders for dynamic segments in the URL.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});
  • {controller} and {action} placeholders represent the controller and action names, respectively.
  • {id?} denotes an optional parameter (? indicates optional).

Controller and Action Naming Conventions:

Conventional routing relies on naming conventions to match controllers and actions.

  • Controllers should have names ending with “Controller” (e.g., HomeController).
  • Action methods should be public and named as per the intended action (e.g., Index, Details, etc.).

Example of Conventional Routing:

Configuration in Program.cs:

Configure routing in the Program.cs file :

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddControllersWithViews();

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (!app.Environment.IsDevelopment())
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        app.Run();
    }
}

MapControllerRoute is used to create a single route. The single route is named default route. Most apps with controllers and views use a route template similar to the default route. REST APIs should use attribute routing.

The route template “{controller=Home}/{action=Index}/{id?}”:

Matches a URL path like /Products/Details/5

Extracts the route values { controller = Products, action = Details, id = 5 } by tokenizing the path. The extraction of route values results in a match if the app has a controller named ProductsController and a Details action.

  • The first path segment, {controller=Home}, maps to the controller name.
  • The second segment, {action=Index}, maps to the action name.
  • The third segment, {id?} is used for an optional id. The ? in {id?} makes it optional. id is used to map to a model entity.

Using this default route, the URL path:

  • /Products/List maps to the ProductsController.List action.
  • /Blog/Article/17 maps to BlogController.Article and typically model binds the id parameter to 17

Controller and Actions:

Create controllers and actions following the naming conventions:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

URL Generation:

Use Url.Action or Html.ActionLink to generate URLs based on route templates.

<a asp-controller="Home" asp-action="Index">Home</a>

Multiple conventional routes

We can define multiple conventional routes. There is no restriction to how many MapControllerRoute we call in Program.cs. We use this to add convention based routing for different style of endpoints

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

The route for the  blog in the example above is called a dedicated conventional route. It’s called a dedicated conventional route because:

  • It uses conventional routing.
  • It’s dedicated to a specific action.

Because controller and action don’t appear in the route template "blog/{*article}" as parameters:

  • They can only have the default values { controller = "Blog", action = "Article" }.
  • This route always maps to the action BlogController.Article.

/Blog/Blog/Article, and /Blog/{any-string} are the only URL paths that match the blog route.

The preceding example:

  • blog route has a higher priority for matches than the default route because it is added first.
  • Is an example of slug style routing where it’s typical to have an article name as part of the URL.

Deep Dive Analysis:

Flexibility and Predictability:

Conventional routing offers a balance between flexibility and predictability, allowing developers to define URL patterns logically while adhering to naming conventions.

Convention over Configuration:

By following conventions, developers can avoid explicit route configuration in most cases, promoting a standardized structure across the application.

Parameter Binding:

Conventional routing automatically binds URL segments to action method parameters, simplifying data retrieval from requests.

Conclusion:

Conventional routing in .NET Core 6 MVC applications streamlines URL mapping by leveraging naming conventions and route templates. Understanding this routing mechanism is essential for building scalable, maintainable, and predictable web applications in ASP.NET Core MVC.

By harnessing the power of conventional routing, developers can create structured and consistent URL patterns, enhancing the overall architecture and usability of their applications.


.NET Core: Understanding Routing, Middleware and Endpoints?

This blog post will take a deep dive in the world of .NET Core Routing. It will try to enhance your understanding of how routing works. What role middleware and endpoints play.

.NET Core provides a powerful framework for building web applications, offering robust features like routing, middleware, and endpoints. These components play crucial roles in managing incoming HTTP requests, processing them, and generating responses.

What is Routing?

Routing refers to the process of matching incoming HTTP requests to specific endpoints or actions within the application. It determines which code should handle a particular request based on the URL pattern.

Apps can configure routing using:

  • Controllers
  • Razor Pages
  • SignalR
  • gRPC Services
  • Endpoint-enabled middleware.
  • Delegates and lambdas registered with routing.

What is a Middleware?

Middleware’s are software code, it is injected into the .NET Core app pipeline to handle requests and responses. In almost all cases they are not the final destination of the request, instead they are used to do some pre-tasks before the request can be passed on to the Action.

  1. Each of these component has a choice if it wants to pass on the control to the next middleware in the pipeline.
  2. These can perform action before and after the next component in the pipeline.

Web Application pipeline using middleware

What are Endpoints?

For each app the smallest unit of code or a method which can be directly hit by an HTTP request is called Endpoints. These URL based addresses can carry relevant data which are parsed and sent to the Method being requested by the endpoint.

Basic Routing.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

As you may have guessed that this is the basic form of routing, we have mapped the HTTP GET call to the root URL to a anonymous function which writes Hello World on the HTTP response. This is the simplest form of middleware.

Let us modify the code a bit more to show how to use custom middleware in the pipeline which runs before the URL is mapped .

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World extended!");

app.Run();

The code above does the following

  • app.Use registers a custom middleware that should run at the beginning of the pipeline.
  • UseRouting  makes sure that the custom middleware euns before the route matching middleware.
  • MapGet registers the endpoint which runs at the end of the pipeline.

Important aspect of this code is that UseRouting is responsible for the custom middleware running before the route matching middleware.

Middleware use the following statements to handle routing Run,Map and Use.

Run,Map and Use Middleware

Run Method

This can be called as terminal middleware. A terminal middleware doesnot call the next component in the pipeline. It is advised that these type of middleware is placed at the end of the pipeline.

Use Method

This middleware can be used for configuration and is designed to call the next middleware in the pipeline and wait for it to complete before continuing further. Use method may be forced to become a terminal component like Run but it isnt advised. If you look at .NET MVC you will see USE is by far the most used middleware.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

This is an example of the components used by .NET Core MVC.

Lets try and see how the flow of Use and Run works. In the following code we will see that the Use Middleware after running the initial logic waits for the next to finish and then continues to finish its logic.

app.Use(async (ctx, nxt) =>
{
    await ctx.Response.WriteAsync("Middleware 1 Fired \n");
    await nxt();
    await ctx.Response.WriteAsync("Middleware 1 Ends \n");
});

app.Use(async (ctx, nxt) =>
{
    await ctx.Response.WriteAsync("Middleware 2 Fired\n");
    await nxt();
    await ctx.Response.WriteAsync("Middleware 2 Ends \n");
});

app.Run(async (ctx ) =>
{
    await ctx.Response.WriteAsync("Middleware RUN 1 Fired\n");
});


app.Run(async (ctx) =>
{
    await ctx.Response.WriteAsync("Middleware RUN 2 Fired \n");
});

The out put looks something like this.

As you can see in the output the second RUN is never called because the RUN middleware is a terminal component and each USE middleware waits for the next component to finish. The flow looks something like this.

Map Method

As the name suggests this method maps the request URL to a predefined action. They try to match the request path to the paths defined in the app.

app.Map("/path1", appVar =>
{
    appVar.Run(async context =>
    {
        await context.Response.WriteAsync("PATH1 RUN FIRED");
    });
});
app.Map("/path2", appVar2 =>
{
    appVar2.Run(async context =>
    {
        await context.Response.WriteAsync("PATH2 RUN FIRED");
    });
});
app.Map("/path3", appVar3 =>
{
    appVar3.Run(async context =>
    {
        await context.Response.WriteAsync("PATH3 RUN FIRED");
    });

});



app.Run(async (context) =>
{
    await context.Response.WriteAsync("Default Path Fired By RUN");
});

app.Run();



In the preceding code we are mapping different Paths/Endpoints to our anonymous methods. The output is something like this

Writing Custom Middleware

.NET Core gives you an option that you can create your own custom middleware. Middleware is generally a class which is exposed through an extension method.

  • This class must have a public contructor which accepts a RequestDelegate
  • A public method named Invoke or InvokeAsync. This method must:
    • Return a Task.
    • Accept a first parameter of type HttpContext.

Following code shows a sample class which sets the culture for the current request. The culture info is passed through the URL. https://localhost:5001/?culture=es-es.

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;

    public RequestCultureMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);

            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }

        // Call the next delegate/middleware in the pipeline.
        await _next(context);
    }
}

public static class RequestCultureMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestCulture(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestCultureMiddleware>();
    }
}

it can be used later as app.UseRequestCulture();

Conclusion

Routing, middleware, and endpoints form the backbone of request handling in ASP.NET Core applications. Understanding how routing directs incoming requests, how middleware processes them, and how endpoints define response generation is crucial for building robust and efficient web applications.

By leveraging these components effectively, developers can design scalable and maintainable applications in .NET Core, catering to various HTTP request-response scenarios.

Powered by WordPress & Theme by Anders Norén