Custom page for schedule job status

Our customer asked for a custom dashboard that shows uses data sources, including ‘Schedule Job’ history. To achieve this we are going to use:

IScheduledJobRepository

And implementation would be quite easy:

[HttpPost]
public async Task<ActionResult> ImportHistory()
{
    var importUsersJobType = typeof(ImportUsersJob);

    var job = scheduledJobRepository
        .List()
        .FirstOrDefault(j => j.TypeName == $"{importUsersJobType.Namespace}.{importUsersJobType.Name}");

    var result = await scheduledJobLogRepository.GetAsync(job.ID, 0, int.MaxValue);

    return Json(result.PagedResult.Select(a => new
    {
        CompletedUtc = a.CompletedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"),
        a.Message,
        Status = a.Status.ToString()
    }));
}

The schedule job type is ‘ImportUserJob’, and we are getting history data of the execution of the job:

var result = await scheduledJobLogRepository.GetAsync(job.ID, 0, int.MaxValue);

And finally return back JSON object of exection data back to FED:

return Json(result.PagedResult.Select(a => new
    {
        CompletedUtc = a.CompletedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"),
        a.Message,
        Status = a.Status.ToString()
    }));

PS: Scott Reed mentioned there is a plugin to overview all of your scheduled jobs please find it here

Custom LineItemValidator

Episerver has quite nice mechanism on validating cart, line item, shipment , , promotion and inventory. I was looking into customizing the line item validtion, to inject some business logic. To do this:

  1. First create new class and inherit from ILineItemValidator
   public class CustomLineItemValidator : ILineItemValidator
    {
        private readonly ILineItemValidator defaultImplementation;
        private readonly IOrderRepository orderRepository;

        public CustomLineItemValidator(ILineItemValidator defaultImplementation,
            IOrderRepository orderRepository)
        {
            this.defaultImplementation = defaultImplementation;
            this.orderRepository = orderRepository;
        }

        [Obsolete]
        public bool Validate(ILineItem lineItem, IMarket market, Action<ILineItem, ValidationIssue> onValidationError)
        {
            if (AlreadyPurchased(lineItem))
            {
                onValidationError(lineItem, ValidationIssue.RemovedDueToUnavailableItem);

                return false;
            }

            return defaultImplementation.Validate(lineItem, market, onValidationError);
        }

        public bool Validate(ILineItem lineItem, MarketId marketId, Action<ILineItem, ValidationIssue> onValidationError)
        {
            if (AlreadyPurchased(lineItem))
            {
                onValidationError(lineItem, ValidationIssue.RemovedDueToUnavailableItem);

                return false;
            }

            return defaultImplementation.Validate(lineItem, marketId, onValidationError);
        }

        private bool AlreadyPurchased(ILineItem lineItem)
        {
            var alreadyPurchased = false;
            var customerId = lineItem?.ParentOrderGroup?.CustomerId;

            if (customerId != null)
            {
                var orders = orderRepository.Load<IPurchaseOrder>(customerId.Value);

                if (orders != null)
                {

                    alreadyPurchased = orders.Any(a => a.GetAllLineItems()
                                                            .Any(l => string.Equals(l.Code,
                                                                                    lineItem.Code,
                                                                                    StringComparison.InvariantCultureIgnoreCase)));

                }
            }

            return alreadyPurchased;
        }
    }

In our example above, we are checking to make sure if the user already purchased the item before and if that is a case we return false as a result of validation. It may be weird but our customer has this requirement and it is meaningful in their context. As you can see we are re-using existing line item validator as well.

2. Next step is to inject our new validator to the DI:

    [InitializableModule]
    public class DependencyResolverInitialization : IConfigurableModule
    {
        private static readonly ILogger logger = LogManager.GetLogger();

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            var services = context.Services;

            services.Intercept<ILineItemValidator>(
                (locator, defaultImplementation) =>
                    new CustomLineItemValidator(defaultImplementation,
                                locator.GetInstance<IOrderRepository>()));
        }
    }

As you can see on DependencyResolverInitialization class, I added a new section to intercept the ILineItemValidator. If you are not sure what I’m speaking of you can read more about it here.

If you are having ‘Commerce Manager’ make sure you do same thing in both CommerceManager and Shopfront projects!

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 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!

Wrap Up – Third Sydney Episerver Meetup

First I want to say thanks @Nicola and 1990 Digital and Episerver for hosting this session. I learned a lot and I’m started starving for the next meetup!

 

Nicola spoke about her new tool ‘EpiserverCmsAudit’. This tool is really awesome and can help you to find the usage of your content type. Very nice dashboard and very easy to install and it is FREE! So if you to give it a go to her post to read more about it and you can take a look at the source code or just install the Nuget package This is quite a bit awesome while website moving to DXC as they can build many websites and many contents! So with this tool, you as a dev, tester or content editor can make sure your change impact on all aspect of your app! GREAT JOB!

 

I spoke about Episerver Headless CMS, eCommerce and how you can integrate that with Microsoft Web Bot. This would be important as Bots are more human-friendly – having said that the concept is still at a very early stage. The concept is quite simple, Web Bot gets the query from user pass to Luis.ai and passes the action to Episerver to get the data back and pass it back to Bot! If you feel like you need more info, you can download the presentation and the source code of the bot can be found here and the bot source code can be found here. If you are interested and still have a question just comment here!

I’m going to speak about this in more detail in DDD, so if you keen to learn more you just need to get the ticket!

How to pass configuration to your custom editor in EPiServer

Recently I’m working on a project which has a requirement. We need focal point selector for an image. So the idea is you pick a focal point and application based on requested image size crop the image based on selected focal point. So I store the focal point (x,y) as a comma separated in string.

public class ImageFile : ImageData
{
	[UIHint("FocalPoint")]
	public virtual string FocalPoint { get; set; }
}

Now we want make the EPiServer custom editor named “FocalPoint”. There are many post about this so I ignore this part but what I need to pass current image URL to be passed to my editor. So for doing this we need EditorDescriptor as below:


	[EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "FocalPoint")]
	public class FocalPointEditorDescriptor : EditorDescriptor
	{
		public FocalPointEditorDescriptor()
		{
			this.ClientEditingClass = "alloy.editors.FocalPointSelector";
		}
		public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
		{
			base.ModifyMetadata(metadata, attributes);
		}
	}

Now we want to change the code to pass current image URL to editor, so editor can load the image and user can select the focal point. Code below is a magic which need to be injected into ModifyMetadata method.


			var contentDataMetadata = metadata as ContentDataMetadata;
			if (contentDataMetadata != null)
			{
				var imageFile = contentDataMetadata.OwnerContent as IContentMedia;
				if (imageFile != null)
				{
					metadata.EditorConfiguration["imageURL"] = UrlResolver.Current.GetUrl(imageFile.ContentLink);
				}
			}

And now we can access the value in our edit in JS:


define([
	"dojo/_base/connect",
	"dojo/_base/declare",
	"dijit/_CssStateMixin",
	"dijit/_Widget",
	"dijit/_TemplatedMixin",
	"dijit/_WidgetsInTemplateMixin",
	"epi/shell/widget/_ValueRequiredMixin",
	"dojo/dom",
	"dojo/on",
	"dojo/text!./templates/FocalPointSelector.html"],

	function (
		connect,
		declare,
		_CssStateMixin,
		_Widget,
		_TemplatedMixin,
		_WidgetsInTemplateMixin,
		_ValueRequiredMixin,
		dom,
		on,
		template) {

		return declare(
			"ras.editors.FocalPointSelector", [
				_Widget,
				_TemplatedMixin,
				_WidgetsInTemplateMixin,
				_CssStateMixin,
				_ValueRequiredMixin],
			{
				templateString: template,
				selector: null,
				intermediateChanges: false,
				image: null,
				value: null,
				onChange: function (value) { },
				postCreate: function () {
					this.image = dojo.create("img", { "style": "width:100%;height:100%", "src": this.imageURL }, this.domNode);
				}
			});
	});

And you can see “imageURL” is being passed to our javascript code and we can use it.

WCF RESTful .Net 4.0

In this post we want to introduce the project that hosts the WCF RESTful service and a client which is consuming the service with AJAX and jQeury. You need to have VS2010 and .Net 4.0 and being sure that you select the “.Net 4.0” when you want to create projects. So first step is creating new blank solution, then create new “WCF Service Application” and named it as “Server”:

Now have your service the only change that you need to apply is on web.config file. Let’s change it as below:

[cc lang=”XML” width=”100%”]

















[/cc]

Visual Studio has created the sample service, operation contract and data contract for you that should fine for our sample and your server for hosting RESTful service has been completed. Next step is to setup the client. You need to ensure that you have internet connection while loading the test client. Create new HTML file and place the code below as a content:

[cc lang=”HTML” width=”100%”]








[/cc]
Now run your service by pressing the “F5” in Visual Studio and open an “Internet Explorer” and open the created HTML file. Now the HTML code will contact the RESTful service and pass two values to the service (as a JSON object) and show the result value from server.

Easy, first WCF fully server and client

In this post I’m going to show how you easy steps to how you can create a server/client application base on WCF. Calling and receiving information from server to client and reverse. First of all create new solution called “ServiceHost”. Open Visual Studio 2010 and click on “File”->”New Project”:

And top section select “.Net Framework 4” and then from the “Installed Templates” select “Other Project Types” and then “Visual Studio Solutions”. On the right hand side select “Bank Solution”. Select the “Location” and solution name. Finally click on “OK”:

Now from the “File” menu click on “Add” and select “NewProject”:

From the list select “Class Library” and change the name to be “ServiceLibrary”:

 

From the solution explorer add a reference “System.ServiceModel” to the “ServiceLibrary” project.

Then:

 

From the “Solution Explorer” deleted automatically generated class file called “Class1.cs” and create new interface file called “ICalculator”:

 

Change the interface to become a public interface. Add extra using “System.ServiceModel” to the header, add “ServiceContract” attribute above the interface. Add new method called as below with “OperationContract” attribute.

Now create new class file called “Calculator”. Inherit the class from the “ICalculator” interface.

Now implement the interface as below:

 

From the menu “File”->”Add”->”New Project”, create a new console application project called “Server”:

And add the reference of “System.ServiceModel” to new created project called “Server”. Add another reference to “ServiceLibrary” project. Then build the project.

Now add new application configuration file to “Server” project.

Then:

Now build your project and you should get successful message from compiler. Now we need to configure the application to host the WCF service. So we need to change the app.config file first and introduce the service and configure it properly. For doing that open the “app.config” and change it to be like this:

 

This is the time for setting up your server runner. Open the “Solution Explorer”->”Server”->”Program.cs” and change it as below:

 

If you are using “Windows 7” or “Windows VISTA” when you want to start the application it may show you this message:

You can check the resolution here or easily run the “cmd.exe” in administrator mode and replace the command below and change the computer name and current user:

“netsh http add urlacl url=http://+:8082/ user={Computer Name}\{Current User}”

Now sever is ready and it is a time for building the client application. Add new “Console Application” project to your solution called “Client”. Then we need to add the reference of “Server” service to “Client” application. For doing that first run the server “Console Application” and make sure that service is running properly. For checking the server open an IE and change the address to http://localhost:8082 and should get the message as below:

 

After making sure that the service is working perfectly, for adding the “Service Reference” to “Client” application, right click on “Client” project and select “Add Service Reference”.

Submit the http://localhost:8082 to the address field and press the “Go” button”:

Then change the “Namespace” to “CalculatorClient” and press the “OK” button. Then open the “Client”->”Program.cs” and change it as below:

Make sure that the “Server” is running and then run the “Client”. You can the result as below:

 

You can download the sample from here