Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • bh 444 posts 1544 karma points
    Sep 11, 2024 @ 19:00
    bh
    0

    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?

  • npack 58 posts 334 karma points
    Sep 11, 2024 @ 22:11
    npack
    100

    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.

    @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

    -Views
        -MyCustomComponent
            -MyCustomComponent.cshtml
    

    Where the cshtml file looks something like this

    @model MyComponentViewModel
    <div>@Model.MyBlockElement.NormalStuff</div>
    <div>@Model.MyExtraData.Name</div>
    
  • npack 58 posts 334 karma points
    Sep 11, 2024 @ 22:18
    npack
    0

    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

  • bh 444 posts 1544 karma points
    Sep 12, 2024 @ 14:40
    bh
    0

    @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!!!

  • bh 444 posts 1544 karma points
    Sep 13, 2024 @ 13:32
    bh
    0

    @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.

  • npack 58 posts 334 karma points
    Sep 13, 2024 @ 14:39
    npack
    1

    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; }
    }
    
  • npack 58 posts 334 karma points
    Sep 13, 2024 @ 14:45
    npack
    1

    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"]
    
  • bh 444 posts 1544 karma points
    Sep 13, 2024 @ 15:01
    bh
    0

    @npack...you're a rockstar! Many thanks! You've given me a lot to work with here! Can't thank you enough!

Please Sign in or register to post replies

Write your reply to:

Draft