Scheduling Reports through code on ActiveReports Server

We have been evaluating ActiveReports Server for integration into our web application and I have to say I am very impressed. The features (when combined with the accompanying Active Reports product for canned reports) are very rich, and we are using many of them: custom security provider, embedded designer control, push reporting, etc.

One need we have is to automatically subscribe some of our users to canned reports when their accounts are made. The good thing about ActiveReports Server is it has a full web service API that is used by their designer control, so anything that is done there can be done with your own code. The bad side is that it is currently unsupported to do so, and thus is undocumented.

So for those who may be trying to do the same thing, I am posting this article showing how I achieved this using their “DesignerService”.

Basically the way I implemented this was to make a wrapper service to abstract their API from my web application. Jumping into the code, it looks like this:


using System;
using System.Linq;
using System.ServiceModel;
using MRMPortal.Service.ActiveReports.DesignerService;

namespace MRMPortal.Service.Services
{
    public static class ActiveReportsService
    {
        public enum ReportFrequency
        {
            Daily,
            Weekly,
            Monthly,
            Quarterly
        }

        public static bool SubscribeToReport(string token, string reportName, string emailAddress, ReportFrequency frequency)
        {
            var sharedScheduleName = frequency.ToString();
            var scheduleName = "Automatically Scheduled " + sharedScheduleName;
            var subject = reportName + " " + sharedScheduleName + " Report";
            var body = "This report subscription was automatically scheduled by the ARKS system on " + DateTime.Now.ToLongTimeString() + ".";

            var designerService = new DesignerServiceClient();

            // get info for the desired report
            var reports = designerService.GetList(token, StorageType.Report);
            var targetReport = reports.ItemDescriptions.Single(r => r.Name == reportName);

            // get info for the desired schedule
            var policyTemplates = designerService.GetList(token, StorageType.SharedPolicyTemplate);
            var targetPolicyTemplate = policyTemplates.ItemDescriptions.Single(r => r.Name == sharedScheduleName);

            // see if the policys already exists (e.g. is was created before, or maybe begun but not completed)
            var policy = GetReportPolicy(token, reportName, scheduleName);

            if (policy == null)
            {
                // create the policy
                var createResult = designerService.CreatePolicy(token, targetReport.Id, scheduleName, PolicyType.ScheduledPolicy, targetPolicyTemplate.Id);

                // get the new policy
                policy = createResult.Policies[0];
            }

            // add a cache policy
            var cachePolicy = new CachePolicy();
            cachePolicy.AbsoluteExpirationAppendage = 604800;
            cachePolicy.Priority = CacheItemPriority.Normal;
            cachePolicy.SlidingExpiration = -1;
            policy.CachePolicy = cachePolicy;

            // set policy properties
            policy.ModifiedBy = "Dominic Hall";
            policy.ModifiedDate = DateTime.Now;

            // set the recipient
            var distribution = new EMailDistribution();
            distribution.Subject = subject;
            distribution.MessageBody = body;
            distribution.To = new[] { emailAddress };
            distribution.AsAttachment = true;
            distribution.AsLink = false;
            policy.Distribution = distribution;

            // create render options
            var renderOptions = new RenderOptions();
            renderOptions.Extension = "PDF";
            renderOptions.Name = scheduleName;
            renderOptions.ReportId = targetReport.Id;
            renderOptions.ReportType = ReportType.SW;

            // create journal entry
            var journalEntry = new JournalEntryTemplate();
            journalEntry.Job = renderOptions;
            journalEntry.EntryPriority = JournalEntryPriority.Normal;
            policy.JournalEntry = journalEntry;

            // create day list: what day(s) the report gets sent out on
            var dayList = new Day[]
            {
                new Day() {DayOfWeek = DayOfWeek.Monday, Ordinal = 0}
            };

            // create recurrance rule
            var recurranceRule = new RecurrenceRule();
            recurranceRule.Frequency = Frequency.Daily; // This is how often the schedule is processed, not how often it is sent.
            recurranceRule.Interval = 7; // once every seven days
            recurranceRule.ByDayList = dayList;
            recurranceRule.StartDayOfWeek = DayOfWeek.Sunday;

            // create recurrance set
            var recurranceSet = new RecurrenceSet();
            recurranceSet.RecurrenceRules = new[] { recurranceRule };
            recurranceSet.ExceptionDates = new DateTime[] {};
            recurranceSet.ExceptionRules = new RecurrenceRule[] { };
            recurranceSet.RecurrenceDates = new DateTime[] { };
            recurranceSet.StartDate = new DateTime(2012, 10, 23, 12, 0, 0, DateTimeKind.Utc); // this must match the start date in the shared template

            // create and attach schedule
            var schedule = new Schedule();
            schedule.Enabled = true;
            schedule.RecurrenceSet = recurranceSet;
            policy.Schedule = schedule;

            // update the policy with the new settings
            var saveResult = designerService.SavePolicy(token, policy);

            // if an error happened, throw and exception
            if (saveResult.Error != null)
                throw new ServiceActivationException(saveResult.Error.Description);

            // return true to indicate success
            return true;
        }

        public static Policy GetReportPolicy(string token, string reportName, string policyName)
        {
            var designerService = new DesignerServiceClient();

            // get info for the desired report
            var reports = designerService.GetList(token, StorageType.Report);
            var targetReport = reports.ItemDescriptions.Single(r => r.Name == reportName);

            var policies = designerService.GetPolicies(token, targetReport.Id);
            var policy = policies.Policies.SingleOrDefault(p => p.Name == policyName);

            return policy;
        }
    }
}

So what is happening here? In a nutshell, Schedules are types of “Policies”, and a report can have a bunch of policies. So we need to add a Policy of type “PolicyType.ScheduledPolicy”. So we use the API method “CreatePolicy” to make a placeholder for the policy. This placeholder, however, doesn’t have any distribution information, it only has the type of Shared Schedule to use, for example, “Weekly”. So we need to then add all the additional properties to the policy and resubmit it through the “SavePolicy” API method.

I perform the additional step of seeing if the policy is there before I create a new one, so that this code can be used to perform an update as well, or at least won’t crash if the schedule already exists.

Some nuances to be aware of:

  1. Even though this is not a “cache” policy, it needs to have CachePolicy settings applied to it.
  2. The renderOptions ReportType is “ReportType.SW” for the internal ActiveReports Server reports. They also have ReportType.AR and ReportType.DDR to support their other canned report types.
  3. The recurranceSet StartDate MUST MATCH EXACTLY the start date of the Shared Schedule that was used

The test code to run this looks like so:


    [TestMethod]
    public void ActiveReportsService_SubscribeToReport_ReturnsCorrectData()
    {
        // Arrange
        const string token = "1"; // The token for your user
        const string reportName = "MyReport"; // The name of the report to schedule
        const string emailAddress = "someone@somewhere.com"; // The email address to receive the subscription
        const ActiveReportsService.ReportFrequency frequency = ActiveReportsService.ReportFrequency.Weekly;

        // Act
        var result = ActiveReportsService.SubscribeToReport(token, reportName, emailAddress, frequency);

        // Assert
        Assert.AreEqual(true, result);
    }

Most of this I know through reverse engineering their API. Some of it I know through the help of Bhupesh Malhotra from Component One and his excellent assistance with getting over roadblocks. If anyone tries this, or has tried it, and knows better ways to do anything here, I would love to share experiences with you. Or leave ideas in the comments.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s