Initializing Commerce from a console application

Vote:
 

I'm trying to initialize commerce from a C# console application that we use to do our imports.  In the following code the context.Container remains null.  What am I doing wrong?  Could you please provide a sample of how to initialize episerver from a console application.  Thank you

var container = new Container();
var structurMapConfig = new StructureMapConfiguration(container);
var context = new ServiceConfigurationContext(HostType.Installer, structurMapConfig);

new CommerceInitialization().ConfigureContainer(context);
new EventsInitialization().ConfigureContainer(context);

var requestCache = ServiceLocator.Current.GetInstance();
var readWriteDatabaseMode = ServiceLocator.Current.GetInstance();
var factory = new SqlDatabaseFactory(requestCache, readWriteDatabaseMode);

context.Container.Configure(ce => ce.For().Use(factory.CreateDefaultHandler()));
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use());
context.Container.Configure(ce => ce.For().Singleton().Use(() => CatalogContext.Current));
context.Container.Configure(ce => ce.For().Use(CatalogConfiguration.Instance));
context.Container.Configure(ce => ce.For().Singleton().Use(() => CatalogContext.MetaDataContext));
context.Container.Configure(ce => ce.For().Use(() => FrameworkContext.Current));
context.Container.Configure(ce => ce.For().Use(CatalogConfiguration.Instance));
context.Container.Configure(ce => ce.For().Use(() => new SqlContext(BusinessFoundationConfigurationSection.Instance.Connection.Database)));

var locator = new StructureMapServiceLocator(context.Container);
ServiceLocator.SetLocator(locator);

var catalogSystem = ServiceLocator.Current.GetInstance();

DataInitialization.InitializeFromCode(context.Container, factory, CacheProvider.Instance);
DynamicDataStoreFactory.Instance = new EPiServerDynamicDataStoreFactory();

#172456
Dec 02, 2016 21:46
Vote:
 

Hi,

What are you trying to do with a console Commerce application? It's not impossible, but it's more and more complex to do, with all the dependencies.

One option is to set up a ServiceAPI site and do your (catalog) operations there.

#172459
Dec 04, 2016 0:08
Vote:
 

Follow the trail http://world.episerver.com/documentation/developer-guides/Episerver-Service-API/catalog-restful-operations/

We did multiple implementation to import catalogs via some windows service.

/K

#172470
Dec 04, 2016 21:43
Vote:
 

I too would recommend using the service API. Commerce isn't designed to start outside a web context, all the initialization is hooked into the asp.net application lifecycle. That said, we do it ourselves for integration tests, and an adapted version of the code we use to do that is available on github. Note that this is not an official Episerver repo and it is not actively maintained. The latest update was a year ago so I don't know if it still works (for example considering the changes in CMS/Commerce 10). And again, this is not a supported scenario.

#172474
Dec 05, 2016 7:39
Vote:
 

Quan,

We're importing products from our PIM (which provides an xml output) through a console application that runs as a job.  This console app has been running for about 3 years now and as development goes, i've inherited this code.  

I have no doubt ServiceAPI is the way to go especially after Ascend last year.  However, like many projects the time to convert this doesn't exist right now.

In our move from commerce 7.5 to 9.19 we saw some breaking changes (of course) in the way we were initializing IoC and I fixed them.  We recently did an upgrade from 9.19 to 9.24.1 to take advantage of some performance improvements you guys made on the backend database side.  However, now our IoC initialization is broken again and this time i'm having quite the time trying to fix it.   

So our console applicaion runs an initialization method before anything else to set up IoC. The following is that method and is what's in production now and works with 9.19:

private void InitializeServiceLocator() {

var container = new Container();
var context = new ServiceConfigurationContext(HostType.Installer, container);

new CommerceInitialization().ConfigureContainer(context);
new EventsInitialization().ConfigureContainer(context);

container.Configure(ce => ce.For<IRequestCache>().Use<NoRequestCache>());
container.Configure(ce => ce.For<IDatabaseMode>().Use<ReadWriteDatabaseMode>());
container.Configure(ce => ce.For<IPriceService>().Use<PriceServiceDatabase>());
container.Configure(ce => ce.For<IPriceDetailService>().Use<PriceDetailDatabase>());
container.Configure(ce => ce.For<IObjectInstanceCache>().Use<HttpRuntimeCache>());
container.Configure(ce => ce.For<ISynchronizedObjectInstanceCache>().Use<RemoteCacheSynchronization>());
container.Configure(ce => ce.For<IMarketService>().Singleton().Use<MarketServiceDatabase>());
container.Configure(ce => ce.For<IWarehouseRepository>().Singleton().Use<WarehouseRepositoryDatabase>());
container.Configure(ce => ce.For<IInventoryService>().Singleton().Use<InventoryServiceProvider>());
container.Configure(ce => ce.For<IApplicationContext>().Singleton().Use<ApplicationContext>());
container.Configure(ce => ce.For<CatalogConfiguration>().Use(CatalogConfiguration.Instance));
container.Configure(ce => ce.For<IRequiredMetaFieldCollection>().Singleton().Use<DefaultRequiredMetaFields>());
container.Configure(ce => ce.For<MetaDataContext>().Singleton().Use(() => CatalogContext.MetaDataContext));
container.Configure(ce => ce.For<FrameworkContext>().Use(() => FrameworkContext.Current));
container.Configure(ce => ce.For<CatalogConfiguration>().Use(CatalogConfiguration.Instance));
container.Configure(ce => ce.For<SqlContext>().Use(() => new SqlContext(BusinessFoundationConfigurationSection.Instance.Connection.Database)));
container.Configure(ce => ce.For<IChangeNotificationQueueFactory>().Singleton().Use<CommerceChangeQueueFactory>());
container.Configure(ce => ce.For<IChangeNotificationManager>().Singleton().Use<ChangeNotificationManager>());
container.Configure(ce => ce.For<ICatalogSystem>().Singleton().Use(() => CatalogContext.Current));

var locator = new StructureMapServiceLocator(container);
ServiceLocator.SetLocator(locator);

var requestCache = ServiceLocator.Current.GetInstance<NoRequestCache>();
var readWriteDatabaseMode = ServiceLocator.Current.GetInstance<ReadWriteDatabaseMode>();

var factory = new SqlDatabaseFactory(requestCache, readWriteDatabaseMode);
container.Configure(ce => ce.For<IDatabaseHandler>().Use(factory.CreateDefaultHandler()));

DataInitialization.InitializeFromCode(container, factory, CacheProvider.Instance);
DynamicDataStoreFactory.Instance = new EPiServerDynamicDataStoreFactory();

}

I think the issue is the constructor for ServiceConfigurationContext.  The way i'm using it above is now marked obsolete.  Here is how i'm trying to initialize it now, which does not work. 

var container = new Container();
var structurMapConfig = new StructureMapConfiguration(container);
var context = new ServiceConfigurationContext(HostType.Installer, structurMapConfig);

The following line of code now thows an IoC exception (Activation error occurred while trying to get instance of type ICatalogSystem, key ""):

ServiceLocator.Current.GetInstance<ICatalogSystem>();

I know this is long, thanks in advance.

#172507
Edited, Dec 05, 2016 16:28
Vote:
 

The exception you get from trying to locate ICatalogSystem might tell you more about what service it is missing. The problem with not running the initialization system is that you will probably have to manually register a lot of services, including services from CMS. And whenever something changes you have to go through the process of figuring out what changed again, and manually registering the service. Not to mention problems you could get with the wrong scope of the instances etc, which isn't as visible. This is the reason why we run the entire initialization system in our test setup, and even then we have to do some adjustments to things that work differently out of web context.

#172511
Dec 05, 2016 20:07
Vote:
 

Perhaps try a different approach - calling...

InitializationModule.FrameworkInitialization(HostType.Installer);

... in your console app will execute the initialization chain. That way, you don't have to manually create CommerceInitialization and EventsInitialization.

In a episerver 9 console app, I do:

public static void Main(string[] args)
{
    GenericHostingEnvironment.Instance = new EPiServerHostingEnvironment();
    InitializationModule.FrameworkInitialization(HostType.Installer);
    Console.WriteLine("EPiServer initialized");

    var container = StructureMapContainerResolver.GetContainer();
    container.GetInstance<IApplication>().Run(); // custom logic here

    InitializationModule.FrameworkUninitialize();
    Console.WriteLine("EPiServer uninitialized");
    Console.ReadLine();
}

Let me know if this seems interesting. EPiServerHostingEnvironment and StructureMapContainerResolver are custom implementations in order to get episerver rocking as a console app.

#172554
Dec 06, 2016 18:50
Vote:
 

Hi, when I'm trying to use this initialization for EpiServer Commerce 10 I'm getting this error:

EPiServer.Framework.Initialization.InitializationException: Initialize action failed for Initialize on class EPiServer.Framework.Initialization.NET45FrameworkInitialization, EPiServer.Framework, Version=10.10.0.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: virtualPath
   at System.Web.VirtualPath.Create(String virtualPath, VirtualPathOptions options)
   at System.Web.VirtualPathUtility.ToAbsolute(String virtualPath, String applicationPath)
   at EPiServer.Web.Internal.VirtualPathUtilityVirtualPathResolver.ToAbsolute(String virtualPath)
   at EPiServer.Web.Hosting.VirtualPathNonUnifiedProvider..ctor(String providerName, NameValueCollection configParameters, IVirtualPathResolver virtualPathResolver)
   at EPiServer.Web.Hosting.VirtualPathNonUnifiedProvider..ctor(String providerName, NameValueCollection configParameters)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at EPiServer.Web.Hosting.VirtualPathRegistrationHandler.CreateVirtualPathProviderInstance(ProviderSettings providerSettings)
   at EPiServer.Web.Hosting.VirtualPathRegistrationHandler.RegisterVirtualPathProvidersFromSettings(ProviderSettingsCollection settings)
   at EPiServer.Web.Hosting.VirtualPathRegistrationHandler.RegisterVirtualPathProviders(InitializationEngine context, ProviderSettingsCollection settings)
   at EPiServer.Framework.Initialization.NET45FrameworkInitialization.RegisterVirtualPathProviders(InitializationEngine context)
   at EPiServer.Framework.Initialization.NET45FrameworkInitialization.Initialize(InitializationEngine context)
   at EPiServer.Framework.Initialization.Internal.ModuleNode.<>c__DisplayClass2_0.<Initialize>b__0()
   at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
   at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
   at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
   --- End of inner exception stack trace ---
   at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
   at EPiServer.Framework.Initialization.InitializationEngine.ExecuteTransition(Boolean continueTransitions)
   at EPiServer.Framework.Initialization.InitializationEngine.Initialize()
   at EPiServer.Framework.Initialization.InitializationModule.<>c.<FrameworkInitialization>b__7_0(InitializationEngine e)
   at EPiServer.Framework.Initialization.InitializationModule.EngineExecute(HostType hostType, Action`1 engineAction)
   at EPiServer.Framework.Initialization.InitializationModule.FrameworkInitialization(HostType hostType)

Any clue what is missing?

#180618
Jul 17, 2017 16:41
Vote:
 

@Pawel, I don't know what you're missing. However, I wanted to try running Epi CMS and Commerce as a console application so I created a project and got it running. You can see the code on my GitHub repo, the project Episerver.Console.Client specifically. You can use it however you want.

I would assume that something in your config is wrong or maybe you're not using a correct hosting environment.

#180623
Jul 18, 2017 10:53
Vote:
 

You can use HostType.Service (or even WebApplication), but you need to hack around with the Context and providing virtualPath and physicalPath. Andreas is using HostType.Installer, which might have limited API access/functionality though (Don't know havent't looked at what installer does :)).

#180630
Jul 18, 2017 14:59
Vote:
 

@Andreas your solution has helped me a lot so thank you very much!

#180640
Edited, Jul 19, 2017 10:17
Vote:
 

Personally I've found the API's provided in Episerver Commerce to be lacking. I'm sure the Episerver guys will not agree with this, but if your team is good with SQL, you may want to consider going directly to the database.

Even if you don't plan on doing this, a basic understanding of the database will be helpful.

Just to give you a 5 second college course on it, the table dbo.CatalogEntry stored products. When classtypeId = 'Variation', you are working with part numbers. When classtypeId = 'Product' you're working with part numbers.

Categories are stored in the catalogNode table.

To gain access to properties for these objects, look at the table CatalogContentProperty. 

Episerver stores all properties in two tables, one is ecfVersionProperty, which is used for versioning, and is the only table used in the back end, while CatalogContentProperty is the table used in the front end.

Again, going directly to the database isn't the ideal scenerio, but I've found Episerver Commerce's API's to be difficult to work with, and I go against the database for all of my console applications.

-Paul

#180655
Edited, Jul 19, 2017 23:05
Vote:
 

@Paul thanks for the tip by we aready are using a lot of custom SQL stored procedures whenever there are performance issues or lacking functionality ;)

#180661
Jul 20, 2017 8:36
Vote:
 

As always - the official response is we advise against such direct database manipulation, because the schema can be changed without notices in future release. You can write SPs to query data, but that still needs careful considerations. Of course there is nothing preventing you from doing such things, technically, but if something is changed and results in your application stops working, or even worse, loss or corrupted data, you're on your own.

The better option is to let us know if you have performance issues, or if you need some functionalities. We can't address all of them, but we will make sure to look into critical/urgent issues that hurt your business.

#180662
Edited, Jul 20, 2017 8:56