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 i = 0; i < columnCount; i++) {
headers[i] = rs.getColumnName(i);
}
data[0] = headers;
// Add the results
ArrayList rows = new ArrayList();
while (!rs.atEnd()) {
object[] row = new object[columnCount];
ILogRecord record = rs.getRecord();
for (int i = 0; i < columnCount; i++) {
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[1] = rows;
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(30, 0, 0, 0)), 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<string, object> arguments) {
Hashtable filters = new Hashtable();
foreach (KeyValuePair<string, object> 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 i = 0; i < rows.Count; i++) {
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