Create a global filter in ASP.NET Core 8 MVC

340 Views Asked by At

I have a posts category on my website. I have created a dropdown filter that filters my posts according to the chosen category.

The filter was created as a controller action method, but I want to make it global.

Here is the filter code - FilterController.cs:

public async Task<IActionResult> Index(string postCategory)
{
    ViewBag.PostCategory = postCategory;


    var filteredPost = from p in _context.Posts select p;

    if (postCategory == "All")
    {
        filteredPost = _context.Posts;
    }
    else if (!string.IsNullOrEmpty(postCategory))
    {
        filteredPost = filteredPost.Where(p => p.PostCategory == postCategory);
    }

    var filteredCategory = new CardsViewModel
    {
        PostCard = await filteredPost.ToListAsync()
    };

    _cache.Set("filteredCategory", filteredCategory);

    string url = Request.Headers["Referer"].ToString();

    return Redirect(url);
}

Currently, all controllers rely on _context.Posts to display data. The code filters _context.Posts inside filteredPost according to the user-selected category. I want to use the filteredPost instead of _context.Posts in my controllers. How can I achieve this?

Example of a controller where I want to use filteredPost instead of _context.Posts:

[HttpGet]
public async Task<IActionResult> Index()
{
    return View(await _context.Posts.ToListAsync());
}

All advice is appreciated. Thank you.

2

There are 2 best solutions below

6
On

Don't think of it in terms of a "global filter" - that's not how web-application frameworks (well) work.

I don't recommend using a shared/common Controller subclass: that'll only cause problems in the long-term; fortunately, all you need is a few extension-methods:

public static class MyHttpContextExtensions
{
    /// <summary>Postfix version of <see cref="String.IsNullOrWhiteSpace" />.</summary>
    public static Boolean IsSet( [NotNullWhen(true)] this String? s ) => !String.IsNullOrWhiteSpace( s );

    /// <summary>Case-insensitive ordinal string equality.</summary>
    public static Boolean EqualsIns( this String? left, String? right ) => StringComparer.OrdinalIgnoreCase.Equals( left, right );

    public static async IQueryable<Post> GetFilteredPosts( this HttpContext httpContext )
    {
        if( httpContext is null ) throw new ArgumentNullException(nameof(httpContext));

        MyDbContext dbContext = httpContext.RequestServices.GetRequiredService<MyDbContext>();
        
        String? postCatFilter = httpContext.Request.Query["postCategory"];
        if( postCatFilter.IsSet() && !"all".EqualsIns( postCatFilter ) )
        {
            return dbContext.Posts.Where( p => p.PostCategory == postCatFilter );
        }
        else
        {
            return dbContext.Posts;
        }
    }

    public static async IQueryable<Post> GetFilteredPosts( this ControllerBase controller )
    {
        return GetFilteredPosts( httpContext: controller.HttpContext );
    }
}

So then in any Controller of yours, all you need to do is (for example):

public class ExampleController
{
    private readonly MyDbContext dbContext;

    public ExampleController( MyDbContext dbContext )
    {
        this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }

    [Route( "/foo" )]
    public async Task<IActionResult> GetFoo()
    {
        List<Post> postsList = await this.GetFilteredPosts().ToListAsync();
        return this.View( "Foo", postsList );
    }
}

Note that the MyDbContext is scoped per request, so the MyDbContext returned from HttpContext.RequestServices is the same instance as ExampleController.dbContext.

0
On

To make your filteredPost logic global and reusable across different controllers, you can follow below steps:

First create a Service Layer this will handle the logic of filtering posts.

public class PostService
{
    private readonly YourDbContext _context;

    public PostService(YourDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Post>> GetFilteredPosts(string postCategory)
    {
        IQueryable<Post> query = _context.Posts;

        if (!string.IsNullOrEmpty(postCategory) && postCategory != "All")
        {
            query = query.Where(p => p.PostCategory == postCategory);
        }

        return await query.ToListAsync();
    }
}

Register the Service in Startup.cs:

public class SomeController : Controller
{
    private readonly PostService _postService;

    public SomeController(PostService postService)
    {
        _postService = postService;
    }

    [HttpGet]
    public async Task<IActionResult> Index(string postCategory = "All")
    {
        var filteredPosts = await _postService.GetFilteredPosts(postCategory);
        return View(filteredPosts);
    }
}

After creating the service, you can inject it into any controller that needs to access the filtered posts.

public class SomeController : Controller
{
    private readonly PostService _postService;

    public SomeController(PostService postService)
    {
        _postService = postService;
    }

    [HttpGet]
    public async Task<IActionResult> Index(string postCategory = "All")
    {
        var filteredPosts = await _postService.GetFilteredPosts(postCategory);
        return View(filteredPosts);
    }
}