Tuesday 7 February 2012

Silverlight - Name Methods vs Anonymous Methods

Today I found myself performing a series of web service calls, all one after the other to reach the data I was after.  Lets ignore the fact that this will create many HTTP requests and the performance implications that are associated, as I was limited by the service interface being used.
Initially I broke each request into 2 methods:
  1. The call to the service to retrieve the data.
  2. The async callback method to process the data.
Initially this worked quite well, code was separated, easy to follow and quite basic.  But as time wore on, more requests were added until there were a significant amount of small methods that do only a very specific task with tight coupling to the method that called it.
Now coming from a VB.NET background this was just the way you had to do things, and you became very familiar with the AddressOf operator very quickly. Along comes C#.NET and the idea of anonymous methods to solve the problem. Most of the methods we them modified to use Anonymous Methods. This allowed all code for a single process to be encapsulated into a single method, significantly reducing the number of tightly coupled methods, greatly increasing readability and maintainability.

The following is an example of some Silverlight code before and after the change. As you can see the amount of code written has been reduced, it is more readable and maintainabled, you do not have to perform some validation as you can be certain of the data being passed in and finnally you have access to the variables within the calling method. Beware though of accessing variables within the main method as they may provide a warning of "Access to modified closure", I won't describe what that is now, too far off topic.

Before Anonymous Methods

        /// <summary>
        /// Populate the the configuration entity
        /// </summary>
        private void PopulateConfigData()
        {
            DataServiceQuery query = (DataServiceQuery<configuration>)from config in _Service.configurationSet
                                                                      select config;
 
            query.BeginExecute(new AsyncCallback(PopulateConfigData_CallBak), query);
        }
 
        /// <summary>
        /// Builds the config entity
        /// </summary>
        /// <param name="ar">Results of Async operation</param>
        private void PopulateConfigData_CallBak(IAsyncResult ar)
        {
            DataServiceQuery query = ar.AsyncState as DataServiceQuery;
 
            if (query == null)
            {
                throw new DataServiceQueryException("No configuration data returned.");
            }
 
            // load configuration information
            DataServiceCollection<configuration> results = new DataServiceCollection<configuration>(query.EndExecute(ar) as IEnumerable<configuration>);
            if (results.Count > 0)
            {
                _Config = results[0];
            }
            else
            {
                throw new DataServiceQueryException("No configuration data returned.");
            }
 
            // continue processing requests
            ContinueProcessing();
        }

After Anonymous Methods

        /// <summary>
        /// Populate the the configuration entity
        /// </summary>
        private void PopulateConfigData()
        {
            DataServiceQuery query = (DataServiceQuery<configuration>)from config in _Service.configurationSet
                                                                      select config;
 
            query.BeginExecute(result =>
                                   {
                                       // load configuration information
                                       DataServiceCollection<configuration> results = new DataServiceCollection<configuration>(query.EndExecute(result) as IEnumerable<configuration>);
                                       if (results.Count > 0)
                                       {
                                           _Config = results[0];
                                       }
                                       else
                                       {
                                           throw new DataServiceQueryException("No configuration data returned.");
                                       }
 
                                       // continue processing requests
                                       ContinueProcessing();
                                   }, null);
        }

Thursday 28 July 2011

CRM 2011 - Creating Service Restrictions

To create a service restriction within CRM 2011 using the Organisation service is not a trivial task.  It appears as though the addition of the restriction is at a significantly lower level then, for example, adding an Account. It has taken quite a bit of investigation to get to this point so here goes.
A typical service restriction will consist of the following:
  1. A "CalendarRule" attached to the calendar of the user (daily calendar rule).
  2. An inner calendar or sub calendar attached to this calendar rule.
  3. At a minimum 2 calendar rules attached to the inner calendar.
The user you are trying to add the restriction for should already have a Calendar assigned to them. If they don't, the best way to initialise this is to create their work hours within CRM and CRM will automatically create it for you, then all you need to do is access the SystemUser.CalendarId value of the user. See below:

    SystemUser user = OrgService.Retrieve(SystemUser.EntityLogicalName, UserId, new ColumnSet("calendarid")).ToEntity<SystemUser>();
    Calendar userCalendar = OrgService.Retrieve(Calendar.EntityLogicalName, user.CalendarId.Id, new ColumnSet(true)).ToEntity<Calendar>();

Once you have the calendar loaded you will find a field (Calendar.CalendarRules) that will contain all the rules for the user's calendar.  This is where you will need to add the daily calendar rule. The standard daily calendar rule for a service restriction will also contain a reference to an Inner Calendar (CalendarRule.InnerCalendar) which is the calendar you must add the other 2 calendar rules to. The following outlines the default settings for a daily calendar rule and its associated inner calendar:

    //create the inner calendar
    Calendar newInnerCalendar = new Calendar
                                    {
                                        BusinessUnitId = new EntityReference(BusinessUnit.EntityLogicalName, userSettings.BusinessUnitId.Value)
                                    };
    Guid innerCalendarId = OrgService.Create(newInnerCalendar);
 
    //create the base rule
    baseRule = new CalendarRule
                    {
                        Duration = 1440,
                        ExtentCode = 1,
                        Pattern = "FREQ=DAILY;COUNT=1",
                        Rank = 0,
                        StartTime = ScheduleDate.ToLocalTime(),
                        TimeZoneCode = userSettings.TimeZoneCode,
                        InnerCalendarId = new EntityReference(Calendar.EntityLogicalName, innerCalendarId)
                    };
                
    // update the users calendar with the new rule
    List<CalendarRule> userRules = userCalendar.CalendarRules.ToList();
    userRules.Add(baseRule);
    userCalendar.CalendarRules = userRules;
 
    OrgService.Update(userCalendar);

NOTE: These are the settings that must be used, a change to these settings will most likely cause the rule and calendar to be ignored by CRM or worse cause an un-handled exception.

NOTE: Using "ToLocalTime()" is required when talking to CRM.  This is because CRM accepts a local time but returns a UTC time. Further Details

Once you have created the daily calendar rule (#1) and the inner calendar associated with the rule (#2) then you must add the 2 calendar rules to the inner calendar. These 2 rules are what defines the service restriction.  The first Calendar Rule below outlines the users availability for that particular day, the second outlines the service restriction:

    // load the inner calendar from CRM
    Calendar innerCalendar = OrgService.Retrieve(Calendar.EntityLogicalName, innerCalendarId, new ColumnSet(true)).ToEntity<Calendar>();
    //daily work rule
    CalendarRule dailyWorkRule = new CalendarRule
                                        {
                                            Duration = 540,
                                            Effort = 1.0,
                                            IsSimple = true,
                                            Offset = 480,
                                            Rank = 1,
                                            SubCode = (intSubCode.Schedulable,
                                            TimeCode = (intTimeCode.Available,
                                            TimeZoneCode = -1
                                        };
 
    //create restriction rule
    CalendarRule restrictionRule = new CalendarRule
                                        {
                                            Duration = 540,
                                            IsSimple = true,
                                            Offset = 480,
                                            Rank = 1,
                                            SubCode = (intSubCode.ResourceServiceRestriction,
                                            TimeCode = (intTimeCode.Filter,
                                            TimeZoneCode = -1,
                                            ServiceId = new EntityReference(Service.EntityLogicalName, ServiceId)
                                        };
 
    List<CalendarRule> rules = innerCalendar.CalendarRules.ToList();
    rules.Add(restrictionRule);
    rules.Add(dailyWorkRule);
 
    innerCalendar.CalendarRules = rules;
    OrgService.Update(innerCalendar);

Of these 2 Calendar Rules the are a couple of fields that may be modified, they include:
  1. Duration - This is the time in minutes the rule is scheduled to last.
  2. Offset - This is the time in minutes from 12:00AM (00:00) until the rule is scheduled to start. (ie. 480 = 7hours. Therefore start time would be 7:00AM)
  3. Service id - This is the service the user is restricted from undertaking during the time period.
This is simply a starting point, when modifying the calendar using the web service, as i said earlier, it is at quite a low level.  This means you must be wise about what you do, take into account existing work daily work hours, vacations, breaks, appointments, etc.  If you take your time to get it right you will be rewarded but beware if you make a mistake it will "break" the calendar and you be required to manually remove records using either the Organisation service or direct access to the tables to get it to display again.