<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Vu Ha Bui</title><link href="http://world.optimizely.com" /><updated>2021-06-10T07:58:58.0000000Z</updated><id>https://world.optimizely.com/blogs/Vu-Ha-Bui/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>DnD support for PropertyList&lt;T&gt;</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2021/6/dnd-support-for-propertylistt/" /><id>&lt;p&gt;Hi all,&lt;/p&gt;
&lt;p&gt;As you might know then PropertyList&amp;lt;T&amp;gt; is very powerfull. It is help us to easy to manage and organize a list of complex property data but the limitation as I am facing with is that it is not official support sorting via drag and drop (or atleast I don&#39;t know how to turn it on - although take hour to googling).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/link/b1b1ac7b1a3746b58bb9e64cb52e8bfb.aspx&quot;&gt;Drag and drop not working on product | Episerver Developer Commun&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then I digged a bit with javascript solution as below then its worked: (I am assumming that you already know about how to create your module via module.config, create your custom editor and map it to your property list property)&lt;/p&gt;
&lt;p&gt;So here is our code: &lt;strong&gt;SortableCollectionEditor&lt;/strong&gt;.js&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;define([
    // dojo
    &quot;dojo/_base/array&quot;,
    &quot;dojo/_base/declare&quot;,
    &quot;dojo/_base/lang&quot;,
    &quot;dojo/aspect&quot;,
    &quot;dojo/dom-class&quot;,
    &quot;dojo/dom-style&quot;,
    &quot;dojo/topic&quot;,

    // EPi Framework
    &quot;epi/shell/widget/_FocusableMixin&quot;,
    &quot;epi/shell/dnd/Target&quot;,


    // epi cms
    &quot;epi-cms/contentediting/editors/CollectionEditor&quot;,
    &quot;epi-cms/contentediting/editors/_TextWithActionLinksMixin&quot;,

    // epi commerce
    &quot;epi-ecf-ui/contentediting/editors/_GridWithDropContainerMixin&quot;
],
    function (
        //dojo
        array,
        declare,
        lang,
        aspect,
        domClass,
        domStyle,
        topic,

        // EPi Framework
        _FocusableMixin,
        Target,

        // epi cms
        CollectionEditor,
        _TextWithActionLinksMixin,

        // epi commerce
        _GridWithDropContainerMixin,
    ) {
        return declare([CollectionEditor, _GridWithDropContainerMixin, _FocusableMixin], {
            // module:
            //      app/editors/sortablecollectioneditor
            // summary:
            //      Editor widget for support propertylist dnd 

            resources: { &quot;drophere&quot;: &quot;You can drop {content} here&quot;, &quot;actions&quot;: { &quot;content&quot;: &quot;content&quot; } },

            postMixInProperties: function () {
                // summary:
                //      Post mix in properties initialization.
                // description:
                //      Fullfills properties that needed to start up the widget.
                // tags:
                //      protected

                this.inherited(arguments);

                this.gridSettings.dndParams = lang.mixin(this.gridSettings.dndParams, {
                    _checkAcceptanceForItems: function (items, acceptedTypes) {
                        var types = array.map(items, function (item) {
                            return item.data.typeIdentifier;
                        });

                        var flag = false;
                        for (var j = 0; j &amp;lt; types.length; ++j) {
                            if (types[j] in acceptedTypes) {
                                flag = !!acceptedTypes[types[j]];
                                break;
                            }
                        }
                        return flag;
                    }
                });
            }
        });
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hope that help!&lt;/p&gt;
&lt;p&gt;Ha Bui&lt;/p&gt;</id><updated>2021-06-10T07:58:58.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Another Fun and Profit with IMemory caching</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2020/5/another-fun-and-profit-with-imemory-caching/" /><id>&lt;p&gt;Hi all,&lt;/p&gt;
&lt;p&gt;As you might know Quan Mai already had a great post:&amp;nbsp;&lt;a href=&quot;/link/ccbaba8537dc44c9816b48df2f6684d9.aspx&quot;&gt;https://world.episerver.com/blogs/Quan-Mai/Dates/2019/12/use-memorycache-in-your-project---for-fun-and-profit/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Today I want to introduce other way base on&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Microsoft.Extensions.Caching.Memory&lt;/h1&gt;
&lt;p&gt;Some references for you:&lt;/p&gt;
&lt;p&gt;1.&amp;nbsp;&lt;a href=&quot;https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/&quot;&gt;https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2.&amp;nbsp;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycache?view=dotnet-plat-ext-3.1&quot;&gt;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycache?view=dotnet-plat-ext-3.1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you make a search with &quot;IMemory&quot; then quite a lot of tutorial but almost is in .Net Core but doesn&#39;t blocking us to apply on .NET and EPiServer as well!&lt;/p&gt;
&lt;p&gt;Okay, lets go step by step!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create your interceptor class:&amp;nbsp;&lt;strong&gt;MemoryCacheInterceptor&lt;/strong&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Intercep via ConfigurationModule and StructureMap interceptors pattern:&amp;nbsp;&lt;strong&gt;MemoryCacheConfigurationModule&lt;/strong&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(FrameworkInitialization))]
public class MemoryCacheConfigurationModule : IConfigurableModule, IInitializableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.ConfigurationComplete += (o, e) =&amp;gt;
        {
            e.Services.Intercept&amp;lt;IObjectInstanceCache&amp;gt;((locator, httpRuntimeCache) =&amp;gt;
            {
                return new MemoryCacheInterceptor();
            });
        };
    }

    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}

/// &amp;lt;summary&amp;gt;
/// Implement IObjectInstanceCache with IMemoryCache
/// &amp;lt;see cref=&quot;Microsoft.Extensions.Caching.Memory.IMemoryCache&quot;/&amp;gt;
/// &amp;lt;/summary&amp;gt;
public class MemoryCacheInterceptor : IObjectInstanceCache, IDisposable
{
    // Interesting things will come here
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our main focus will be &lt;strong&gt;MemoryCacheInterceptor &lt;/strong&gt;class, as you see its inherited from&amp;nbsp;&lt;strong&gt;IObjectInstanceCache&amp;nbsp;&lt;/strong&gt;and &lt;strong&gt;IDisposable&lt;/strong&gt;&amp;nbsp;then we should implement those methods below:&lt;/p&gt;
&lt;p&gt;0. &lt;strong&gt;Properties&lt;/strong&gt; and&amp;nbsp;&lt;strong&gt;Constructor&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private static readonly ILog _log = LogManager.GetLogger(typeof(MemoryCacheInterceptor));

private readonly IMemoryCache _memoryCache;
private readonly object _dependencyObject;
private CancellationTokenSource _rootTokenSource;

private readonly ConcurrentDictionary&amp;lt;string, CancellationTokenSource&amp;gt; _tokenSources;

public MemoryCacheInterceptor()
{
    _memoryCache = new MemoryCache(new MemoryCacheOptions());
    _dependencyObject = new object();
    _rootTokenSource = new CancellationTokenSource();
    _tokenSources = new ConcurrentDictionary&amp;lt;string, CancellationTokenSource&amp;gt;();
    _log.Info(&quot;Started NitecoMemeoryCacheInterceptor&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you see:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_memoryCache&amp;nbsp;&lt;/strong&gt;: Create new instance of &lt;strong&gt;MemoryCache&amp;nbsp;&lt;/strong&gt;with posibility of customize option on&amp;nbsp;&lt;strong&gt;MemoryCacheOptions&amp;nbsp;&lt;/strong&gt;like how much memory will be used ... See more in:&amp;nbsp;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions?view=dotnet-plat-ext-3.1&quot;&gt;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions?view=dotnet-plat-ext-3.1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_dependencyObject&amp;nbsp;&lt;/strong&gt;: Dummy object cache of all master keys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_rootTokenSource&amp;nbsp;&lt;/strong&gt;that is &lt;strong&gt;IMemory&lt;/strong&gt; techinique based on&amp;nbsp;&lt;strong&gt;CancellationToken&amp;nbsp;&lt;/strong&gt;that will help us to&amp;nbsp;&lt;span&gt;to invalidate a set of cache all in one go. You will see relations betweeen a &lt;strong&gt;CacheEntry&lt;/strong&gt; and the token later on &lt;strong&gt;Insert&lt;/strong&gt; method!&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_tokenSources&amp;nbsp;&lt;/strong&gt;: Tokens provider for each cache key&lt;/p&gt;
&lt;p&gt;1.&amp;nbsp;public void &lt;strong&gt;Clear&lt;/strong&gt;()&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Clear()
{
    if (_rootTokenSource != null &amp;amp;&amp;amp; !_rootTokenSource.IsCancellationRequested &amp;amp;&amp;amp; _rootTokenSource.Token.CanBeCanceled)
    {
        _rootTokenSource.Cancel();
        _rootTokenSource.Dispose();
    }

    _rootTokenSource = new CancellationTokenSource();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.&amp;nbsp;public object &lt;strong&gt;Get&lt;/strong&gt;(string key)&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public object Get(string key)
{
    return _memoryCache.Get(key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.&amp;nbsp;public void &lt;strong&gt;Remove&lt;/strong&gt;(string key)&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Remove(string key)
{
    _memoryCache.Remove(key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.&amp;nbsp;public void &lt;strong&gt;Dispose&lt;/strong&gt;()&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Dispose()
{
    _memoryCache.Dispose();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5.&amp;nbsp;public void &lt;strong&gt;Insert&lt;/strong&gt;(string key, object value, CacheEvictionPolicy evictionPolicy)&lt;/p&gt;
&lt;p&gt;That is main part and we should take a coffee then focusing on ;)&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Insert(string key, object value, CacheEvictionPolicy evictionPolicy)
{
    if (evictionPolicy == null)
    {
        // depend on root token only
        _memoryCache.Set(key, value, new CancellationChangeToken(_rootTokenSource.Token));
        return;
    }

    // Try setup dummy master object cache.
    EnsureMasterKeys(evictionPolicy.MasterKeys);

    using (var cacheEntry = _memoryCache.CreateEntry(key))
    {
        // Propagate tokens to current cache entry. 
        AddDependencyTokens(evictionPolicy);

        var cacheEntryOption = GetCachEntryOption(key, evictionPolicy);
        cacheEntry.SetOptions(cacheEntryOption.AddExpirationToken(new CancellationChangeToken(_rootTokenSource.Token)));
        cacheEntry.SetValue(value);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay are you ready?&lt;/p&gt;
&lt;p&gt;Simple part in the function is: Check null of &lt;strong&gt;evictionPolicy&lt;/strong&gt; then insert to memory but remember to tight this cache key on our root token source to eviction all cache on &lt;strong&gt;Clear&lt;/strong&gt; method! Done!&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;if (evictionPolicy == null)
{
    // depend on root token only
    _memoryCache.Set(key, value, new CancellationChangeToken(_rootTokenSource.Token));
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we ensure master keys are ready to use (have cached object) with&amp;nbsp;&lt;strong&gt;EnsureMasterKeys&lt;/strong&gt;(evictionPolicy.MasterKeys) method:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private void EnsureMasterKeys(string[] masterKeys)
{
    if (masterKeys != null)
    {
        foreach (string key in masterKeys)
        {
            object cached;
            if (!_memoryCache.TryGetValue(key, out cached))
            {
                var token = _tokenSources.GetOrAdd(key, new CancellationTokenSource());
                _memoryCache.Set(key, _dependencyObject,
                    new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(DateTimeOffset.MaxValue)
                    .SetPriority(CacheItemPriority.NeverRemove)
                    .AddExpirationToken(new CancellationChangeToken(_rootTokenSource.Token))
                    .AddExpirationToken(new CancellationChangeToken(token.Token))
                    .RegisterPostEvictionCallback(PostEvictionCallback));
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our idea here is: Loop through master kesy, if it&#39;s not existed then insert with an absoluted maximum time and highest cache priority so in theory it will never been automactically removed by&amp;nbsp;Garbage Collection (GC). Then create&amp;nbsp; token source for it, add the token and root token source together to the cache entry option.&lt;/p&gt;
&lt;p&gt;Oh but &lt;strong&gt;what&lt;/strong&gt; and &lt;strong&gt;why&lt;/strong&gt; do we need &lt;strong&gt;PostEvictionCallback&amp;nbsp;&lt;/strong&gt;? and &lt;strong&gt;when&lt;/strong&gt; it will be trigerred?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What &lt;/strong&gt;and &lt;strong&gt;When&lt;/strong&gt;:&lt;span&gt;The given callback will be fired after the cache entry is evicted from the cache.&amp;nbsp;&lt;/span&gt; &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.cacheentryextensions.registerpostevictioncallback?view=dotnet-plat-ext-3.1&quot;&gt;Read more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why&amp;nbsp;&lt;/strong&gt;: Whenever the master cache is evicted then we should remove all children depend on the master token!&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private void PostEvictionCallback(object key, object value, EvictionReason evictionReason, object state)
{
    CancellationTokenSource token;
    if (_tokenSources.TryRemove((string)key, out token)
        &amp;amp;&amp;amp; token != null &amp;amp;&amp;amp; !token.IsCancellationRequested &amp;amp;&amp;amp; token.Token.CanBeCanceled)
    {
        token.Cancel();
        token.Dispose();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last part: Create new cache entry, add dependency tokens and of course with root token as well and then set to memory!&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using (var cacheEntry = _memoryCache.CreateEntry(key))
{
    // Propagate tokens to current cache entry. 
    AddDependencyTokens(evictionPolicy);

    var cacheEntryOption = GetCachEntryOption(key, evictionPolicy);
    cacheEntry.SetOptions(cacheEntryOption.AddExpirationToken(new CancellationChangeToken(_rootTokenSource.Token)));
    cacheEntry.SetValue(value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The magic part&amp;nbsp;&lt;strong&gt;AddDependencyTokens&lt;/strong&gt;(evictionPolicy)&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private void AddDependencyTokens(CacheEvictionPolicy evictionPolicy)
{
    var dependencies = evictionPolicy.CacheKeys;
    if (dependencies == null)
    {
        dependencies = evictionPolicy.MasterKeys;
    }
    else if (evictionPolicy.MasterKeys != null)
    {
        dependencies = dependencies.Concat(evictionPolicy.MasterKeys).ToArray();
    }

    if (dependencies == null) return;

    foreach (var k in dependencies)
    {
        object v;
        _memoryCache.TryGetValue(k, out v);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hmm, quite strange right? Firstly, combination cache keys with master keys (we&#39;re considering cache keys and master keys are dependencies) and then just do: &lt;strong&gt;TryGetValue&amp;nbsp;&lt;/strong&gt;? Is that enough? Yes, because of when you use:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using (var cacheEntry = _memoryCache.CreateEntry(key))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then you put this entry on the top of scope, see more:&amp;nbsp; &lt;strong&gt;Microsoft.Extensions.Caching.Memory.CacheEntry.CacheEntry&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;internal CacheEntry(object key, Action&amp;lt;CacheEntry&amp;gt; notifyCacheEntryDisposed, Action&amp;lt;CacheEntry&amp;gt; notifyCacheOfExpiration)
{
	...
	_scope = CacheEntryHelper.EnterScope(this);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&amp;nbsp; &lt;strong&gt;Microsoft.Extensions.Caching.Memory.MemoryCache.TryGetValue&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public bool TryGetValue(object key, out object result)
{
    ...
    value.PropagateOptions(CacheEntryHelper.Current);
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next is simple but important function &lt;strong&gt;GetCachEntryOption&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private MemoryCacheEntryOptions GetCachEntryOption(string key, CacheEvictionPolicy evictionPolicy)
{
    var cacheEntryOption = new MemoryCacheEntryOptions();

    switch (evictionPolicy.TimeoutType)
    {
        case CacheTimeoutType.Undefined:
            break;
        case CacheTimeoutType.Sliding:
            cacheEntryOption = cacheEntryOption.SetSlidingExpiration(evictionPolicy.Expiration);
            break;
        case CacheTimeoutType.Absolute:
            cacheEntryOption = cacheEntryOption.SetAbsoluteExpiration(evictionPolicy.Expiration);
            break;
    }

    var tokenSource = _tokenSources.GetOrAdd(key, new CancellationTokenSource());
    return cacheEntryOption
            .AddExpirationToken(new CancellationChangeToken(tokenSource.Token))
            .RegisterPostEvictionCallback(PostEvictionCallback);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once againe you see &lt;strong&gt;PostEvictionCallback &lt;/strong&gt;here with the same logic: When cache is evicted then we will cancel the token so that it will evict all others dependent cache entries!&lt;/p&gt;
&lt;p&gt;Viola! Happy Coding!&lt;/p&gt;</id><updated>2020-05-07T20:16:28.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Export content with versions</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2020/1/export-content-with-versions/" /><id>&lt;p&gt;Hi guys,&lt;/p&gt;
&lt;p&gt;As you knows, currently, EPiServer just supports latest published version in languages when you uses the default admin tool (Export / Import Data).&lt;/p&gt;
&lt;p&gt;Of course in most case, this is enough but sometimes you need to export / import with versions as well. (at least in case editor created a common draft and doesn&#39;t want to rework)&lt;/p&gt;
&lt;p&gt;How can we archive that? The solution (of-course by default EPiServer doesn&#39;t support it then you also should accepts some tips and tricks in code). No worry so much because of we still base on EPiServer default one quite a lot)&lt;/p&gt;
&lt;p&gt;Below is our solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use structure map interceptor to apply our tips and tricks via InitializeModule&lt;/li&gt;
&lt;li&gt;Intercep on&amp;nbsp;&lt;strong&gt;DefaultDataExporter&lt;/strong&gt; to export contents with versions (instead of only latest published one)&lt;/li&gt;
&lt;li&gt;Intercep on&amp;nbsp;&lt;strong&gt;DefaultContentImporter&amp;nbsp;&lt;/strong&gt;to import content version&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some small things should be considered like:&lt;/p&gt;
&lt;p&gt;+ Keep saved date&lt;/p&gt;
&lt;p&gt;+ Keep saved by (require user migrating)&lt;/p&gt;
&lt;p&gt;Okay, lets start steps by steps:&lt;/p&gt;
&lt;h3&gt;1. Use structure map interceptor to apply our tips and tricks via InitializeModule&lt;/h3&gt;
&lt;p&gt;Your code should looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class DataExporterInitializationModule : IConfigurableModule, IInitializableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
         context.ConfigurationComplete += (o, e) =&amp;gt;
         {
             // Your interceptor logic here
             e.Services.Intercept&amp;lt;IDataExporter&amp;gt;((locator, defaultDataExporter) =&amp;gt;
             ...);

             e.Services.Intercept&amp;lt;IContentImporter&amp;gt;((locator, defaultDataImporter) =&amp;gt;
             ...);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Intercep on&amp;nbsp;&lt;strong&gt;DefaultDataExporter&lt;/strong&gt; to export contents with versions (instead of only latest published one)&lt;/h3&gt;
&lt;p&gt;We will override &lt;strong&gt;&lt;em&gt;ExportContent&lt;/em&gt; &lt;/strong&gt;method, check our new option (ExportVersion) or just keep default one:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;protected override void ExportContent(XmlTextWriter xml
    , ContentReference contentToExport
    , IContentTransferContext context
    , ITransferContentData transferContent) {
	if (_transferExportOptionsEx.ExportVersion) {
		ExportContentWithVerion(xml, contentToExport, context, transferContent, base.ExportContent);
	}
	else {
		base.ExportContent(xml, contentToExport, context, transferContent);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then get all versions&amp;nbsp; and build raw transfer content data via:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;protected virtual TransferContentData BuildRawTransferContent(IContentTransferContext context
            , List&amp;lt;ContentLanguageSetting&amp;gt; contentLanguageSettings
            , IRawContentRetrieverEx rawContentRetieverEx, IContent version)
        {
            var transferVersionContent = new TransferContentData()
            {
                RawContentData = rawContentRetieverEx.CreateRawContent(version)
            };
            if (contentLanguageSettings != null)
                transferVersionContent.ContentLanguageSettings = contentLanguageSettings;

            PropertyExportContext propertyExportContext = new PropertyExportContext
            {
                TransferContext = context,
                TransferOptions = Options,
                Output = transferVersionContent.RawContentData,
                Source = version
            };
            _propertyExporter.ExportProperties(version
                , transferVersionContent.RawContentData.Property
                , propertyExportContext);

            return transferVersionContent;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Viola! You have just done a big task! Export Content With Version task!&lt;/p&gt;
&lt;p&gt;The last one is import the exported content versions (keep version status, keep saved date and saved by)! Take a coffee and relax before we go to the rest!&lt;/p&gt;
&lt;p&gt;... (coffee break)&lt;/p&gt;
&lt;h3&gt;3. Intercep on&amp;nbsp;&lt;strong&gt;DefaultContentImporter&amp;nbsp;&lt;/strong&gt;to import content version&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;This one is hard part and get much of your pains :( Remember do testings carefully. No pains no gains right :))&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We should override IContent Import method (&lt;strong&gt;protected override IContent Import&lt;/strong&gt;) with some tricks as below:&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;3.1 Tricks to keep language content version: (because of our exported versions are mixing with multiple languages and those versions are flatten)&lt;/h4&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;CultureInfo originalSelectedLanguage = null;
            if (importedContentData.SelectedLanguage != null)
                originalSelectedLanguage = CultureInfo.GetCultureInfo(importedContentData.SelectedLanguage.Name);

            var selectedLanguageAction = importedContentData.GetType().GetProperty(&quot;SelectedLanguage&quot;);
            var contentLanguage = importedContentData.GetLanguageBranch();
            if (!string.IsNullOrEmpty(contentLanguage))
            {
                selectedLanguageAction.SetValue(importedContentData, CultureInfo.GetCultureInfo(contentLanguage));
            }&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.2 Tricks to keep content versions status (because of EPiServer just &quot;accept&quot; publish action OMG, other actions just for new one):&lt;/h4&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var status = importedContentData.GetStatus();
            var propSaveAction = context.GetType().GetProperty(&quot;SaveAction&quot;);
            SaveAction originalSaveActions = SaveAction.Publish | SaveAction.SkipValidation;
            if (!string.IsNullOrEmpty(status) &amp;amp;&amp;amp; int.Parse(status) &amp;lt; (int)VersionStatus.Published)
            {
                propSaveAction.SetValue(context, SaveAction.CheckOut | SaveAction.ForceNewVersion | SaveAction.SkipValidation);
            }&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.3 Small things to keep saved date and saved by&lt;/h4&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContextCache.Current[&quot;PageSaveDB:ChangedBy&quot;] = string.Empty;
ContextCache.Current[&quot;PageSaveDB:PageSaved&quot;] = string.Empty;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, last one is: call to base method:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;base.Import(importedContentData
                    , requiredDestinationAccess
                    , context, options, out importedPageGuid);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is all ? Not yet,&amp;nbsp;something you should deal with:&lt;/p&gt;
&lt;p&gt;+ In your interceptor class of&amp;nbsp;DefaultContentImporter, please consider to save original data and then set it back right after content version is imported to avoid any impact.&lt;/p&gt;
&lt;p&gt;+ EPiServer DefaultContentImporter only save your version if it&#39;s the new one :(&lt;/p&gt;
&lt;p&gt;We can resolve those things like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;try
            {
                // check the current content version is new or not
                var importedContentGuid = new Guid(importedContentData.GetContentGuid());
                var handleContentGuidMethod = defaultContentImporter.GetType()
                    .GetMethod(&quot;HandleContentGuid&quot;, BindingFlags.NonPublic | BindingFlags.Instance);
                var guid = (Guid)handleContentGuidMethod.Invoke(
                    defaultContentImporter
                    , new object[] { importedContentGuid, context });
                PermanentLinkMap permanentLinkMap = permanentLinkMapper.Find(guid);

                var baseContent = base.Import(importedContentData
                    , requiredDestinationAccess
                    , context, options, out importedPageGuid);

                if (permanentLinkMap != null &amp;amp;&amp;amp; (context.SaveAction &amp;amp; SaveAction.Publish) != SaveAction.Publish)
                    contentRepository.Save(baseContent, context.SaveAction, requiredDestinationAccess);

                return baseContent;
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                throw;
            }
            finally
            {
                selectedLanguageAction.SetValue(importedContentData, originalSelectedLanguage);
                propSaveAction.SetValue(context, originalSaveActions);
                ContextCache.Current[&quot;PageSaveDB:ChangedBy&quot;] = orgPageSaveDBChangeBy;
                ContextCache.Current[&quot;PageSaveDB:PageSaved&quot;] = orgPageSaveDBPageSaved;
            }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulations! Now please try it by your self with AlloyMvc template! Below is my result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/9d0df7c3362445cbb237e503472dc0d9.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Full source code can be found under:&amp;nbsp;&lt;a href=&quot;https://github.com/NitecoOPS/ExportImportWithVersion&quot;&gt;https://github.com/NitecoOPS/ExportImportWithVersion&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hope that the article will help to reduce your headache! ;)&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;
&lt;p&gt;.HaBui&lt;/p&gt;</id><updated>2020-01-21T15:20:21.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Get rid of EPiServer Commerce Migrate redirect loop</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2019/9/get-rid-episerver-commerce-migrate-redirect-loop/" /><id>&lt;p&gt;Phew! Take 2 hours to get rids of the infinitive loop from EPiServer ECommerce after upgrade from 9.2 to 12.17!&lt;/p&gt;
&lt;p&gt;You can Google search with: &lt;em&gt;episerver commerce migration loop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then some guys below appear:&lt;/p&gt;
&lt;p&gt;1.&amp;nbsp;&lt;a href=&quot;/link/71876581bd08425998854838c93ae25c.aspx&quot;&gt;https://world.episerver.com/forum/developer-forum/Episerver-Commerce/Thread-Container/2019/5/passing-thru-episervercommercemigrate-when-created-new-site-in-azure/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2.&amp;nbsp;&lt;a href=&quot;/link/13ffd45f454440e9a2c97c9db0a06f27.aspx&quot;&gt;https://world.episerver.com/forum/developer-forum/Episerver-Commerce/Thread-Container/2016/10/too-many-redirects-error-in-the-browser-after-package-update/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some good instructions but does not work for my case: MigrateRedirect -&amp;gt; &amp;lt;RequireLogin&amp;gt; -&amp;gt; Owin -&amp;gt; MigrateRedirect -&amp;gt; &amp;lt;RequireLogin&amp;gt; -&amp;gt; ...&lt;/p&gt;
&lt;p&gt;You can see more in those class:&lt;/p&gt;
&lt;p&gt;+&amp;nbsp; &lt;span&gt;EPiServer.Commerce.Internal.Migration.MigrationInitializationModule&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;+&amp;nbsp; &lt;span&gt;EPiServer.Commerce.Internal.Migration.MigrationManager&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Okay, so how can we resolve this? Some angeles below will help:&lt;/p&gt;
&lt;p&gt;1.&amp;nbsp; &lt;span&gt;// EPiServer.Commerce.Internal.Migration.MigrationManager&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;public virtual void MigrateAsync()&lt;/p&gt;
&lt;p&gt;2. IoC with StructureMap and Interceptors holy light&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Commerce.Initialization.InitializationModule))]
[ModuleDependency(typeof(CmsCoreInitialization))]
internal class ContainerInitialization : IConfigurableModule, IInitializableModule&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void ConfigureContainer(ServiceConfigurationContext context)
{
    ...
    context.ConfigurationComplete += (o, e) =&amp;gt;
            {
                e.Services.Intercept&amp;lt;MigrationManager&amp;gt;((locator, defaultMigrationManager) =&amp;gt;
                    new MigrationManagerInterceptor(defaultMigrationManager
                            , locator.GetInstance&amp;lt;MigrationStore&amp;gt;()
                            , locator.GetInstance&amp;lt;MigrateActionUrlResolver&amp;gt;()));
            };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and last one:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class MigrationManagerInterceptor : MigrationManager
    {
        private MigrationManager _defaultMigrationManager;

        public MigrationManagerInterceptor(
            MigrationManager defaultMigrationManager
            , MigrationStore migrationStore
            , MigrateActionUrlResolver migrateActionUrlResolver)
            : base(migrationStore, migrateActionUrlResolver)
        {
            _defaultMigrationManager = defaultMigrationManager;
        }

        public override void RedirectToMigrationView()
        {
            MigrateAsync();
            return;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good bye redirect loop! Cheer!&lt;/p&gt;
&lt;p&gt;// Ha Bui&lt;/p&gt;</id><updated>2019-10-01T00:02:48.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>EPiServer Addons Development - VisualStudio Extensions</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2019/8/episerver-addons-development---visualstudio-extensions/" /><id>&lt;p&gt;Good day everyone,&lt;/p&gt;
&lt;p&gt;As you know that, split your solution to multiple project and packaged as nuget is common way for now.&lt;/p&gt;
&lt;p&gt;That will help us to isolate, idependent and better for deployment / maintaince. EPiServer Add-ons also follow this and as you see, so many helpfull add-on packages are available for you! (Of course, continue or dis-continue maintained, upgrade version to adapt with many EPiServer version is a long story).&lt;/p&gt;
&lt;p&gt;Today I just focus on : The way (nuget) for developer. How to help developer is easier create their package?&lt;/p&gt;
&lt;p&gt;From this view point I created an visual studio extesion that is packaged my protected add-on template (public is the same with very small change, but I think we should have it in near future!).&lt;/p&gt;
&lt;p&gt;Some screenshots for you:&lt;/p&gt;
&lt;p&gt;Add new add-on project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/4c7d04bdcf0542acb92673b5d715433a.aspx&quot; width=&quot;578&quot; height=&quot;205&quot; /&gt;&lt;/p&gt;
&lt;p&gt;select template and:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/0328ca2b438e449fb06f1a1f5b62733c.aspx&quot; width=&quot;679&quot; height=&quot;458&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Click [Create] and you will see add-on project structure:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/a1a12e2956894ec78784fb61cf92652a.aspx&quot; width=&quot;687&quot; height=&quot;311&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Build and you will see the package in: LocalPackage folder under your solution root folder ($(SolutionDir)LocalPackages) that is my harded code that will help you if your solution added local repository point to this folder! (You can change to another folder as you wish)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/8b445dfa26034e9bbdc956b69858d64a.aspx&quot; width=&quot;777&quot; height=&quot;124&quot; /&gt;&lt;/p&gt;
&lt;p&gt;
&lt;p&gt;Nice? :) (clap clap clap) 0:)&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;Happy Coding! (Source code and extension binary will availabel soon in comments ha ha ha)&lt;/p&gt;
&lt;p&gt;Ha Bui&lt;/p&gt;
&lt;/p&gt;</id><updated>2019-08-28T15:45:59.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>EPiServer Nature Language (wrap up base on Google NLP and Budou)</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2019/8/episerver-nature-language-wrap-up-base-on-google-nlp-and-budou/" /><id>&lt;p&gt;Hi all,&lt;/p&gt;
&lt;p&gt;
&lt;p&gt;If nothing change then tomorrow is Friday (oh my gods, this is weekend!).&lt;/p&gt;
&lt;p&gt;So firstly, I want to say happy weekend to everyone! Later on, I want to share my exprerience about Nature Language Processing ...&lt;/p&gt;
&lt;p&gt;Hey! Don&#39;t think about AI or decesion tree ... No no, just a small C# port for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Budou (&lt;a href=&quot;https://github.com/google/budou&quot;&gt;https://github.com/google/budou&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Google NLP API (&lt;a href=&quot;https://cloud.google.com/natural-language/&quot;&gt;https://cloud.google.com/natural-language/&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And wrap all under DB storage (EF custom table) and of course is great caching layer of EPiServer (&lt;a href=&quot;/link/5341f632537c4b0ab6b8fb651bd310f8.aspx?userid=748045d9-fea9-db11-8952-0018717a8c82&quot;&gt;Johan Bj&amp;ouml;rnfot&lt;/a&gt;:&amp;nbsp;&lt;a href=&quot;/link/507bd0a091f24692aec6f5c8bfbf3f36.aspx&quot;&gt;https://world.episerver.com/blogs/Johan-Bjornfot/Dates1/2018/5/iobjectinstancecache-readthrough/&lt;/a&gt;) (&lt;em&gt;will available soon via nuget package&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;It&#39;s for someone want to have better words wrap for CJK (China, Japanese, Korean) languages on your website.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step 1: Register Google NLP&lt;/li&gt;
&lt;li&gt;Step 2: Export credential&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Step 3: Play with my simple project (cmd) in &lt;a href=&quot;https://github.com/NitecoOPS/BudouCSharp&quot;&gt;https://github.com/NitecoOPS/BudouCSharp&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;P.S: Nuget link will be update soon for you :) (could we have some comments to encorage me?)&lt;/em&gt;&lt;/p&gt;&lt;/p&gt;</id><updated>2019-08-22T19:54:01.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Custom model for LinkItemCollection property</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2018/4/custom-model-for-linkitemcollection-property/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;In many CMS project, we have many requirements that use LinkItemCollection property but with customize model (default is LinkItem).&lt;/p&gt;
&lt;p&gt;Below is the way we do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define property with type &lt;strong&gt;LinkItemColleciton&lt;/strong&gt; in your content type like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/link/cd3b943f1d1e41feb5f8f1377c6ce270.aspx&quot; alt=&quot;Image Step1_Add_Property.JPG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2. Add new EditorDescriptor like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/0b77ab9a231f415cb8149f1ec3b32a66.aspx&quot; alt=&quot;Image Step2_Add_Custom_Editor_Descriptor.JPG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;3. Add new .js file for your custom model like this (match with the path in step 2):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/59bdc6c435b546c6bf53b28ca00061a8.aspx&quot; alt=&quot;Image Step3_Add_Custom_Item_Model.JPG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;3. The last step is &lt;strong&gt;HACK&lt;/strong&gt; way, because of item collection editor need the model above injected from the beginning (@EPiServer guy: should be improved&amp;nbsp;by lazy load !?)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/11df267ba7fd49aaa898937f110316a9.aspx&quot; alt=&quot;Image Last_Step.JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That all!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-04-13T13:09:08.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Duplicate language versions after import</title><link href="https://world.optimizely.com/blogs/Vu-Ha-Bui/Dates/2018/4/duplicate-language-versions-after-import/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;When I work with EPiSerser Commerce&#39;s import catalog I have an issue after import catalog successfully like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/cd95181496944b1fb2f1c0063d2f008f.aspx&quot; alt=&quot;Image issue1.JPG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After 3 hours investigate I found down that is by:&lt;/p&gt;
&lt;p&gt;Default catalog language is:&amp;nbsp;en&lt;strong&gt;-&lt;/strong&gt;gb&amp;nbsp;but the available languages are: en-&lt;strong&gt;GB&lt;/strong&gt;. That because of this EPiSErver function&amp;nbsp;&lt;em&gt;VerifyLanguageSetup&amp;nbsp;&lt;/em&gt;also call&amp;nbsp;&lt;em&gt;Distinct&amp;nbsp;&lt;/em&gt;but without&amp;nbsp;&lt;em&gt;CurrentCultureIgnoreCase.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-04-12T05:20:28.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>