Zuhaib

A very lazy but meticulous blogger

Archive for the ‘.NET’ tag

.NET Type Forwarding – Moving Types Between Assemblies

with 10 comments

I learned about this really cool feature in the .NET framework while reading an MS certification book, and could not stop blogging about it. Type forwarding in .NET allows you to move type from one assembly to another without recompiling applications that use the old assembly.

Important Notes:

  1. Microsoft Visual Basic 2005 does not support the use of the TypeForwardedToAttribute attribute to forward types written in Visual Basic 2005. Applications written in Visual Basic 2005 can use forwarded types written in other languages.
  2. The compilers do not support forwarding generic types in .NET 2.0, 3.0 and 3.5. Support for this was added in .NET 4.0.

For this example, I would write one assembly called Animal that would contain a class called Dog, which would be consumed by an application called Consumer1. Later, I would move the Dog class to a new assembly called Canine, and we would see how we can make the Consumer1 application still work using .NET type forwarding feature.

Animal (Class Library)

As discussed here is the Dog class that would reside inside the Animal assembly.

Dog.cs

namespace Animal
{
    using System;
    public class Dog
    {
        /// <summary>
        /// Make the dog bark.
        /// </summary>
        public void Bark()
        {
            Console.WriteLine("Arrgh.... Woof Woof!");
        }
    }
}

It’s a very simple class that contains a public method called Bark, which would output "Arrgh…. Woof Woof!" to the console when invoked.

Consumer1 (Console Application)

I would now write our first application that would consume the animal assembly. Its a simple console application that has reference to the Animal assembly.

namespace Consumer1
{
    using System;
    using Animal;
    /// <summary>
    /// The program.
    /// </summary>
    internal class Program
    {
        #region Methods
        /// <summary>
        /// The entry point.
        /// </summary>
        /// <param name="args">
        /// The command line arguments.
        /// </param>
        private static void Main(string[] args)
        {
            Console.WriteLine("Consumer 1 Application");
            Dog dog = new Dog();
            dog.Bark();
            Console.ReadKey();
        }
        #endregion
    }
}

In the main method of the Consumer1 application I have created an instance of the Dog class and then called its Bark method. When we run this application we would get the following output.

Output:

Consumer 1 Application

Arrgh…. Woof Woof!

Canine (Class Library)

I moved the Dog class from Animal assembly to the new assembly called Canine.

namespace Animal
{
    using System;
    public class Dog
    {
        /// <summary>
        /// Make the dog bark.
        /// </summary>
        public void Bark()
        {
            Console.WriteLine("Arrgh.... Woof Woof! (Inside New Assembly)");
        }
    }
}

The only difference in the class definition here, is the text that I output to the console when the Bark method is invoked. I have changed it so that we can easily spot out that our old application (Consumer1) is accessing the Dog class from the new Canine assembly.

Also you might wonder, if the assembly name is Canine then why is the namespace for the Dog class still Animal? It’s necessary to keep the old namespaces while moving types between assemblies for the old applications to find the type in the new assembly.

If we deploy the Animal assembly without the Dog class then existing installed applications that were compiled against the old Animal assembly would break. Unless, we use type forwarding in the modified Animal assembly and let it know where to look for the Dog class when its requested. To enable type forwarding we need to do the following things:

  1. Add a reference of the new Canine assembly to the Animal assembly.
  2. Add the TypeForwardedToAttribute attribute to the animal assembly and specify the type to be forwarded.

Normally we add all assembly attributes to the AssemblyInfo.cs file. The TypeForwardedToAttribute attribute resides in the System.Runtime.CompilerServices namespace. Add the following line to the AssemblyInfo.cs.

using System.Runtime.CompilerServices;

// Type forward the dog class to the Canine assembly
[assembly: TypeForwardedTo(typeof(Animal.Dog))]

When any application would look for the Dog class in the Animal assembly it would be forwarded to the Canine assembly. Now when we deploy the new Animal & Canine assembly though the Consumer1 application does not have a reference to the Canine assembly, it would still find the Dog class and produce the following output.

Output:

Consumer 1 Application

Arrgh…. Woof Woof! (Inside New Assembly)

Deploying both the assemblies are important. Otherwise the Consumer1 application will not find the Dog class, and would crash.

Source Code:

Download the source code accompanying this post here. TypeForwardedToSample.zip

Written by Zuhaib

January 4th, 2010 at 3:22 am

C# – Change log4net Log Path and Level Programmatically

with 6 comments

Log4Net makes it incredibly easy to implement logging functionality in your application. Log4Net configuration can be little bit tricky, but its easy to learn.

Most of the time when I am working on a project the application configurations including log path and level are fetched from the database.

Log4Net doesn’t provide any straight forward way of fetching log file path or level from database (because its not necessary). To change the log file path and level programmatically we have to write our own log file appender.

For this demonstration I will write one rolling file appender. The custom file appender class should inherit from the log4net’s RollingFileAppender class.  We have to override the File property of the appender and change the log path. My rolling file appender class looks like this:

My Rolling File Appender:

namespace MyLog4Net.LogAppenders
{
    using System;

    using log4net;
    using log4net.Appender;

    using MyLog4Net.Interfaces;
    using MyLog4Net.Services;

    /// <summary>
    /// My log4net Rolling file appender.
    /// </summary>
    public class MyLog4NetFileAppender : RollingFileAppender
    {
        #region Constants and Fields

        /// <summary>
        /// Component parameter business layer
        /// </summary>
        private static IConfigService service;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MyLog4NetFileAppender"/> class.
        /// </summary>
        public MyLog4NetFileAppender()
            : this(new ConfigService())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MyLog4NetFileAppender"/> class.
        /// </summary>
        /// <param name="configService">
        /// The config service.
        /// </param>
        public MyLog4NetFileAppender(IConfigService configService)
        {
            service = configService;

            // get the log level
            // must be a proper log4net Threshold
            string logLevel = service.GetLogLevel();

            // set the log level
            LogManager.GetRepository().Threshold = LogManager.GetRepository().LevelMap[logLevel];
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the log file name.
        /// </summary>
        /// <value>The log file name.</value>
        public override string File
        {
            get
            {
                return base.File;
            }

            set
            {
                try
                {
                    // get the log directory
                    string logDirectory = service.GetLogPath();

                    // get the log file name from the config file.
                    string logFileName = value.Substring(value.LastIndexOf('\\') + 1);

                    // build the new log path
                    if (!logDirectory.EndsWith("\\") || !logDirectory.EndsWith("/"))
                    {
                        logDirectory += "\\";
                    }

                    // replace the new log file path
                    base.File = logDirectory + logFileName;
                }
                catch (Exception ex)
                {
                    // TODO: Log the error
                    // use the default
                    base.File = value;
                }
            }
        }

        #endregion
    }
}

Setting The Log Level (Threshold):

public MyLog4NetFileAppender(IConfigService configService)
{
    // Service layer that will be used to fetch config data
    service = configService;

    // get the log level
    // must be a proper log4net Threshold
    string logLevel = service.GetLogLevel();

    // set the log level
    LogManager.GetRepository().Threshold = LogManager.GetRepository().LevelMap[logLevel];
}

In the constructor of this class I fetch the log threshold configured in the database using the service.GetLogLevel() method. The LogManager.GetRepository() returns the default log4net repository of the calling assembly. LogManager.GetRepository().LevelMap gives us a set of default log4net levels. We change the log level by pasing the value fetched from the database to the current repositories Threshold property.

Setting The Log Path:

public override string File
{
    get
    {
        return base.File;
    }

    set
    {
        try
        {
            // get the log directory
            string logDirectory = service.GetLogPath();

            // get the log file name from the config file.
            string logFileName = value.Substring(value.LastIndexOf('\\') + 1);

            // build the new log path
            if (!logDirectory.EndsWith("\\") || !logDirectory.EndsWith("/"))
            {
                logDirectory += "\\";
            }

            // replace the new log file path
            base.File = logDirectory + logFileName;
        }
        catch (Exception ex)
        {
            // TODO: Log the error
            // use the default
            base.File = value;
        }
    }
}

When log4net is initializing the File property of the file appender would be called. Here the value would be the file name that was set in the log4net configuration file. In this property we extract the file name and then append the log path that we fetched from the database.

Using Our Custom Appender:

To use our custom appender we would create a log4net appender section in the config file and use our appender in the appender type. I like placing my log4net configuration is a different file. My log4net configuration file looks like:

Log4Net Configuration File:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
    </configSections>

    <!-- log4net -->
    <log4net>
        <!-- Define some output appenders -->
        <appender name="MyRollingFileAppender" type="MyLog4Net.LogAppenders.MyLog4NetFileAppender">
            <param name="File" value="..\\Log\\MyApplication-log-file.txt" />
            <param name="AppendToFile" value="true" />
            <param name="RollingStyle" value="Size" />
            <param name="StaticLogFileName" value="true" />
            <layout type="log4net.Layout.PatternLayout">
                <param name="Header" value="[Header]&#13;&#10;" />
                <param name="Footer" value="[Footer]&#13;&#10;" />
                <param name="ConversionPattern" value="%d [%t] %-5p %C %M - %m%n" />
            </layout>
        </appender>
        <!-- Setup the root category, add the appenders and set the default level -->
        <!-- Setup the root category, add the appenders and set the default level
                ALL
                DEBUG
                INFO
                WARN
                ERROR
                FATAL
                OFF
            For example, setting the threshold of an appender to DEBUG will also allow INFO,
            WARN, ERROR and FATAL messages to log along with DEBUG messages. (DEBUG is the
            lowest level). This is usually acceptable as there is little use for DEBUG
            messages without the surrounding INFO, WARN, ERROR and FATAL messages.
            Similarly, setting the threshold of an appender to ERROR will filter out DEBUG,
            INFO and ERROR messages but not FATAL messages.
        -->
        <root>
            <level value="ALL" />
            <appender-ref ref="MyRollingFileAppender" />
        </root>
    </log4net>
</configuration>

Notice that instead of using log4net’s RollingFileAppender class I am using our MyLog4NetFileAppender class.

Initializing Log4Net:

To initialize log4net put the following code in the AssemblyInfo.cs file.

/* Configure log4net
   log4net configuration will be searched in the .config file*/
[assembly: XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

The above code tells log4net to look for log4net.config file in the application and initialize itself.

Now everything is ready you can now run your application. To check if the code works or not you can put breakpoints in the constructor and File property of the appender.

Source:

Download the source code for this article MyLog4Net.zip

Written by Zuhaib

November 10th, 2009 at 11:07 am

Visual Studio 2010 Beta 2 Now Out For Public

without comments

Finally Visual Studio 2010 Beta 2 is out for public (and not just subscribers).  To download visit this link or download the ISO from here. Make sure you completely uninstall Visual Studio 2010 Beta 1 before installing Beta 2.

image_thumb_04B9B051

Visual Studio 2010 brings many new features that we were only able to achieve using 3rd party add-ins like CodeRush/Resharper.

Please visit the below links to learn more about VS 2010 and .NET 4.0 features:

Written by Zuhaib

October 22nd, 2009 at 7:49 am

MSBuild Context Menu: Build .NET Project/Solution From Explorer

with 4 comments

Sometimes I don’t like opening visual studio just for building the latest source code from the repository. I don’t remember who wrote the original registry tweak for this, but the credit goes to the original author.

The original tweak allowed to compile Visual Studio 2005 solutions as I use Visual Studio 2008 I had to modify the original tweak to make it work with VS2008. Click Here to download the modified files.

image1

Unzip the files and you will see two folders. VS2005 is the original one and VS2008 is the modified version. Each  folder contains two registry files. One to add the commands to windows explorer and another to remove them. If you have both VS2005 and VS2008 installed on your machine you can add both the versions to the registry or you can add either of them.

image2 Once you have merged the files in your windows registry you can see two commands added to your explorer context menu when you right click on any solution or project file. One for Debug build and one for Release build.

When you click on either of the commands a command window will open and you can see you build status.

image3

Download the modified files MSBuild-VS2005-VS2008.zip

Credit goes to the original author.

Written by Zuhaib

May 15th, 2009 at 10:50 am

Writing Provider Independent .NET Data Access Code

with 3 comments

Ever wanted to support different databases from different providers? Tired of writing different layers for different providers? Too much pain to maintain the code? Not anymore.

How many times have you been asked to support a different database when you have already written your data access layer for a specific database? We all know how painful it is. Every time you are asked to do so there is very less code that you can actually reuse. So what could be done minimize changes?

Normally our data access layer is tightly coupled with the database. Today when watching a screencast from DNRTV by Jean-Paul S. Boodhoo on design patterns he demoed a feature in .NET framework 2.0 called DbProviderFactory. He then dived into many other patterns but I will concentrate only on DbProviderFactory.

.NET 2.0 has factory classes which can determine the database type from a provided data provider name. So let me show you a quick example. To use this feature you have to add reference to the System.Configuration assembly.

Lets create an interface for our db provider class.

Provider Factory

ITestDbProviderFactory.cs

namespace NTierTest.Data 
{ 
    using System.Data; 

    /// <summary> 
    /// Database Provider Factory Class 
    /// </summary> 
    public interface ITestDbProviderFactory 
    { 
        #region Public Methods 

        /// <summary> 
        /// Creates an <see cref="IDbCommand"/> 
        /// </summary> 
        /// <returns></returns> 
        IDbCommand CreateCommand(); 

        /// <summary> 
        /// Creates an <see cref="IDbConnection"/> 
        /// </summary> 
        /// <returns></returns> 
        IDbConnection CreateConnection(); 

        #endregion 
    } 
}

 

The ITestDbProviderFactory is a very simple interface which has two methods. CreateConnection will return a IDbConnection and CreateCommand will create a IDbCommand object.

Note we are returning abstract types. The provider specific classes like SqlConnection Or OracleConnection implement these abstract types.

Now lets create a class which will implement this interface

TestDbProviderFactory.cs

 

namespace NTierTest.Data
{
    using System.Configuration;
    using System.Data;
    using System.Data.Common;

    /// <summary>
    /// A DbProvider Class that uses AbstractFactoryPattern To Return the Connection and Command
    /// This implementation uses Mono State Pattern
    /// </summary>
    public class TestDbProviderFactory : ITestDbProviderFactory
    {
        #region Constants and Fields

        /// <summary>
        /// .NET FrameWork 2.0 Abstract Provider Factory
        /// It is marked static .. to ensure one instance through out the application
        /// </summary>
        private static DbProviderFactory _frameworkProviderFactory;

        /// <summary>
        /// Loaded From The Configuration File
        /// </summary>
        private static ConnectionStringSettings _settings;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Static constructor will be called only once through out the application
        /// </summary>
        static TestDbProviderFactory()
        {
            _settings = ConfigurationManager.ConnectionStrings[1];

            _frameworkProviderFactory = DbProviderFactories.GetFactory(_settings.ProviderName);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Returns a <see cref="IDbCommand"/> Object
        /// </summary>
        /// <returns>A Command Object</returns>
        public IDbCommand CreateCommand()
        {
            return _frameworkProviderFactory.CreateCommand();
        }

        ///<summary>
        /// Return a <see cref="IDbConnection"/> Object
        ///</summary>
        ///<returns>Connection object</returns>
        public IDbConnection CreateConnection()
        {
            IDbConnection connection = _frameworkProviderFactory.CreateConnection();
            connection.ConnectionString = _settings.ConnectionString;

            return connection;
        }

        #endregion
    }
}

 

In this class we have created a private static instance of DbProviderFactory class which is a .net abstract factory class for doing provider related operations. We will use this class to create a provider specific connection and command object.

/// <summary>
/// Static constructor will be called only once through out the application
/// </summary>
static TestDbProviderFactory()
{
    _settings = ConfigurationManager.ConnectionStrings[1];

    _frameworkProviderFactory = DbProviderFactories.GetFactory(_settings.ProviderName);
}

We also have a private static ConnectionStringSettings object. We will use the ConfigurationManager class to get the connection string from the web.config/app.config file.

connectionString="Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True"
providerName="System.Data.SqlClient" />

 

We initialize the ConnectionStringSettings object and the DbProviderFactory object in the static constructor. To ensure one copy of these variables. The same could be done using Singleton pattern. But I prefer using the Monostatic pattern because we can acheive the same thing that we can acheive using the singleton pattern and the client will not know that he is using a single copy of the varialbles internally.

_settings = ConfigurationManager.ConnectionStrings[1]; reads the connection string settings from the configuration file. Notice I am getting the second connection string setting. Even if you have only one connection string setting defined in the web.config ASP.NET internally has a connection string already configured for the default asp.net database aspnet.mdf even if it does not exist.

On line number 49 we are instantiating the DbProviderFactory object using the System.Data.Common.DbProviderFactories class. We give the provider name to the GetFactory method of this class. This line will create a provider specific DbProviderFactory object for us.

I already have a connection string configured in my web.config file

So in the CreateConnection method we use the _frameworkProviderFactory.CreateConnection() method to get an IDbConnection object and then set the ConnectionString property of the created object to the ConnectionString property of the static ConnectionStringsSetting object.

public IDbCommand CreateCommand()
{
    return _frameworkProviderFactory.CreateCommand();
}

public IDbConnection CreateConnection()
{
    IDbConnection connection = _frameworkProviderFactory.CreateConnection();
    connection.ConnectionString = _settings.ConnectionString;

    return connection;
}

Data Access Layer

Now its time to write a data access layer. The interface for our data access layer contains only one method GetAllEmployees, which will return a DataTable.

IEmployeeRepository.cs

namespace NTierTest.Core
{
    using System.Data;

    /// <summary>
    /// Employee repository
    /// </summary>
    public interface IEmployeeRepository
    {
        #region Public Methods

        /// <summary>
        /// Gets all employees.
        /// </summary>
        /// <returns>Employee data table</returns>
        DataTable GetAllEmployees();

        #endregion
    }
}

 

After implementing this interface here is my data access layer

EmployeeRepository.cs

 
namespace NTierTest.Data
{
    using System;
    using System.Data;

    /// <summary>
    /// Employee tasks
    /// </summary>
    public class EmployeeRepository : IEmployeeRepository
    {
        #region Constants and Fields

        private ITestDbProviderFactory _providerFactory;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the EmployeeRepository class.
        /// Using poor mans dependency injection.
        /// </summary>
        public EmployeeRepository()
            : this(new TestDbProviderFactory())
        {
        }

        /// <summary>
        /// Initializes a new instance of the EmployeeRepository class.
        /// </summary>
        /// <param name="provider"></param>
        public EmployeeRepository(ITestDbProviderFactory provider)
        {
            this._providerFactory = provider;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets all employees.
        /// </summary>
        /// <returns>Employee data table</returns>
        public DataTable GetAllEmployees()
        {
            try
            {
                DataTable rawData = new DataTable();

                // Get the connection from the provider factory
                using (IDbConnection connection = this._providerFactory.CreateConnection())
                {
                    // create the command object using the conneciton object
                    IDbCommand command = connection.CreateCommand();

                    // Inline query has been used for the sake of this demo
                    // use stored procedure
                    command.CommandText = "Select * From Employees";
                    command.CommandType = CommandType.Text;

                    // open the connection
                    connection.Open();

                    // execute reader
                    IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);

                    // load dataset from reader
                    // since reader cannot be used when the connection is closed I am using datatable
                    using (reader)
                    {
                        rawData.Load(reader);
                    }
                }
                // return the data
                return rawData;
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        #endregion
    }
}

 

We need a connection and command object to read data from our database, thats why I have created an instance of our provider factory. The data access layer will use this provider factory class to get the appropriate connection and command object.

private ITestDbProviderFactory _providerFactory;

 

Because our data access layer will be called from the service layer. I have created two constructor. One take a TestProviderFactory object and the empty constructor is used to feed the dependencies. This technique is called poor man’s dependency injection.

public EmployeeRepository() : this(new TestDbProviderFactory())
{
}

public EmployeeRepository(ITestDbProviderFactory provider)
{
    this._providerFactory = provider;
}

In the GetAllEmployees method I create a DataTable object then get the connection from the _providerFactory.CreateConnection method. The CreateConnection method to our surprise return the correct connection object, in this case a SqlConnection.

DbProviderFactory returning the correct connection object

Then I create IDbCommand object using the create connection, this way I don’t have to configure the connection property of the command.

I have used inline SQL query just for this demo sake. We should not use this in production. We should use stored procedure because sql for SQL Server and Oracle are different.

// create the command object using the conneciton object
IDbCommand command = connection.CreateCommand();

// Inline query has been used for the sake of this demo
// use stored procedure
command.CommandText = "Select * From Employees";
command.CommandType = CommandType.Text;

// open the connection
connection.Open();

// execute reader
IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);

 

I use the DataTable.Load method to load the reader into the data table. Because the reader needs an open connection we cannot send reader between layers unless we have a persistent connection. Our service layer will use this data table object to create employee collection object.

// load dataset from reader
// since reader cannot be used when the connection is closed I am using datatable
using (reader)
{
    rawData.Load(reader);
}

 

This completes our data access layer. Now lets write the service layer.

Service Layer

IEmployeeService.cs

namespace NTierTest.Core
{
    using System.Collections.Generic;

    ///<summary>
    ///Employee Service contract
    ///</summary>
    public interface IEmployeeService
    {
        #region Public Methods

        /// <summary>
        /// Gets all employees.
        /// </summary>
        /// <returns>List of Employees</returns>
        IList<IEmployee> GetAllEmployees();

        #endregion
    }
}

The IEmployeeService interface is contains only one method GetAllEmployees which returns an IList of IEmployee.

EmployeeService.cs

namespace NTierTest.Service
{
    using System;
    using System.Collections.Generic;
    using System.Data;

    ///<summary>
    ///Does all the employee related tasks
    ///</summary>
    public class EmployeeService : IEmployeeService
    {
        //Method...

        #region Public Methods

        /// <summary>
        /// Gets all employees.
        /// </summary>
        /// <returns>An <see cref="IList{T}"/> of <see cref="IEmployee"/></returns>
        public IList<IEmployee> GetAllEmployees()
        {
            IEmployeeRepository employeeRepository = new EmployeeRepository();

            IList<IEmployee> resutls = new List<IEmployee>();

            foreach (DataRow row in employeeRepository.GetAllEmployees().Rows)
            {
                resutls.Add(
                    new Employee(
                        row["FirstName"].ToString(),
                        row["LastName"].ToString(),
                        DateTime.Parse(row["BirthDate"].ToString())));
            }

            return resutls;
        }

        #endregion
    }
}

As you can see I create an instance of the EmployeeRepository class and make a call to the GetAllEmployees method and iterate over all the rows and then create Employee objects and add them to a IList IEmployees. Simple isn’t it?

Now what? Its all ready lets see it in action. I have already written a test to test the features.

namespace NTierTest.Test
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Employee Service Test Harness
    /// </summary>
    [TestFixture]
    public class EmployeeTestServiceTest
    {
        #region Public Methods

        /// <summary>
        /// Gets all employees_ should get all employees.
        /// </summary>
        [Test]
        public void TestGetAllEmployeesShouldGetAllEmployees()
        {
            IEmployeeService service = new EmployeeService();
            IList<IEmployee> employees = service.GetAllEmployees();

            foreach (IEmployee employee in employees)
            {
                Console.WriteLine(employee.FirstName);
            }

            Assert.IsTrue(employees.Count > 0);
        }

        #endregion
    }
}

The test is very simple it creates an instance of the EmployeeService, Gets a list of employees and outputs it to the console.

I know this is not an actual way to do TDD. I am a beginner and I wanted to show that it actually creates instance of the correct provider, thats why I have not mocked anything.

Test Result

Test Results Results

As you can see everything works as expected. Try it out yourself and say what do you think? Got thoughts? shout it out.

Download Source Code

DbProvider Test

Written by Zuhaib

November 5th, 2008 at 3:20 pm