Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoke MVC ViewComponent with generic type parameter #9828

Closed
ajruckman opened this issue Apr 29, 2019 · 7 comments
Closed

Invoke MVC ViewComponent with generic type parameter #9828

ajruckman opened this issue Apr 29, 2019 · 7 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates

Comments

@ajruckman
Copy link

ajruckman commented Apr 29, 2019

Is your feature request related to a problem? Please describe.

I would like to be able to invoke a Razor Pages ViewComponent with a generic type parameter. For example, I want to be able to pass any List to a component, like this:

public Task<IViewComponentResult> InvokeAsync<T>(List<T> data)
{
    ...
    return Task.FromResult<IViewComponentResult>(View("Default", new ComponentConfig { }));
}

and to somehow call the component in a .cshtml file:

@await Component.InvokeAsync<Schema.Schema.VM>("ListTable", new {data = Model.AuditResult})

but I don't know where the type specifier would go in the .cshtml file.

Describe alternatives you've considered

Using an "object" type instead of a generic list parameter. But this isn't very clean.

below is kindof a different issue but maybe it's relevant; if this worked, I might not need generic parameters

I tried to ensure that the 'object' passed is any type of List with a function like this:

public bool IsList(object o)
{
    if(o == null) return false;
    return o is IList                &&
           o.GetType().IsGenericType &&
           o.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>));
}
public Task<IViewComponentResult> InvokeAsync(object data)
{
    if (!IsList(data))
    {
        throw new Exception("component parameter is not a list");
    }
    ...
    return Task.FromResult<IViewComponentResult>(View("Default", new ComponentConfig { }));
}

but this function doesn't work; it seems like the 'object' is a:

<>f__AnonymousType1`5[System.Collections.Generic.List`1[Schema.Schema+VM],System.String,System.Boolean,System.Boolean,System.String]

rather than a System.Collections.GenericList; maybe Razor is modifying the 'object' or something, or maybe I misunderstand how generics work.

@pranavkm pranavkm added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates area-blazor Includes: Blazor, Razor Components and removed area-blazor Includes: Blazor, Razor Components labels Apr 29, 2019
@enetstudio
Copy link

Razor component is actually a Blazor component. You meant ViewComponent, right ? Language is the source of misunderstandings...

@ajruckman
Copy link
Author

Yes you are right, my bad... I will edit it.

@ajruckman ajruckman changed the title Invoke Razor component with generic type parameter Invoke Razor Pages ViewComponent with generic type parameter Apr 30, 2019
@danroth27 danroth27 removed the area-blazor Includes: Blazor, Razor Components label Apr 30, 2019
@mkArtakMSFT mkArtakMSFT added area-blazor Includes: Blazor, Razor Components and removed area-blazor Includes: Blazor, Razor Components labels May 1, 2019
@mkArtakMSFT mkArtakMSFT added area-blazor Includes: Blazor, Razor Components and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels May 9, 2019
@rynowak
Copy link
Member

rynowak commented May 26, 2019

Hi @ajruckman - we don't have support for using generics in View Components.

@rynowak rynowak added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates and removed area-blazor Includes: Blazor, Razor Components labels May 26, 2019
@rynowak rynowak changed the title Invoke Razor Pages ViewComponent with generic type parameter Invoke MVC ViewComponent with generic type parameter May 26, 2019
@ajruckman
Copy link
Author

Hi @rynowak, I was pretty sure that was the case so that's why I thought I should use the feature request template.

Do you think there is any possibility of having generic View Components in the future? If not I will understand but I think they could be very powerful.

Thank you!

@rynowak
Copy link
Member

rynowak commented May 26, 2019

It's really unlikely that we'll add this as a feature to view components since we consider that feature area pretty much done.

What would help us understand the need for this more would be an example of seeing where T is used. For instance, we've had many requests for open generics for views over the years, and generally the requirements that are presented in those requests don't really align with generics.

Most of the infrastructure we provide for output (views/action results) uses the runtime type of the object. So having a base class (or using object) is just as good for producing output.

@ajruckman
Copy link
Author

I have pretty much solved all of the problems I had when I opened this issue with object and dynamic types. I'm sure this is a very niche use case but I will still try to explain why generic components would make what I want to do easier and safer.

I define classes that act as schema for things I want to display on a page. Each property has multiple attributes that define how it should be displayed. Here is a sample:

public class Change
{
        [Contexts(Context.List, Context.Form, Context.Info)]
        [HiddenIn(Context.Form)]
        [ReadOnly]
        public int ID { get; set; }

        [Contexts(Context.List, Context.Form, Context.Info)]
        [InputType(InputTypes.Select)]
        public string Type { get; set; } = null!;

        [Contexts(Context.Info)]
        [DisplayName("Type Description")]
        public string TypeDescription { get; set; } = null!;
}

One use case I have for generic components is that I want to pass a List<Change>, which I get from a database query, to a component, and iterate over the rows in it to display an HTML table. Right now, I accept an object in my component, which I ensure is an IEnumerable. Then I use reflection to get the class of elements in the List, instantiate a new instance of that class with Activator.CreateInstance, iterate over all the properties in the instance, and iterate over all the attributes in each property. This way, I can translate the attributes in the class to define how the class should be displayed based on the given context (List, Info, Form, Select...).

With generics, I wouldn't have to use reflection to get the class of elements in the List, and then create a new instance of that class.

I have other classes like Change, such as Facility, Property, Purchase, etc.. I want to be able to display any of these classes in a list by passing a list of them to my List component. With generics, I wouldn't have to accept an object, so my code could be verified at compile time, to make sure I'm passing the right thing to my components. And I have several other components that accept the classes too, such as InfoTable, FormTable, etc., but these others don't accept a List, just a single instance of the class.

This is probably way overcomplicated so I understand that it's likely not worth it to add generics support for components if it would require a lot of work to do so, but...

@mkArtakMSFT
Copy link
Member

Thanks for all the details, @ajruckman.
This is not really something we'll add support for.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates
Projects
None yet
Development

No branches or pull requests

6 participants