Automating repetitive tasks in application development is a key to hassle free software development live cycle (SDLC). Reports take a high position in current business world. The purpose of large part of software most business uses is making reports. In this article I will show our experience in implementing continues integration (CI) to develop, test and deploy SSRS reports.
Tools
In our infrastructure we are using following tools: Visual Studio 2012, VisualSVN server, JIRA, TeamCity.VS solution
A visual studio solution was organized in the way where each department has it's own project. Sales, Accounting, HR etc. Also we have Console Application in the solution for accessing reportservice2010.asmx service to deploy and configure Items on the SSRS. The typical project consists of shared data sources, shared datasets and reports.reportservice2010.asmx service wrapper
To manipulate SSRS items on the server, such as reports, datasourse and datasets there is a service microsoft created. Also there is a tool RS.exe to use it. But because of lack of documentation we made a decision to use service directly. In the main function we instantiate connection to server, cleaning up and deploying. The full source code for console app you can find bellow.
internal class Program
{
private static ReportingService2010SoapClient client;
private static readonly TrustedUserHeader TrustedUserHeader = new TrustedUserHeader();
private static void Main(string[] args)
{
string parent = args[0];
string projectPath = args[1];
string environment = args[2];
client = new ReportingService2010SoapClient(environment);
Debug.Assert(client.ClientCredentials != null, "client.ClientCredentials != null");
client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
client.ClientCredentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
client.Open();
CleanUp(parent, projectPath);
CreateDataSources(parent, projectPath);
DeployDatasets(parent, projectPath);
DeployReport(projectPath, parent);
HideDrillDownReports(parent);
}
private static void DeployDatasets(string parent, string projectPath)
{
var dir = new DirectoryInfo(projectPath);
foreach (FileInfo file in dir.EnumerateFiles("*.rsd"))
{
string name = file.Name.Substring(0, file.Name.IndexOf(file.Extension, StringComparison.Ordinal));
byte[] definition;
CatalogItem item;
Warning[] warnings;
using (FileStream stream = File.OpenRead(file.FullName))
{
definition = new Byte[stream.Length];
stream.Read(definition, 0, (int)stream.Length);
stream.Close();
}
client.CreateCatalogItem(TrustedUserHeader, "DataSet", name, parent, true, definition, null, out item, out warnings);
client.SetProperties(TrustedUserHeader, item.Path, new[] { new Property { Name = "Hidden", Value = "True" } });
}
}
private static void HideDrillDownReports(string parent)
{
CatalogItem[] items;
client.ListChildren(TrustedUserHeader, parent, true, out items);
foreach (var item in items.Where(i => i.TypeName == "Report"))
{
CatalogItem parentReport = item;
foreach (var subreport in items.Where(i => i.Name.StartsWith(parentReport.Name) && i.Name != parentReport.Name))
{
client.SetProperties(TrustedUserHeader, subreport.Path, new[] { new Property { Name = "Hidden", Value = "True" } });
}
}
}
private static void DeployReport(string projectPath, string parent)
{
var dir = new DirectoryInfo(projectPath);
foreach (FileInfo file in dir.EnumerateFiles("*.rdl"))
{
string name = file.Name.Substring(0, file.Name.IndexOf(file.Extension, StringComparison.Ordinal));
byte[] definition;
CatalogItem item;
Warning[] warnings;
using (FileStream stream = File.OpenRead(file.FullName))
{
definition = new Byte[stream.Length];
stream.Read(definition, 0, (int) stream.Length);
stream.Close();
}
client.CreateCatalogItem(TrustedUserHeader, "Report", name, parent, true, definition, null, out item, out warnings);
try
{
ExpirationDefinition expiration = new TimeExpiration {Minutes = 30};
client.SetCacheOptions(TrustedUserHeader, item.Path, true, expiration);
}
catch {}
}
}
private static void CleanUp(string parent, string projectPath)
{
CatalogItem[] catalogItems;
var dir = new DirectoryInfo(projectPath);
client.ListChildren(TrustedUserHeader, parent, false, out catalogItems);
foreach (CatalogItem item in catalogItems)
{
if (item.TypeName == "Report")
{
if (!dir.GetFiles(string.Format("{0}.rdl", item.Name)).Any())
{
client.DeleteItem(TrustedUserHeader, item.Path);
}
}
}
}
private static void CreateDataSources(string parent, string projectPath)
{
var dir = new DirectoryInfo(projectPath);
CatalogItem[] items;
client.ListChildren(TrustedUserHeader, parent, true, out items);
foreach (FileInfo rds in dir.EnumerateFiles("*.rds"))
{
if (items.Any(i => i.TypeName == "DataSource" && i.Name == rds.Name.Substring(0, rds.Name.LastIndexOf('.'))))
continue;
var tempDataSource = new DataSourceDefinition();
XDocument xmlDoc = XDocument.Load(rds.FullName);
Debug.Assert(xmlDoc.Root != null, "xmlDoc.Root != null");
XElement connectionProperties = xmlDoc.Root.Element("ConnectionProperties");
Debug.Assert(connectionProperties != null, "connectionProperties != null");
XElement extension = connectionProperties.Element("Extension");
Debug.Assert(extension != null, "extension != null");
XElement connString = connectionProperties.Element("ConnectString");
Debug.Assert(connString != null, "connString != null");
tempDataSource.Extension = extension.Value;
tempDataSource.ConnectString = connString.Value;
CatalogItem item;
client.CreateDataSource(TrustedUserHeader, xmlDoc.Root.Attribute("Name").Value, parent, false,tempDataSource, null, out item);
client.SetProperties(TrustedUserHeader, item.Path,new[] {new Property {Name = "Hidden", Value = "True"}});
}
}
}
Environments
We have three environments Development, UAT and Production. Development environment is everything local. UAT is pointing to Test databases. And production is where end users executes their reports.
After developer finished with a task and ready to resolve the issue he/she commits changes to svn repository with required comment describes what he/she have done.
CI
TeamCity server checks for changes to SVN repository and if there is downloads source code and build the command line project to create exe which will be used to deploy reports.
Security
The security is organised with following security groups. Report User Group has access to Home page of SSRS. Department group has access to department folder which is reflection of department project of VS solution. members of that group has access to all departments reports but other department. The Power Report Users group included in each department group and gives its members access to all reports on the server.


No comments:
Post a Comment