Widgets

In this tutorial we will add a widget to display all the products. We will then add some parameter to limit the number to display.

Let's start by creating the following "Migration14.cs" file:

using FluentMigrator;

namespace DemoShop.Shop.Migrations;

[Migration(201908141856)]
public class Migration14 : AutoReversingMigration {
    public override void Up() {
        Insert.IntoTable("WidgetTypes").Row(new { Name = "Products", ComponentName = "DemoShop.Shop.ViewComponents.Products", AllowContentWidgets = false });
    }
}

Next we'll add a View Component for our widgets. Add the following file called "ProductsViewComponent.cs" to a new folder called "ViewComponents":

using System.Linq;
using System.Threading.Tasks;
using DemoShop.Shop.Services;
using DemoShop.Shop.ViewModels.Shared.Components;
using Microsoft.AspNetCore.Mvc;

namespace DemoShop.Shop.ViewComponents;

[ViewComponent(Name = "DemoShop.Shop.ViewComponents.Products")]
public class ProductsViewComponent : ViewComponent {
    private readonly IShopService _shopService;

    public ProductsViewComponent(IShopService shopService) {
        _shopService = shopService;
    }

    public async Task<IViewComponentResult> InvokeAsync() {
        return View((await _shopService.GetProductsAsync()).Select(p => new ProductViewModel(p)).ToList());
    }
}

Next we'll add our view model.

  1. Add a folder called “Shared” to the “ViewModels” folder.
  2. Within that folder create a sub-folder called “Components”.
  3. Now the following file called "ProductViewModel.cs" within this folder:
using DemoShop.Shop.Models;

namespace DemoShop.Shop.ViewModels.Shared.Components;

public class ProductViewModel {
    public ProductViewModel(Product product) {
        Id = product.Id;
        Name = product.Name;
    }

    public int Id { get; }
    public string Name { get; }
}

Finally we'll add the view.

  1. First add a folder called "Components" in the "Views/Shared" folder.
  2. Within that folder create a sub-folder called “Products”.
  3. Now add the following file called "Default.cshtml" within this folder:
@model IList<DemoShop.Shop.ViewModels.Shared.Components.ProductViewModel>
@if (Model.Count > 0) {
    <ul>
        @foreach (var product in Model) {
            <li><a asp-action="Details" asp-route-id="@product.Id">@Format(product.Name)</a></li>
        }
    </ul>
}

You can go ahead and add your widget.

Whilst this works fine, often you'll need to customise the widget by some parameters. We'll extend the widget above and a way to limit the number of products to display.

First we'll update the widget type we added above to specify the parameters type. Create the following "Migration15.cs" file:

using System;
using FluentMigrator;

namespace DemoShop.Shop.Migrations;

[Migration(201908141858)]
public class Migration15 : Migration {
    public override void Up() {
        Update.Table("WidgetTypes").Set(new { ParametersTypeAssembly = "DemoShop.Shop", ParametersTypeClass = "DemoShop.Shop.Models.ProductsParameters", ParametersTypeAction = "Products", ParametersTypeController = "WidgetParameters", ParametersTypeArea = "DemoShop.Shop" }).Where(new { ComponentName = "DemoShop.Shop.ViewComponents.Products" });
    }

    public override void Down() {
        throw new NotImplementedException();
    }
}

Now we'll add the following model called "ProductsParameters.cs" to the "Models" folder:

using System.ComponentModel.DataAnnotations;

namespace DemoShop.Shop.Models;

public class ProductsParameters {
    [Required]
    public int NumProducts { get; set; } = 5;
}

Next let's add the following “WidgetParametersController.cs” file to the “Controllers” folder:

using DemoShop.Shop.Models;
using Kit.Mvc.Filters;
using Kit.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc;

namespace DemoShop.Shop.Controllers;

[Area("DemoShop.Shop"), Route("parameters/widgets"), HtmlFieldPrefix("Parameters")]
public class WidgetParametersController : Controller {
    [AjaxOrChildActionOnly, HttpGet("products")]
    public IActionResult Products([BindRouteData] ProductsParameters parameters) {
        return PartialView(parameters);
    }
}

Next we'll add a view for the “Products” action. Create a file called "Products.cshtml" in a new "Views/WidgetParameters" folder with the following content:

@model DemoShop.Shop.Models.ProductsParameters
<div class="card shadow">
    <div class="card-header"><h2>@Text["Parameters"]</h2></div>
    <div class="card-body">
        <div class="form-group">
            <label asp-for="NumProducts" class="form-label"></label>
            <editor for="NumProducts" />
        </div>
    </div>
</div>

Finally let's update our view component to make use of our parameters. Open the “ViewComponents/ProductsViewComponent.cs” file and replace the “InvokeAsync” action method with the following (adding any missing namespaces):

public async Task<IViewComponentResult> InvokeAsync(ProductsParameters parameters) {
    return View((await _shopService.GetProductsAsync(1, parameters.NumProducts)).Select(p => new ProductViewModel(p)).ToList());
}

Now you can go ahead and update your widget to limit the number of products to display.

Scheduled Tasks »