Skip to content

Custom history storage

Daniel Palme edited this page Aug 3, 2022 · 11 revisions

ReportGenerator supports coverage trend charts. By default a XML file, containing the coverage information, is stored in a local directory. Each execution of ReportGenerator results in one additional XML file.

If you want to store these files in a custom storage (e.g. Amazon S3 or Azure Blob Storage) you can write a plugin. This may be helpful, when you use services like Azure DevOps (VSTS) or Bamboo.
For Azure Blob Storage you can use this plugin: https://github.com/LoremFooBar/ReportGenerator.AzureBlobHistoryStorage

Depending on the version of ReportGenerator you have to follow the corresponding instructions:

Version 4.x

Implementation

To create a custom history storage you have to place a DLL, which contains one implementation of the IHistoryStorage interface, in the installation directory of ReportGenerator. In order to let ReportGenerator know about the DLL, you have to pass the path of the DLL to ReportGenerator via the -plugins command line parameter.
ReportGenerator supports both the full .NET Framework and .NET Core. If you implement your plugin as a .NET Standard 2.0 library, your plugin will work in both environments.

The IHistoryStorage interface has the following methods:

  • IEnumerable<string> GetHistoryFilePaths();
    Returns all existing history file paths. The file paths should end with the names that have been passed to the SaveFile(Stream stream, string fileName) in the past.

  • Stream LoadFile(string filePath);
    Load the file with given path. The path is one the paths that are returned by IEnumerable<string> GetHistoryFilePaths();.

  • SaveFile(Stream stream, string fileName);
    Saves a new historic file with the given file name. The stream contains the file and does not have to be disposed.

Example

In this example the historic files are retrieved and stored in an Amazon S3 Bucket.

  • Create a new .NET Standard 2.0 Library project in Visual Studio
  • Add the ReportGenerator.Core Nuget package to your project
  • Add the AWSSDK.S3 Nuget package to your project
  • Create a new class that implements the IHistoryStorage interface.
  • Add the following implementation (or change it as you like):
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Palmmedia.ReportGenerator.Core.Reporting.History;

namespace MyCompany.CustomHistoryStorage
{
    public class AmazonS3HistoryStorage : IHistoryStorage
    {
        private readonly string MyBucketName = "*** Provide bucket name ***";

        private readonly string AwsAccessKeyId = "*** Provide AwsAccessKeyId ***";

        private readonly string AwsSecretAccessKey = "*** Provide AwsSecretAccessKey ***";

        public AmazonS3HistoryStorage()
        {
            Dictionary<string, string> arguments = this.GetCommandLineArgumentsByKey();

            string value = null;

            if (arguments.TryGetValue("BUCKETNAME", out value))
            {
                this.MyBucketName = value;
            }

            if (arguments.TryGetValue("AWSACCESSKEYID", out value))
            {
                this.AwsAccessKeyId = value;
            }

            if (arguments.TryGetValue("AWSSECRETACCESSKEY", out value))
            {
                this.AwsSecretAccessKey = value;
            }
        }

        public IEnumerable<string> GetHistoryFilePaths()
        {
            var credentials = new BasicAWSCredentials(AwsAccessKeyId, AwsSecretAccessKey);
            using (var client = new AmazonS3Client(credentials, Amazon.RegionEndpoint.USEast1))
            {
                ListObjectsV2Request request = new ListObjectsV2Request
                {
                    BucketName = MyBucketName,
                    MaxKeys = 200
                };

                ListObjectsV2Response response;
                do
                {
                    response = client.ListObjectsV2Async(request).Result;

                    // Process response.
                    foreach (S3Object entry in response.S3Objects)
                    {
                        yield return entry.Key;
                    }

                    request.ContinuationToken = response.NextContinuationToken;
                }
                while (response.IsTruncated == true);
            }
        }

        public Stream LoadFile(string filePath)
        {
            var credentials = new BasicAWSCredentials(AwsAccessKeyId, AwsSecretAccessKey);
            using (var client = new AmazonS3Client(AwsAccessKeyId, AwsSecretAccessKey, Amazon.RegionEndpoint.USEast1))
            {
                TransferUtility fileTransferUtility = new TransferUtility(client);

                return fileTransferUtility.OpenStream(MyBucketName, filePath);
            }
        }

        public void SaveFile(Stream stream, string fileName)
        {
            var credentials = new BasicAWSCredentials(AwsAccessKeyId, AwsSecretAccessKey);
            using (var client = new AmazonS3Client(AwsAccessKeyId, AwsSecretAccessKey, Amazon.RegionEndpoint.USEast1))
            {
                TransferUtility fileTransferUtility = new TransferUtility(client);

                fileTransferUtility.Upload(stream, MyBucketName, fileName);
            }
        }

        private Dictionary<string, string> GetCommandLineArgumentsByKey()
        {
            var namedArguments = new Dictionary<string, string>();

            foreach (var arg in Environment.GetCommandLineArgs())
            {
                var match = Regex.Match(arg, "-(?<key>\\w{2,}):(?<value>.+)");

                if (match.Success)
                {
                    namedArguments[match.Groups["key"].Value.ToUpperInvariant()] = match.Groups["value"].Value;
                }
            }

            return namedArguments;
        }
    }
}
  • Compile
  • Pass the path of the DLL to ReportGenerator via the -plugins command line parameter

Attention: You have to make sure, that only a single custom IHistoryStorage implementation exists in your plugin(s). Otherwise ReportGenerator would not be able to chose the correct one.

Download: Sample project (Version 4.x)

Version 3.x

Implementation

To create a custom history storage you have to place a DLL, which contains one implementation of the IHistoryStorage interface, in the installation directory of ReportGenerator.

The interface has the following methods:

  • IEnumerable<string> GetHistoryFilePaths();
    Returns all existing history file paths. The file paths should end with the names that have been passed to the SaveFile(Stream stream, string fileName) in the past.

  • Stream LoadFile(string filePath);
    Load the file with given path. The path is one the paths that are returned by IEnumerable<string> GetHistoryFilePaths();.

  • SaveFile(Stream stream, string fileName);
    Saves a new historic file with the given file name. The stream contains the file and does not have to be disposed.

Example

In this example the historic files are retrieved and stored in an Amazon S3 Bucket.

  • Create a new Class Library project in Visual Studio
  • Add a reference to ReportGenerator.exe and to System.ComponentModel.Composition
  • Add the AWSSDK.S3 Nuget package to your project
  • Create a new class that implements the IHistoryStorage interface.
  • Add the following implementation (or change it as you like):
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Palmmedia.ReportGenerator.Reporting.History;

namespace MyCompany.CustomHistoryStorage
{
    [System.ComponentModel.Composition.Export(typeof(IHistoryStorage))]
    public class AmazonS3HistoryStorage : IHistoryStorage
    {
        private readonly string MyBucketName = "*** Provide bucket name ***";

        private readonly string AwsAccessKeyId = "*** Provide AwsAccessKeyId ***";

        private readonly string AwsSecretAccessKey = "*** Provide AwsSecretAccessKey ***";

        public AmazonS3HistoryStorage()
        {
            Dictionary<string, string> arguments = this.GetCommandLineArgumentsByKey();

            string value = null;

            if (arguments.TryGetValue("BUCKETNAME", out value))
            {
                this.MyBucketName = value;
            }

            if (arguments.TryGetValue("AWSACCESSKEYID", out value))
            {
                this.AwsAccessKeyId = value;
            }

            if (arguments.TryGetValue("AWSSECRETACCESSKEY", out value))
            {
                this.AwsSecretAccessKey = value;
            }
        }

        public IEnumerable<string> GetHistoryFilePaths()
        {
            using (var client = new AmazonS3Client(AwsAccessKeyId, AwsSecretAccessKey))
            {
                ListObjectsV2Request request = new ListObjectsV2Request
                {
                    BucketName = MyBucketName,
                    MaxKeys = 200
                };

                ListObjectsV2Response response;
                do
                {
                    response = client.ListObjectsV2(request);

                    // Process response.
                    foreach (S3Object entry in response.S3Objects)
                    {
                        yield return entry.Key;
                    }

                    request.ContinuationToken = response.NextContinuationToken;
                }
                while (response.IsTruncated == true);
            }
        }

        public Stream LoadFile(string filePath)
        {
            TransferUtility fileTransferUtility = new TransferUtility(AwsAccessKeyId, AwsSecretAccessKey);
            return fileTransferUtility.OpenStream(MyBucketName, filePath);
        }

        public void SaveFile(Stream stream, string fileName)
        {
            TransferUtility fileTransferUtility = new TransferUtility(AwsAccessKeyId, AwsSecretAccessKey);
            fileTransferUtility.Upload(stream, MyBucketName, fileName);
        }

        private Dictionary<string, string> GetCommandLineArgumentsByKey()
        {
            var namedArguments = new Dictionary<string, string>();

            foreach (var arg in Environment.GetCommandLineArgs())
            {
                var match = Regex.Match(arg, "-(?<key>\\w{2,}):(?<value>.+)");

                if (match.Success)
                {
                    namedArguments[match.Groups["key"].Value.ToUpperInvariant()] = match.Groups["value"].Value;
                }
            }

            return namedArguments;
        }
    }
}

Attention: Don't forget to add the Export attribute, otherwise ReportGenerator will not recognize your report builder.

  • Compile
  • Drop the DLL in the installation directory of ReportGenerator.

Attention: You have to make sure, that only a single custom IHistoryStorage implementation exists in that directory. Otherwise ReportGenerator would not be able to chose the correct one.

Download: Sample project (Version 3.x)