Tuesday, February 7, 2017

Add Custom Element, Extend Existing Element With Custom Attributes in EPiServer TinyMCE

In one of our project we had to add custom element and custom attributes in TinyMCE for angular directive. However, TinyMCE was stripping off the non-registered element and attributes during the validation

Fortunately, EPiServer provides easy way to Add and Extend TinyMCE element and attributes.

First of all we need to define below class


namespace EPiServer7App.EPiServerCore.Core.TinyMce
{
    [TinyMCEPluginNonVisual(PlugInName = "TinyMceExtendedValidElements", AlwaysEnabled = true, EditorInitConfigurationOptions = "{ custom_elements : 'uib-accordion,uib-accordion-group', extended_valid_elements: 'uib-accordion[close-others],uib-accordion-group[heading]' }")]
    public class TinyMceExtendedValidElements
    {
    }
}



Then, we need to define this EmptyHandler other wise js will throw error


using System.Web;

namespace EPiServer7App.EPiServerCore.Core.TinyMce
{
    public class EmptyFileHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
        }
    }
}


Then, we need to update the web.config location settings

 <location path="util/editor/tinymce/plugins">
    <system.webServer>
      <handlers>
        <add name="TinyMceExtendedValidElements" path="/util/editor/tinymce/plugins/TinyMceExtendedValidElements/editor_plugin.js" verb="GET" type="EPiServer7App.EPiServerCore.Core.TinyMce.EmptyFileHandler, EPiServer7App.EPiServerCore, Version=1.0.0.0, Culture=neutral" />
      </handlers>
    </system.webServer>
  </location>

Then, we will update the episerver.config file to allow TinyMCE merge of custom element.

<episerver xmlns="http://EPiServer.Configuration.EPiServerSection">
  <applicationSettings globalErrorHandling="Off" disableVersionDeletion="false" httpCacheVaryByCustom="path" httpCacheVaryByParams="id,epslanguage" httpCacheability="Public" uiEditorCssPaths="~/Static/css/editor.css" urlRebaseKind="ToRootRelative" pageUseBrowserLanguagePreferences="false" uiShowGlobalizationUserInterface="true" subscriptionHandler="EPiServer.Personalization.SubscriptionMail,EPiServer" uiMaxVersions="20" pageValidateTemplate="false" uiUrl="~/manage/CMS/" utilUrl="~/util/" pageFolderVirtualPathProvider="SitePageFiles" />
  <workflowSettings>
    <workflowHost type="EPiServer.WorkflowFoundation.AspNetWorkflowManager,EPiServer.WorkflowFoundation" />
    <externalServices>
      <externalService type="EPiServer.WorkflowFoundation.Workflows.ApprovalService,EPiServer.WorkflowFoundation" />
      <externalService type="EPiServer.WorkflowFoundation.Workflows.ReadyForTranslationService,EPiServer.WorkflowFoundation" />
      <externalService type="EPiServer.WorkflowFoundation.Workflows.RequestForFeedbackService,EPiServer.WorkflowFoundation" />
    </externalServices>
  </workflowSettings>
  <tinyMCE mergedConfigurationProperties="valid_elements, extended_valid_elements, invalid_elements, valid_child_elements" />
</episerver>


After all of the above steps TinyMCE will not strip off the custom elements and attibutes.

Friday, February 3, 2017

Creating Split Shipment, Address For Line Item in EPiServer

In one of our project I had to create multiple shipment and addresses depending on the line item type.

The story is, If user includes gift card in the cart then gift card code needs to be sent on email to differnt user. Therefore, i had to add different address to store First Name and Email and add seperate shipment for each gift voucher card.

In order to do that i had to add as many order addresses in cart and for each addresses i had to add shipment and then assign line item to shipment. I also had to add those multiple addresses to order form as well.

Adding Order Address For Each Line Item

 
 foreach (var gift in giftsLineItems)
            {
                var addressId = $"{OrderConstants.GIFT_PREFIX}{gift.LineItem.LineItemId}";
                var giftAddress = giftVoucherOrderAddresses.FirstOrDefault(o => o.Name.Equals(addressId, StringComparison.InvariantCultureIgnoreCase)) ?? _checkoutService.AddNewOrderAddress();
                giftAddress.Name = addressId;
                giftAddress.Email = giftCodeAndEmail[gift.LineItem.LineItemId].Email;
                giftAddress.FirstName = giftCodeAndEmail[gift.LineItem.LineItemId].FirstName;
                giftAddress.CountryCode = _currentMarket.GetCurrentMarket().Countries.First();
                giftAddress.CountryName = _countryManager.GetCountryByCountryCode(giftAddress.CountryCode).Name;
                giftAddress.AcceptChanges();

                /* The shipment code will be described below */
            }


Adding Shipment and Assigning Shipment to Line Item

 
/* Add/Update Shipment based on shipping address id 
 * shipping address is will be unique for each line item as it is based on line item id
 * AddShipmentLineItemShipment functions adds shipment and binds it to line item
 * giftsLineItems is just wrapper for line item giftLineItem object contains Commerce Line Item 
 */
 var shipment = orderForm.Shipments.FirstOrDefault(s => addressId.Equals(s.ShippingAddressId)) ?? orderForm.Shipments.AddNew();
 AddShipmentLineItemShipment(gift.LineItem, shipment, freeShippingEmailDefault, addressId);
 shipment.AcceptChanges();
 gift.LineItem.AcceptChanges();


Adding Shipping Method, Shipping Rate and Assigning it to Line Item

 
 private void AddShipmentLineItemShipment(LineItem lineItem, Shipment shipment, ShippingMethodDto.ShippingMethodRow shippingMethod, string addressId)
        {
            var orderForm = _cartService.GetOrderForms().First();
            var shippingRate = _checkoutService.GetShippingRate(shipment, shippingMethod.ShippingMethodId);

            shipment.ShippingMethodId = shippingMethod.ShippingMethodId;
            shipment.ShippingMethodName = shippingMethod.Name;
            shipment.ShippingAddressId = addressId;
            shipment.SubTotal = shippingRate.Money.Amount;
            shipment.ShippingSubTotal = shippingRate.Money.Amount;

            int lineItemIndex = orderForm.LineItems.IndexOf(lineItem);
            lineItem.ShippingMethodId = shippingMethod.ShippingMethodId;
            lineItem.ShippingMethodName = shippingMethod.Name;
            lineItem.ShippingAddressId = addressId;
            shipment.AddLineItemIndex(lineItemIndex);
        }



GetShippingMethod 

The above method gets the rate for shipping method. This is the function available in QuickSilver template. I have included here as well

  
  public ShippingRate GetShippingRate(Shipment shipment, Guid shippingMethodId)
        {
            var method = ShippingManager.GetShippingMethod(shippingMethodId).ShippingMethod.Single();
            return GetRate(shipment, method);
        }

        private ShippingRate GetRate(Shipment shipment, ShippingMethodDto.ShippingMethodRow shippingMethodRow)
        {
            var type = Type.GetType(shippingMethodRow.ShippingOptionRow.ClassName);
            var shippingGateway = (IShippingGateway)Activator.CreateInstance(type, _currentMarket.GetCurrentMarket());
            string message = null;
            return shippingGateway.GetRate(shippingMethodRow.ShippingMethodId, shipment, ref message);
        }


Monday, January 30, 2017

Extending EPiServer Commerce Classes

In EPiServer we can extend the existing Commerce Classes. Such as PurchaseOrder, OrderForm and LineItem.

We can use this extended class to use meta class property as class property as oppose to calling object as indexer with hard coded property name.
for e.g


var orderForm = orderGroup.OrderForms[0]
var stockRoomId = orderForm["StockRoomId"]

/* We can use extended class to get value of stock room id */

var tcmsOrderForm = orderGroup.OrderForms[0] as TcmsOrderForm
var stockRoomId = tcmsOrderForm.StockRoomId;



First all of we need to define create the custom meta class and custom meta property in Commerce Section.

We will assign the associated meta property to meta classes.

We will have following custom classes

  • TcmsOrderForm
  • TcmsLineItem

We will also have following meta properties(Not all are included)

  • StockRoomId
  • PeriodId
  • UnitOfMeasure
  • IsPrescribed

Meta properties  will need to be assigned to associated Meta classes in Commerce section.
Once we have meta property and meta classes relation defined we are ready to create extended commerce classes.

Extended Purchase Order (TcmsPurchaseOrder)

using Mediachase.Commerce.Orders;
using System;
using System.Data;
using System.Linq;
using EPiServer7App.EPiServerCore.Utilities;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended purchase order class which stores the date sent and date actioned
    /// </summary>
    [Serializable]
    public class TcmsPurchaseOrder : PurchaseOrder
    {
        public const string MetaClass_Name = "PurchaseOrder";

        public const string MetaField_DateActioned = "DateActioned";
        public const string MetaField_DateSent = "DateSent";
        public const string MetaField_OrderCreateLastStep = "OrderCreateLastStep";
        public const string MetaField_TrackingNumberIsFacilityGenerated = "TrackingNumberIsFacilityGenerated";

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsPurchaseOrder"/> class.
        /// </summary>
        public TcmsPurchaseOrder()
        {
            InitializeCustomFields();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public TcmsPurchaseOrder(IDataReader reader)
            : base(reader)
        {
            InitializeCustomFields();
        }


        #endregion

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected void InitializeCustomFields()
        {
            DateActioned = null;
            DateSent = null;
            OrderCreateLastStep = Enums.OrderCreateStep.StockTake;
            TrackingNumberIsFacilityGenerated = false;
        }

        /// <summary>
        /// Gets or sets the date actioned.
        /// </summary>
        /// <value>
        /// The date actioned.
        /// </value>
        public DateTime? DateActioned
        {
            get { return this.GetDateTimeValue(MetaField_DateActioned, null); }
            set { this[MetaField_DateActioned] = value; }
        }


        /// <summary>
        /// Gets or sets the date sent.
        /// </summary>
        /// <value>
        /// The date sent.
        /// </value>
        public DateTime? DateSent
        {
            get { return this.GetDateTimeValue(MetaField_DateSent, null); }
            set { this[MetaField_DateSent] = value; }
        }

        /// <summary>
        /// Gets or sets the order create last step.
        /// </summary>
        /// <value>
        /// The order create last step.
        /// </value>
        public Enums.OrderCreateStep OrderCreateLastStep
        {
            get { return (Enums.OrderCreateStep) this.GetIntWithDefaultValue(MetaField_OrderCreateLastStep, (int) Enums.OrderCreateStep.StockTake); }
            set { this[MetaField_OrderCreateLastStep] = (int) value; }
        }

        public bool IsEditable
        {
            get
            {
                var nonEditableStatus = new[]
                {
                    Enums.TcmsOrderStatus.Completed,
                    Enums.TcmsOrderStatus.SubmittedToDistributor,
                    Enums.TcmsOrderStatus.Cancelled
                };

                return !nonEditableStatus.Contains(EnumHelper.ParseOrDefault<Enums.TcmsOrderStatus>(Status));
            }
        }

        /// <summary>
        /// Gets or sets if the tracking number (purchase order number) has been set by the facility as
        /// opposite of being system generated.
        /// </summary>
        public bool TrackingNumberIsFacilityGenerated
        {
            get { return ((bool?) this[MetaField_TrackingNumberIsFacilityGenerated]).GetValueOrDefault(false);}
            set { this[MetaField_TrackingNumberIsFacilityGenerated] = value; }
        }
    }
}

Extended Order Form (TcmsOrderForm)

using System;
using System.Data;
using System.Linq;
using System.Runtime.Serialization;
using EPiServer7App.EPiServerCore.Utilities;
using Mediachase.Commerce.Orders;
using Mediachase.Commerce.Storage;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended version of the OrderForm
    /// </summary>
    [Serializable]
    public class TcmsOrderForm : OrderForm
    {
        public const string MetaClass_Name = "OrderFormEx";

        public const string MetaField_StockRoomId = "StockRoomId";
        public const string MetaField_PeriodId = "PeriodId";
        public const string MetaField_Comments = "Comments";

        #region Constructors
        public TcmsOrderForm()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public TcmsOrderForm(IDataReader reader)
            : base(reader)
        {
            InitializeCustomFields();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <param name="context">The context.</param>
        protected TcmsOrderForm(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            InitializeCustomFields();
        } 
        #endregion

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected void InitializeCustomFields()
        {
            StockRoomId = Guid.Empty;
            PeriodId = Guid.Empty;
        }

        /// <summary>
        /// Gets or sets the stock room identifier.
        /// </summary>
        /// <value>
        /// The stock room identifier.
        /// </value>
        public Guid StockRoomId
        {
            get { return this.GetGuidValue(MetaField_StockRoomId, Guid.Empty).Value; }
            set { this[MetaField_StockRoomId] = value; }
        }


        /// <summary>
        /// Gets or sets the period identifier.
        /// </summary>
        /// <value>
        /// The period identifier.
        /// </value>
        public Guid PeriodId
        {
            get { return this.GetGuidValue(MetaField_PeriodId, Guid.Empty).Value; }
            set { this[MetaField_PeriodId] = Convert.ToString(value); }
        }

        /// <summary>
        /// Gets or sets the comments.
        /// </summary>
        /// <value>
        /// The comments.
        /// </value>
        public string Comments
        {
            get { return GetString(MetaField_Comments); }
            set { this[MetaField_Comments] = value; }
        }

        protected override void PopulateCollections(DataTableCollection tables, string filter)
        {
            filter = string.Format("OrderFormId = '{0}'", OrderFormId);
            base.PopulateCollections(tables, filter);            
        }

    }
}

Extended Line Item (TcmsLineItem)

using System;
using System.Runtime.Serialization;
using Mediachase.Commerce.Orders;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended class for LineItem with TCMS specific values
    /// </summary>
    public class TcmsLineItem : LineItem
    {
        public const string MetaClass_Name = "LineItemEx";

        public const string MetaField_UnitOfMeasure = "UnitOfMeasure";
        public const string MetaField_ActualCatalogEntryId = "ActualCatalogEntryId";
        public const string MetaField_IsPrescribed = "IsPrescribed";
        
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsLineItem"/> class.
        /// </summary>
        public TcmsLineItem()
        {
            Initialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsLineItem"/> class.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <param name="context">The context.</param>
        protected TcmsLineItem(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            Initialize();
        } 
        #endregion

        /// <summary>
        /// Initializes this instance with default values
        /// </summary>
        /// <remarks>The base class doesn't expose this as virtual. Sigh!</remarks>
        protected virtual void Initialize()
        {
            UnitOfMeasure = Enums.UnitOfMeasure.Carton;
            IsPrescribed = false;
        }

        /// <summary>
        /// Gets or sets the unit of measure.
        /// </summary>
        /// <value>
        /// The unit of measure.
        /// </value>
        public Enums.UnitOfMeasure UnitOfMeasure
        {
            get { return (Enums.UnitOfMeasure)GetInt(MetaField_UnitOfMeasure); }
            set { this[MetaField_UnitOfMeasure] = (int)value; }
        }
     
        /// <summary>
        /// Gets or sets a value indicating whether [is prescribed].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [is prescribed]; otherwise, <c>false</c>.
        /// </value>
        public bool IsPrescribed
        {
            get { return GetBool(MetaField_IsPrescribed); }
            set { this[MetaField_IsPrescribed] = value; }
        }      
    }
}



ecf.order.config

We also need to update the ecf.order.config section to include the custom class definition

<?xml version="1.0"?>
<Orders newOrderStatus="Draft" autoConfigure="true" shipmentAutoReleaseTimeout="1:0:0">
  <MappedTypes>
    <ShoppingCartType name="Mediachase.Commerce.Orders.Cart,Mediachase.Commerce" />
    
    <!-- Custom Lines -->
    <PurchaseOrderType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsPurchaseOrder,EPiServer7App.Commerce" />
    <OrderFormType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsOrderForm,EPiServer7App.Commerce" />
    <LineItemType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsLineItem,EPiServer7App.Commerce" />    
    <!-- END Custom Lines-->
    
    <!-- ORIGINAL LINES -->
    
    <!--
    <PurchaseOrderType name="Mediachase.Commerce.Orders.PurchaseOrder,Mediachase.Commerce" />
    <OrderFormType name="Mediachase.Commerce.Orders.OrderForm,Mediachase.Commerce" />
    <LineItemType name="Mediachase.Commerce.Orders.LineItem,Mediachase.Commerce" />
    -->

    <PaymentPlanType name="Mediachase.Commerce.Orders.PaymentPlan,Mediachase.Commerce" />    
    <OrderGroupAddressType name="Mediachase.Commerce.Orders.OrderAddress,Mediachase.Commerce" />
    <ShipmentType name="Mediachase.Commerce.Orders.Shipment,Mediachase.Commerce" />
    <OrderSearchType name=" Mediachase.Commerce.Orders.Search.OrderSearch,Mediachase.Commerce" />
  </MappedTypes>
  <MetaClasses>
    <PurchaseOrderClass name="PurchaseOrder" />
    <PaymentPlanClass name="PaymentPlan" />
    <ShoppingCartClass name="ShoppingCart" />]
    <OrderFormClass name="OrderFormEx" />
    <LineItemClass name="LineItemEx" />    
    <ShipmentClass name="ShipmentEx" />
    <OrderAddressClass name="OrderGroupAddressEx" />
  </MetaClasses>
  <Connections confConnectionStringName="EcfSqlConnection" transConnectionStringName="EcfSqlConnection" />
  <Cache enabled="true" shippingCollectionTimeout="0:0:10" paymentCollectionTimeout="0:0:10" statusCollectionTimeout="0:0:10" countryCollectionTimeout="0:0:10" taxCollectionTimeout="0:0:10" jurisdictionCollectionTimeout="0:0:10" />
  <Roles>
    <add name="OrderSupervisorRole" value="Order Supervisor" />
    <add name="OrderManagerRole" value="Order Manager" />
    <add name="ShippingManagerRole" value="Shipping Manager" />
    <add name="ReceivingManagerRole" value="Receiving Manager" />
  </Roles>
</Orders>

After defining custom classes and configuration we are now ready to use the classes
Below are the utility functions and example to use the above classes.


var purchaseOrder = new TcmsPurchaseOrder { TrackingNumber = "DummyTrackingNumber" };

var orderForm = purchaseOrder.OrderForms.AddNew() as TcmsOrderForm;
    
orderForm.LineItems.Add(new TcmsLineItem()
  {
       Quantity = 1,
       ActualCatalogEntryId = 24,
       ExtendedPrice = (decimal)20.30,
       IsPrescribed = true,
       UnitOfMeasure = Enums.UnitOfMeasure.Carton,
       StockTakeQuantityInPieces = 12,
  });
     
     
  //Assumes that we're only using one form!
  return orderGroup.OrderForms[0] as TcmsOrderForm;

Extension method to convert normal classes to extended classes

using System.Collections.Generic;
using EPiServer7App.Commerce.MetaFields.Orders;
using Mediachase.Commerce.Orders;

namespace EPiServer7App.Commerce.Services.Extensions
{
    /// <summary>
    /// Creates extension methods for order group implementations such as Cart and Purchase Order
    /// </summary>
    public static class OrderGroupExtensions
    {
        /// <summary>
        /// Gets the TCMS order form.
        /// </summary>
        /// <param name="orderGroup">The order group.</param>
        /// <returns></returns>
        public static TcmsOrderForm GetTcmsOrderForm(this OrderGroup orderGroup)
        {
            if (orderGroup == null)
                return null;

            if (orderGroup.OrderForms.Count < 1)
                return null;

            //Assumes that we're only using one form!
            return orderGroup.OrderForms[0] as TcmsOrderForm;
        }

        /// <summary>
        /// To the TCMS order form.
        /// </summary>
        /// <param name="orderForm">The order form.</param>
        /// <returns></returns>
        public static TcmsOrderForm ToTcmsOrderForm(this OrderForm orderForm)
        {
            if (orderForm == null)
                return null;

            return orderForm as TcmsOrderForm;
        }

        /// <summary>
        /// To the TCMS line item.
        /// </summary>
        /// <param name="lineItem">The line item.</param>
        /// <returns></returns>
        public static TcmsLineItem ToTcmsLineItem(this LineItem lineItem)
        {
            if (lineItem == null)
                return null;

            return lineItem as TcmsLineItem;
        }
    }
}



Tuesday, January 24, 2017

Adding EPiServer Meta Property through Code And Assigning it to MetaClass

In this post, we will go through the feature to add meta property to OrderForm, LineItem and CredtiCardPayment class.

We are going to use MetaClass and MetaField class of Mediachase.MetaDataPlus to add/update property to class

 

var metaLineItemClass = MetaClass.Load(MetaDataContext.Instance, "LineItemEx")


The above function will return the instance of LineItemEx  class. Using this instance you can add meta field to give meta class.

 

var metaProp = MetaField.Load(MetaDataContext.Instance, "GiftVoucherCode")



The above function will return the instance of field "GiftVoucherCode" meta property. Do not forget, MetaField are unique and can only be one created but can be assigned to any meta class.


 
if (metaProp == null)
                metaProp = MetaField.Create(MetaDataContext.Instance, "Mediachase.Commerce.Orders", "Gift Voucher Code", "Gift Voucher Code", "Gift Voucher Code", MetaDataType.ShortString, 100, true, false, false, false);
if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);


The above piece of code first checks if the meta field is already created. If not then it creates one.
It also checks if the meta field is already associated with meta class then it ignores it else assigns the meta field to meta class using AddField function in meta class.

Below is the complete code that I have used in my project.

We will have class MetaClassAndFieldsSyncInitializer which will add property to meta class.


 using System;
using System.Collections.Generic;
using System.Linq;
using EPiServer.Reference.Commerce.Shared.Settings;
using Mediachase.MetaDataPlus;
using Mediachase.MetaDataPlus.Configurator;

namespace EPiServer.Reference.Commerce.Shared.Infrastructure.Initialization
{
    public class MetaClassAndFieldsSyncInitializer
    {
        private readonly MetaDataContext _metaDataContext;
        private const string PRODUCT_TYPE = "ProductType";
        private const string LINE_ITEM_EX = "LineItemEx";
        private const string ORDER_FORM_EX = "OrderFormEx";
        private const string ORDER_GROUP_ADDRESS_EX = "OrderGroupAddressEx";
        private const string ORDER_CREDIT_CARD_PAYMENT = "CreditCardPayment";


        private List<string> _productTypes = Enum.GetNames(typeof(Enums.ProductType)).ToList();
            
        public MetaClassAndFieldsSyncInitializer(MetaDataContext metaDataContext)
        {
            if (metaDataContext == null)
                throw new ArgumentNullException("metaDataContext");
            _metaDataContext = metaDataContext;
        }

        public void Sync()
        {
            AddBaseProductProductType();
            AddLineItemFields();
            AddOrderFormFields();
            AddOrderGroupAddressFields();
            AddOrderPaymentFields();
        }

        private void AddBaseProductProductType()
        {
            var metaProp = AddProductTypeField();
        }

        private void AddLineItemFields()
        {
            var metaLineItemClass = MetaClass.Load(_metaDataContext, LINE_ITEM_EX);

            /* Will add them back when nedd them */
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.GiftVoucherCode);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders.System.LineItem", MetaFieldsConstants.LineItem.GiftVoucherCode, "Gift Voucher Code", "Gift Voucher Code", MetaDataType.ShortString, 20, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.LineItemAbstract);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.LineItem.LineItemAbstract, "LineItem Abstract", "LineItem Abstract", MetaDataType.LongHtmlString, 5000, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.ImageUrl);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.LineItem.ImageUrl, "Image Url", "Image Url", MetaDataType.ShortString, 1000, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = AddProductTypeField();
            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);
        }

        private void AddOrderGroupAddressFields()
        {
            var metaOrdeGroupAddressClass = MetaClass.Load(_metaDataContext, ORDER_GROUP_ADDRESS_EX);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderGroupAddress.Suburb_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderGroupAddress.Suburb_Field, "Suburb", "Suburb", MetaDataType.ShortString, 100, true, true, false, false);
            if (metaProp != null && !metaOrdeGroupAddressClass.MetaFields.Contains(metaProp))
                metaOrdeGroupAddressClass.AddField(metaProp);
        }

        private void AddOrderFormFields()
        {
            var metaOrderFormClass = MetaClass.Load(_metaDataContext, ORDER_FORM_EX);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderForm.SubscribeToNewsletters_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderForm.SubscribeToNewsletters_Field, "Subscribe To NewsLetter", "Subscribe To NewsLetter", MetaDataType.Boolean, 10, true, false, false, false);
            if(metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderForm.ReOrdering_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderForm.ReOrdering_Field, "Reordering", "Reordering", MetaDataType.Boolean, 10, true, false, false, false);
            if (metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);
        }

        private void AddOrderPaymentFields()
        {
            var metaOrderFormClass = MetaClass.Load(_metaDataContext, ORDER_CREDIT_CARD_PAYMENT);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderPayment.PaymentProviderResponse_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderPayment.PaymentProviderResponse_Field, "Payment Provider Response", "Payment Provider Response", MetaDataType.LongString, 10000, true, false, false, false);
            if (metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);
        }

        private MetaField AddProductTypeField()
        {
            MetaField metaProp = MetaField.Load(_metaDataContext, PRODUCT_TYPE);

            if (metaProp == null)
            {
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Catalog", PRODUCT_TYPE, "Product Type", "Product type such as(Walker, Crawler, Toddler and etc", MetaDataType.EnumSingleValue, 1000, true, false, false, false);
            }

            foreach (var pr in _productTypes)
            {
                var existItem = metaProp.Dictionary.GetItem(pr);
                if (existItem == null)
                    metaProp.Dictionary.Add(pr, pr);

            }

            return metaProp;
        }
    }
}

We will also this SharedDependencyInitialization which will be call once before initialization of site to add/update any meta class or meta field.



using System.Reflection;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Reference.Commerce.Shared.Data.BusinessFoundation.Initialization;
using EPiServer.Reference.Commerce.Shared.Utilities;
using EPiServer.ServiceLocation;
using Mediachase.BusinessFoundation.Data;
using Mediachase.MetaDataPlus;

namespace EPiServer.Reference.Commerce.Shared.Infrastructure.Initialization
{
 [ModuleDependency(typeof(ServiceContainerInitialization))]
 [InitializableModule]
 public class SharedDependencyInitialization : IConfigurableModule
 {
  private bool _isInitialized;

  public void Initialize(InitializationEngine context)
  {
   if (_isInitialized)
    return;

      SyncMetaObjects();

                    _isInitialized = true;
  }

  public void Uninitialize(InitializationEngine context)
  {
   _isInitialized = false;
  }

  public void ConfigureContainer(ServiceConfigurationContext context)
  {
   context.Container.Configure(ctx => ctx.AddRegistry<SharedDependencyRegistry>());
  }

  private static void SyncMetaObjects()
     {
            new MetaClassAndFieldsSyncInitializer(MetaDataContext.Instance).Sync();
        }
 }
}

Wednesday, December 7, 2016

Using ExpandoObject with Dictionary to get rid of magic string

In most of the project we used the dictionary and along with dictionary there comes magic string.

In order to get rid of the magic string from code I utilize the ExpandoObject to add/update dictionary item by converting ExpandoObject into dynamic instance.

Usage

 
     Dictionary<string, string> dictoinary = new Dictionary<string, string>();
     dictoinary["FName"] = "Murtaza";
     dictoinary["LName"] = "Ali";
     dictoinary["DOB"] = "1/1/1001";

     var dynamicDictioinary = new DictionaryExpando<string>(dictoinary) as dynamic ;
     dynamicDictioinary.Gender = "Definitely Not Female";
     Console.WriteLine(dynamicDictioinary.FName);
     dynamicDictioinary.FName = "Murtaza Override";
     Console.WriteLine(dynamicDictioinary.FName);
     Console.WriteLine(dynamicDictioinary.Gender);

DictionaryExpando Class

 
    [Serializable]
    class DictionaryExpando<TValue> : DynamicObject
    {
        /// <summary>
        /// Instance of object passed in
        /// </summary>
        public Dictionary<string, TValue> Instance;

        /// <summary>
        /// Cached type of the instance
        /// </summary>
        private Type InstanceType;

        private PropertyInfo[] InstancePropertyInfo
        {
            get
            {
                if (_InstancePropertyInfo == null && Instance != null)
                    _InstancePropertyInfo =
                        Instance.GetType()
                                .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
                return _InstancePropertyInfo;
            }
        }

        private PropertyInfo[] _InstancePropertyInfo;


        public Dictionary<string,TValue> Properties = new Dictionary<string, TValue>();


        /// <summary>
        /// Allows passing in an existing instance variable to 'extend'.        
        /// </summary>
        /// <remarks>
        /// You can pass in null here if you don't want to 
        /// check native properties and only check the Dictionary!
        /// </remarks>
        /// <param name="instance"></param>
        public DictionaryExpando(Dictionary<string, TValue> instance)
        {
            Initialize(instance);
        }


        protected virtual void Initialize(Dictionary<string, TValue> instance)
        {
            Instance = instance;
            if (instance != null)
                InstanceType = instance.GetType();
        }


        /// <summary>
        /// Try to retrieve a member by name first from instance properties
        /// followed by the collection entries.
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = default(TValue);

            // first check the Properties collection for member
            if (Properties.Keys.Contains(binder.Name))
            {
                result = Properties[binder.Name];
                return true;
            }


            // Next check for Public properties via Reflection
            if (Instance != null)
            {
                try
                {
                    return GetProperty(Instance, binder.Name, out result);
                }
                catch
                {
                }
            }

            // failed to retrieve a property
            result = null;
            return false;
        }


        /// <summary>
        /// Property setter implementation tries to retrieve value from instance 
        /// first then into this object
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {

            // first check to see if there's a native property to set
            if (Instance != null)
            {
                try
                {
                    bool result = SetProperty(Instance, binder.Name, value);
                    if (result)
                        return true;
                }
                catch
                {
                }
            }

            // no match - set or add to dictionary
            Properties[binder.Name] = (TValue)value;
            return true;
        }

        /// <summary>
        /// Dynamic invocation method. Currently allows only for Reflection based
        /// operation (no ability to add methods dynamically).
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (Instance != null)
            {
                try
                {
                    // check instance passed in for methods to invoke
                    if (InvokeMethod(Instance, binder.Name, args, out result))
                        return true;
                }
                catch
                {
                }
            }

            result = null;
            return false;
        }


        /// <summary>
        /// Reflection Helper method to retrieve a property
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="name"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        protected bool GetProperty(object instance, string name, out object result)
        {
            if (instance == null)
                instance = this;
            result = default(TValue);
            try
            {
                var dictonary = instance as Dictionary<string, TValue>;
                result = dictonary[name];
                return true;

            }
            catch (Exception)
            {
            }

            return false;
        }

        /// <summary>
        /// Reflection helper method to set a property value
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        protected bool SetProperty(object instance, string name, object value)
        {
            if (instance == null)
                instance = this;

            try
            {
                Dictionary<string, TValue> dictionary = instance as Dictionary<string, TValue>;
                dictionary[name] = (TValue)value;
                return true;
            }
            catch (Exception)
            {
            }

            return false;
        }

        /// <summary>
        /// Reflection helper method to invoke a method
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="name"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        protected bool InvokeMethod(object instance, string name, object[] args, out object result)
        {
            if (instance == null)
                instance = this;

            // Look at the instanceType
            var miArray = InstanceType.GetMember(name,
                                                 BindingFlags.InvokeMethod |
                                                 BindingFlags.Public | BindingFlags.Instance);

            if (miArray != null && miArray.Length > 0)
            {
                var mi = miArray[0] as MethodInfo;
                result = mi.Invoke(Instance, args);
                return true;
            }

            result = null;
            return false;
        }



        /// <summary>
        /// Convenience method that provides a string Indexer 
        /// to the Properties collection AND the strongly typed
        /// properties of the object by name.
        /// 
        /// // dynamic
        /// exp["Address"] = "112 nowhere lane"; 
        /// // strong
        /// var name = exp["StronglyTypedProperty"] as string; 
        /// </summary>
        /// <remarks>
        /// The getter checks the Properties dictionary first
        /// then looks in PropertyInfo for properties.
        /// The setter checks the instance properties before
        /// checking the Properties dictionary.
        /// </remarks>
        /// <param name="key"></param>
        /// 
        /// <returns></returns>
        public object this[string key]
        {
            get
            {
                try
                {
                    // try to get from properties collection first
                    return Properties[key];
                }
                catch (KeyNotFoundException ex)
                {
                    // try reflection on instanceType
                    object result = null;
                    if (GetProperty(Instance, key, out result))
                        return result;

                    // nope doesn't exist
                    throw;
                }
            }
            set
            {
                if (Properties.ContainsKey(key))
                {
                    Properties[key] = (TValue)value;
                    return;
                }

                // check instance for existance of type first
                var miArray = InstanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty);
                if (miArray != null && miArray.Length > 0)
                    SetProperty(Instance, key, value);
                else
                    Properties[key] = (TValue)value;
            }
        }


        /// <summary>
        /// Returns and the properties of 
        /// </summary>
        /// <param name="includeProperties"></param>
        /// <returns></returns>
        public IEnumerable<KeyValuePair<string, object>> GetProperties(bool includeInstanceProperties = false)
        {
            if (includeInstanceProperties && Instance != null)
            {
                foreach (var prop in this.InstancePropertyInfo)
                    yield return new KeyValuePair<string, object>(prop.Name, prop.GetValue(Instance, null));
            }

            foreach (var key in this.Properties.Keys)
                yield return new KeyValuePair<string, object>(key, this.Properties[key]);

        }


        /// <summary>
        /// Checks whether a property exists in the Property collection
        /// or as a property on the instance
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(KeyValuePair<string, object> item, bool includeInstanceProperties = false)
        {
            bool res = Properties.ContainsKey(item.Key);
            if (res)
                return true;

            if (includeInstanceProperties && Instance != null)
            {
                foreach (var prop in this.InstancePropertyInfo)
                {
                    if (prop.Name == item.Key)
                        return true;
                }
            }

            return false;
        }

    }



Instance

The Isntance property will return the real dictionary object to perform additional operations if required. You may need convert dynamic object back into ExpandoObject

 public Dictionary<string, TValue> Instance;
  

TryGetMember and GetProperty

TryGetMember is overridden method of ExpandoObject in order to fetch the value of provided propertyname. The dynamic property name can be retrieved from binder.Name.

GetProperty fetches the value from dictionary for provided key.

 
     public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = default(TValue);

            // first check the Properties collection for member
            if (Properties.Keys.Contains(binder.Name))
            {
                result = Properties[binder.Name];
                return true;
            }


            // Next check for Public properties via Reflection
            if (Instance != null)
            {
                try
                {
                    return GetProperty(Instance, binder.Name, out result);
                }
                catch
                {
                }
            }

            // failed to retrieve a property
            result = null;
            return false;
        }

  protected bool GetProperty(object instance, string name, out object result)
        {
            if (instance == null)
                instance = this;
            result = default(TValue);
            try
            {
                var dictonary = instance as Dictionary<string, TValue>;
                result = dictonary[name];
                return true;

            }
            catch (Exception)
            {
            }

            return false;
        }


TrySetMember and SetProperty

TrySetMember is overridden method of ExpandoObject in order to set the value of provided propertyname. The dynamic property name can be retrieved from binder.Name.

SetProperty sets the value to dictionary for provided key

 
 public override bool TrySetMember(SetMemberBinder binder, object value)
        {

            // first check to see if there's a native property to set
            if (Instance != null)
            {
                try
                {
                    bool result = SetProperty(Instance, binder.Name, value);
                    if (result)
                        return true;
                }
                catch
                {
                }
            }

            // no match - set or add to dictionary
            Properties[binder.Name] = (TValue)value;
            return true;
        }

      protected bool SetProperty(object instance, string name, object value)
        {
            if (instance == null)
                instance = this;

            try
            {
                Dictionary<string, TValue> dictionary = instance as Dictionary<string, TValue>;
                dictionary[name] = (TValue)value;
                return true;
            }
            catch (Exception)
            {
            }

            return false;
        }


Note: If you wan to use the remove function of dictionary then you may still need to use the magic string to remove the key from dictionary.

Saturday, December 3, 2016

Make Every Page In EPiServer As Draggable As Single Tile

In one of our EPiServer Project we wanted to make every page as draggable and wanted to make them appear as single tile. Therefore, I came up with approach, In this approach we will make every base page available to implement ISingleTileView interface and we would register one template coordinator for given Interface.

ISingleTileView


 public interface ISingleTileView
    {
        string Title { get; }
        string TileDescription { get; }
        string ImageUrl { get; }
        string LinkToUrl { get; }
    }


We had 5 different base classes for CMS and ECommerce. All of them are listed below.

  1. SitePageData
  2. SiteBlockData
  3. BaseVariantContent
  4. BaseProductContent
  5. BaseCategory
The Class definitions of above all classes are defined as

SitePageData

 
 public abstract class SitePageData : PageData, ISingleTileView
    {
        public string Title { get; }
        public string TileDescription { get; }
        public string ImageUrl { get; }
        public string LinkToUrl { get; }
    }


SiteBlockData

 
 public abstract  SiteBlockData : BlockData, ISingleTileView
    {
        public string Title { get; }
        public string TileDescription { get; }
        public string ImageUrl { get; }
        public string LinkToUrl { get; }
    }


BaseVariantContent

 
public class BaseVariantcContent : VariationContent, ISingleTileView
    {
        public string Title { get; }
        public string TileDescription { get; }
        public string ImageUrl { get; }
        public string LinkToUrl { get; }
    } 

BaseProductContent

 
public class BaseProductContent : ProductContent, ISingleTileView
    {
        public string Title { get; }
        public string TileDescription { get; }
        public string ImageUrl { get; }
        public string LinkToUrl { get; }
    }


BaseCategory

 
   public class BaseCategory : NodeContent, ISingleTileView
    {
        public string Title { get; }
        public string TileDescription { get; }
        public string ImageUrl { get; }
        public string LinkToUrl { get; }
    }

You can populate above properties with what ever other properties.

We have used below CMS properties to return value of ISingleTileView properties

Assigning Value To ISingleTileView Properties

 
        #region ISingle Tile View

        [Ignore]
        [ScaffoldColumn(false)]
        public string Title
        {
            get { return Heading; }
        }

        [Ignore]
        [ScaffoldColumn(false)]
        public string TileDescription
        {
            get { return Abstract.ToHtmlString(); }
        }

        [Ignore]
        [ScaffoldColumn(false)]
        public string ImageUrl
        {
            get { return this.GetDefaultAsset(AssetMediaNames.THUMBNAIL).GetFriendlyUrl(); }
        }

        [Ignore]
        [ScaffoldColumn(false)]
        public string LinkToUrl
        {
            get { return ContentLink.GetFriendlyUrl(); }
        }

        #endregion

TemplateCoordinator

 
  [ServiceConfiguration(typeof(IViewTemplateModelRegistrator))]
    public class TemplateCoordinator : IViewTemplateModelRegistrator
    {
        public const string BlockFolder = "~/Views/Shared/Blocks/";
        public const string PagePartialsFolder = "~/Views/Shared/PagePartials/";

        public void Register(TemplateModelCollection viewTemplateModelRegistrator)
        {
            // All Pages will be Single Tile View will rendered below Partial view
            viewTemplateModelRegistrator.Add(typeof(ISingleTileView), new TemplateModel()
            {
                Name = "Single Tile View For Any Page",
                Inherit = true,
                Tags = new[] { Constants.TemplateDescriptorTags.SingleTile },
                Path = BlockPath("Single.Tile.cshtml"),
                TemplateTypeCategory = TemplateTypeCategories.MvcPartialView,
                AvailableWithoutTag = true,
                Default = false
            });

        public static string BlockPath(string fileName)
        {
            return string.Format("{0}{1}", BlockFolder, fileName);
        }

        public static string PagePartialPath(string fileName)
        {
            return string.Format("{0}{1}", PagePartialsFolder, fileName);
        }
    }


SingleTile Partial View

In the following path we have our sinlgetileview partial view ~/Shared/Blocks/Single.Tile.cshtml

 
@using EPiServer.Reference.Commerce.Site.Core.Settings
@using EPiServer.Reference.Commerce.Site.Infrastructure.Extensions
@model EPiServer.Reference.Commerce.Site.Core.ContentTypes.TemplateCoordinatorInterface.ISingleTileView
<li>
    <div class="product-item">
<div class="product-item-image" style="background-image: url(@Html.ResizeImageUrl(Model.ImageUrl, preset: AssetMediaNames.ImageResizerPresets.PRODUCT_RANGE_TILE));">
<img alt="@Model.Title" src="@Html.ResizeImageUrl(Model.ImageUrl, preset: AssetMediaNames.ImageResizerPresets.PRODUCT_RANGE_TILE)" />
        </div>
<h2>
@Model.Title</h2>
<div>
@Html.Raw(Model.TileDescription)
        </div>
<a class="btn btn-default" href="@Model.LinkToUrl">More info</a>
    </div>
</li>


Entity Framework Code First Custom Database Initialization and Check if Table Already Exists

In one of our Project we had to use existing database and we wanted to use the code first entity framework as well.

There was requirement to make sure that some of the tables are already existed and all the new ones will be created using the code first approach.

Therefore, I decided to customized the MigrateDatabaseToLatestVersion.

First we will verify that the prerequisite tables are already there and then based on the auto update app setting we will migrate database to latest version.

GiftVoucherDatabaseInitialization 


public class GiftVoucherDatabaseInitialization : MigrateDatabaseToLatestVersion<GiftVoucherEntityTypesConfigurationDbContext, Migrations.Configuration>
    {
        private string IsCodeFirstAutoMigrationEnabled = "IsCodeFirstAutoMigrationEnabled";

        public GiftVoucherDatabaseInitialization(string connectionStringName) : base(connectionStringName)
        {
        }

        public override void InitializeDatabase(GiftVoucherEntityTypesConfigurationDbContext context)
        {
            var queryString = @"
                         SELECT 1 FROM sys.tables AS T
                         INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id
                         WHERE S.Name = 'dbo' AND T.Name = '{0}'";
            if (context.Database.Exists())
            {
                bool exists = context.Database
                      .SqlQuery<int?>(string.Format(queryString, "tbl_PurchaseVoucher"))
                      .SingleOrDefault() != null;

                if(!exists)
                    throw new ContentNotFoundException("Business Foundataion Entity{PurchaseVoucher} Not Found");

                exists = context.Database
                      .SqlQuery<int?>(string.Format(queryString, "tbl_PurchaseVoucherRedemption"))
                      .SingleOrDefault() != null;

                if (!exists)
                    throw new ContentNotFoundException("Business Foundation Entity{PurchaseVoucherRedemption} Not Found");
            }

            if (IsMigrationEnabled())
            {
                base.InitializeDatabase(context);
            }
        }

        private bool IsMigrationEnabled()
        {
            var migration = ConfigurationManager.AppSettings[IsCodeFirstAutoMigrationEnabled];
            return migration != null && migration.ToLower().Equals("true");
        }
    }

In Global.asax file, we will set the database initializer for above context

Setting Up Database Initializer


        protected void Application_Start()
        {
            // Add the database migration strategy to update the database on app start
            Database.SetInitializer(new GiftVoucherDatabaseInitialization(ConnectionStringConfig.CommerceConnectionStringName));

            // Run the initializer, but don't force
            new GiftVoucherEntityTypesConfigurationDbContext().Database.Initialize(true);
        }


GiftVoucherEntityTypesConfigurationDbContext

The GiftVoucherEntityTypesConfigurationDbContext looks like below



public class GiftVoucherEntityTypesConfigurationDbContext : DbContext
    {
        private readonly ICustomerContextFacade _customerContextFacade;

        public GiftVoucherEntityTypesConfigurationDbContext()
      : this("EcfSqlConnection", null)
     {
      
     }

        public GiftVoucherEntityTypesConfigurationDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
            
        }

        public DbSet<PurchaseVoucher> PurchaseVouchers { get; set; }
  public DbSet<PurchaseVoucherRedemption> PurchaseVoucherRedemptions { get; set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
   // Dynamically load all configuration
   var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => !string.IsNullOrEmpty(type.Namespace))
                .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
            foreach (var type in typesToRegister)
            {
                dynamic configurationInstance = Activator.CreateInstance(type);
                modelBuilder.Configurations.Add(configurationInstance);
            }
            
            base.OnModelCreating(modelBuilder);
        }
     }