Improve the documentation
diff --git a/src/DotPulsar/Abstractions/IReaderBuilder.cs b/src/DotPulsar/Abstractions/IReaderBuilder.cs
index e77ad01..4363697 100644
--- a/src/DotPulsar/Abstractions/IReaderBuilder.cs
+++ b/src/DotPulsar/Abstractions/IReaderBuilder.cs
@@ -15,7 +15,7 @@
 namespace DotPulsar.Abstractions
 {
     /// <summary>
-    /// A reader building abstraction
+    /// A reader building abstraction.
     /// </summary>
     public interface IReaderBuilder
     {
diff --git a/src/DotPulsar/ConsumerOptions.cs b/src/DotPulsar/ConsumerOptions.cs
index 19172cd..5422d6f 100644
--- a/src/DotPulsar/ConsumerOptions.cs
+++ b/src/DotPulsar/ConsumerOptions.cs
@@ -14,6 +14,9 @@
 
 namespace DotPulsar
 {
+    /// <summary>
+    /// The consumer building options.
+    /// </summary>
     public sealed class ConsumerOptions
     {
         internal const SubscriptionInitialPosition DefaultInitialPosition = SubscriptionInitialPosition.Latest;
@@ -33,13 +36,44 @@
             Topic = topic;
         }
 
+        /// <summary>
+        /// Set the consumer name. This is optional.
+        /// </summary>
         public string? ConsumerName { get; set; }
+
+        /// <summary>
+        /// Set initial position for the subscription. The default is 'Latest'.
+        /// </summary>
         public SubscriptionInitialPosition InitialPosition { get; set; }
+
+        /// <summary>
+        /// Set the priority level for the shared subscription consumer. The default is 0.
+        /// </summary>
         public int PriorityLevel { get; set; }
+
+        /// <summary>
+        /// Number of messages that will be prefetched. The default is 1000.
+        /// </summary>
         public uint MessagePrefetchCount { get; set; }
+
+        /// <summary>
+        /// Whether to read from the compacted topic. The default is 'false'.
+        /// </summary>
         public bool ReadCompacted { get; set; }
+
+        /// <summary>
+        /// Set the subscription name for this consumer. This is required.
+        /// </summary>
         public string SubscriptionName { get; set; }
+
+        /// <summary>
+        /// Set the subscription type for this consumer. The default is 'Exclusive'.
+        /// </summary>
         public SubscriptionType SubscriptionType { get; set; }
+
+        /// <summary>
+        /// Set the topic for this consumer. This is required.
+        /// </summary>
         public string Topic { get; set; }
     }
 }
diff --git a/src/DotPulsar/ConsumerStateChanged.cs b/src/DotPulsar/ConsumerStateChanged.cs
index 6366e16..8c78145 100644
--- a/src/DotPulsar/ConsumerStateChanged.cs
+++ b/src/DotPulsar/ConsumerStateChanged.cs
@@ -16,6 +16,9 @@
 {
     using Abstractions;
 
+    /// <summary>
+    /// Representation of a consumer state change.
+    /// </summary>
     public sealed class ConsumerStateChanged
     {
         internal ConsumerStateChanged(IConsumer consumer, ConsumerState consumerState)
@@ -24,7 +27,14 @@
             ConsumerState = consumerState;
         }
 
+        /// <summary>
+        /// The consumer that changed state.
+        /// </summary>
         public IConsumer Consumer { get; }
+
+        /// <summary>
+        /// The state that it changed to.
+        /// </summary>
         public ConsumerState ConsumerState { get; }
     }
 }
diff --git a/src/DotPulsar/Extensions/ProducerExtensions.cs b/src/DotPulsar/Extensions/ProducerExtensions.cs
index bf37687..9c3007c 100644
--- a/src/DotPulsar/Extensions/ProducerExtensions.cs
+++ b/src/DotPulsar/Extensions/ProducerExtensions.cs
@@ -19,6 +19,9 @@
 
     public static class ProducerExtensions
     {
+        /// <summary>
+        /// Get a builder that can be used to configure and build a Message.
+        /// </summary>
         public static IMessageBuilder NewMessage(this IProducer producer)
             => new MessageBuilder(producer);
     }
diff --git a/src/DotPulsar/Extensions/PulsarClientExtensions.cs b/src/DotPulsar/Extensions/PulsarClientExtensions.cs
index 8a8698a..77e95aa 100644
--- a/src/DotPulsar/Extensions/PulsarClientExtensions.cs
+++ b/src/DotPulsar/Extensions/PulsarClientExtensions.cs
@@ -19,12 +19,21 @@
 
     public static class PulsarClientExtensions
     {
+        /// <summary>
+        /// Get a builder that can be used to configure and build a Producer instance.
+        /// </summary>
         public static IProducerBuilder NewProducer(this IPulsarClient pulsarClient)
             => new ProducerBuilder(pulsarClient);
 
+        /// <summary>
+        /// Get a builder that can be used to configure and build a Consumer instance.
+        /// </summary>
         public static IConsumerBuilder NewConsumer(this IPulsarClient pulsarClient)
             => new ConsumerBuilder(pulsarClient);
 
+        /// <summary>
+        /// Get a builder that can be used to configure and build a Reader instance.
+        /// </summary>
         public static IReaderBuilder NewReader(this IPulsarClient pulsarClient)
             => new ReaderBuilder(pulsarClient);
     }
diff --git a/src/DotPulsar/Message.cs b/src/DotPulsar/Message.cs
index cc462c0..afeb39c 100644
--- a/src/DotPulsar/Message.cs
+++ b/src/DotPulsar/Message.cs
@@ -20,6 +20,9 @@
     using System.Collections.Generic;
     using System.Linq;
 
+    /// <summary>
+    /// The message received by consumers and readers.
+    /// </summary>
     public sealed class Message
     {
         private readonly List<KeyValue> _keyValues;
@@ -58,27 +61,89 @@
             }
         }
 
+        /// <summary>
+        /// The id of the message.
+        /// </summary>
         public MessageId MessageId { get; }
+
+        /// <summary>
+        /// The raw payload of the message.
+        /// </summary>
         public ReadOnlySequence<byte> Data { get; }
+
+        /// <summary>
+        /// The name of the producer who produced the message.
+        /// </summary>
         public string ProducerName { get; }
+
+        /// <summary>
+        /// The sequence id of the message.
+        /// </summary>
         public ulong SequenceId { get; }
+
+        /// <summary>
+        /// The redelivery count (maintained by the broker) of the message.
+        /// </summary>
         public uint RedeliveryCount { get; }
 
+        /// <summary>
+        /// Check whether the message has an event time.
+        /// </summary>
         public bool HasEventTime => EventTime != 0;
+
+        /// <summary>
+        /// The event time of the message (unix time in milliseconds).
+        /// </summary>
         public ulong EventTime { get; }
+
+        /// <summary>
+        /// The event time of the message.
+        /// </summary>
         public DateTimeOffset EventTimeAsDateTimeOffset => DateTimeOffset.FromUnixTimeMilliseconds((long) EventTime);
 
+        /// <summary>
+        /// Check whether the key been base64 encoded.
+        /// </summary>
         public bool HasBase64EncodedKey { get; }
+
+        /// <summary>
+        /// Check whether the message has a key.
+        /// </summary>
         public bool HasKey => Key != null;
+
+        /// <summary>
+        /// The key as a string.
+        /// </summary>
         public string? Key { get; }
+
+        /// <summary>
+        /// The key as bytes.
+        /// </summary>
         public byte[]? KeyBytes => HasBase64EncodedKey ? Convert.FromBase64String(Key) : null;
 
+        /// <summary>
+        /// Check whether the message has an ordering key.
+        /// </summary>
         public bool HasOrderingKey => OrderingKey != null;
+
+        /// <summary>
+        /// The ordering key of the message.
+        /// </summary>
         public byte[]? OrderingKey { get; }
 
+        /// <summary>
+        /// The publish time of the message (unix time in milliseconds).
+        /// </summary>
         public ulong PublishTime { get; }
+
+        /// <summary>
+        /// The publish time of the message.
+        /// </summary>
         public DateTimeOffset PublishTimeAsDateTimeOffset => DateTimeOffset.FromUnixTimeMilliseconds((long) PublishTime);
 
+        /// <summary>
+        /// The properties of the message.
+        /// </summary>
         public IReadOnlyDictionary<string, string> Properties => _properties ??= _keyValues.ToDictionary(p => p.Key, p => p.Value);
     }
 }
diff --git a/src/DotPulsar/MessageId.cs b/src/DotPulsar/MessageId.cs
index 6708f65..c9fc8c7 100644
--- a/src/DotPulsar/MessageId.cs
+++ b/src/DotPulsar/MessageId.cs
@@ -17,6 +17,9 @@
     using Internal.PulsarApi;
     using System;
 
+    /// <summary>
+    /// Unique identifier of a single message.
+    /// </summary>
     public sealed class MessageId : IEquatable<MessageId>
     {
         static MessageId()
@@ -25,7 +28,14 @@
             Latest = new MessageId(long.MaxValue, long.MaxValue, -1, -1);
         }
 
+        /// <summary>
+        /// The oldest message available in the topic.
+        /// </summary>
         public static MessageId Earliest { get; }
+
+        /// <summary>
+        /// The next message published in the topic.
+        /// </summary>
         public static MessageId Latest { get; }
 
         internal MessageId(MessageIdData messageIdData)
@@ -42,9 +52,24 @@
 
         internal MessageIdData Data { get; }
 
+        /// <summary>
+        /// The id of the ledger.
+        /// </summary>
         public ulong LedgerId => Data.LedgerId;
+
+        /// <summary>
+        /// The id of the entry.
+        /// </summary>
         public ulong EntryId => Data.EntryId;
+
+        /// <summary>
+        /// The partition.
+        /// </summary>
         public int Partition => Data.Partition;
+
+        /// <summary>
+        /// The batch index.
+        /// </summary>
         public int BatchIndex => Data.BatchIndex;
 
         public override bool Equals(object o)
diff --git a/src/DotPulsar/MessageMetadata.cs b/src/DotPulsar/MessageMetadata.cs
index 61ef1bc..bb89777 100644
--- a/src/DotPulsar/MessageMetadata.cs
+++ b/src/DotPulsar/MessageMetadata.cs
@@ -18,6 +18,9 @@
     using Internal.PulsarApi;
     using System;
 
+    /// <summary>
+    /// The message metadata builder.
+    /// </summary>
     public sealed class MessageMetadata
     {
         public MessageMetadata()
@@ -25,48 +28,72 @@
 
         internal readonly Internal.PulsarApi.MessageMetadata Metadata;
 
+        /// <summary>
+        /// The delivery time of the message (unix time in milliseconds).
+        /// </summary>
         public long DeliverAtTime
         {
             get => Metadata.DeliverAtTime;
             set => Metadata.DeliverAtTime = value;
         }
 
+        /// <summary>
+        /// The delivery time of the message.
+        /// </summary>
         public DateTimeOffset DeliverAtTimeAsDateTimeOffset
         {
             get => Metadata.GetDeliverAtTimeAsDateTimeOffset();
             set => Metadata.SetDeliverAtTime(value);
         }
 
+        /// <summary>
+        /// The event time of the message (unix time in milliseconds).
+        /// </summary>
         public ulong EventTime
         {
             get => Metadata.EventTime;
             set => Metadata.EventTime = value;
         }
 
+        /// <summary>
+        /// The event time of the message.
+        /// </summary>
         public DateTimeOffset EventTimeAsDateTimeOffset
         {
             get => Metadata.GetEventTimeAsDateTimeOffset();
             set => Metadata.SetEventTime(value);
         }
 
+        /// <summary>
+        /// The key of the message as a string.
+        /// </summary>
         public string? Key
         {
             get => Metadata.PartitionKey;
             set => Metadata.SetKey(value);
         }
 
+        /// <summary>
+        /// The key of the message as bytes.
+        /// </summary>
         public byte[]? KeyBytes
         {
             get => Metadata.GetKeyAsBytes();
             set => Metadata.SetKey(value);
         }
 
+        /// <summary>
+        /// The ordering key of the message.
+        /// </summary>
         public byte[]? OrderingKey
         {
             get => Metadata.OrderingKey;
             set => Metadata.OrderingKey = value;
         }
 
+        /// <summary>
+        /// The properties of the message.
+        /// </summary>
         public string? this[string key]
         {
             get
@@ -99,6 +126,9 @@
             }
         }
 
+        /// <summary>
+        /// The sequence id of the message.
+        /// </summary>
         public ulong SequenceId
         {
             get => Metadata.SequenceId;
diff --git a/src/DotPulsar/ProducerOptions.cs b/src/DotPulsar/ProducerOptions.cs
index 858a6fb..9d2197b 100644
--- a/src/DotPulsar/ProducerOptions.cs
+++ b/src/DotPulsar/ProducerOptions.cs
@@ -14,6 +14,9 @@
 
 namespace DotPulsar
 {
+    /// <summary>
+    /// The producer building options.
+    /// </summary>
     public sealed class ProducerOptions
     {
         internal const ulong DefaultInitialSequenceId = 0;
@@ -24,8 +27,19 @@
             Topic = topic;
         }
 
+        /// <summary>
+        /// Set the producer name. This is optional.
+        /// </summary>
         public string? ProducerName { get; set; }
+
+        /// <summary>
+        /// Set the initial sequence id. The default is 0.
+        /// </summary>
         public ulong InitialSequenceId { get; set; }
+
+        /// <summary>
+        /// Set the topic for this producer. This is required.
+        /// </summary>
         public string Topic { get; set; }
     }
 }
diff --git a/src/DotPulsar/ProducerStateChanged.cs b/src/DotPulsar/ProducerStateChanged.cs
index 0e3babd..7b69cce 100644
--- a/src/DotPulsar/ProducerStateChanged.cs
+++ b/src/DotPulsar/ProducerStateChanged.cs
@@ -16,6 +16,9 @@
 {
     using Abstractions;
 
+    /// <summary>
+    /// Representation of a producer state change.
+    /// </summary>
     public sealed class ProducerStateChanged
     {
         internal ProducerStateChanged(IProducer producer, ProducerState producerState)
@@ -24,7 +27,14 @@
             ProducerState = producerState;
         }
 
+        /// <summary>
+        /// The producer that changed state.
+        /// </summary>
         public IProducer Producer { get; }
+
+        /// <summary>
+        /// The state that it changed to.
+        /// </summary>
         public ProducerState ProducerState { get; }
     }
 }
diff --git a/src/DotPulsar/PulsarClient.cs b/src/DotPulsar/PulsarClient.cs
index 7043ae6..290ac74 100644
--- a/src/DotPulsar/PulsarClient.cs
+++ b/src/DotPulsar/PulsarClient.cs
@@ -22,6 +22,9 @@
     using System.Threading;
     using System.Threading.Tasks;
 
+    /// <summary>
+    /// Pulsar client for creating producers, consumers and readers.
+    /// </summary>
     public sealed class PulsarClient : IPulsarClient
     {
         private readonly IConnectionPool _connectionPool;
@@ -38,9 +41,15 @@
             DotPulsarEventSource.Log.ClientCreated();
         }
 
+        /// <summary>
+        /// Get a builder that can be used to configure and build a PulsarClient instance.
+        /// </summary>
         public static IPulsarClientBuilder Builder()
             => new PulsarClientBuilder();
 
+        /// <summary>
+        /// Create a producer.
+        /// </summary>
         public IProducer CreateProducer(ProducerOptions options)
         {
             ThrowIfDisposed();
@@ -55,6 +64,9 @@
             return producer;
         }
 
+        /// <summary>
+        /// Create a consumer.
+        /// </summary>
         public IConsumer CreateConsumer(ConsumerOptions options)
         {
             ThrowIfDisposed();
@@ -70,6 +82,9 @@
             return consumer;
         }
 
+        /// <summary>
+        /// Create a reader.
+        /// </summary>
         public IReader CreateReader(ReaderOptions options)
         {
             ThrowIfDisposed();
@@ -84,6 +99,9 @@
             return reader;
         }
 
+        /// <summary>
+        /// Dispose the client and all its producers, consumers and readers.
+        /// </summary>
         public async ValueTask DisposeAsync()
         {
             if (Interlocked.Exchange(ref _isDisposed, 1) != 0)
diff --git a/src/DotPulsar/ReaderOptions.cs b/src/DotPulsar/ReaderOptions.cs
index c9a4d77..2ee644c 100644
--- a/src/DotPulsar/ReaderOptions.cs
+++ b/src/DotPulsar/ReaderOptions.cs
@@ -14,6 +14,9 @@
 
 namespace DotPulsar
 {
+    /// <summary>
+    /// The reader building options.
+    /// </summary>
     public sealed class ReaderOptions
     {
         internal const uint DefaultMessagePrefetchCount = 1000;
@@ -27,10 +30,29 @@
             Topic = topic;
         }
 
+        /// <summary>
+        /// Set the reader name. This is optional.
+        /// </summary>
         public string? ReaderName { get; set; }
+
+        /// <summary>
+        /// Number of messages that will be prefetched. The default is 1000.
+        /// </summary>
         public uint MessagePrefetchCount { get; set; }
+
+        /// <summary>
+        /// Whether to read from the compacted topic. The default is 'false'.
+        /// </summary>
         public bool ReadCompacted { get; set; }
+
+        /// <summary>
+        /// The initial reader position is set to the specified message id. This is required.
+        /// </summary>
         public MessageId StartMessageId { get; set; }
+
+        /// <summary>
+        /// Set the topic for this reader. This is required.
+        /// </summary>
         public string Topic { get; set; }
     }
 }
diff --git a/src/DotPulsar/ReaderStateChanged.cs b/src/DotPulsar/ReaderStateChanged.cs
index 2ce04a2..c727e0f 100644
--- a/src/DotPulsar/ReaderStateChanged.cs
+++ b/src/DotPulsar/ReaderStateChanged.cs
@@ -16,6 +16,9 @@
 {
     using Abstractions;
 
+    /// <summary>
+    /// Representation of a reader state change.
+    /// </summary>
     public sealed class ReaderStateChanged
     {
         internal ReaderStateChanged(IReader reader, ReaderState readerState)
@@ -24,7 +27,14 @@
             ReaderState = readerState;
         }
 
+        /// <summary>
+        /// The reader that changed state.
+        /// </summary>
         public IReader Reader { get; }
+
+        /// <summary>
+        /// The state that it changed to.
+        /// </summary>
         public ReaderState ReaderState { get; }
     }
 }