In order to overcome the limit, I have come up two custom workflow activity classes which let you create any offset date, using the workflow base class that I just created.
1. InvariantOffsetDays
using System; using System.Globalization; using System.Workflow.ComponentModel; using Microsoft.Crm.Workflow; using Microsoft.Crm.Sdk; namespace CrmSpikes.Workflow { ///After you have registered the workflow assembly, and added the custom workflow activity to your workflow by providing a base date, you can then access the generated offset dates as shown below:/// Calculate a series of dates using a base date with a set of predefined offset numbers. /// @author Daniel Cai, http://danielcai.blogspot.com/ /// [CrmWorkflowActivity("Invariant Offset Days", "Custom Workflow")] public class InvariantOffsetDays : SingleActivityBase { private DateTime _baseDate; #region Workflow Parameters public static DependencyProperty BaseDateProperty = DependencyProperty.Register("BaseDate", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmInput("Base Date")] public CrmDateTime BaseDate { get { return (CrmDateTime)GetValue(BaseDateProperty); } set { SetValue(BaseDateProperty, value); } } public static DependencyProperty Offset7DaysProperty = DependencyProperty.Register("Offset7Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("7 Days after the base date")] public CrmDateTime Offset7Days { get { return (CrmDateTime)GetValue(Offset7DaysProperty); } set { SetValue(Offset7DaysProperty, value); } } public static DependencyProperty Offset14DaysProperty = DependencyProperty.Register("Offset14Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("14 Days after the base date")] public CrmDateTime Offset14Days { get { return (CrmDateTime)GetValue(Offset14DaysProperty); } set { SetValue(Offset14DaysProperty, value); } } public static DependencyProperty Offset21DaysProperty = DependencyProperty.Register("Offset21Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("21 Days after the base date")] public CrmDateTime Offset21Days { get { return (CrmDateTime)GetValue(Offset21DaysProperty); } set { SetValue(Offset21DaysProperty, value); } } public static DependencyProperty Offset28DaysProperty = DependencyProperty.Register("Offset28Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("28 Days after the base date")] public CrmDateTime Offset28Days { get { return (CrmDateTime)GetValue(Offset28DaysProperty); } set { SetValue(Offset28DaysProperty, value); } } public static DependencyProperty Offset35DaysProperty = DependencyProperty.Register("Offset35Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("35 Days after the base date")] public CrmDateTime Offset35Days { get { return (CrmDateTime)GetValue(Offset35DaysProperty); } set { SetValue(Offset35DaysProperty, value); } } public static DependencyProperty Offset42DaysProperty = DependencyProperty.Register("Offset42Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("42 Days after the base date")] public CrmDateTime Offset42Days { get { return (CrmDateTime)GetValue(Offset42DaysProperty); } set { SetValue(Offset42DaysProperty, value); } } public static DependencyProperty Offset49DaysProperty = DependencyProperty.Register("Offset49Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("49 Days after the base date")] public CrmDateTime Offset49Days { get { return (CrmDateTime)GetValue(Offset49DaysProperty); } set { SetValue(Offset49DaysProperty, value); } } public static DependencyProperty Offset56DaysProperty = DependencyProperty.Register("Offset56Days", typeof(CrmDateTime), typeof(InvariantOffsetDays)); [CrmOutput("56 Days after the base date")] public CrmDateTime Offset56Days { get { return (CrmDateTime)GetValue(Offset56DaysProperty); } set { SetValue(Offset56DaysProperty, value); } } #endregion #region SequenceActivity protected override void ExecuteBody() { _baseDate = BaseDate.UniversalTime; Offset7Days = CalculateOffsetDate(7); Offset14Days = CalculateOffsetDate(14); Offset21Days = CalculateOffsetDate(21); Offset28Days = CalculateOffsetDate(28); Offset35Days = CalculateOffsetDate(35); Offset42Days = CalculateOffsetDate(42); Offset49Days = CalculateOffsetDate(49); Offset56Days = CalculateOffsetDate(56); } private CrmDateTime CalculateOffsetDate(int offset) { DateTime resultDate = _baseDate.AddDays(offset); return CrmDateTime.FromUniversal(resultDate); } #endregion } }
InvariantOffsetDays class uses a set of predefined offset numbers to generate a series of offset dates based on the provided base CRM date. The offset is hard-coded in the class due to the way how workflow activity works. You may change it to any intervals or any combination of offset numbers.
2. VariantOffsetDays class
using System; using System.Globalization; using System.Workflow.ComponentModel; using Microsoft.Crm.Workflow; using Microsoft.Crm.Sdk; namespace CrmSpikes.Workflow { /// <summary> /// Calculate a new date using a base date and an offset number (positive or negative). /// @author Daniel Cai, http://danielcai.blogspot.com/ /// </summary> [CrmWorkflowActivity("Variant Offset Days", "Custom Workflow")] public class VariantOffsetDays : SingleActivityBase { #region Workflow Parameters public static DependencyProperty BaseDateProperty = DependencyProperty.Register("BaseDate", typeof(CrmDateTime), typeof(VariantOffsetDays)); [CrmInput("Base Date")] public CrmDateTime BaseDate { get { return (CrmDateTime)GetValue(BaseDateProperty); } set { SetValue(BaseDateProperty, value); } } public static DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(CrmNumber), typeof(VariantOffsetDays)); [CrmInput("Offset (Positive or Negative)")] public CrmNumber Offset { get { return (CrmNumber)GetValue(OffsetProperty); } set { SetValue(OffsetProperty, value); } } public static DependencyProperty ResultDateProperty = DependencyProperty.Register("ResultDate", typeof(CrmDateTime), typeof(VariantOffsetDays)); [CrmOutput("Result Date")] public CrmDateTime ResultDate { get { return (CrmDateTime)GetValue(ResultDateProperty); } set { SetValue(ResultDateProperty, value); } } #endregion #region SequenceActivity protected override void ExecuteBody() { DateTime baseDate = BaseDate.UniversalTime; DateTime resultDate = baseDate.AddDays(Offset.Value); ResultDate = CrmDateTime.FromUniversal(resultDate); } #endregion } }VariantOffsetDays class accepts two parameters, which are the base date, and the offset days (int, either positive or negative), as shown below. It’s a more flexible solution than InvariantOffsetDays, the trade-off is it can only generate one offset date at one time.
Both classes can be used to create any type of offset, including month-based offset or year-based offset, which is your call.
The reason that I wrote this blog post was that two persons asked similar questions within a month on CRM Development Forum and CRM Forum about how to specify an offset date for a CRM workflow that cannot be done using the native CRM workflow design tool. There doesn't seem to be any available solution on Internet to address this issue, so I decided to write the custom workflow along with the workflow base class.
Note: Please make sure to include the workflow base class that I created in order to compile the code.
Download the source code and compiled assembly below.
Hope this helps if you ever need to do the same thing.
I ran into this and made a product suggestion. The issues is that you can never get exactly 90 days. This is because months could be 92 days if you have (2) 31 day months together.
ReplyDeleteExcellent code.
Here is the product suggestion link. Please vote!
Thanks Pierre
https://connect.microsoft.com/dynamicssuggestions/feedback/details/558156/crm-workflow-days-operator-should-be-a-number-field-not-a-pick-list
Thanks Pierre.
ReplyDeleteI have just voted, we now have 2 votes. :-)
Hi Daniel,
ReplyDeleteIn the code, where is the SetValue function defined?
SetValue function comes from System.Workflow.ComponentModel.DependencyObject class, which a workflow SequenceActivity class inherits from. So you shouldn't worrry about it. Please do make sure that you have got my base class at http://danielcai.blogspot.com/2010/05/mscrm-40-convenience-workflow-base.html.
ReplyDeleteExcellent post!
ReplyDeleteI am trying to create a workflow that will send an email on the 1st of each month.
Anybody have any ideas?
Thanks
@Anonymous, sorry for not being able to respond sooner. In your case, I would write Windows Service application to do that, as timeout workflow has certain performance overhead to the server. Alternatively you can write a console application, and use Windows Scheduled Tasks to invoke the console application on the 1st of each month. Either way you should have better control.
ReplyDelete