Time Logging – Convert HTML – C# example

In this article, you will find an example of using custom message handlers to convert HTML from ZIP archive to PDF and log the execution time of this process.

Sometimes, in order to optimize the performance, you may need to find out the web request execution time. For example, you want to know how much time is taken to convert HTML-based file from a ZIP archive to PDF.

Execution Time Logging

Creating a Message Handler for a Custom Schema Implementation

Let’s create a custom handler that you can use for custom schema (protocol) implementation. Take the next steps:

  1. Use the necessary Namespace, which is the Aspose.Html.Net.
  2. Define your own CustomSchemaMessageHandler class that will be derived from the MessageHandler class.
  3. Initialize an instance of the CustomSchemaMessageHandler class and define a Filter property for it.
  4. Create the CustomSchemaMessageFilter class that is derived from the MessageFilter class.
  5. Override the Match() method of the MessageFilter class to implement the custom message handler behaviour.

The following code snippet shows how to create the CustomSchemaMessageHandler class:

 1using Aspose.Html.Net;
 2...
 3
 4    // Define the CustomSchemaMessageHandler class that is derived from the MessageHandler class
 5    abstract class CustomSchemaMessageHandler : MessageHandler
 6    {
 7        protected CustomSchemaMessageHandler(string schema)
 8        {
 9            Filters.Add(new CustomSchemaMessageFilter(schema));
10        }
11    }
12
13    // Define the CustomSchemaMessageFilter class that is derived from the MessageFilter class
14    class CustomSchemaMessageFilter : MessageFilter
15    {
16        private readonly string schema;
17        public CustomSchemaMessageFilter(string schema)
18        {
19            this.schema = schema;
20        }
21
22    	// Override the Match() method
23    	public override bool Match(INetworkOperationContext context)
24        {
25            return string.Equals(schema, context.Request.RequestUri.Protocol.TrimEnd(':'));
26        }
27    }

The CustomSchemaMessageHandler(schema) constructor instantiates the CustomSchemaMessageHandler object and takes the schema as a parameter. The Add() method append the CustomSchemaMessageFilter object at the end of the collection. The Match() method tests whether a context satisfies the filter criteria.

Creating the ZipFileSchemaMessageHandler for Working with ZIP archives

The following code snippet shows how to create the ZipFileSchemaMessageHandler class for working with ZIP archives:

 1using System.IO;
 2using System.Linq;
 3using System.Net;
 4using Aspose.Html;
 5using Aspose.Html.Net;
 6using Aspose.Html.Services;
 7using Aspose.Zip;
 8...
 9
10	// Define the ZipFileSchemaMessageHandler class that is derived from the CustomSchemaMessageHandler class
11	class ZipFileSchemaMessageHandler : CustomSchemaMessageHandler
12	{
13	    private readonly Archive archive;
14
15	    public ZipFileSchemaMessageHandler(Archive archive) : base("zip-file")
16	    {
17	        this.archive = archive;
18	    }
19
20	    // Override the Invoke() method
21	    public override void Invoke(INetworkOperationContext context)
22	    {
23	        var pathInsideArchive = context.Request.RequestUri.Pathname.TrimStart('/').Replace("\\", "/");
24	        var stream = GetFile(pathInsideArchive);
25
26	        if (stream != null)
27	        {
28	            // Checking: if a resource is found in the archive, then return it as a Response
29	            var response = new ResponseMessage(HttpStatusCode.OK);
30	            response.Content = new StreamContent(stream);
31	            response.Headers.ContentType.MediaType = MimeType.FromFileExtension(context.Request.RequestUri.Pathname);
32	            context.Response = response;
33	        }
34	        else
35	        {
36	            context.Response = new ResponseMessage(HttpStatusCode.NotFound);
37	        }
38
39	        // Invoke the next message handler in the chain
40	        Next(context);
41	    }
42
43	    Stream GetFile(string path)
44	    {
45	        var result = archive
46	            .Entries
47	            .FirstOrDefault(x => x.Name == path);
48	        return result?.Open();
49	    }
50	}
51 

In the above example, searching for a resource (zip archive) at its URI is realized. If a resource is found, FromFileExtension() method returns the MimeType of the resource.

Message Handler for a Web Request Execution Time Logging

The following code snippet shows how to create StartRequestDurationLoggingMessageHandler and StopRequestDurationLoggingMessageHandler to log the time taken for web request execution.

 1using System;
 2using System.Collections.Concurrent;
 3using System.Diagnostics;
 4using Aspose.Html;
 5using Aspose.Html.Net;
 6...
 7
 8	// Define the RequestDurationLoggingMessageHandler class that is derived from the MessageHandler class
 9	abstract class RequestDurationLoggingMessageHandler : MessageHandler
10	{
11	    private static ConcurrentDictionary<Url, Stopwatch> requests = new ConcurrentDictionary<Url, Stopwatch>();
12
13	    protected void StartTimer(Url url)
14	    {
15	        requests.TryAdd(url, Stopwatch.StartNew());
16	    }
17
18	    protected TimeSpan StopTimer(Url url)
19	    {
20	        var timer = requests[url];
21	        timer.Stop();
22	        return timer.Elapsed;
23	    }
24	}
25
26	// Create StartRequestDurationLoggingMessageHandler that starts the timer
27	class StartRequestDurationLoggingMessageHandler : RequestDurationLoggingMessageHandler
28	{
29	    // Override the Invoke() method
30	    public override void Invoke(INetworkOperationContext context)
31	    {
32	        // Start the stopwatch
33	        StartTimer(context.Request.RequestUri);
34
35	        // Invoke the next message handler in the chain
36	        Next(context);
37	    }
38	}
39
40	// StopRequestDurationLoggingMessageHandler stops the timer
41	class StopRequestDurationLoggingMessageHandler : RequestDurationLoggingMessageHandler
42	{
43	    // Override the Invoke() method
44	    public override void Invoke(INetworkOperationContext context)
45	    {
46	        // Stop the stopwatch
47	        var duration = StopTimer(context.Request.RequestUri);
48
49	        // Print the result
50	        Console.WriteLine($"Elapsed: {duration:g}, resource: {context.Request.RequestUri.Pathname}");
51	
52	        // Invoke the next message handler in the chain
53	        Next(context);
54	    }
55	}

Adding the Message Handlers to the Pipeline

The key concept of message handlers work is chaining them together. We have created several message handlers and should add them to the pipeline in a specific order to implement the example of HTML from ZIP archive to PDF conversion time logging. Let’s perform the following steps:

  1. Create an instance of the Configuration class.
  2. Realize ZIP Custom Schema. Use the Add() method to append ZipFileSchemaMessageHandler to the end of the pipeline.
  3. Execute Duration Logging. Use the Insert() method to add the StartRequestDurationLoggingMessageHandler first in the pipeline, and the Add() method to append the StopRequestDurationLoggingMessageHandler to the end of the pipeline.
  4. Initialize an HTML document with specified configuration.
  5. Create the PDF Device and render HTML to PDF.
 1using Aspose.Html;
 2using Aspose.Html.Net;
 3using Aspose.Html.Rendering.Pdf;
 4using Aspose.Html.Services;
 5using Aspose.Zip;
 6...
 7
 8    // Prepare path to a source zip file
 9    string documentPath = Path.Combine(DataDir, "test.zip");
10
11    // Prepare path for converted file saving
12    string savePath = Path.Combine(OutputDir, "zip-to-pdf-duration.pdf");
13
14    // Create an instance of the Configuration class
15    using var configuration = new Configuration();
16    var service = configuration.GetService<INetworkService>();
17    var handlers = service.MessageHandlers;
18
19    // Custom Schema: ZIP. Add ZipFileSchemaMessageHandler to the end of the pipeline
20    handlers.Add(new ZipFileSchemaMessageHandler(new Archive(documentPath)));
21
22    // Duration Logging. Add the StartRequestDurationLoggingMessageHandler at the first place in the pipeline
23    handlers.Insert(0, new StartRequestDurationLoggingMessageHandler());
24
25    // Add the StopRequestDurationLoggingMessageHandler to the end of the pipeline
26    handlers.Add(new StopRequestDurationLoggingMessageHandler());
27
28    // Initialize an HTML document with specified configuration
29    using var document = new HTMLDocument("zip-file:///test.html", configuration);
30
31    // Create the PDF Device
32    using var device = new PdfDevice(savePath);
33
34    // Render HTML to PDF
35    document.RenderTo(device);

The Configuration() constructor creates an instance of the Configuration class. After the configuration is created, the GetService<INetworkService>(), MessageHandlers.Add() and MessageHandlers.Insert() methods are invoked. The Insert() and Add() methods append the custom message handlers in the collection of existing message handlers. The figure shows the chain of message handlers for this example:

Text “The chain of message handlers”

Note: The HTMLDocument(address, configuration) constructor takes the absolute path to the ZIP archive. But all related resources have relative paths in the HTML document and in the example’s code.

You can download the complete examples and data files from GitHub.

Subscribe to Aspose Product Updates

Get monthly newsletters & offers directly delivered to your mailbox.