I have a blocklist component cProductGrid. cProductGrid lists all children of the Products node in a 4-column grid.
I have an external SQL database that has the price for each of those products.
What I need to be able to do is display the price of each product from the external SQL database in my cProductGrid.
But, I'm struggling to wrap my head around how that would work. I could probably figure it out client side with some ajax, but I was hoping to be able to accomplish this server side.
For blocklist elements that need a 'controller' I've daisy chained an AspNetCore ViewComponent (using Microsoft.AspNetCore.Mvc;) from my block template before. It needs its own model and its own view, but it works pretty well with async data sources such as an api or database call. The alternative would perhaps be to put a bunch of code in your template itself but that seems pretty bad from an MVC viewpoint. Implementing some caching of this data to avoid a zillion DB or API calls helps keep things performant.
The template for the block list element ends up almost empty -- containing just an 'Invoke' command that takes us into the ViewComponent world.
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<BlockGridItem>
@* HTML Markup is stored in Views/Components since this element is implemented as a component so it can have its own 'controller'-esque code behind *@
@*Parameters must be wrapped in an anonymous type with properties that match the invoke parameter names*@
@await Component.InvokeAsync(typeof(MyCustomComponent), new {
myBlockElement = (MyBlockElement)Model.Content!
})
Then you need a "controller" for your ViewComponent that contains the Invoke method. This controller isn't a typical controller. It derives from ViewComponent but it can take DI injection args through its constructor just like a controller can -- such as a logger or your configuration object which might have an api endpoint or a connection string. Its invoke method can take whatever args you want but I have found it useful to take an instance of your block element. the naming of the args must match what is called by the view template above. It is temperamental anonymous type.
The view component also has its own template and its own model but I usually pass along my block element in the model
public class MyCustomComponent : ViewComponent
{
// The invoke method is called when the component is first displayed
// This invoke method can take args, passed when invoking from the parent view.
public async Task<IViewComponentResult> InvokeAsync(MyBlockElement myBlockElement)
{
var model = new MyCustomComponentViewModel();
model.MyBlockElement = myBlockElement;
try
{
model.MyExtraData = await GoGetSomeExternalData();
}catch(Exception ex)
{
this.logger.LogError(ex, "Problem retrieving data for block grid element ");
}
return View(this.GetType().Name, model);
}
}
The model is something that contains your block element PLUS whatever extra data you retrieve
public class MyCustomComponentViewModel
{
// This can be whatever we want it to be. It can pass along fields from a block list element's content,
// It might contain additional 'hijacking-added' properties along with the block list element
public MyBlockElement MyBlockElement { get; set; } = null!;
public ExtraData? MyExtraData { get; set; } = null;
}
Then you create a subfolder in your Views directory for these ViewComponent views
I bet some Umbraco users would suggest bringing the external data into the umbraco database via some kind of event handler from the back office but I have never done that.
@npack I was thinking View Component (VC) too, I just couldn't wrap my head around it. Using my cProductGrid to Invoke the VC. That sounds exactly like what I was looking for. Many thanks!!! Well written response and example...i appreciate the time and effort!!!
@npack I made progress on this yesterday, but I got stuck when it comes to injecting IConfiguration. I should have explained in the OP that cProductGrid is a BlockListItem.
I'm trying to invoke the Pricing View Component from my BlockListItem. I need to inject the IConfiguration, and that's where I'm stuck. I've Googled it and I can't find any examples. I can't figure out how to reference IConfig in the BlockListItem to be able to pass it to the View Component via the Invoke.
The ViewComponent constructor can take dependency injection arguments then store them in private variables until Invoke is called, so no need to pass them through the Invoke call. I typically use IOptions but I if you're used to IConfiguration it probably works similarly. I'm not as familiar with IConfiguration
public class MyCustomComponent : ViewComponent
{
private readonly MyConfigurationPropertiesClass configuration;
public MyCustomComponent (IOptions<MyConfigurationPropertiesClass> configurationOptions)
{
this.configuration = configurationOptions.Value;
}
public async Task<IViewComponentResult> InvokeAsync(OtherCustomInvokeArgs otherArgs)
{
Some code here;
}
}
Where the configuration options is set up for injection in the program.cs or startup.cs like this
// Custom AppSettings section - IConfiguration would be different
var configurationSection = builder.Configuration.GetSection(MyConfigurationPropertiesClass.SECTION_NAME);
builder.Services.Configure<MyConfigurationPropertiesClass>(configurationSection)
And my MyConfigurationPropertiesClass just contains properties that mirror my appsettings layout
public class MyConfigurationPropertiesClass
{
public static string SECTION_NAME = "MyCustomApp";
public string SomeApiEndpoint { get; set; }
}
If you really need the IConfiguration in your template and I misunderstood then maybe this is what you need?
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
Configuration value for 'ConnectionStrings>>>DefaultConnection': @Configuration["ConnectionStrings:DefaultConnection"]
How to Integrate Data from External SQL Database
I have a blocklist component cProductGrid. cProductGrid lists all children of the Products node in a 4-column grid.
I have an external SQL database that has the price for each of those products.
What I need to be able to do is display the price of each product from the external SQL database in my cProductGrid.
But, I'm struggling to wrap my head around how that would work. I could probably figure it out client side with some ajax, but I was hoping to be able to accomplish this server side.
Any suggestions?
For blocklist elements that need a 'controller' I've daisy chained an AspNetCore ViewComponent (using Microsoft.AspNetCore.Mvc;) from my block template before. It needs its own model and its own view, but it works pretty well with async data sources such as an api or database call. The alternative would perhaps be to put a bunch of code in your template itself but that seems pretty bad from an MVC viewpoint. Implementing some caching of this data to avoid a zillion DB or API calls helps keep things performant.
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-8.0
Here is an example:
The template for the block list element ends up almost empty -- containing just an 'Invoke' command that takes us into the ViewComponent world.
Then you need a "controller" for your ViewComponent that contains the Invoke method. This controller isn't a typical controller. It derives from ViewComponent but it can take DI injection args through its constructor just like a controller can -- such as a logger or your configuration object which might have an api endpoint or a connection string. Its invoke method can take whatever args you want but I have found it useful to take an instance of your block element. the naming of the args must match what is called by the view template above. It is temperamental anonymous type.
The view component also has its own template and its own model but I usually pass along my block element in the model
The model is something that contains your block element PLUS whatever extra data you retrieve
Then you create a subfolder in your Views directory for these ViewComponent views
Where the cshtml file looks something like this
I bet some Umbraco users would suggest bringing the external data into the umbraco database via some kind of event handler from the back office but I have never done that.
Also, if this weren't in a block list or block grid, but just a normal umbraco page, I would use "route hijacking" as explained in https://docs.umbraco.com/umbraco-cms/reference/routing/custom-controllers for a simpler approach
@npack I was thinking View Component (VC) too, I just couldn't wrap my head around it. Using my cProductGrid to Invoke the VC. That sounds exactly like what I was looking for. Many thanks!!! Well written response and example...i appreciate the time and effort!!!
@npack I made progress on this yesterday, but I got stuck when it comes to injecting IConfiguration. I should have explained in the OP that cProductGrid is a BlockListItem.
I'm trying to invoke the Pricing View Component from my BlockListItem. I need to inject the IConfiguration, and that's where I'm stuck. I've Googled it and I can't find any examples. I can't figure out how to reference IConfig in the BlockListItem to be able to pass it to the View Component via the Invoke.
The ViewComponent constructor can take dependency injection arguments then store them in private variables until Invoke is called, so no need to pass them through the Invoke call. I typically use IOptions but I if you're used to IConfiguration it probably works similarly. I'm not as familiar with IConfiguration
Where the configuration options is set up for injection in the program.cs or startup.cs like this
And my MyConfigurationPropertiesClass just contains properties that mirror my appsettings layout
If you really need the IConfiguration in your template and I misunderstood then maybe this is what you need?
@npack...you're a rockstar! Many thanks! You've given me a lot to work with here! Can't thank you enough!
is working on a reply...