ASP.NET 5 View Components

One of my favorite features in ASP.NET 5 / MVC 6 is View Components. Like I said of Tag Helpers, I think they are what MVC was missing in terms of reusable components.

View Components are similar to partial views, except that they don’t have a visual design surface; all of its contents must be produced by .NET code. Only “tangible” content can be returned, no redirects, or any other contents that require HTTP status codes.

A View Component needs to either inherit from ViewComponent (no API documentation yet) or be a POCO class decorated with the ViewComponentAttribute. If it inherits from ViewComponent, it gets a lot of useful infrastructure methods, otherwise, it is possible to inject some context, such as a ViewContext reference.

A View Component looks like this:

public class SumViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(int a, int b)
    {
        return this.Content($"<span class=\"result\">{a + b}</span>");
    }
}

As you can see, a View Component can take parameters, any number, for that matter, and, by convention, its entry point must be called Invoke, return an instance of IViewComponentResult, and here is where its parameters are declared.

A View Component is rendered like this:

@Component.Invoke("Sum", 1, 2);    <!-- <span class="result">3</span> –>

As you can see, the argument to Invoke must be the View Component’s class name, without the ViewComponent suffix. We can override this and set our own name, by applying the ViewComponentAttribute:

[ViewComponent(Name = "Sum")]
public class ArithmeticOperation
{
    public IViewComponentResult Invoke(int a, int b)
    {
        return new ContentViewComponentResult($"<span class=\"result\">{a + b}</span>");
    }
}

Notice that this uses a POCO class that does not inherit from ViewComponent. However, the Razor syntax will be exactly the same, because of the Name passed in the ViewComponentAttribute attribute. It basically only needs to be public, concrete and non-generic.

It is also possible to use an asynchronous version:

[ViewComponent(Name = "Sum")]
public class ArithmeticOperation
{
    public async Task<IViewComponentResult> InvokeAsync(int a, int b)
    {
        return new ContentViewComponentResult($"<span class=\"result\">{a + b}</span>");
    }
}

In this case, the Razor syntax must be instead:

@await Component.InvokeAsync("Sum", 1, 2);    <!-- <span class="result">3</span> –>

Because the ViewComponent class exposes some properties that give access to the execution context, namely, ViewContext, it is also possible to have it in the POCO version, by decorating a ViewContext property with ViewContextAttribute:

[ViewContext]
public ViewComponentContext ViewContext { get; set; }

The ViewComponentContext class gives direct access to the arguments, the ViewData and ViewBag collections, the FormContext and a couple of other useful context properties, so it is generally good to have it near by. It is even possible to access the view’s model:

var v = this.ViewContext.View as RazorView;
dynamic page = v.RazorPage;
var model = page.Model;

View Components can benefit of dependency injection. Just add the types that you wish to inject to the constructor, works with both the POCO and the ViewComponent version:

public class SumViewComponent : ViewComponent
{
    public SumViewComponent(ILoggerFactory loggerFactory)
    {
        //do something with loggerFactory
    }
}

One last remark: view components are automatically loaded from the executing assembly, but it is possible to load them from other assemblies, we just need to get an handle to the web application’s IViewComponentDescriptorCollectionProvider. From it, we can get to the ViewComponents collection and add our own:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    var vcdcp = app.ApplicationServices.GetService<IViewComponentDescriptorCollectionProvider>();
    var list = vcdcp.ViewComponents.Items as IList<ViewComponentDescriptor>;
 
    var viewComponentType = typeof(MyViewComponent);
 
    var attr = viewComponentType.GetTypeInfo().GetCustomAttributes<ViewComponentAttribute>(true).SingleOrDefault();
    var fullName = attr?.Name ?? viewComponentType.DisplayName(true);
 
    list.Add(new ViewComponentDescriptor
    {
        FullName = fullName,
        ShortName = viewComponentType.DisplayName(false),
        Type = viewComponentType
    });
 
    //rest goes here
}

This makes it very easy to register View Components that are declared in different assemblies.

Have fun! Winking smile

 

                             

No Comments

Add a Comment

As it will appear on the website

Not displayed

Your website