Copy files from the NuGet package to the destination project when installing the nuget package

Vote:
 

I'm working on the packaging part of module development and I was able to successfully create the package and install it on the destination site.

But I have an issue with copying files to the module folder in the destination project when installing. It creates some references for folders, not real folders. There are no physical files created in the modules folder in the destination project. But we noticed that it was created in the global NuGet folder on my computer (C:\Users<User>.nuget\packages).

I'm using Package.nuspec file and .targets file as follows:

Package.nuspec

<?xml version="1.0"?>
<package >
  <metadata xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
    <id>QBankModule6</id>
    <version>6.0.74</version>
    <title>QBank Module 6</title>
    <authors>QBNK AB</authors>
    <owners />
    <iconUrl>http://nuget.episerver.com/Framework/Styles/images/icons/default_package_icon.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>QBank Module is a module for media support. The module includes a QBank tool for EPiServer.Cms.TinyMce 2.1.1.0 or higher versions and media references.  IMPORTANT: After first install, go to Admin mode to initiate QBank.</description>
    <tags>EPiServerModulePackage ThirdPartyAddOn Media Images DAM Mediabank Streaming QBank</tags>
    <dependencies>
      <group targetFramework="net5.0">
        <dependency id="EPiServer.CMS.Core" version="[12.0.3, 13)" />
        <dependency id="RestSharp" version="106.11.7" />
        <dependency id="log4net" version="[2.0.0, 2.9999)" />
        <dependency id="HtmlAgilityPack" version="[1.8.4.0, 1.8.4.9)" />
      </group>
    </dependencies>
    <contentFiles>
      <files include="any/any/modules/_protected/QBankModule/ClientResources/Scripts/Editors/QBankMediaPicker.js" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/ClientResources/Scripts/qbank-connector.js" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/ClientResources/Scripts/QBankResponsiveLight-1.0.js" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/ClientResources/Scripts/jquery.js" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/ClientResources/Styles/qbankplugin.css" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/TinyPlugin/editor_plugin.js" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/module.config" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/lang/QbankModule_lang.xml" buildAction="None" copyToOutput="true"/>
      <files include="any/any/modules/_protected/QBankModule/QBankModule.zip" buildAction="None" copyToOutput="true"/>
      
      <files include="any/any/QbankApi.dll" buildAction="Compile" copyToOutput="true"/>
      <files include="any/any/Qbank.Connector.Core.dll" buildAction="Compile" copyToOutput="true"/>
      <files include="any/any/Qbank.Connector.EpiCore.dll" buildAction="Compile" copyToOutput="true"/>
      <files include="any/any/Qbank.Connector.EpiCore11.dll" buildAction="Compile" copyToOutput="true"/>
      <files include="any/any/Qbank.Connector.EpiServerModule5.dll" buildAction="Compile" copyToOutput="true"/>
      
    </contentFiles>
                        
    
  </metadata>
  <files>
    <file src="Qbank.Connector.EpiServerModule5/build/QBankModule6.targets" target="lib/net5.0" />
    <file src="Qbank.Connector.EpiServerModule5/bin/net5.0/QbankApi.dll" target="lib/net5.0" />
    <file src="Qbank.Connector.EpiServerModule5/bin/net5.0/Qbank.Connector.Core.dll" target="lib/net5.0" />
    <file src="Qbank.Connector.EpiServerModule5/bin/net5.0/Qbank.Connector.EpiCore.dll" target="lib/net5.0" />
    <file src="Qbank.Connector.EpiServerModule5/bin/net5.0/Qbank.Connector.EpiCore11.dll" target="lib/net5.0" />
    <file src="Qbank.Connector.EpiServerModule5/bin/net5.0/Qbank.Connector.EpiServerModule5.dll" target="lib/net5.0" />
    <!--<file src="Qbank.Connector.EpiServerModule5/ClientResources/Images/qbank-loader.gif" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Images/qbank-loader.gif" />-->
    <!--<file src="Qbank.Connector.EpiServerModule5/ClientResources/Images/qbankadd.png" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Images/qbankadd.png" />-->
    <file src="Qbank.Connector.EpiServerModule5/ClientResources/Scripts/Editors/QBankMediaPicker.js" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Scripts/Editors/QBankMediaPicker.js" />
    <file src="Qbank.Connector.EpiServerModule5/ClientResources/Scripts/qbank-connector.js" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Scripts/qbank-connector.js" />
    <file src="Qbank.Connector.EpiServerModule5/ClientResources/Scripts/QBankResponsiveLight-1.0.js" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Scripts/QBankResponsiveLight-1.0.js" />
    <file src="Qbank.Connector.EpiServerModule5/ClientResources/Scripts/jquery.js" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Scripts/jquery.js" />
    <file src="Qbank.Connector.EpiServerModule5/ClientResources/Styles/qbankplugin.css" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Styles/qbankplugin.css" />
    <file src="Qbank.Connector.EpiServerModule5/TinyPlugin/editor_plugin.js" target="contentFiles/any/any/modules/_protected/QBankModule/TinyPlugin/editor_plugin.js" />
    <!--<file src="Qbank.Connector.EpiServerModule5/TinyPlugin/Img/qbank.png" target="contentFiles/any/any/modules/_protected/QBankModule/TinyPlugin/Img/qbank.png" />-->
    <file src="Qbank.Connector.EpiServerModule5/module.config" target="contentFiles/any/any/modules/_protected/QBankModule/module.config" />
    <file src="Qbank.Connector.EpiServerModule5/Lang/QbankModule_lang.xml" target="contentFiles/any/any/modules/_protected/QBankModule/lang/QbankModule_lang.xml" />
  
    <!--CDN Solution-->
    <!--<file src="Qbank.Connector.EpiServerModule5/ClientResources/Images/qbankaudio.png" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Images/qbankaudio.png" />-->
    <!--<file src="Qbank.Connector.EpiServerModule5/ClientResources/Images/qbankdocument.png" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Images/qbankdocument.png" />-->
    <!--<file src="Qbank.Connector.EpiServerModule5/ClientResources/Images/qbankvideo.png" target="contentFiles/any/any/modules/_protected/QBankModule/ClientResources/Images/qbankvideo.png" />-->
    <!--<file src="Qbank.Connector.EpiServerModule5/build/QBankModule6.props" target="build" />-->
    <file src="Qbank.Connector.EpiServerModule5/build/QBankModule6.targets" target="build" />
    <file src="Qbank.Connector.EpiServerModule5/QbankModule.zip" target="contentFiles/any/any/modules/_protected/QBankModule/QBankModule.zip" />

  </files>
</package>

QBankModule6.targets

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

  <ItemGroup>
    <SourceScripts Include="$(MSBuildThisFileDirectory)..\..\contentFiles\any\any\modules\_protected\**\*.*"/>
  </ItemGroup>

  <Target Name="CopyZipFiles" BeforeTargets="Build">
    <Copy
            SourceFiles="@(SourceScripts)"
            DestinationFolder="$(MSBuildProjectDirectory)\modules\_protected\%(RecursiveDir)"
        />
  </Target>
</Project>

Also, I found following post an implemented solutions mentioned in the post. But ended up with the same result.

https://stackoverflow.com/questions/65218578/why-are-content-files-from-nuget-packages-added-as-links-in-net-core-projects-a

Basically, I need to understand how to copy some files from the NuGet package to the destination project when installing the NuGet package.

Any input from someone who has experience on this would be great!

Thanks in advance!

#278381
Edited, Apr 12, 2022 21:14
Vote:
 

Please see this blog post which explains in detail how to package modules for CMS 12.

https://blog.tech-fellow.net/2021/09/03/how-to-pack-your-shell-module-for-optimizely-content-cloud/

#278417
Edited, Apr 13, 2022 1:31
Vote:
 

I guess it is by design, the files are referenced in your projects after nuget file installation and that's it, no need for file copy.
The files will be copied at build/publish time anyway.

#278426
Edited, Apr 13, 2022 9:25
Vote:
 

I'm not sure what the exact issue is here, but I'd like to point out a few things;

  • Try keep everything in a zip file, except for the target file and binaries.
  • The target file, that should move the files, should be located in 'build\{target framework}\' folder and have the same name as the package ID. This seems to be correct, except that you don't have the target framework in the path now.
  • <contentFiles> should probably only contain the zip file. 
  • You should probably not depend on log4net, just use the built-in logging in .NET Core.
  • I would recommend using this slightly modified target file, since it's using increment build and speeds up build times (only moves files when they've changed):
<Project>
  <ItemGroup>
    <ModuleFiles Include="$(MSBuildThisFileDirectory)..\..\contentFiles\any\any\modules\_protected\**\*.*"/>
  </ItemGroup>

  <Target
    Name="CopyModuleFiles"
    BeforeTargets="Build"
    Inputs="@(ModuleFiles)"
    Outputs="@(ModuleFiles -> '$(MSBuildProjectDirectory)\modules\_protected\%(RecursiveDir)%(Filename)%(extension)')">
    <Copy
      SourceFiles="@(ModuleFiles)"
      DestinationFiles="@(ModuleFiles -> '$(MSBuildProjectDirectory)\modules\_protected\%(RecursiveDir)%(Filename)%(extension)')" />
  </Target>
</Project>
#278436
Apr 13, 2022 18:52
Vote:
 

Internally we're using somthing similar to this target file to build shell modules including zipping them. Import this file into your project:

<Project>
  <PropertyGroup>
    <!-- Where the targets files are located -->
    <BuildDirectory Condition=" '$(BuildDirectory)' == '' ">$(MSBuildThisFileDirectory)</BuildDirectory>

    <!-- Where the artifacts should be produced -->
    <ArtifactsDirectory Condition=" '$(ArtifactsDirectory)' == '' ">$(SolutionDir)artifacts\</ArtifactsDirectory>

    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
    <ModuleName>$(MSBuildProjectName)</ModuleName>
    <ModuleFile>$(ArtifactsDirectory)$(ModuleName).zip</ModuleFile>
    <ModuleTempDirectory>$(ArtifactsDirectory)$(ModuleName)\</ModuleTempDirectory>
    <ClientResourcesDirectory>clientResources\</ClientResourcesDirectory>
    <ClientResourcesOutputDirectory>$(ClientResourcesDirectory)dist\</ClientResourcesOutputDirectory>

    <!-- This property needs to be set for the module to be created -->
    <ClientResourcesBuildCommand></ClientResourcesBuildCommand>
  </PropertyGroup>

  <ItemGroup>
    <!-- These items do not apply when packing with nuspec files -->
    <Content Remove="module.config" />
    <None Include="module.config" />
    <None
      Include="$(BuildDirectory)copyModuleFiles.targets"
      Visible="false"
      Pack="true"
      PackagePath="build\$(TargetFramework)\$(ModuleName).targets" />
    <None
      Include="$(ModuleFile)"
      Visible="false"
      Pack="true"
      PackagePath="contentFiles\any\any\modules\_protected\$(ModuleName)\" />
  </ItemGroup>

  <!--
    Note: 'DispatchToInnerBuilds' only works in multi-target projects.
    We only want to compile the client resources once, even though the
    project has multiple targets. Change to 'Build' if multi-targeting
    is removed.
  -->
  <Target Name="BuildClientResources"
          BeforeTargets="DispatchToInnerBuilds"
          Condition=" '$(ClientResourcesBuildCommand)' != '' ">
    <Message Text="Restoring dependencies and building client resources using 'npm'. This may take several minutes..." Importance="high" />
    <Exec WorkingDirectory="$(ClientResourcesDirectory)" Command="npm install" />
    <Exec WorkingDirectory="$(ClientResourcesDirectory)" Command="$(ClientResourcesBuildCommand)" />
  </Target>

  <Target Name="CreateModule"
          AfterTargets="BuildClientResources"
          Inputs="module.config;@(ModuleClientResources)"
          Outputs="$(ModuleFile)">
    <ItemGroup>
      <ModuleClientResources Include="$(ClientResourcesOutputDirectory)\**\*"></ModuleClientResources>
    </ItemGroup>

    <Copy
      SourceFiles="module.config"
      DestinationFolder="$(ModuleTempDirectory)" />

    <XmlPoke
      XmlInputPath="$(ModuleTempDirectory)module.config"
      Query="/module/@clientResourceRelativePath"
      Value="$(Version)" />

    <Copy
      SourceFiles="@(ModuleClientResources)"
      DestinationFiles="@(ModuleClientResources -> '$(ModuleTempDirectory)$(Version)\$(ClientResourcesOutputDirectory)%(RecursiveDir)%(Filename)%(Extension)')" />

    <ZipDirectory
      SourceDirectory="$(ModuleTempDirectory)"
      DestinationFile="$(ModuleFile)"
      Overwrite="true" />

    <RemoveDir Directories="$(ModuleTempDirectory)" />
  </Target>
</Project>

The project file...

<Project Sdk="Microsoft.NET.Sdk.Razor">
  <Import Project="createModule.targets" />

  <PropertyGroup>
    <TargetFrameworks>net5.0</TargetFrameworks>
    
    ...

    <ClientResourcesBuildCommand Condition=" '$(Configuration)' == 'Release' ">npm run build</ClientResourcesBuildCommand>
  </PropertyGroup>

  ...
  
</Project>
#278440
Apr 13, 2022 19:08
Vote:
 

Hi All, we managed to get this to pack and it's working fine to use the module. However, when adding all content into a zip file and then creating the package leads us to get an error. It says 

Unable to find a module by assembly "Qbank.Connector.EpiCore11"

We have used the following menu provider and we notice this error occurs because of the "GetType()" method.

[MenuProvider]
    sealed  public class MenuProvider : IMenuProvider
    {
        public IEnumerable<MenuItem> GetMenuItems()
        {
            var name = "Qbank";
            var path = MenuPaths.Global + "/cms/admin/qbank";
            var url = Paths.ToResource(GetType(), "qbanksettings");

            var link = new UrlMenuItem(name, path, url)
            {
                SortIndex = 200,
                AuthorizationPolicy = CmsPolicyNames.CmsAdmin
            };

            return new List<MenuItem>
            {
                link
            };
        }

    }

it seems like when we zip the content of the module even though we can install it when using it can't identify the module by the assembly file. we have following folder structure

_protected
| - QBankModule
     | - QBankModule.zip
          |- 0.8.7
          |- module.config

module.config

<?xml version="1.0" encoding="utf-8"?>
<module name="QbankModule" clientResourceRelativePath="0.8.7"  loadFromBin="false" moduleJsonSerializerType="None" prefferedUiJsonSerializerType="Net"> 
  <assemblies>
    <add assembly="Qbank.Connector.EpiServerModule5.Views" />
    <add assembly="Qbank.Connector.EpiServerModule5" />
    <add assembly="Qbank.Connector.EpiCore11" />
    <add assembly="Qbank.Connector.EpiCore" />
    <add assembly="Qbank.Connector.Core" />
  </assemblies>
  
  <clientResources>
    <add name="epi-cms.widgets.base" path="ClientResources/Styles/qbankplugin.css" resourceType="Style"/>
    <add name="qbankmoduleResources" path="ClientResources/scripts/jquery.js" resourceType="Script"/>
  </clientResources>
  
  <dojo>
    <paths>
      <add name="qbank" path="ClientResources/scripts" />
    </paths>
  </dojo>

  <clientModule>
    <moduleDependencies>
      <add dependency="Shell"/>
      <add dependency="Cms" type="RunAfter"/>
    </moduleDependencies>
    <requiredResources>
      <add name="qbankmoduleResources"/>
    </requiredResources>
  </clientModule>
</module>

Can anyone help with this? Is there anything that we missing here related to this zipping things and packing things?

#280934
Edited, May 26, 2022 15:50
Vote:
 

You have two options, create an initialization module or add a extension method to call in startup.

 [InitializableModule]
    [ModuleDependency(typeof(InitializationModule))]
    public class InitializeQBank : IConfigurableModule
    {
        private static ILogger _log = LogManager.GetLogger(typeof(InitializeAvaTaxUI));

        /// <summary>
        /// Configure container
        /// </summary>
        /// <param name="context"></param>
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<ProtectedModuleOptions>(x =>
            {
                if (!x.Items.Any(x => x.Name.Equals("QbankModule")))
                {
                    x.Items.Add(new ModuleDetails
                    {
                        Name = "QbankModule"
                    });
                }
            });
        }

        /// <summary>
        ///     Initialize
        /// </summary>
        /// <param name="context">The context.</param>
        public void Initialize(InitializationEngine context)
        {
            
        }

        /// <summary>
        ///     Uninitialize
        /// </summary>
        /// <param name="context">The context.</param>
        public void Uninitialize(InitializationEngine context)
        {
            
        }
    }
public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// Convenient method to add CMS with both core services, Ui and runtime hosting.
        /// </summary>
        /// <param name="services">the services.</param>
        /// <returns>The configured service container</returns>
        public static IServiceCollection AddQBank(this IServiceCollection services)
        {
            services.Configure<ProtectedModuleOptions>(x =>
            {
                if (!x.Items.Any(x => x.Name.Equals("QbankModule")))
                {
                    x.Items.Add(new ModuleDetails
                    {
                        Name = "QbankModule"
                    });
                }
            });

            return services;
        }
#280938
May 26, 2022 19:15
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.