Using C# HttpClient() from Sync and Async code

The .NEt 4.5 C# System.Net.Http.HttpClient() is a very nice http client implementation, but can be tricky to use if you (like me) are not a trained asynchronous programming coder. So here is a quick cheat sheet on how to work around the Task<>, async and await methods when using the HttpClient().

EXAMPLE 1: HTTPCLIENT GET WITH RETURN VALUE:

THE GET METHOD:

public static async Task<string> Get(string queryString)
{
  using(var httpClient = new HttpClient())
  {
    string authUserName = "user"
    string authPassword = "password"
    string url = "https://someurl.com";

    // If you do not have basic authentication, you may skip these lines
    var authToken = Encoding.ASCII.GetBytes($"{authUserName}:{authPassword}");
    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));

    // The actual Get method
    using (var result = await httpClient.GetAsync($"{url}{queryString}"))
    {
      string content = await result.Content.ReadAsStringAsync();
      return content;
    }
  }
}

THE USAGE:

// From synchronous code
string queryString = "?hello=world";
string result = Get(queryString).Result;

// From asynchronous code
string queryString = "?hello=world";
string result = await Get(queryString);

 

EXAMPLE 2: HTTPCLIENT PUT “FIRE-AND-FORGET” WITHOUT RETURN VALUE:

THE PUT METHOD:

public static async Task Put(string postData)
{
  using(var httpClient = new HttpClient())
  {
    string authUserName = "user";
    string authPassword = "password"
    string url = "https://someurl.com";
        
    // If you have no basic authentication, you can skip thses lines
    var authToken = Encoding.ASCII.GetBytes($"{authUserName}:{authPassword}");
    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
    
    // The actual put method including error handling
    using(var content = new StringContent(postData))
    {
      var result = await httpClient.PutAsync($"{url}", content);
      if (result.StatusCode == HttpStatusCode.OK)
      {
        return;
      }
      else
      {
        // Do something with the contents, like write the statuscode and
        // contents to a log file
        string resultContent = await result.Content.ReadAsStringAsync();
        // ... write to log
      }
    }
  }
}

THE USAGE:

// You can call the method from asynchronous
// and it will actually run asynchronous. In this fire-and-forget 
// pattern, there is no need to wait for the answer
Put("data");

// ... but if you will wait, simply call ".Wait()":
Put("data").Wait();

MORE TO READ:

 

Posted in .net, c#, General .NET | Tagged , , | Leave a comment

Sitecore Postbacks not working? Contents not updated on button click? Try removing caching from your rendering

This is a classic Sitecore blunder – suddenly postbacks or button clicks have no effect, as if the web site no longer are connected to your webforms codebehind or the MVC renderings. And you have not changed any code.

But have you enabled caching of your rendering?

Sitecore Caching Settings

Sitecore Caching Settings

The Sitecore rendering cache is a true HTML cache. The output markup of the rendering is cached, and the contents of the cache is stored in one or more versions, depending of your choices:

  • Clear on Index Update: Cache cleared when the item is updated in the index
  • Vary By Data: One cache per URL
  • Vary By Device: One cache per device
  • Vary By Login: One cache for anonymous users, another for authenticated users
  • Vary By Parm: One cache per rendering parameter
  • Vary By Query String: One cache per query string parameter
  • Vary by User: On version per user

So a true HTML cache means that your code is called once, and the next time the rendering is rendered, the contents is a string-output operation of the markup stored in the cache. So there is no postbacks from Webforms. Or if you expect the MVC rendering to update the markup on a button click – that’s not going to happen.

Caching have 2 further properties:

  • Your caching settings can be overridden on the presentation details of an item.
  • If a cached rendering have a placeholder containing other renderings, the renderings in the placeholder is also cached.

BTW, you can always clear the cache: Check out the /sitecore/admin/cache.aspx page, or read about how to clear cache individually from code.

MORE TO READ:

 

Posted in Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , | Leave a comment

Sitecore SOLR error: Invalid Date in Date Math String

There seems to be an issue with certain combinations of Sitecore, SOLR and the local machine datetime settings. This is the error:

ManagedPoolThread #11 12:20:50 INFO Job started: Index_Update_IndexName=sitecore_master_index
ManagedPoolThread #11 12:20:50 ERROR Exception
Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.InvokeMethod(MethodInfo method, Object[] parameters, Object obj)
at Sitecore.Jobs.JobRunner.RunMethod(JobArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Jobs.Job.ThreadEntry(Object state)

Nested Exception

Exception: SolrNet.Exceptions.SolrConnectionException
Message: <?xml version=”1.0″ encoding=”UTF-8″?>
<response>
<lst name=”responseHeader”><int name=”status”>400</int><int name=”QTime”>46</int></lst><lst name=”error”><str name=”msg”>Invalid Date in Date Math String:’2017-03-20T13.56.31Z'</str><int name=”code”>400</int></lst>
</response>

Source: SolrNet
at SolrNet.Impl.SolrConnection.PostStream(String relativeUrl, String contentType, Stream content, IEnumerable`1 parameters)
at SolrNet.Impl.SolrConnection.Post(String relativeUrl, String s)
at SolrNet.Impl.SolrBasicServer`1.SendAndParseHeader(ISolrCommand cmd)
at Sitecore.ContentSearch.SolrProvider.SolrBatchUpdateContext.AddRange(IEnumerable`1 group, Int32 groupSize)
at Sitecore.ContentSearch.SolrProvider.SolrBatchUpdateContext.Commit()
at Sitecore.ContentSearch.AbstractSearchIndex.PerformUpdate(IEnumerable`1 indexableInfo, IndexingOptions indexingOptions)

Nested Exception

Exception: System.Net.WebException
Message: The remote server returned an error: (400) Bad Request.
Source: System
at System.Net.HttpWebRequest.GetResponse()
at HttpWebAdapters.Adapters.HttpWebRequestAdapter.GetResponse()
at SolrNet.Impl.SolrConnection.GetResponse(IHttpWebRequest request)
at SolrNet.Impl.SolrConnection.PostStream(String relativeUrl, String contentType, Stream content, IEnumerable`1 parameters)

The error is thrown by SOLR when a datetime string have an invalid format. SOLR only allows datetime strings in the format of YYYY-MM-DDThh:mm:ssZ.

Please note that in the error message above, the format is in fact wrong, as the time contains . instead of :

  • Wrong: 2017-03-20T13.56.31Z
  • Correct: 2017-03-20T13:56:31Z

My machine is configured so the time format contains . not :, and that is why SOLR gets the wrong format.

2017-10-25_13-10-27-3Sitecore have addressed this in patch 178247, where they modify the default System.DateTime converter to ignore any local culture info. The configuration change is simple:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <contentSearch>
      <indexConfigurations>
        <defaultSolrIndexConfiguration>
          <indexFieldStorageValueFormatter>
            <converters>
              <converter handlesType="System.DateTime" typeConverter="Sitecore.ContentSearch.Converters.IndexFieldUTCDateTimeValueConverter, Sitecore.ContentSearch" set:typeConverter="Sitecore.Support.ContentSearch.Converters.IndexFieldUTCDateTimeValueConverter, Sitecore.Support.178247"/>
            </converters>
          </indexFieldStorageValueFormatter>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

And the code change very small:

namespace Sitecore.Support.ContentSearch.Converters
{
  public class IndexFieldUTCDateTimeValueConverter : IndexFieldUtcDateTimeValueConverter
  {
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
      culture = CultureInfo.InvariantCulture;
      return base.ConvertTo(context, culture, value, destinationType);
    }
  }
}

Thanks to Sitecore support for the patch.

MORE TO READ:

Posted in Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , | Leave a comment

Sitecore separate users from CORE database – move membership provider to separate database

Since Sitecore 6, Sitecore have had the .NET membership tables in the CORE database. And since every instance of Sitecore needs access to the CORE database, it might seem as a good place to store them.

But separating the users from the CORE database will make upgrades easier, as you can have 2 instances of Sitecore running without disturbing user access. You also have more scalability options when separating the two.

The separation is easy:

STEP 1: MAKE A COPY OF THE CORE DATABASE

Get yourself a copy of your CORE database. Name the new database something user-membership-like, for example “Users“.

Never mind the Sitecore tables in the newly copied database. Unless you are in need of disk space, you can leave them. They will never be used.

STEP 2: ADD A CONNECTION STRING TO THE CONNECTIONSTRINGS.CONFIG FILE

In /App_Config/ConnectionStrings.Config, add a new connection string to your “Users” database:

<add name="Users" 
   connectionString="user id=*****;password=*****;Data Source=*****;Database=Users" />

STEP 3: MODIFY WEB.CONFIG

In the web.config, find the “membership“, “rolemanager” and “profile” sections, and modify the “connectionStringName” property from “core” to “Users“:

<membership defaultProvider="sitecore" hashAlgorithmType="SHA1">
  <providers>
    <clear />
    <add name="sitecore" type="Sitecore.Security.SitecoreMembershipProvider, Sitecore.Kernel" realProviderName="sql" providerWildcard="%" raiseEvents="true" />
    <add name="sql" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Users" applicationName="sitecore" minRequiredPasswordLength="1" minRequiredNonalphanumericCharacters="0" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" />
    <add name="switcher" type="Sitecore.Security.SwitchingMembershipProvider, Sitecore.Kernel" applicationName="sitecore" mappings="switchingProviders/membership" />
  </providers>
</membership>
<roleManager defaultProvider="sitecore" enabled="true">
  <providers>
    <clear />
    <add name="sitecore" type="Sitecore.Security.SitecoreRoleProvider, Sitecore.Kernel" realProviderName="sql" raiseEvents="true" />
    <add name="sql" type="System.Web.Security.SqlRoleProvider" connectionStringName="Users" applicationName="sitecore" />
    <add name="switcher" type="Sitecore.Security.SwitchingRoleProvider, Sitecore.Kernel" applicationName="sitecore" mappings="switchingProviders/roleManager" />
  </providers>
</roleManager>
<profile defaultProvider="sql" enabled="true" inherits="Sitecore.Security.UserProfile, Sitecore.Kernel">
  <providers>
    <clear />
    <add name="sql" type="System.Web.Profile.SqlProfileProvider" connectionStringName="Users" applicationName="sitecore" />
    <add name="switcher" type="Sitecore.Security.SwitchingProfileProvider, Sitecore.Kernel" applicationName="sitecore" mappings="switchingProviders/profile" />
  </providers>
  <properties>
    <clear />
    <add type="System.String" name="SC_UserData" />
  </properties>
</profile>

MORE TO READ:

 

Posted in Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , | 3 Comments

Sitecore Rule – Personalize based on any field in any facet in your Contact

This Sitecore Personalization Rule was developed by my colleague Martin Rygaard with the purpose of being able to personalize on any field in any facet on a contact.

Contact Facet Rule Set Editor

Contact Facet Rule Set Editor

STEP 1: CREATE THE CONDITION

Create a new “Condition” below /sitecore/system/Settings/Rules/Definitions/Elements/???

The text of the Condition is:

where the [facetpath,,,facetpath] has [facetvalue,,,facetvalue]

STEP 2: CREATE A WHENCONDITION

This condition traverses the Contact path and returns true if the value matches the value described:

using System.Collections;
using Sitecore.Analytics;
using Sitecore.Analytics.Model.Framework;
using Sitecore.Analytics.Tracking;
using Sitecore.Diagnostics;
using Sitecore.Rules;
using Sitecore.Rules.Conditions;

namespace MyNamespace
{
  public class ContactFacetHasValue<T> : WhenCondition<T> where T : RuleContext
  {
    public string FacetValue { get; set; }
    
    public string FacetPath { get; set; }

    protected override bool Execute(T ruleContext)
    {
        Contact contact = Tracker.Current.Session.Contact;

        if (contact == null)
        {
          Log.Info(this.GetType() + ": contact is null", this);
          return false;
        }

        if (string.IsNullOrEmpty(FacetPath))
        {
          Log.Info(this.GetType() + ": facet path is empty", this);
          return false;
        }

        var inputPropertyToFind = FacetPath;

        string[] propertyPathArr = inputPropertyToFind.Split('.');
        if (propertyPathArr.Length == 0)
        {
          Log.Info(this.GetType() + ": facet path is empty", this);
          return false;
        }

        Queue propertyQueue = new Queue(propertyPathArr);
        string facetName = propertyQueue.Dequeue().ToString();
        IFacet facet = contact.Facets[facetName];
        if (facet == null)
        {
          Log.Info(string.Format("{0} : cannot find facet {1}", this.GetType(), facetName), this);
          return false;
        }

        var datalist = facet.Members[propertyQueue.Dequeue().ToString()];
        if (datalist == null)
        {
          Log.Info(string.Format("{0} : cannot find facet {1}", this.GetType(), facetName), this);
          return false;
        }
        
        if(typeof(IModelAttributeMember).IsInstanceOfType(datalist))
        {
          var propValue = ((IModelAttributeMember)datalist).Value;
          return (propValue != null ? propValue.Equals(FacetValue) : false);
        }
        if(typeof(IModelDictionaryMember).IsInstanceOfType(datalist))
        {
          var dictionaryMember = (IModelDictionaryMember) datalist;

          string elementName = propertyQueue.Dequeue().ToString();
          IElement element = dictionaryMember.Elements[elementName];
          if (element == null)
          {
            Log.Info(string.Format("{0} : cannot find element {1}", this.GetType(), elementName), this);
            return false;
          }

          string propertyToFind = propertyQueue.Dequeue().ToString();
          var prop = element.Members[propertyToFind];
          if (prop == null)
          {
            Log.Info(string.Format("{0} : cannot find property {1}", this.GetType(), propertyToFind), this);
            return false;
          }

          var propValue = ((IModelAttributeMember) prop).Value;
          return (propValue != null ? propValue.Equals(FacetValue) : false);
        }
        if (typeof(IModelCollectionMember).IsInstanceOfType(datalist))
        {
          var collectionMember = (IModelCollectionMember)datalist;
          var propertyToFind = propertyQueue.Dequeue().ToString();
          for (int i = 0; i < collectionMember.Elements.Count; i++)
          {
            IElement element = collectionMember.Elements[i];
            var prop = element.Members[propertyToFind];
            if (prop == null)
            {
              Log.Info(string.Format("{0} : cannot find property {1}", this.GetType(), propertyToFind), this);
              return false;
            }
            var propValue = ((IModelAttributeMember) prop).Value;
            if (propValue.Equals(FacetValue))
              return true;
          }
        }

      return false;
    }
  }
}

STEP 3: TEST IT

This is an example of a Contact, with facets, among these is the “Personal” facet with the “FirstName” attribute:

Facet

Facet

When creating a Personalization rule where “Personal.FirstName” has “Brian” and applying it to my page:

Contact Facet Rule Set Editor

Contact Facet Rule Set Editor

Rule In Use

Rule In Use

I should only be able to see this title when logged in as a user which contact facet FirstName is “Brian”:

Yes, I am a Brian

Yes, I am a Brian

MORE TO READ:

 

 

Posted in .net, c#, Sitecore 7, Sitecore 8 | Tagged , , , , | Leave a comment

C# Using Newtonsoft and dynamic ExpandoObject to convert one Json to another

The scenario where you convert one input Json format to another output Json is not uncommon. Before C# dynamic and ExpandoObject you would serialize the input Json to POCO model classes and use a Factory class to convert to another set of POCO model classes, that then would be serialized to Json.

With the dynamic type and the ExpandoObject you have another weapon of choice, as you can deserialize the input Json to a dynamic object, and convert the contents to another dynamic object that is serialized. Imagine the following input and output Json formats:

Input format:

{
	"username": "someuser@somewhere.com",
	"timeStamp": "2017-09-20 13:50:16.560",
	"attributes": {
		"attribute": [{
			"name": "Brian",
			"count": 400
		},
		{
			"name": "Pedersen",
			"count": 100
		}]
	}
}

Output format:

{
	"table": "USER_COUNT",
	"users": [{
		"uid": "someuser@somewhere.com",
		"rows": [{
			"NAME": "Brian",
			"READ_COUNT": 400
		},
		{
			"NAME": "Pedersen",
			"READ_COUNT": 100
		}]
	}]
}

Converting from the input format to the output format can be achieved with a few lines of code:

// Convert input Json string to a dynamic object
dynamic input = JsonConvert.DeserializeObject(myQueueItem);

// Create a dynamic output object
dynamic output = new ExpandoObject();
output.table = "USER_COUNT";
output.users = new dynamic[1];
output.users[0] = new ExpandoObject();
output.users[0].uid = input.username;
output.users[0].rows = new dynamic[input.attributes.attribute.Count];
int ac = 0;
foreach (var inputAttribute in input.attributes.attribute)
{
    var row = output.users[0].rows[ac] = new ExpandoObject();
    row.NAME = inputAttribute.name;
    row.READ_COUNT = inputAttribute.count;
    ac++;
}

// Serialize the dynamic output object to a string
string outputJson = JsonConvert.SerializeObject(output);

I’ll try to further explain what happens. The Newtonsoft.Json DeserializeObject() method takes a json string and converts it to a dynamic object.

The output Json is created by creating a new dynamic object of the type ExpandoObject(). With dynamic ExpandoObjects we can create properties on the fly, like so:

// Create a dynamic output object
dynamic output = new ExpandoObject();
// Create a new property called "table" with the value "USER_COUNT"
output.table = "USER_COUNT";

This would, when serialized to a Json, create the following output:

{
"table": "USER_COUNT"
}

To create an array of objects, you need to first create a new dynamic array and then assign an ExpandoObject to the position in the array:

// Create a dynamic output object
dynamic output = new ExpandoObject();
// Create a new array called "users"
output.users = new dynamic[1];
// An an object to the "users" array
output.users[0] = new ExpandoObject();
// Create a new property "uid" in the "users" array
output.users[0].uid = input.username;

This generates the following Json output:

{
	"users": [{
		"uid": "someuser@somewhere.com"
		}]
}

MORE TO READ:

Posted in c#, General .NET | Tagged , , , | Leave a comment

.NET Session state is not thread safe

When working with the .NET session state you should bear in mind that the HttpContext.Current.Session cannot be transferred to another thread. Imagine that you, from the Global.asax would like to read the SessionID each time a session is started:

// This method inside Global.asax is called for every session start
protected void Session_Start(object sender, EventArgs e)
{
  MyClass.DoSomethingWithTheSession(HttpContext.Current);
}

To speed up performance you wish to use a thread inside DoSomethingWithTheSession. The thread will read the Session ID:

public class MyClass
{															   
  public static void DoSomethingWithTheSession(HttpContext context) 
  {
    if (context == null)  
	  return;

    // Here the context is not null
	ThreadPool.QueueUserWorkItem(DoSomethingWithTheSessionAsync, context);
  }

  private static void DoSomethingWithTheSessionAsync(object httpContext)
  { 
    HttpContext context = (HttpContext)httpContext;
	
	// Oops! Here the context is NULL
	string sessionID = context.Session.SessionID; 
  }
}

The code above will fail because the HttpContext is not thread safe. So in DoSomethingWithTheSession(), the context is set, but in DoSomethingWithTheSessionAsync, the context will null.

THE SOLUTION: TRANSFER THE SESSION VALUES INSTEAD OF THE SESSION OBJECT:

To make it work, rewrite the DoSomethingWithTheSessionAsync() method to reteieve the values needed, not the HttpContext object itself:

public class MyClass
{															   
  public static void DoSomethingWithTheSession(HttpContext context) 
  {
    if (context == null)  
      return;

    // Transfer the sessionID instead of the HttpContext and everything is fine
    ThreadPool.QueueUserWorkItem(DoSomethingWithTheSessionAsync, 
      context.Session.SessionID);
  }

  private static void LogReportFeatureflagsAsync(object session)
  { 
    // This works fine, as the string is thread safe.
    string sessionID = (string)session;
	
    // Do work on the sessionID
  }
}

MORE TO READ:

 

Posted in .net, c#, General .NET | Tagged , , , , , , | Leave a comment

Edit special field types in Sitecore Experience Editor – Custom Experience Editor Buttons replaces the Edit Frame

The Sitecore Experience Editor allows inline editing of simple field types like text and rich text (HTML) field, and a few complex ones like links. But editing checkboxes, lookup values, multiselect boxes, or any custom field you might have developed yourself requires some custom setup.

Previously, the Edit Frame have been the weapon of choice. The Edit Frame opens a tiny shell with the fields of your choice when clicking on the control to edit.
Unfortunately this has the downside that it hides the Experience Editor’s own buttons, so it is becoming deprecated, and isn’t even available when using MVC to render the front end.

The Edit Frame will hide the standard Experience Editor Buttons

The Edit Frame will hide the standard Experience Editor Buttons

But fear not, as the Edit Frame functionality have just been moved to the Experience Editor Buttons.

STEP 1: SET UP THE AVAILABLE BUTTONS

Go to the CORE database. Find the /sitecore/content/Applications/WebEdit/Custom Experience Buttons.

For your own pleasure, create a nice folder structure that matches your component structure, and add a “Field Editor Button” in the structure:

A Field Editor Button placed in a folder below Custom Experience Buttons.

A Field Editor Button placed in a folder below Custom Experience Buttons.

In the “Fields” field of that button, add the fields that needs to be editable, as a Pipe separated list, like this:

  • FieldName1|FieldName2|FieldName3

STEP 2: CONFIGURE THE RENDERING

In the “Experience Editor Buttons”, add the button you created:

The button is added to the Experience Editor Buttons

The button is added to the Experience Editor Buttons

STEP 3: TEST IT

Now, when clicking the rendering, the button you added is available:

Experience Editor Buttons

Experience Editor Buttons

And when clicking it, the Edit Frame opens, and the fields are available for editing:

Edit Frame

Edit Frame

MORE TO READ:

Posted in Sitecore 7, Sitecore 8 | Tagged , , , , | Leave a comment

Sitecore Scheduled Task – Schedule time format and other quirks

The Sitecore task runner, usually called Scheduled Tasks, is a simple way of executing code with intervals. You configure scheduled tasks in Sitecore, at /sitecore/system/Tasks/Schedules:

Scheduled Task

Scheduled Task

The quirkiest configuration setting is the “Schedule” field, which is a pipe separated string determining when the task should run:

{start timestamp}|{end timestamp}|{days to run bit pattern}|{interval}

  • Start timestamp and End timestamp: Determines the start and end of the scheduled task.
    Format is the Sitecore ISO datetime, YearMonthDayTHoursMinutesSeconds.
    Example: 20000101T000000 = January 1st 2000 at 00:00:00.
    (the font Sitecore uses does not help reading the timestamp at all, I know).
    NOTE: If you do the format wrong, the task will run.
  • Days to run: A 7 bit pattern determining which days the task must run:
    1 = Sunday
    2 = Monday
    4 = Tuesday
    8 = Wednesday
    16 = Thursday
    32 = Friday
    64 = Saturday
    So, 127 means to run the task every day. To run the task on Saturday and Sunday, add the 2 values, 1+64 = 65.
  • Interval: How long time between each run. 00:05:00 means that the task will run with 5 minute intervals.

WHY DOESN’T MY TASK RUN WITH MY SPECIFIED INTERVALS?

Sitecore uses no less than 2 sitecore.config settings to determine when the task runner should run:

<scheduling>
  <frequency>00:05:00</frequency>
  <agent type="Sitecore.Tasks.DatabaseAgent" method="Run" interval="00:05:00">
    <param desc="database">master</param>
    <param desc="schedule root">/sitecore/system/Tasks/Schedules</param>
    <LogActivity>true</LogActivity>
  </agent>
</scheduling>

The frequency setting determine when the global Sitecore task runner should run at all.

The agent determine when the tasks configured in the master database at the root /sitecore/system/Tasks/Schedules should run.

So, in the example above, my task runner runs every 5 minutes, checking the config file for tasks to run. It will then run the agent with 5 minute intervals. If another task is running, it could block the task runner, delaying the agent from running. With the above settings, my best case scenario is that my agent runs every 5 minutes.

The tasks configured in Sitecore could also block. If a task should run every 5 minutes, but the execution time is 11 minutes, the agent would run the task again after 15 minutes, in the best case scenario. To avoid this, you can mark your task as “async” in the configuration, but beware that long running (or never ending) tasks will then run simultaneously, slowing down Sitecore.

CAN I HAVE TASKS RUNNING ON MY CM SERVER ONLY?

Yes, you can add a new folder in Sitecore, and then add a new agent that points to the new folder as root, to the sitecore.config file of the CM server.

See more here: Sitecore Scheduled Tasks – Run on certain server instance.

CAN I RUN TASKS AT THE SAME TIME EVERY DAY?

Kind of. You can have your task running once a day within the same interval, using a little code.

See more here: Run Sitecore scheduled task at the same time every day.

IN WHAT CONTEXT DOES MY TASK RUN?

Sitecore have created a site called “scheduler” where the context is defined:

<sites>
  <site name="scheduler" database="master" language="da" enableTracking="false" domain="sitecore" />
</sites>

To run the task in a different context, use a context switcher.

DO I HAVE A HTTP CONTEXT WHEN RUNNING SCHEDULED TASKS?

No.

DO I HAVE A USER WHEN RUNNING SCHEDULED TASKS?

Do not expect to have a user. Expect the Sitecore Scheduled Task – Schedule time format and other quirks to be NULL, unless you use a UserSwitcher.

CAN I RUN THE SAME CODE FROM DIFFERENT TASKS?

Yes. Sitecore have split the definition of the code to run from the definition of the schedule. The code is defined as a “command” where you define the class and the method to run:

Task Commands

Task Commands

The schedule simply points to the command to run, and you can have as many schedules as you want:

Pointing to a command

Pointing to a command

WHAT ARE THE “ITEMS” FIELD FOR?

Items Field

Items Field

No one really knows what the items field are for, but according to old Sitecore folklore, you can add a pipe separated list of item GUIDS (or even item paths), and the “itemArray” property of the method you call will contain the list of items:

public void Execute(Item[] itemArray, CommandItem commandItem, ScheduleItem scheduleItem)
{
  foreach (Item item in itemArray)
  {
    // do something with the item
  }
}

MORE TO READ:

 

Posted in c#, Sitecore, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , | 1 Comment