ViewComponent not found in external assembly

I am using the latest VS.2017 updates and templates for an MVC.NET Core web app. I figured I would like ViewComponents in an external assembly as I read several posts stating that this is not possible without odd tricks.

I have my main web application and then I created a .NET Framework class library named MySite.Components which is an "external assembly". In it I have installed ViewFeatures NuGet. I created a View View CSHTML component in its /Views/Shared/Components/GoogleAdsense/Default.cshtml.

I noticed that my CSPROJ already has GoogleAdSense as an inline resource:

 <ItemGroup>
  <None Include="app.config" />
  <None Include="packages.config" />
  <EmbeddedResource Include="Views\Shared\Components\GoogleAdsense\Default.cshtml" />
 </ItemGroup>

      

The view component is actually pretty simple:

namespace MySite.Components.ViewComponents {
     [ViewComponent(Name = "GoogleAdsense")]
     public class GoogleAdsense : ViewComponent {
        public async Task<IViewComponentResult> InvokeAsync(string adSlot, string clientId, string adStyle = "")
        {
          var model = await GetConfigAsync(adSlot, clientId, adStyle); 
          return View(model); 
        }

        private Task<GoogleAdUnitCompModel> GetConfigAsync(string adSlot, string clientId, string adStyle)
        {
             GoogleAdUnitCompModel model = new GoogleAdUnitCompModel
           {
            ClientId = clientId,    // apparently we can't access App_Data because there is no AppDomain in .NET core
            SlotNr = adSlot,
            Style = adStyle
           };
           return Task.FromResult(model); 
        }
     }
}

      

Then in the main project (ASP.NET Core Web Application) I installed the File NuGet Provider and changed my launch:

services.Configure<RazorViewEngineOptions>(options =>
        {
            options.FileProviders.Add(new EmbeddedFileProvider(
                 typeof(MySite.Components.ViewComponents.GoogleAdsense).GetTypeInfo().Assembly,
                 "MySite.Components.ViewComponents"
            ));
        });

      

Then I try to use the view component in the view like this:

@using MySite.Components.ViewComponents
            :
@Component.InvokeAsync(nameof(GoogleAdsense), new { adSlot = "2700000000", clientId = "ca-pub-0000000000000000", adStyle="" }) 

      

And I get the error

*InvalidOperationException: A view component named 'GoogleAdsense' could not be found.*

      

Also tried to use the non-nameof () notation that uses a generic parameter for InvokeAsync, but that won't work either, but with

 *"Argument 1: cannot convert from 'method group' to 'object'"*

      

And using the TagHelper form just renders it as unrecognized HTML:

<vc:GoogleAdsense adSlot = "2700000000" clientId = "ca-pub-0000000000000000"></vc:GoogleAdsense>

      

Finally, on the main assembly (the actual web app), I used GetManifestResourceNames () on the external assembly type to make sure it was inline and the list returned was specified as:

[0] = "MySite.Components.Views.Shared.Components.GoogleAdsense.Default.cshtml"

      

+3


source to share


2 answers


I was able to get the ViewComponent working with an external solution by building and installing a NuGet package from an "external" assembly to a consumer solution without issue. I originally tried to add a reference to the dll without creating my own NuGet package and it didn't work.



I would recommend trying the NuGet package first. If it still doesn't work, can you post both projects so I can help debug?

0


source


I did a lot of trial and error and was finally able to get this working. There are several tutorials out there, but they are all for .NET Core 1.0 and I also found that they don't work when using a DLL reference from another solution.

First, let's talk about the component name. The component name is determined either by convention or by attribute. For designation by convention, the class name must end with "ViewComponent", and then the component name will go all the way to "ViewComponent" (how controller names work). If you are just decorating the class with [ViewComponent]

, the component name will clearly be the class name. You can also directly set the name to something else with the Name parameter.

All three of these examples create the component name "GoogleAdsense".

public class GoogleAdsenseViewComponent : ViewComponent { }

[ViewComponent]
public class GoogleAdsense : ViewComponent { }

[ViewComponent(Name = "GoogleAdsense")]
public class Foo: ViewComponent { }

      

After that, make sure your views are in the correct folder structure.

├── Views
│   ├── Shared
│   │   ├── Components
│   │   │   ├── GoogleAdsense <--component name
│   │   │   │   ├── Default.cshtml

      

Then all views must be included as embedded resources. Right click> Properties in View and set Build Action to Embedded Resource. You can also do it manually in .csproj (and use globbing if you have a lot of views).



  <ItemGroup>
    <EmbeddedResource Include="Views\Shared\Components\GoogleAdsense\Default.cshtml" />
  </ItemGroup>

      

This is for the original project. Note that you must make an assembly for any changes to your views to be displayed as they are included in the DLL. This sounds obvious, but it's a change from how you usually interact with views.

Now to the consuming project. In ConfigureServices in Startup.cs, you have to add your component assembly as MVC ApplicationPart

and as EmbeddedFileProvider

. EmbeddedFileProvider

provides access to views embedded in an assembly, and ApplicationPart

installs MVC to include in your search paths.

var myAssembly = typeof(My.External.Project.GoogleAdsenseViewComponent).Assembly;

services.AddMvc().AddApplicationPart(myAssembly);

services.Configure<RazorViewEngineOptions>(options =>
{
    options.FileProviders.Add(new EmbeddedFileProvider(myAssembly, "My.External.Project"));
});

      

If there are multiple ViewComponents in this assembly, this should be sufficient for all of them. You can optionally provide a base namespace for EmbeddedFileProvider

. I found times when it was needed and times when it wasn't, so your best bet is to just provide it. This namespace should be the Default Namespace property of your project (Properties -> Application -> Default Namespace).

Finally, to call the ViewComponent, use the component name. Remember that the name of the component can be different from the name of the class. If you have not used [ViewComponent]

to specify the component name as the class name, you cannot use nameof

.

@await Component.InvokeAsync("GoogleAdsense")

      

0


source







All Articles