Walkthrough for creating an IIS Report

Published on May 20, 2008 by SinghGurpreet

Updated on May 28, 2008 by SinghGurpreet

Average Rating  Rate It (1)

RSS

Introduction 

IIS reports have grown into an incredibly powerful tool to build your own set of reports to view. Not only it ships with basic set of reports but also you can write your own reports and plug them in IIS Reports platform. This walkthrough will talk about how to create a basic report. The report gives a comparison between aspx page and number of hits.Creating a report follows the same process of creating a UI module. Steps to create a simple UI module can be found at http://learn.iis.net/page.aspx/441/understanding-ui-extension-authoring/

The steps for creating an IIS Report can be categorized in two pieces. It comprises of

  • Creating a Server piece
  • Creating a Client piece

Creating server piece which comprises of two parts

  • Creating a ModuleProvider
  • Create a ModuleService

Creating Client piece comprises of three parts

  • Creating a Module
  • Creating a Reporting Feature
  • Creating a Report

So we will start with server piece and then we will move over to client piece.

Creating a Module Provider

This is very much similar to writing a module provider for Inetmgr UI.

internal class TestModuleProvider : SimpleDelegatedModuleProvider {

        
public override ModuleDefinition GetModuleDefinition(IManagementContext context) {
            
return new ModuleDefinition(Name, "TestIisReportsClient.TestModule, " + AssemblyRef.TestReportClient);
        
}

        
public override Type ServiceType {
            
get {
                
return typeof(TestModuleService);
            
}
        }

        
public override bool SupportsScope(ManagementScope scope) {
            
return (scope == ManagementScope.Server || scope == ManagementScope.Site);
        
}

        
public override string FriendlyName {
            
get {
                
return "IIS Reports Test";
            
}
        }
    }

The TestModuleProvider derives from SimpleDelegatedModuleProvider. This gives Module the flexibility to Server Administrators to delegate reports to site administrators.

Creating a Module Service

The TestModuleService derives from Module Service and exposes a method GetReport which can be called by Module Service Proxy or WMSVC. Internally the arguments received are passed to ExecuteLogParserQuery which makes use of LogParser to parse the query and generate the output. Once ILogRecordSet is generated, it is packed in object array and sent over to the client. TestModuleService makes use of GetFromClause() and GetCurrentSite() to get the path of the files which need to be parsed.

internal class TestModuleService : ModuleService {

        [ModuleServiceMethod(PassThrough 
= true)]
        
public object[] GetReport(IDictionary arguments) {
            
object[] dataObject = null;

            try 
{
                dataObject 
ExecuteLogParserQuery(arguments);
            
}
            
catch (Exception ex) {
                RaiseException(ex.Message)
;
            
}
            
return dataObject;
        
}

        
private string GetQueryString(IDictionary arguments) {
            
string endDate ((DateTime)arguments["ToDate"]).ToString("yyyy-MM-dd");
            string 
startDate ((DateTime)arguments["FromDate"]).ToString("yyyy-MM-dd");
            string 
from GetFromClause();
            return 
"Select TOP 25 TO_LOWERCASE (cs-uri-query) as Site, Count(*) as hits from" + from
                + 
" where Site is not null and date BETWEEN TO_TIMESTAMP(\'" + startDate + "\',\'yyyy-MM-dd\') AND TO_TIMESTAMP(\'" + endDate + "\',\'yyyy-MM-dd\') group by Site order by Hits desc";
        
}

        
private object[] ExecuteLogParserQuery(IDictionary arguments) {
            
string query GetQueryString(arguments);
            object
[] data = new object[2];
            
ILogRecordset rs ExecuteQuery(query);

            int 
columnCount rs.getColumnCount();
            object
[] headers = new object[columnCount];
            for 
(int 0i < columnCounti++) {
                headers[i] 
rs.getColumnName(i);
            
}
            data[
0headers;

            
// Add the results
            
ArrayList rows = new ArrayList();
            while 
(!rs.atEnd()) {
                
object[] row = new object[columnCount];
                
ILogRecord record rs.getRecord();
                for 
(int 0i < columnCounti++) {
                    
object value = record.getValue(i);
                    if 
(value is DBNull) {
                        
value = String.Empty;
                    
}

                    row[i] 
= value;
                
}

                rs.moveNext()
;
                
rows.Add(row);
            
}

            
//Adding rows to object Array
            
data[1rows;
            return 
data;
        
}

        
private ILogRecordset ExecuteQuery(string query) {
            LogQueryClass logParser 
= new LogQueryClass();
            
ICOMIISW3CInputContext ctx = new COMIISW3CInputContextClassClass();

            return 
logParser.Execute(query, ctx);
        
}

        
private string GetFromClause() {
            Site site 
GetCurrentSite();
            if 
(site == null) {
                ServerManager mgr 
GetServerManager();
                
StringBuilder sb = new StringBuilder();
                bool 
notFirst = false;
                foreach 
(Site s in mgr.Sites) {
                    
if (notFirst) {
                        sb.Append(
',');
                    
}
                    notFirst 
= true;
                    
sb.Append(GetFromClauseForSite(s));
                
}
                
return sb.ToString();
            
}
            
return GetFromClauseForSite(site);
        
}

        
private ServerManager GetServerManager() {
            
return ManagementUnit.ReadOnlyServerManager;
        
}

        
private string GetFromClauseForSite(Site s) {
            
return "'" + Path.Combine(Environment.ExpandEnvironmentVariables(s.LogFile.Directory), "W3SVC" + s.Id.ToString()) + "\\*.log'";
        
}

        
private Site GetCurrentSite() {
            
if (ManagementUnit.ConfigurationPath.PathType == ConfigurationPathType.Site) {
                ServerManager mgr 
GetServerManager();
                
Site site mgr.Sites[ManagementUnit.ConfigurationPath.SiteName];

                return 
site;
            
}

            
return null;
        
}
    }

 Creating the Module

In order to generate an IIS Report, we need to have a module. Each module can have many reporting features, each reporting feature can have multiple reports and each report can have multiple reporting filters. For simplicity, we will create a module which will have one reporting feature and reporting feature will have just one report. The report will expose two filters, To Date and From Date which gives the flexibility of choosing the data between two specific dates.

TestModule just gets the IExtensibilityManager and registers the TestReportingFeature with the extensibility manager. IIS Reports module queries IExtensibility Manager for extension “ReportingFeature” at runtime and displays the report.

Using Add Reference… from the Project menu, add reference to Microsoft.Web.Management.IisReports.Client.dll using the Browse tab and search for \Windows\system32\inetsrv directory.

internal class TestModule : Module {

        
private ReportingFeature testReportingFeature;

        protected override void 
Initialize(IServiceProvider serviceProvider, Microsoft.Web.Management.Server.ModuleInfo moduleInfo) {
            
base.Initialize(serviceProvider, moduleInfo);

            
IExtensibilityManager extensibilityManager =
                
(IExtensibilityManager)serviceProvider.GetService(typeof(IExtensibilityManager));
            
Debug.Assert(extensibilityManager != null"Extensibility Manager is null");

            
testReportingFeature = new TestReportingFeature(this);

            if 
(extensibilityManager != null) {
                extensibilityManager.RegisterExtension(
typeof(ReportingFeature), testReportingFeature);
            
}
        }
    }

The report definition used for in this demonstration purpose is ChartReportDefinition. IIS Reports gives you the flexibility of having three kinds of report definitions. ChartReportDefinition which is the most advanced one creates a chart and a table for the data, TableReportDefinition just shows the table for the data. TestReportDefinition exposes two filters FromDate and ToDate to filter out the data based on the dates. It has a method GetData which internally makes call to service proxy and then to module service to get an object array. It then creates a datatable out of it, which is used by IIS Reports for displaying the chart and the table.

internal class TestReportDefinition : ChartReportDefinition {
        
private ReportFilter[] _filters;

        public 
TestReportDefinition(ReportingFeature feature)
            : 
base(feature, "TestReportName""Test Report Name", ReportCategory.WebServer) {
            ReportFilter filter1 
=
                new 
ReportFilter("FromDate""From Date", DataType.DateTime, DateTime.Now.Subtract(new TimeSpan(30000)), true);
            
ReportFilter filter2 =
                new 
ReportFilter("ToDate""To Date", DataType.DateTime, DateTime.Now, true);

            
_filters = new ReportFilter[] { filter1, filter2 };
        
}

        
public override string XValueColumn {
            
get {
                
return "Site";
            
}
        }

        
public override IList<string> YValueColumns {
            
get {
                
return new string[] { "Hits" };
            
}
        }

        
public override IList<string> Columns {
            
get {
                
return new string[] { "Site""Hits" };
            
}
        }

        
public override IEnumerable<ReportFilter> Filters {
            
get {
                
return _filters;
            
}
        }

        
protected override DataTable GetData(IDictionary<stringobject> arguments) {
            Hashtable filters 
= new Hashtable();
            foreach 
(KeyValuePair<stringobject> filter in arguments) {
                filters.Add(filter.Key, filter.Value)
;
            
}

            
object[] data ((TestReportingFeature)Feature).ServiceProxy.GetReport(filters);
            object
[] row;
            
ArrayList rows (ArrayList)data[1];
            
DataTable table = new DataTable();
            
table.Columns.Add("site"typeof(string));
            
table.Columns.Add("hits"typeof(int));

            for 
(int 0i < rows.Counti++) {
                DataRow tableRow 
table.NewRow();
                
row (object[])rows[i];
                
tableRow["site"(string)row[0];
                
tableRow["hits"(int)row[1];

                
table.Rows.Add(tableRow);
            
}
            
return table;
        
}
    }

Creating the ServiceProxy

internal class TestReportServiceProxy : ModuleServiceProxy {
        
public object[] GetReport(IDictionary arguments) {
            
return (object[])Invoke("GetReport", arguments);
        
}
    }

Miscellaneous

ClassesInterop.cs

Since Logparser returns a COM object, so we need an interop.cs, the file can be downloaded from Interop.cs

AssemblyRef.cs

This class is used by module provider to load the client module.

internal static class AssemblyRef {
        
private static string testReportClient;

        internal static string 
TestReportClient {
            
get {
                
if (testReportClient == null) {
                    AssemblyName assemblyName 
= typeof(AssemblyRef).Assembly.GetName();
                    string 
assemblyFullName assemblyName.FullName;
                    
testReportClient assemblyFullName.Replace(assemblyName.Name, "TestIisReportsClient");
                
}

                
return testReportClient;
            
}
        }
    }

Comments

  1. Submitted on Jun 09 2008 by
    ryancammer
    unless i'm missing something, this would be very cool if the classes that you use in the example weren't internal. it makes it a bit difficult to copy and paste the code to create a working example. also, some setup steps would be greatly appreciated.
  2. Submitted on Aug 09 2008 by
    jhd.honza@usa.net
    you mean no LINQ? :-)

You must Log In to comment.