Ilya Zhidkov
Ilya Zhidkov

Reputation: 95

How can I implement slug-based routing in ASP .NET Core?

I am working on educational platform for various subjects. With a help of Entity Framework Core, I have designed database schema such that:

Every subject belongs to a category and a category can have one or more sub-categories (see diagram attached).

database_schema

With that in place, I would like to achieve routing something like:

/mathematics/integration/substitution
/programming/paradigms/oop
/economics/macro/unemployment
eg.
/{category}/{sub-category}/{subject}

where:

/mathematics (just like programming and economics) is a root category (eg. does not have a parent. It lists all the mathematics related sub-categories)
/integration is a sub-category (which also lists all the integration related subjects)
/substitution is a subject


I tried using both Attribute and Conventional routing as well as Areas but got lost in file structure / complexity for what seems like a simple task...


my routing so far:

endpoints.MapAreaControllerRoute(
   name: "mathematics",
   areaName: "mathematics",
   pattern: "mathematics/{controller=Home}/{action=Index}/{slug?}"
);

How would you approach this problem? What controllers and actions do I need?

see https://isibalo.com for reference. It is in Czech language, but you get the idea. Just look at the URL routing. How did he accomplish that?

Upvotes: 1

Views: 7469

Answers (3)

MahmoudVahedim
MahmoudVahedim

Reputation: 61

use this route for handling area and slug

 endpoints.MapControllerRoute(
  name: "areas",
  pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);

endpoints.MapControllerRoute(
 name: "slug",      
 pattern: "pages/{id}/{*Slug}",
 defaults: new { controller = "Pages", action = "Detail"}

);

Upvotes: 0

Joel Wiklund
Joel Wiklund

Reputation: 1875

I used this slug code generator to create the slug which I saved in the database. Then I fetched this slug and sent it to the View. In the link to the action controller I have two asp-routes, one for the ID and one for the Slug.

Index.cshtml

<a asp-controller="Home" asp-action="Details" asp-route-id="@Model.Restaurants[i].Id" asp-route-slug="@Model.Restaurants[i].Slug">@Model.Restaurants[i].Name</a>

In the controller I have this action method (I don't care about the slug value inside the method, I just use the id value):

HomeController.cs

[HttpGet("Home/Details/{id}/{slug}")]
public async Task<IActionResult> Details(int id, string slug)
{
    var restaurant = await _unitOfWork.RestaurantRepository.GetAsync(id);
    var viewModel = new HomeDetailsVM 
    {
        Address = restaurant.Address,
        ...
    }; 

    return View(viewModel);       
}

Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
    if (env.EnvironmentName == "Development")
    {
        app.UseDeveloperExceptionPage();
        app.UseShowAllServicesMiddleware();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseStaticFiles();
    app.UseCookiePolicy();

    // Enable middleware to serve generated Swagger as a JSON endpoint.
    app.UseSwagger();

    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"));

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
        endpoints.MapRazorPages();
    });
}

This is how it looks in the browser:

enter image description here

Upvotes: 0

Ilya Zhidkov
Ilya Zhidkov

Reputation: 95

I resolved this issue using both Areas and Attribute routing.

Startup.cs

endpoints.MapAreaControllerRoute(
    "mathematics",
    "mathematics",
    "mathematics/{controller=Home}/{action=Index}/{slug?}"
);

MathematicsController.cs

[Area("Mathematics")]
[Route("[controller]")]
public class MathematicsController : Controller
{
    private readonly ApplicationDbContext _context;

    public MathematicsController(ApplicationDbContext context) => _context = context;

    [HttpGet("{slug}")]
    public async Task<IActionResult> Index(string slug)
    {
        var category = await _context.Categories
            .FirstOrDefaultAsync(s => s.Slug == slug);

        if (category == null)
            return View("Errors/NotFound", Response.StatusCode = 404);

        var subjects = await _context.Subjects
            .Include(c => c.Category)
            .Where(s => category.Slug == slug)
            .ToListAsync();

        return View("Index", new SubjectListViewModel { Subjects = subjects });
    }
    
    [HttpGet("{category}/{slug}")]
    public async Task<IActionResult> Show(string category, string slug)
    {
        var subject = await _context.Subjects.FirstOrDefaultAsync(s => s.Slug == slug);

        return subject == null
            ? View("Errors/NotFound", Response.StatusCode = 404)
            : View("Show", subject);
    }
}

Finally, supply both category (which can also be a sub-category) and a subject.

Mathematics\Index.cshtml

<a
   asp-area="Mathematics"
   asp-controller="Mathematics"
   asp-action="Show"
   asp-route-department="@subject.Category.Slug"
   asp-route-slug="@subject.Slug"
>
   @subject.Title
</a>

Upvotes: 2

Related Questions