Create a Campaign, Discount programmatically – Episerver Commerce

We had a scenario that customer wants to generate a coupon dynamically and use the coupon as credit for the user. The code as everything else in Episerver is quite straight forward:

The first step is to create a sales campaign:

        private SalesCampaign EnsureCampaignCreated()
        {
            var campaign = contentRepository
                .GetChildren<SalesCampaign>(SalesCampaignFolder.CampaignRoot)?
                .FirstOrDefault(a => a.Name == "Credit");
            
            if (campaign == null)
            {
                campaign = contentRepository
                    .GetDefault<SalesCampaign>(SalesCampaignFolder.CampaignRoot);
                campaign.Name = "Credit";
                campaign.Created = DateTime.UtcNow;
                campaign.IsActive = true;
                campaign.ValidFrom = DateTime.Today;
                campaign.ValidUntil = DateTime.Today.AddYears(1);
                var campaignContentRefrence = contentRepository.Save(campaign, SaveAction.Publish, AccessLevel.NoAccess);

                campaign = contentRepository.Get<SalesCampaign>(campaignContentRefrence);
            }

            return campaign;
        }

As you can see we just make sure the campaign is being created and our key in this stage is ‘Campaign Name’ which may be not good enough for some scenarios but for us is enough. Second I’m loading ALL campaigns and iterate through them to find a specific campaign. Again in our scenario, it will not that much effect but if the commerce you are dealing with has MANY campaigns you can use LoaderOptione as below:

Next step is to create a discount:

        private string CreateDiscountPromotion(ContentReference campaignLink, string orderNumber, Money couponAmount)
        {
            var catalougLink = siteSettingsService.StartPage.DefaultCatalogue;

            var couponCode = $"CREDIT-{Guid.NewGuid().ToString().Replace("-", "").Substring(6)}";

            var promotion = contentRepository.GetDefault<BuyQuantityGetItemDiscount>(campaignLink);
            promotion.IsActive = true;
            promotion.Name = $"CREDIT-{orderNumber}";
            promotion.Coupon.Code = couponCode;
            promotion.Condition.Items = new List<ContentReference>() { catalougLink };
            promotion.Condition.RequiredQuantity = 1;
            promotion.Condition.MatchRecursive = true;
            promotion.DiscountTarget.Items = new List<ContentReference>() { catalougLink };
            promotion.Discount.UseAmounts = true;
            promotion.Discount.Amounts = new List<Money>() { couponAmount };

            contentRepository.Save(promotion, SaveAction.Publish, AccessLevel.NoAccess);

            return couponCode;
        }

As you can see generating a discount is very straight forward. I used ‘BuyQuantityGetItemDiscount’ which fits for my purpose but you can use any out of the box or custom discount you want. Discount which I used ‘BuyQuantityGetItemDiscount’ require to define what catalog item can this discount can be applied. I’m including all current site catalog (I’m getting it from start page which is custom property) but you can use any other method (e.g. iterate through all catalogs and include all catalogs). There is a flag ‘MatchRecursive’ which is set to true, means all descendants of selected items will include in promotion.

So finally we need to call these method full code as bellow:

        private string CreateCreditVoucher(string orderNumber, Money couponAmount)
        {
            var campaign = EnsureCampaignCreated();

            return CreateDiscountPromotion(campaign.ContentLink, orderNumber, couponAmount);
        }

        private SalesCampaign EnsureCampaignCreated()
        {
            var campaign = contentRepository
                .GetChildren<SalesCampaign>(SalesCampaignFolder.CampaignRoot)?
                .FirstOrDefault(a => a.Name == "Credit");
            
            if (campaign == null)
            {
                campaign = contentRepository
                    .GetDefault<SalesCampaign>(SalesCampaignFolder.CampaignRoot);
                campaign.Name = "Credit";
                campaign.Created = DateTime.UtcNow;
                campaign.IsActive = true;
                campaign.ValidFrom = DateTime.Today;
                campaign.ValidUntil = DateTime.Today.AddYears(1);
                var campaignContentRefrence = contentRepository.Save(campaign, SaveAction.Publish, AccessLevel.NoAccess);

                campaign = contentRepository.Get<SalesCampaign>(campaignContentRefrence);
            }

            return campaign;
        }

        private string CreateDiscountPromotion(ContentReference campaignLink, string orderNumber, Money couponAmount)
        {
            var catalougLink = siteSettingsService.StartPage.DefaultCatalogue;

            var couponCode = $"CREDIT-{Guid.NewGuid().ToString().Replace("-", "").Substring(6)}";

            var promotion = contentRepository.GetDefault<BuyQuantityGetItemDiscount>(campaignLink);
            promotion.IsActive = true;
            promotion.Name = $"CREDIT-{orderNumber}";
            promotion.Coupon.Code = couponCode;
            promotion.Condition.Items = new List<ContentReference>() { catalougLink };
            promotion.Condition.RequiredQuantity = 1;
            promotion.Condition.MatchRecursive = true;
            promotion.DiscountTarget.Items = new List<ContentReference>() { catalougLink };
            promotion.Discount.UseAmounts = true;
            promotion.Discount.Amounts = new List<Money>() { couponAmount };
            
            contentRepository.Save(promotion, SaveAction.Publish, AccessLevel.NoAccess);

            return couponCode;
        }

Discount and promotion has far more features that I haven’t included, you can read more about it HERE

Episerver Ascend Sydney 2020

First Episerver Ascend in the APAC area was very exciting. First I realized how many friends I found through Episerver and it is not just the platform I loved it helped me to find heaps of good friends.

Starting the day Episerver Regional Director APAC Natalia Gamarra kicked off the event with the agenda:

Next Episerver CEO Alex Atzberger spoke about Personalization and Privacy and how it can change the user acquisition:

And focus of Episerver to “empowering growing companies to compete digitally”

Jacob Khan VP, Solution Architecture spoke about the road map and Episerver focus on the personalization:

Next presenter was Niteco spoke about best practices when dealing with digital transformation. Nicola Ayan started with best technical best practices for digital transformation:

And then Michelle Tran follows the conversation on best practice for design and strategy:

Next Empired spoke about a digital project for Laser Clinics. Kevin Miller presented on behalf of Empired and Louise Chamberlain on behalf of Laster Clinics.

Deane Barker had a really good presentation about the content. He thinks “Content ha a soul” which is I agree with him, we need to think about the content as part of our solution:


And this is my favorite:

And finally Adi Wickramaratne on behalf of St John New Zealand. LEVO Digital did the digital transformation.

No alternative text description for this image

IT was really nice day, learned heaps and see many old friends. Thanks so much, Episerver, especially

Alexander Atzberger

Joey Moore

Natalia Gamarra

Jacob Khan

Malin Dahlin

Frankie Lui

“Some” best practices I learned on commerce personalization

The concept of personalization is a wide range. I’m working on a project that needs personalization and started to learn. One cool thing I learn from “Foundation” project (Foundation is a very good sample of all Episerver offerings) is how to build a code to use Episerver Tracking Commerce. Again I’m learning and please advise me if I can improve any part of it 🙂

Please read through how to install tracking Nuget package from here

The next step is to create a separate project in your solution and add a commerce tracking package to the solution. Then add a class as below:

public interface ICommerceTrackingService
    {
        Task<TrackingResponseData> TrackOrder(HttpContextBase httpContext, IPurchaseOrder order);
    }

    public class CommerceTrackingService: ICommerceTrackingService
    {
        private readonly IContextModeResolver contextModeResolver;
        private readonly TrackingDataFactory trackingDataFactory;
        private readonly ITrackingService trackingService;
        private readonly ServiceAccessor<IContentRouteHelper> contentRouteHelperAccessor;

        public CommerceTrackingService(IContextModeResolver contextModeResolver,
            TrackingDataFactory trackingDataFactory,
            ITrackingService trackingService,
            ServiceAccessor<IContentRouteHelper> contentRouteHelperAccessor)
        {
            this.contextModeResolver = contextModeResolver;
            this.trackingDataFactory = trackingDataFactory;
            this.trackingService = trackingService;
            this.contentRouteHelperAccessor = contentRouteHelperAccessor;
        }

        public async Task<TrackingResponseData> TrackOrder(HttpContextBase httpContext, IPurchaseOrder order)
        {
            if (contextModeResolver.CurrentMode != ContextMode.Default)
            {
                return null;
            }

            var trackingData = trackingDataFactory.CreateOrderTrackingData(order, httpContext);
            return await trackingService.TrackAsync(trackingData, httpContext, contentRouteHelperAccessor().Content);
        }
    }

I put both interface and class in the same file to simplify the post but if you want you can separate them.

Next step is to change you StructureMap to scan new project (this is optional depending on your StructureMap structure)

            context.StructureMap().Configure(config =>
            {
                config.Scan(scan =>
                {
                    scan.TheCallingAssembly();
                    scan.Assembly("EPiServer.Sample.Commerce.Personalization");
                    scan.WithDefaultConventions();
                    scan.LookForRegistries();
                });
            });

The next step is to call the track order. We want to do that when the order is being placed. So after cart converted to purchase order you can call your TrackOrder function:

commerceTrackingService.TrackOrder(httpContext, purchaseOrder);

This will push all relevant data from order to tracking engine to help the recommendation engine to get a better understanding of the user behavior.

I recommend to checkout “Foundation” project that has a very comprehensive sample.

Episerver and ASP.Net Core

Recently Episerver announced for Episerver ASP.Net Core program. This is really exciting news, as .Net Community (aka Microsoft!) announced focus more on .Net Core (means no more new version of .Net Framework!). Details of how Episerver is going to do it will be available here. So what is good about it:

  • The presentation layer can be written on lovely ASP.Net Core platform
  • .Net Core is lightweight compare to .Net framework. The presentation conceptually would be lean and faster! (I not sure if it would be a case anyway!)
  • Developers would be happy that they are working on edge technology!

Now what I think would be interim challenges until upgrading the whole platform:

  • Running two applications (for CMS) and three applications (for Commerce).
  • For page rendering, another HTTP call required that to be a potential failure point – means when something goes wrong developer needs more time to find the issue and that means more APPLICATION LOGGING!
  • Page Templates (aka Razor Templates) should be running on both PRESENTATION and EDITOR view. This perhaps makes it quite complicated as now we need ANOTHER project to share MODELS! And the concept of ‘Feature Group‘ we are using for a while needs to be reconsidered – Having said that there could be some ways to avoid duplicated views!

I have a very positive feeling about this Episerver move and I know it would be complicated both for Episerver Devs and external Devs and we all love challenges!

Sydney Episerver Meetup – Winter 2019 – Outcome

It was a really good gathering and I personally learn a lot.

Damien Dias from Episerver went through Episerver CMS and eCommerce certification exams. He gave a very good brief about what is his journey to get the certifications and shared tips around the main area of the exams.

The second part we had Darren Stahlhut from Luminary that he spoke about the really cool framework called Blazor from Microsoft (it is still not released!) and he married up Blazor with Episerver which was really cool. You read about it more here.

UPDATED – Episerver Sydney Winter 2019 Meetup

I’m so excited about this meetup. It is going to be on 10 July 2019 6pm – 8pm at Episerver Sydney office! In this session:

Damian Dias will talk about Commerce and CMS Certifications best practices.

Nicola Ayan will talk about Episerver Find and share lessons learned from recent projects.

Marcus Babajews will give you a short update on Episerver Campaign

UPDATE: We are so excited that Darren Stahlhut is joining us and present Episerver Blazor a proof of concept and add-on Episerver Cognitive Services. We are so excited to have him in Sydney 🙂

Please signup via link below:

https://www.eventbrite.com/e/episerver-developer-meetup-sydney-tickets-62842623980

Episerver CMS Cheat Sheet

During the time I worked with CMS, I built up list of some cool stuff. I thought to share them with you. Maybe in future we can have a Episerver CMS/Commerce cheat sheet or knowledge based in Github so we can all use them. This is just beginning 🙂

==========================CUSTOM LINK ITEM COLLECTION RENDERING=====================================
    @Html.PropertyFor(m => m.MainBody)

    // FullRefreshPropertiesMetaData asks on-page edit to reload the page 
    // to run the following custom rendering again after the value has changed.
    @Html.FullRefreshPropertiesMetaData(new []{ "RelatedContentLinks" })

    // EditAttributes enables on page-edit when you have custom rendering.
    <p @Html.EditAttributes(m => m.CurrentPage.RelatedContentLinks) >
    @if (Model.CurrentPage.RelatedContentLinks != null)
    {        
        <span>See also:</span>
        foreach (LinkItem item in Model.CurrentPage.RelatedContentLinks)
        {
            <a href="@item.Href">@item.Text</a>
        }
    }
    </p>
    ...


-------------------------------------------OR-------------------------------------------------------
    ...
    @Html.PropertyFor(m => m.MainBody)

    <h2>Releated Content</h2>
    @Html.PropertyFor(m => m.CurrentPage.RelatedContentLinks, "SeeAlso")
    ...
----------------------------------------------------------------------------------------------------
	
	
-------------------------------------------OR-------------------------------------------------------	
public class ArticlePage : PageData
{
    ...

    [UIHint("SeeAlso")]
    public virtual LinkItemCollection RelatedContentLinks { get; set; }

    ...
----------------------------------------------------------------------------------------------------


TIPS:
-----------
global folder for the media and block -> EPiServer.Core.SiteSettings.Current.GlobalBlocksRoot
site folder for the media and block EPiServer.Core.SiteSettings.Current.SiteBlocksRoot
-----------
Loaders:
IContentLoader (just readonly) vs IContentRepository  (CRUD) vs DataFactory (legacy, backward compatibility)
-----------

- Attributes:
Content Types: [ContentType], [Access], [AvailableContentTypes], [ImageUrl]

Property: [Display(
            Name = "Classification",
            Description = "Genre or type of article.",
            GroupName = "Details", Order = 2)]
			
Group: 
[GroupDefinitions]
public static class GroupNames
{
   [Display(GroupName="MyNews", Order=1)]
   public const string News = "News";
   [RequiredAccess(AccessLevel.Publish)]
   public const string Contact= "Contact";
}


Version status 
	NotCreated. The version has not been saved yet.
	CheckedOut. The currently edited version
	AwaitingApproval. The editor is done and has marked the version as ready for approval
	Rejected. The version did not pass the approval
	CheckedIn. The version is approved and ready for publish
	DelayedPublished. The version will be automatically published at a future date and time
	Published. The version is published
	PreviouslyPublished. The version has been published but is now replaced with a different version
	
Status transitions
	Default/None. Saves a version maintaining the current status unless a new version is created. In this case the new version is created in a checked out state. (Default has replaced None in CMS 10)
	Publish. Publishes a version.
	Schedule. Schedule a version for automatic publishing at a later date. (New in CMS 10)
	CheckOut. Checks out a version to indicate that it is being worked on. (New in CMS 10)
	CheckIn. Checks in a version indicating that it is ready to be published
	RequestApproval. Saves a version to a state indicating that it is ready to be approved
	Reject. Rejects a version. This is normally done during an approval review.
	Save. Save action which outcome will depend on the current status. Will in some cases maintain the status and in other cases check out the current version.
Optional parameter
	ForceNewVersion. Specifies that the content saved should be created as a new version.
	ForceCurrentVersion. Specifies that the save should update the existing version.
	SkipValidation. Specifies that the content should be saved without performing the usual validation.
	SkipSetCommonDraft. Specifies that the version should not be set as common draft (the version used by default in the CMS edit user interface)
	DelayedPublish. Used in combination with SaveAction. CheckIn to specify that the version should be automatically published at a future date and time. (Deprecated in CMS 10)
----------------------

force to save/publish by anonymouse user (e.g. schedule job) contentRepository.Save(myPage, EPiServer.DataAccess.SaveAction.Publish, EPiServer.Security.AccessLevel.NoAccess);


custom icon for content type:

[UIDescriptorRegistration]
public class ContainerPageUIDescriptor : UIDescriptor<ContainerPage>
{
	public ContainerPageUIDescriptor()
		: base("epi-iconObjectPage")
	{
	}
}

change the default view

	[UIDescriptorRegistration]
	public class StartPageUIDescriptor : UIDescriptor
	{
		public StartPageUIDescriptor(): base(ContentTypeCssClassNames.Page)
		{
			DefaultView = CmsViewNames.AllPropertiesView;
			EnableStickyView = false;
		}
	}
	
change the default icon of media

[UIDescriptorRegistration]

public class DocumentUIDescriptor : UIDescriptor<Document>
{
    public DocumentUIDescriptor() : base("icon-document")
    {
        DefaultView = CmsViewNames.AllPropertiesView;
    }
}
	
change editor descriptor

	[EditorDescriptorRegistration(TargetType = typeof(string))]
	public class StringEditorDescriptor : EditorDescriptor
	{
		public StringEditorDescriptor()
		{
			ClientEditingClass = "dijit.form.ValidationTextBox";
		}
	}
====================================================================================================


TemplateResolver

The EPiServer.Web.TemplateResolver.TemplateResolving event is raised. If an event handler selects a template, that template is used with no further handling.
All templates matching the desired content type are filtered according to the rendering mode: If t is a page, a suitable controller is selected; if it is partial rendering, a suitable partial controller or partial view is selected. For partial templates, the list is filtered according to main template.
If the template is requested with a specific tag, the list is filtered on that tag. If no template matching the tag exists, all templates from 2 are taken into account.
If any display channel is active, and there are template tags matching that channel, templates are filtered for DisplayChannel. 
From remaining templates, the shortest inheritance chain to the TemplateModel marked as Default and not inherited, is selected.
If no match from 5, the shortest inheritance chain to TemplateModel that is marked as Default, is selected.
If no match from 6, the shortest inheritance chain to TemplateModel, is selected.
The EPiServer.Web.TemplateResolver.TemplateResolved event is raised, providing a chance to replace the selected template.



--------------------------custom root folder for block selector----------------------------
[UIHint("teaserblock")]
public virtual ContentReference TeaserBlockReference { get; set; }

[EditorDescriptorRegistration(TargetType = typeof(ContentReference), UIHint = "teaserblock")]
public class BlockReferenceEditorDescriptor : ContentReferenceEditorDescriptor<TeaserBlock>
{
    public override IEnumerable<ContentReference> Roots
    {
        get
        {
            //Sample to override the default root for the repository.
            //Take the reference from configuration or site initialization and do not hard-code it.
            return new ContentReference[] { new ContentReference(90 ) };
        }
    }
}

-------------------------------------------------------------

PERMISSION:

- Protecting a controller via a permission

   [AuthorizePermission("MyCustomPermissions", "EditSettings")]
    public class EditSettingsController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
	
- 

  [PermissionTypes]
    public static class MyCustomPermissions
    {
        public const string GroupName = "MyCustomPermissions";

        static MyCustomPermissions()
        {
            EditSettings = new PermissionType(GroupName, "EditSettings");
            ViewSettings = new PermissionType(GroupName, "ViewSettings");
        }

        public static PermissionType EditSettings { get; private set; }

        public static PermissionType ViewSettings { get; private set; }
    }
	
--------------------------------------------------------------------------
Property settings:

[EPiServer.Core.PropertySettings.PropertySettings(typeof(CustomStringSettings), true)]
public class CustomPropertyString : PropertyString
{
}

[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class PlugInInitialization : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            PropertyControlClassFactory.Instance.RegisterClass(typeof(PropertyString), typeof(CustomPropertyString));
            PropertyControlClassFactory.Instance.RegisterClass(typeof(PropertyLongString), typeof(CustomPropertyString));
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
    }

Episerver CMS 11 – Certification

If you are Episerver CMS Certified Developer, every two year you need to the exams. And this month was my turn! I started to look around and found many stuff related to exam changed. To summarized it I gathered all items I thought is important to read and summarized them as below. But this doesn’t mean you should not read whole Episerver World documentation!

Product Knowledge

Concept
Learning path
http://www.episerver.com/products/platform/cms/

Editing
http://webhelp.episerver.com/latest/getting-started/logging-in.htm
Administering
http://webhelp.episerver.com/latest/cms-admin/administration-view.htm

Installation, Operation, and Configuration
Environment & Deployment
http://world.episerver.com/documentation/developer-guides/CMS/Deployment/
Error handling
http://world.episerver.com/documentation/developer-guides/CMS/logging/
License model
https://license.episerver.com/
Add-ons
http://world.episerver.com/documentation/developer-guides/CMS/add-ons/
http://webhelp.episerver.com/latest/addons/addons.htm
Multisite
https://world.episerver.com/documentation/developer-guides/CMS/Deployment/Setting-up-multiple-sites/
DXC Service
https://world.episerver.com/digital-experience-cloud-service/

Content Model
Rendering
http://world.episerver.com/documentation/developer-guides/CMS/rendering/
Content types and models
http://world.episerver.com/documentation/developer-guides/CMS/Content/
Properties
http://world.episerver.com/documentation/developer-guides/CMS/Content/Properties/
Validation
http://world.episerver.com/documentation/developer-guides/CMS/Content/Validation/
Content approvals
https://world.episerver.com/documentation/developer-guides/CMS/Content/content-approvals/
A/B testing
https://world.episerver.com/documentation/developer-guides/CMS/key-performance-indicators-kpis/
Forms
https://world.episerver.com/documentation/developer-guides/forms/configuring-episerver-forms/

Creating Websites
Technical architecture
http://world.episerver.com/documentation/developer-guides/CMS/architecture/
Authentication and authorization
http://world.episerver.com/documentation/developer-guides/CMS/security/
Globalization and localization
http://world.episerver.com/documentation/developer-guides/CMS/globalization/
Performance and scalability
https://talk.alfnilsson.se/2015/11/25/code-best-practices-and-performance-optimization-the-summary/
https://world.episerver.com/digital-experience-cloud-service/development-considerations/performance-considerations/
Data storage
http://world.episerver.com/documentation/developer-guides/CMS/dynamic-data-store/
https://world.episerver.com/documentation/developer-guides/CMS/Content/Persisting-IContent-instances/
Personalization
http://world.episerver.com/documentation/developer-guides/CMS/personalization/
Indexed search
https://world.episerver.com/documentation/developer-guides/CMS/search/
https://world.episerver.com/documentation/developer-guides/find/

Advanced Concepts
Scheduled jobs
http://world.episerver.com/documentation/developer-guides/CMS/scheduled-jobs/
Initialization modules
http://world.episerver.com/documentation/developer-guides/CMS/initialization/
Extending the Episerver UI & Plugin
http://world.episerver.com/documentation/developer-guides/CMS/user-interface/
Content providers
http://world.episerver.com/documentation/developer-guides/CMS/Content/Content-providers/
Integration
http://world.episerver.com/documentation/developer-guides/CMS/routing/partial-routing/
https://world.episerver.com/documentation/developer-guides/Episerver-Service-API/
https://world.episerver.com/documentation/developer-guides/CMS/Content/content-delivery-api/
Notifications
https://world.episerver.com/documentation/developer-guides/CMS/using-notifications/

——————NOTES—————————
https://world.episerver.com/documentation/Items/System-Requirements/system-requirements—episerver/

Breaking changes in the past two major releases
https://world.episerver.com/documentation/upgrading/Episerver-CMS/cms-11/breaking-changes-cms-11/
https://world.episerver.com/documentation/upgrading/Episerver-CMS/10/breaking-changes-cms-10/

New features in the past two major releases

  • https://world.episerver.com/documentation/Release-Notes/?packageGroup=CMS
  • https://world.episerver.com/features/

Episerver – Sydney Meetup – 27 Feb 2019

We are so pleased to announced our next Episerver Meetup 🙂

This is very exciting as this meetup will be held in Episerver office in Sydney and more exciting which Damien Dias from Episerver is going to speak about the upgrade process. This is what we are dealing many times! So would be really helpful to see his best practices and advises!

Next Nicola Ayan EMVP is going to talk about how you can build multi-step forms with conditional logic branches using Episerver Forms 🙂 I myself is love to learn it 🙂

And finally I speak about the Web Bots and run an example to show how Bot can be integrated with Episerver Commerce!

We are really excited about this and I hope to see you all SOOON!

Please RSVP ASAP using link bellow!

https://www.eventbrite.com/e/episerver-developer-meetup-sydney-february-27th-tickets-54587729368

 

LATE Spring Sydney Episerver Meetup!

Studio 60 is going to host next Episerver meetup in Sydney and heaps of good things are going to happen.

Damien Dias from Episerver would present about his awesome experience with Upgrading OLD Episerver website to a new one. It is awesome that we hear someone is with Episerver for a long time and now want to invest for the future and upgrade the existing site and as a technical guru you need to know best practices to make sure customer will get best out of the upgrade!

 

Second Nicola our EMVP will present how we can take Episerver Form to next level and make it as a wizard which each step is conditional and based on the previous user answer we give more relative steps!

 

And myself (if there is enough time) love to speak about how we can easily connect an existing Episerver eCommerce website (read it Quicksilver) to Azure Web Bot! Cool ha!! I love bots as they are the more human type of thing instead of a web page! To me, the future of the internet is a bot!

 

Please RSVP HERE as we have limited sit 🙂 and it is FREE plus we have pizza and beer!