Tag : sitecore

Autowiring XConnect Models

I’ve been playing around with XConnect for the last few weeks and so far I like what I see.

While playing around with custom models, I noticed that model schemas need to be manually registered. While not actually a big deal, this is an additional step that can be forgotten. I prefer to eliminate these “forgettable” tasks. I sought to determine a way of automatically wiring in these models. I’m pretty handy with reflection and figured if I could tag these models with an attribute, I could figure something out.

The Standard Configuration

For starters, let’s take a look at the configuration to see how you would normally add a model:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
    <xconnect>
    <runtime type="Sitecore.XConnect.Client.Configuration.RuntimeModelConfiguration,Sitecore.XConnect.Client.Configuration">
        <schemas hint="list:AddModelConfiguration">
            <!-- value of 'name' property must be unique -->
            <schema name="documentationmodel" type="Sitecore.XConnect.Client.Configuration.StaticModelConfiguration,Sitecore.XConnect.Client.Configuration" patch:after="schema[@name='collectionmodel']">
                <param desc="modeltype">Documentation.Model.CollectionModel, Documentation.Model</param>
            </schema>
        </schemas>
    </runtime>
    </xconnect>
</sitecore>
</configuration>

Let’s break this down a bit. RuntimeModelConfiguration is the runtime model the XConnect client uses. It has a public method AddModelConfiguration This method is called in the XML at runtime. This entire config block essentially translates to:

var runtime = new RuntimeModelConfiguration();
var documentationmodel = new StaticModelConfiguration();
runtime.AddModelConfiguration(modeltype: documentationmodel);

The Model

First, I need an attribute:

public class XModelAttribute : Attribute
{
}

That was easy.
Now I need a model:

[XModel]
public class TestModel
{
    public static XdbModel Model => BuildModel();
    public static string ModelName => typeof(TestModel).FullName;
    protected static XdbModelVersion ModelVersion => new XdbModelVersion(0, 1);

    private static XdbModel BuildModel()
    {
        var builder = new XdbModelBuilder(ModelName, ModelVersion);
        builder.ReferenceModel(CollectionModel.Model);
        return builder.BuildModel();
    }
}

The above is a sample model. I used the dev docs as an example, but trimmed it down a bit.

The Registration

I wanted to customize as little as possible to make upgrades a bit easier. I also wanted to ensure compatibility in case someone wanted to manually register models. I decided the cleanest way to do this would be to extend the RuntimeModelConfiguration

public class CustomRuntimeModelConfiguration : RuntimeModelConfiguration
{
    public CustomRuntimeModelConfiguration()
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        {
            var types = assembly.GetTypes().Where(t => t.IsDefined(typeof(XModelAttribute), true));
            foreach (var type in types)
            {
                AddModelConfiguration(new StaticModelConfiguration($"{type.FullName}, {type.Assembly}"));
            }
        }
    }
}

When this configuration is initialized, it scans the current assemblies for any classes decorated with the XModel attribute, and creates a new StaticModelConfiguration for it. It’s then added to the runtime model.

Remember this from before?

var runtime = new RuntimeModelConfiguration();
var documentationmodel = new StaticModelConfiguration();
runtime.AddModelConfiguration(modeltype: documentationmodel);

We’re doing the same thing now, but from the constructor.

All that’s left is to patch the config so that we’re using the Custom runtime config.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <xconnect>
            <runtime type="Sitecore.XConnect.Client.Configuration.RuntimeModelConfiguration,Sitecore.XConnect.Client.Configuration">
                <patch:attribute name="type" value="Foundation.XConnect.CustomRuntimeModelConfiguration, Foundation" />
            </runtime>
        </xconnect>
    </sitecore>
</configuration>

The Conclusion

Our custom XConnect models will now autowire themselves! The actual customization is fairly light. Manual registration is still possible, as well.

Now you’re probably wondering, “isn’t tagging these classes with an attribute is almost as forgettable as adding an entry to a config file?”

Well… yeah, technically. At least it’s in the same file now, though!

What Helix is NOT

Last week I had the pleasure of attending a workshop on Helix, presented by Derek Correia. The mindset I had going into this was “I’ve seen the presentations, I’ve been working with Helix a bit, this will be nothing new.” I was pleasantly surprised to be wrong.

If you’re not already aware, Helix is Sitecore’s recommended set of design principles. You can see a live example of Helix by checking out the Habitat demo. Helix is impressively well documented and I recommend checking it out.

Rather than talk about what Helix is, I want to focus on a list Derek presented of what Helix is NOT.

  • Helix is NOT System Architecture
  • Helix is NOT Software Design Patterns
  • Helix is NOT about reusability

Helix is NOT System Architecture

There are a whole lot of great tools out there to suit many project needs, but Helix doesn’t force you to use any of them. Sure, the Habitat demo has integrations with Unicorn for item serialization. There’s probably some form of CI that’s building it. Do you have to use any of these? No. Hedgehog has made some changes to TDS to allow it to integrate with Helix more easily. Want to use Jenkins for CI? What about TeamCity? You pick. I recommend TeamCity, but what do I know about what’s best for you? There are tons of tools out there for many different tasks – go with what you’re comfortable working with.

Helix is NOT Software Design Patterns

Derek talked a lot about this. Helix doesn’t force you to write code one way or another. There are a few recommendations, however. I’d recommend you check out “Clean Code” by Robert C. Martin. The only thing Helix asks is that you respect boundaries. Features shouldn’t reference other features or Projects. Foundations shouldn’t reference Features or Projects. That’s the key. How you write the code within these modules is up to you.

Helix is NOT About Reusability

This one was the most interesting one to me, and it raised a few eyebrows at the workshop. I was under the impression that the modularity of Helix was to promote DRY code that can be taken from one solution to the next. If I make a “Search Module,” the goal of that module was to be fully self-contained so that it can be reuseTaking on Needless Dependenciesd elsewhere. That is not the case. Helix is about untangling dependencies and making solutions readable.

A side-effect about containing things within a single module is that it could encourage you to write the same code more than once. The simple solution is to refactor reusable code into a Foundation module, however the Helix architecture doesn’t state that you need to. For the Helix architecture, it’s perfectly acceptable to write the same code in two difference places to reduce the dependencies. To be clear, that’s not something I’d recommend. I’d encourage identifying similar functionality and creating a Foundation module for that instead.

 

 

 

 

 

 

Final Thoughts

I found the workshop more interesting than I thought I was going to. I applaud Sitecore for setting this up and look forward to more of them in the future. Derek did a great job with the presentation and kept everybody pretty engaged.

Sitecore Query Cheat Sheet

Since starting with Sitecore, I’ve had a copy of John West’s “Sitecore Query Cheat Sheet” Word Doc file on my hard drive, ready at a moment’s notice. It has been one of the most helpful documents I’ve found, and is pretty timeless.

What is this document, you ask? Well, it contains some very helpful information for building XPath / Sitecore queries. It gives examples of common standard template fields, common standard item attributes, operators, axes, supported functions, and more.

I’ve noticed that recently, the link seems to have gone dead. Since this is such an important document, I wanted to make sure that others can find it. The Wayback Machine from Archive.org has thankfully saved a copy of the document. If you’re looking for it, you can download it here.

 

Custom Product Sync Button

Today’s adventure is about custom ribbon buttons, asynchronous commands, and progress dialogs.

One of my recent projects deals with a 3rd party integration with a PIM (Product Information Management) system and Sitecore. This is not a very heavy integration. The PIM is the system of authority when it comes to a product’s description, price, color options, sizes, etc. Sitecore obtains this information via a REST API provided by the PIM. This information is fetched and transformed into Sitecore items, which we then store in a bucketed “Products” folder.

While this information doesn’t change very frequently, it can change. The client needed to have some way to trigger an update. I wanted to keep everything in Sitecore, rather than have some sort of custom admin page, which was the initial suggestion. I broke it down into 2 goals:

  1. Create some sort of button in Sitecore
  2. Create some sort of feedback dialog so the user knew what was happening

 

I started off creating the custom ribbon. There are many posts out there about how to create ribbons. I’ll add my favorite to the end of this post.

Custom Ribbon

With the pretty part out of the way, I moved on to wiring that button in.

The first step is building out the “Are you sure?” prompt:

public class ProductSyncCommand : Command
{
    // Sitecore will call Execute. This is the entry point.
    public override void Execute(CommandContext context)
    {
        // Here we're creating the dialog prompt to confirm the user's actions
        Sitecore.Context.ClientPage.Start(this, nameof(DialogProcessor));
    }
	public void DialogProcessor(ClientPipelineArgs args)
	{
		if (!args.IsPostBack)
		{
			SheerResponse.YesNoCancel("Are you sure you want to sync products?", "500px", "200px", true);
			args.WaitForPostBack(true);
		}
		else
		{
		    // TODO
		}
	}
}

The result is a pretty prompt, that doesn’t actually do anything:

Are you sure?

Next step is to add in logic for syncing the products.

private void SyncProducts(object[] parameters)
{
        // Tell Sitecore the job is Running 
        Sitecore.Context.Job.Status.State = JobState.Running;
	// Instead of using security disabler, I setup a dedicated account specifically for this
	using (new UserSwitcher("sitecore\\ProductJobAccount", true))
	{
		var products = GetAllProducts(); // some method that gets all products from the PIM

                // performance boost on creating items
		using (new BulkUpdateContext())
		{
			// Iterate through each product
			foreach (var product in products)
			{
				ProcessProduct(product); // Some method to handle adding/updating/removing this product to/from Sitecore
			}
		}
		RefreshIndex(); // some method that refreshes the tree from the bucket that the products are stored in
		try
		{
			PublishItems(); // some method that publishes the bucket that products are stored in
		}
		catch (Exception ex)
		{
			Log.Error(ex.Message, ex, this);
		}
	}
        // Tell Sitecore the job has finished
        Sitecore.Context.Job.Status.State = JobState.Finished;
}

We’ll also replace //TODO from earlier with:

    ProgressBox.Execute("Sync&nbsp;Products",&nbsp;"Sync&nbsp;Products",&nbsp;SyncProducts);
    SheerResponse.Alert("Product&nbsp;synchronization&nbsp;has&nbsp;completed");

At this point, we’ve wired in all the logic to do the following:

  1. switch into the “sitecore\\ProductJobAccount” (Security reasons. See side note below)
  2. Get all products from the PIM
  3. Enter a BulkUpdateContext. BulkUpdateContext speeds up item creation
  4. Iterate through each product and perform whatever business logic is in ProcessProduct();
  5. Refresh the indexes for the bucket tree
  6. Publish the items to the front end

As a side note: instead of using SecurityDisabler(), I recommend creating an account whose sole purpose is to do a job like this. This is better from a security standpoint, as you can ensure the account only has permission to do the job. It also allows for better audit trails.

Currently, this would show a progress bar, but provide no further feedback. I want to take this a step further, and provide step-by-step feedback to the user. To go about doing this, you can add messages to the job status. These messages appear on the progress bar.

Here’s the finished product:

public class ProductSyncCommand : Command
{
	public override void Execute(CommandContext context)
	{
		Sitecore.Context.ClientPage.Start(this, nameof(DialogProcessor));
	}

	public void DialogProcessor(ClientPipelineArgs args)
	{
		if (!args.IsPostBack)
		{
			SheerResponse.YesNoCancel("Are you sure you want to sync products?", "500px", "200px", true);
			args.WaitForPostBack(true);
		}
		else
		{
			if (args.Result == "yes")
			{
				ProgressBox.Execute("Sync Products", "Sync Products", SyncProducts);
				SheerResponse.Alert("Product synchronization has completed");
			}
		}
	}

	private void SyncProducts(object[] parameters)
	{
		Job job = Sitecore.Context.Job;
		job.Status.State = JobState.Running;
		// Instead of using security disabler, I setup a dedicated account specifically for this
		using (new UserSwitcher("sitecore\\ProductJobAccount", true))
		{
			job.Status.Messages.Add("Beginning fetch");
			var products = GetAllProducts(); // some method that gets all products from the PIM
			job.Status.Messages.Add("Fetch complete");

			// performance boost on creating items
			using (new BulkUpdateContext())
			{
				job.Status.Messages.Add("Syncing Products...");
				foreach (var product in products)
				{
					ProcessProduct(product); // Some method to handle adding/updating/removing this product to/from Sitecore
					job.Status.Processed++;
					job.Status.Messages.Add("Processed item " + job.Status.Processed);
				}
			}
			job.Status.Messages.Add("Sync complete...");
			job.Status.Messages.Add("Rebuilding indexes...");
			RefreshIndex(); // some method that refreshes the tree from the bucket that the products are stored in
			job.Status.Messages.Add("Done rebuilding indexes...");

			try
			{
				job.Status.Messages.Add("Publishing items...");
				PublishItems(); // some method that publishes the bucket that products are stored in
				job.Status.Messages.Add("Publish complete...");
			}
			catch (Exception ex)
			{
				Log.Error(ex.Message, ex, this);
			}
		}
		job.Status.Messages.Add("Job complete!");
		job.Status.State = JobState.Finished;
	}
}

You’ll see I added several calls to job.Status.Messages.Add(). These messages display one after the other on the progress bar prompt. When it runs, it’ll look like this:

Progress Dialog

The finished product met the business requirements, and fulfilled the goals I had set for the feature as well. The client now has the ability to trigger a product sync at the click of a button.

Useful Resources:

 

Update:

Since the writing of this post, the BulkUpdateContext and RefreshIndexes has been removed from the solution. We were running into issues keeping the indexes in sync, and removing BulkUpdateContext seems to have made it a little slower, but more consistent