views:

298

answers:

3

I've found many similar questions but nothing with quite the answer I'm after. Part of my problem is due to the inability to use Generics in Attributes.

I am probably trying to over-complicate things, so if you can think of an easier way of doing this I'm all ears.

My specific problem relates to ASP.NET MVC, using Attributes (Filters) on an Action method. I'm trying to create a filter that will paginate the results passed to the ViewData.Model like this:

[PagedList(PageSize = 2, ListType = typeof(Invoice))]
public ViewResult List()
{
    var invoices = invoicesRepository.Invoices; // Returns an IQueryable<Invoice>
    return View(Invoices);
}

My filter's OnActionExecuted override then looks like:

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    ViewResult result = (ViewResult)filterContext.Result;
    var list = (IQueryable<?>)result.ViewData.Model; // Here I want to use the ListType in place of the ?

    // Perform pagination
    result.ViewData.Model = list.Skip((Page - 1) * PageSize).Take(PageSize.ToList();
}

I realise I could replace my

var list = (IQueryable<?>)result.ViewData.Model;

With

var list = (IQueryable)result.ViewData.Model;
IQueryable<Object> oList = list.Cast<Object>();

But my view is strongly typed to expect an

IQueryable<Invoice>

not an

IQueryable<Object>
+3  A: 

Could you potentially create a generic method in your filter that does what you need to do, and then within OnActionExecuted use reflection to call that generic method with ListType?


EDIT: For example, you would create a new method with this signature:

private void GenericOnActionExecuted<T>( ActionExecutedContext filterContext )

This method would have the same code as you posted. Then you would rewrite OnActionExecuted like so:

public void OnActionExecuted( ActionExecutedContext filterContext )
{
 MethodInfo genericMethod =
  GetType().GetMethod( "GenericOnActionExecuted",
   BindingFlags.Instance | BindingFlags.NonPublic );

 MethodInfo constructedMethod = genericMethod.MakeGenericMethod( ListType );

 constructedMethod.Invoke( this, new object[] { filterContext } );
}
Tinister
I would still need to pass the type somehow, and that's the part I'm stuck on: Type type = typeof(Invoice); // I can pass this as an attribute myNewMethod<type>(); // But I can't do this?
jeef3
I edited the response with some example code.
Tinister
ah yes, perfect!
jeef3
A: 

From what I've been able to figure out, this ranges from difficult to impossible as you're trying to achieve it.

If I understand your question correctly, you're wanting to do something like this:

  var someGenericResult = (IQueryable<typeof(SomeType)>)result.ViewData.Model;

You might be able to try using the information here, but I don't know if it will help you in your specific case as you're using interfaces to pass information -- as you should be -- and there's still the issue of type conversion.

andymeadows
Thanks andymeadows! After following your link it appears that it is answering my question in the same way as the other two. Unfortunately I can only award one "Accepted Answer" it seems :(
jeef3
No worries. The other comments are MUCH more detailed. Glad you got it working!
andymeadows
A: 

I have never used ASP.NET MVC myself but based on your explanation I would say you have two options. Both of these solutions require you to add a generic version of OnActionExecuted to your attribute.

protected void DoActionExecuted<T>(ActionExecutedContext filterContext)
{
    var result = (ViewResult) filterContext.Result;
    var list = (IQueryable<T>) result.ViewData.Model;
    result.ViewData.Model = list.Skip((Page - 1)*PageSize)
                                .Take(PageSize)
                                .ToList();
}

You can either use reflection to call a method with a dynamic type parameter:

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    GetType().GetMethod("DoActionExecuted",
                        BindingFlags.NonPublic | BindingFlags.Instance)
             .MakeGenericMethod(ListType)
             .Invoke(this, new[] {filterContext});
}

Or you can create a private attribute subclass that knows about the generic type at compile time:

[PagedInvoiceList(PageSize = 2, ListType = typeof (Invoice))]
public ViewResult List() { ... }

private class PagedInvoiceListAttribute : PagedListAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        DoActionExecuted<Invoice>(filterContext);
    }
}
Nathan Baulch
Thanks very much Nathan! This is the same answer that Tinister provided, but it appears I can only award one person with an "Accepted Answer" :(
jeef3