[v13.15.0] Using Automapper to create a new ProductContent instance

Vote:
 

Hi,

Currently, I have a profile like this -

CreateMap<PimProductDto, GenericProduct>()
                .ForMember(dest => dest.PimId, opt => opt.MapFrom(src => src.ECommerceProductId))
                .ForAllOtherMembers(opt => opt.Ignore());

I'd like to map the Dto (which contains all the data I want to add) to a new instance of the product, which I can then process further and ultimately, save to the catalog repo.

However, with my current mapping, I get the following Null Reference error on startup -

Object reference not set to an instance of an object.

at EPiServer.Commerce.Catalog.DataAnnotations.ValidUrlAttribute..ctor(LocalizationService localizationService, UrlSegmentOptions urlSegmentOptions)
   at System.RuntimeTypeHandle.CreateCaInstance(RuntimeType type, IRuntimeMethodInfo ctor)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimePropertyInfo property, RuntimeType caType)
   at AutoMapper.Configuration.MappingExpressionBase.Configure(TypeMap typeMap)
   at AutoMapper.ProfileMap.BuildTypeMap(IConfigurationProvider configurationProvider, ITypeMapConfiguration config)
   at AutoMapper.ProfileMap.Register(IConfigurationProvider configurationProvider)
   at AutoMapper.MapperConfiguration.Seal()
   at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
   at Client.Web.Startup.CreateMapperConfiguration() in D:\Projects\Client\src\Presentation\Client.Web\Startup.cs:line 48
   at Client.Web.Startup.<>c.<ConfigureContainer>b__4_0(ConfigurationExpression x) in D:\Projects\Client\src\Presentation\Client.Web\Startup.cs:line 86
   at StructureMap.PipelineGraph.Configure(Action`1 configure)
   at StructureMap.Container.Configure(Action`1 configure)
   at Client.Web.Startup.ConfigureContainer(ServiceConfigurationContext context) in D:\Projects\Client\src\Presentation\Client.Web\Startup.cs:line 79
   at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
   at EPiServer.Framework.Initialization.Internal.ModuleNode.ConfigureContainer(ServiceConfigurationContext context)
   at EPiServer.Framework.Initialization.InitializationEngine.ConfigureCurrentModules(Boolean final)
   at EPiServer.Framework.Initialization.InitializationEngine.ExecuteTransition(Boolean continueTransitions)
   at EPiServer.Framework.Initialization.InitializationModule.EngineExecute(HostType hostType, Action`1 engineAction)
   at EPiServer.Framework.Initialization.InitializationModule.FrameworkInitialization(HostType hostType)
   at EPiServer.Global..ctor()
   at Client.Web.EPiServerApplication..ctor() in D:\Projects\Client\src\Presentation\Client.Web\Global.asax.cs:line 26
   at ASP.global_asax..ctor()

This is presumably to do with the destination model being an instance of ProductContent and thus requiring something a bit more specialized for configuring the mapping/profile...

Does anyone have any experience creating a new instance of a page/ProductContent through Automapper? The only thing similar I've been able to find around this issue was this unresolved thread in the dev-to-dev forum - https://world.episerver.com/forum/developer-forum/Developer-to-developer/Thread-Container/2018/7/automapper-custom-resolver-throws-null-exception-in-dependencyresolverinitialization/

I did try QM's suggestion in the thread above but that just resulted in responseless pages with no error message. I imagine that it (or my interpretation of it) broke the routing somehow...

This doesn't seem like anything ground-breaking, so hopefully, this is actually possible?

Any help would be greatly appreciated!

Cheers.

#217468
Feb 24, 2020 15:39
Vote:
 

Hi James

It seems you're using Automapper with DI. Please share your DI container registration class

#217517
Feb 25, 2020 4:05
Vote:
 

Hi Vincent,

I am indeed. Registration class looks like this -

[InitializableModule]
    [ModuleDependency(typeof(ServiceContainerInitialization))]
    public class Startup : IConfigurableModule
    {
        public static MapperConfiguration CreateMapperConfiguration()
        {
            var mappingConfig = new MapperConfiguration(config =>
            {
                config.AddProfile<FeaturesMappingProfile>();
                config.AddProfile<ViewModelsMappingProfile>();
                config.AddProfile<SearchMappingProfile>();

                // NOTE: Mapping in question is in here
                config.AddProfile<CatalogMappingProfile>();
            });

            return mappingConfig;
        }
        
        public void Initialize(InitializationEngine context)
        {
            RouteConfig.Configure(RouteTable.Routes);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        }

        public void Uninitialize(InitializationEngine context)
        {
            // No logic currently required
        }

        public void Preload(string[] parameters)
        {
            // No logic currently required
        }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            var container = context.StructureMap();

            container.Configure(x =>
            {
                x.AddRegistry<InfrastructureRegistry>();
                x.AddRegistry<FeaturesRegistry>();
                x.AddRegistry<CommerceModelRegistry>();
                x.AddRegistry<PimRegistry>();

                var mappingConfig = CreateMapperConfiguration();
                x.For<IConfigurationProvider>().Use(mappingConfig);

                x.For<IMapper>().Use(mappingConfig.CreateMapper());
                x.For<IClient>().Use(CreateSearchClient()).Singleton();

                x.For<IBackgroundJobClient>().Singleton().Use(() => new BackgroundJobClient());

                x.Scan(scan =>
                {
                    scan.WithDefaultConventions();
                    scan.TheCallingAssembly();
                    scan.LookForRegistries();
                });
            });

            DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
            System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
            GlobalConfiguration.Configuration.UseStructureMapActivator(context.StructureMap());
        }

        public void Configuration(IAppBuilder app)
        {
            // Hangfire Setup
        }

        private static IClient CreateSearchClient()
        {
            return Client.CreateFromConfig();
        }
    }
#217522
Feb 25, 2020 7:59
Vote:
 

Hi James

It is little supicious the ServiceContainerInitialization on ModuleDependency attribute might be too earlier to register Automapper. Have you tried to change to other Initialization modules e.g. CommerceInitialization

#217563
Edited, Feb 25, 2020 22:44
Vote:
 

Hi Vincent,

That does seem to resolve the runtime issue - though I'm now getting issues with the indexing (see below)... don't suppose you have any ideas around that?

An exception occurred while indexing content [Link 13] [GUID 822ab8c0-a37a-412c-9bc8-62d0f93d0e9e] [Type GlobalHomePage] [Name Corporate Homepage]: Self referencing loop detected for property 'ManifestModule' with type 'System.Reflection.RuntimeModule'. Path 'Carousel.Property[0].PrincipalAcccessor.Accessor.Method.Module.Assembly'. The loop was detected in Castle.Proxies.GlobalHomePageProxy. To correct this you can exclude one of the properties (or otherwise mapped fields) that are causing the loop by modifying the Client class' conventions. You may also modify the serializer to ignore self references. If you require the fields causing the loop to be serialized you may annotate the one of the classes in the loop with [JsonObject(IsReference = true)] or modify the JsonContract for one of the types to use IsReference = true which also can be done by modifying the Client class' conventions. (see log for more information)

#217742
Feb 28, 2020 15:42
Vote:
 

You probably want to ignore the PrincipalAccessor from your content type, by adding [JsonIgnore] to it 

#217867
Mar 02, 2020 13:59
Vote:
 

Hi QM,

Having looked at some of our other projects, I can see the dependency on the init modules was probably fine without the one on Commerce, so the indexing is back to working.

As it stands, I can map from a ProductContent object to something else, but the error (in original) occurs when mapping to one from something else. I think the issue is with have Automapper is trying to construct a new instance of the Product - I've been trying to find a way to handle this but have so far not been successful.

I was hoping the 'ConstructUsing()' method might do the trick but the error is seemingly thrown before that gets hit... :/

I don't suppose you've ever used Automapper to map to Product or Variant content before?

#217868
Mar 02, 2020 14:06
Vote:
 

In order to create content you need to use IContentFactory.CreateContent.  It should work with ConstructUsing and the factory.

#217876
Mar 02, 2020 17:32
Vote:
 

Hi Mark,

I was playing around with the 'ConstructUsing()' method yesterday to no avail - I've tried again today with your suggestion of using the IContentFactory but the issue seems to be that the error occurs before it ever even gets chance to hit the ConstructUsing logic...

I've tried using ConvertUsing also, since that is meant to do less stuff behind the scenes, but again, no luck with that either. I suspect I'm going to have to give up on using Automapper for this particular set of mappings since it doesn't seem like it plays nicely with Product/Variation Content.

#217962
Mar 03, 2020 9:42
* 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.