0

I am trying to expand a sigular branded website to support multiple different brands using the same code base.

A service has been created which retrieves the brand settings corrispoinding with the brand name found in the URL.

I want to set up the IViewLocation expander and inject into it this new brand retrieving service.

Like so:

public class ClientViewLocationExpander : IViewLocationExpander {
    private static readonly IBrandSettingsRetreiver<BrandSettingsEntity> _brandSettings;
    public ClientViewLocationExpander(IBrandSettingsRetreiver<BrandSettingsEntity> brandSettings) {
        _brandSettings = brandSettings
    }
}

On:

IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) {
    ...
}

I want to be able to use _brandSettings to get the name of the current brand in use, and put that brand name into the location list. So when BrandA is in use, Razor looks for the BrandA home page.

Views/Home/{BrandA}/index.cshtml
Views/Home/{BrandB}/index.cshtml
Views/Home/{BrandC}/index.cshtml

The Issue

    services.Configure<RazorViewEngineOptions>(options => {
        options.ViewLocationExpanders.Add(new ClientViewLocationExpander());
    });

This part of the IViewRenderService will complain if you try to use Dependency Injection on the IViewRenderService implementation. I have had a dig around the web looking for tricks or workarounds to get what I am trying to work.

I am rather, missing something obvious, or what I am trying to achieve simply isn't attainable.

Thanks for any help provided.

2 Answers2

1

I recently try to implement custom location expander and come with the similar requirement.

You don't have to use constructor DI pattern for everything instead you can access IBrandSettingsRetreiver<BrandSettingsEntity> via HttpContext.RequestService from ViewLocationExpanderContext

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
      var brandSetting = context.ActionContext.HttpContext.RequestServices.GetInstance<BrandSettingsRetreiver<BrandSettingsEntity>>();
    }

In addition, I would suggest you get name of brand from PopulateValues method instead of ExpandViewLocations method and pass brand via context.Values into ExpandViewLocations.

    public void PopulateValues(ViewLocationExpanderContext context)
    {
        var brandSetting = context.ActionContext.HttpContext.RequestServices.GetInstance<BrandSettingsRetreiver<BrandSettingsEntity>>();

        context.Values["brand"] = brandSetting.GetBrandName();
    }

   public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
       var brand = context.Values["brand"];
       if(!string.IsNullOrWhiteSpace(brand))
       {
          // do your business here
       }
    }

I hope this helps.

Vincent
  • 482
  • 8
  • 21
0

Perhaps people will have better ways of doing this, but I managed to find a method which worked for me, which I am just going to call the 'manual injection'.

Basically, I set up the IViewLocationExpander with the IBrandSettingsRetreiver in the constructor parameters like a normal DI setup.

Where it complains in the startup, I did this:

    var brandGetter = services.BuildServiceProvider().GetRequiredService<IBrandSettingsRetreiver<BrandSettingsEntity>>();
    services.AddScoped<IViewRenderService, ViewRenderService>();
    services.Configure<RazorViewEngineOptions>(options => {
                options.ViewLocationExpanders.Add(new ClientViewLocationExpander(brandGetter));
    });

For now, this seems to be getting my services to do what I want them too.

Hope it helps.

  • 1
    There is an interesting problem with calling BuildServiceProvider() explained here https://stackoverflow.com/questions/56042989/what-are-the-costs-and-possible-side-effects-of-calling-buildserviceprovider-i/56058498#56058498 . It doesn't seem good idea by doing this way. – Vincent Apr 12 '23 at 14:07