Working with ViewComponents in .NET Core

24 Nov 2018, CSHARP , .NET CORE

1017 Palavras, por José luiz

#ASP.NET Core , #ViewComponents , #Reusable View Components



ViewComponents

Intro

Hello guys, how do are you today? So, today I will talk about ViewComponents,, this amazing feature inside .NET Core (1.0, 1.1, 2.0), if you have been developing software, probably you know about DRY (Don’t Repeat Yourself), and build the application using this concept will save our time!.



So that, I will show to you how to transform part of your code in Component and reuse in our application.

We will create a three kind of ViewComponents: and one Extra Content!!

  1. Simple HTML Required to read..;)
  2. Fix List
  3. Read data from Database
  4. Update Components Using Javascript (Yeah!!!! Javascript)

As you know, I try coverage all about the subject, so, good read!

Simple HTML

This is a basis for learning ViemComponents ok?
First Let’s create a new project using the command line:

dotnet new mvc --name NetCoreComponent --auth Individual

And to check if all good, run this command:

dotnet run

After let’s navigate on localhost: (look all port your dotnet run)

Home Page It’s our HomePage, not big deal yet.

Now Let’s create a Simple ViewComponents just return an HTML contains a message to our user…

I use Visual Studio Code.. :)

  1. Create A Folder Called Components to organize our code
  2. Create a Class called “Simple.cs” inside this Folder Bellow my code:
  3. Create a CSHTML (Check below)
using Microsoft.AspNetCore.Mvc;
namespace NetCoreComponent.Components
{
    public class Simple : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View();
        }
    }
}

Every Component:

  • Must inherit of ViewComponent (You can create using decoration or Just end with Sufix ViewComponent)
  • Have a support to DI (Dependency Injection )
  • Must have a method IViewComponentResult Invoke() or IViewComponentResult InvokeAsync() to async
  • Must have a CSHTML

Isn’t possible use IPageFilter or IAsyncPageFilter


We need to create our CSHTML, so you can choose with of this path:

  • /Pages/Components/{View Component Name}/{View Name}
  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}
  • /Views/Shared/Components/{View Component Name}/{View Name}

The default view name for a view component is Default.cshtml. You can specify a different view name when creating the view component result or when calling the View method.

I like to create in Views/Shared/Components/{View Component Name}/Default.cshtml path.
So, our component will be:

Views/Shared/Components/Simple/Default.cshtml

And our code:

<h1>Simple Component HTML</h1>
<p>Hello, please don't forget to send a message... :)</p>
<ul>
  <li>list 1</li>
  <li>list 2</li>
  <li>list 3</li>
</ul>


Invoking a view component as a Tag Helper

To use the view component, call the following inside a view that you need use

@await Component.InvokeAsync("Simple")

But It’s better use Tag Helper for this add “@addTagHelper *, NetCoreComponent” in our cshtml file (Add in “_ViewImports” to enable in all CSHTML files), after that, you can call like this:

<vc:simple></vc:simple>

Now our application must show the ViewComponents;


Now we know about the basic of Components, let’s do something more interesting..

Fix List

In this ViewComponents lets create another class called Todo.cs like this:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace NetCoreComponent.Components
{
    public class Todo : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View(new List<string>() { "First", "Second", "Thirth", "Fourth" });
        }
    }
}

The pointer here is passing data from ViewComponents to View, so that, Let’s create our CSHTML in our path:
Views/Shared/Components/Todo/Default.cshtml path.
with this code:

@model IEnumerable<string>
  <h1>Fix List</h1>
  <ul>
    @foreach (var item in Model) {
    <li>@item</li>
    }
  </ul>
</string>

And Finally, call in our index.cshtml:

<vc:todo></vc:todo>

Restart our app:

It’s possible to pass any data form Components to View, so, It’s possible to work with parameters as well:

Change the class and inside the parameter str

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace NetCoreComponent.Components
{
    public class Todo : ViewComponent
    {
        public IViewComponentResult Invoke(string str)
        {
            string[] all = str.Split(new char[] { ';' });
            return View(all);
        }
    }
}

And Finally, call in our index.cshtml:

<vc:todo str="The;View;Its;Amazing"></vc:todo>

Restart our app:


Ok, to quit kidding, Let’s see Database now!
For this, I use MariaDB, if you don’t know to integrate MariaDB with .NET Core, look this


Read data from Database

In this ViewComponents lets create a another class called TodoDB.cs like this: (Remeber I use MariaDB) and look! The method is InvokeAsync yes! We use async… and parameters

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NetCoreComponent.Data;
using NetCoreComponent.Models;

namespace NetCoreComponent.Components
{

    public class TodoDb : ViewComponent
    {
        private readonly ApplicationDbContext db;

        public TodoDb(ApplicationDbContext context) => this.db = context;

        public async Task<IViewComponentResult> InvokeAsync(bool done)
        {
            var items = await GetItemsAsync(done);
            return View(items);
        }

        private Task<List<TodoList>> GetItemsAsync(bool done)
        {
           db.TodoList.Where(a => a.done == done).ToListAsync();
        }
    }
}

The pointer here is demonstrate using the database, so that, Let’s create our CSHTML in our path:
Views/Shared/Components/TodoDB/Default.cshtml path.
with this code:

@model IEnumerable<TodoList>
  <h1>My Todo List DB</h1>
  <ul>
    @foreach (var item in Model) {
    <li>@item.task</li>
    }
  </ul>
</TodoList>

And Finally, call in our index.cshtml:

<vc:todo-db done="true"></vc:todo-db>

The name of our components is TodoDb, so, because Camel Case, we need change D for -d, if our componets called TodoWithCommercial then we call lije this vc:todo-with-commercial

Restart our app:

Just Check DB:

Ok, If we need update via javascript?

Update Components Using Javascript

I create a simple Javascript for this purpose, first create a button on you index.html with the class, like this:

<button class="update">Update</button>
<div class="row todo">
  <vc:todo-db done="true"></vc:todo-db>
</div>

For called, we need to create a method on Home Controller, so, Let’s do! Check the syntax below for call the component.

        public IActionResult UpdateTodoList(bool done)
        {
            object paramets = new { done = done };
            return ViewComponent("TodoDb", paramets);
        }

create and import the javascript:

let btn = document.querySelector(".update");
let divEle = document.querySelector(".todo");

   btn.addEventListener("click", (e) => {
  e.preventDefault();

  let url = "/Home/UpdateTodoList?done=true";
  divEle.innerHTML = "Update...";
  btn.disabled = true;

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);

  xhr.addEventListener("load", () => {
    if (xhr.status === 200) {
      divEle.innerHTML = xhr.responseText;
      btn.disabled = false;
    }
  });

  setTimeout(() => xhr.send(), 1500);

});

Finished, now we can update component just click on a button!

Yes, I put another via database and just click on button.. :)

Conclusion

Work on ViewComponents It’s very interesting to ensure we don’t repeat code and make our application more extensible.

I have been using ViewComponents and I’m happy with the results.
The link to this project is in GitHub, take a look there…

Project

Note...

Hy, I'm José Luiz and my commitment is to no bullshit post, no sponsored posts, no ads, and no paywalls.

If you enjoy my content, please consider supporting what I do.

Support my work and save the World!

All support is send to MSF & Rotary :)