4
\$\begingroup\$
class HtmlLogger:ILogger,IDisposable 
{
    private System.IO.StreamWriter  _file;
    private bool _disposed;

    public HtmlLogger()
    {
        _disposed = false;
        _file = new StreamWriter(@"somepath");
        _file.Write("<HTML><BODY>");
    }

    public void Log(string message)
    {
        _file.Write("<DIV>{0}</DIV>", message);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (_file != null)
                {
                    _file.Write("</BODY></HTML>");
                    _file.Flush();
                    _file.Dispose();
                }

            }
            _file = null;
            _disposed = true;
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ You need to encode messages using HTMLEncode or some sort of message encoding to the HTML format i.e. encode <,>,/,",',& and related symbols. There are already some methods in .NET actually in System.Web namespace \$\endgroup\$ Oct 4, 2011 at 2:41
  • \$\begingroup\$ You can use simply Base64 encoding to write log with, and later, you can use javascript in HTML to process DIV nodes as Base64 strings and decode them inline to show the user correct log messages, for example, using jQuery and Base64 plugin. \$\endgroup\$ Oct 4, 2011 at 3:33

2 Answers 2

2
\$\begingroup\$

I think it is good idea to use existing classes, I've already made some of the refacorings along with some design choices. For additional info, please read this book:

EDIT: I think that explicit command calls (open log, write something and close it) before dispose the log object are much more readable, easy to understand and maintain later, than impementing the dispose pattern in another object (so you will get at least two objects to control lifecycle: custom class, and StreamWriter class - that will break the Einstein rule, - it's just not good to control 2 objects, when you could simply control one). In particular, that is going to be true when the new contracts will be applied later to the code. Disposable pattern is to heavy for such simple task. Additionally, as an advantage of using one object to control, and - consequently, - very simplified logic, it's easier to control log writer object's lifetime. At second, simple class with a good and clead design can be extended easily to supprt for an additional logic and do will not likely contains any of the caveats in the future.

public interface IHTMLLogger
{
    ILogWriter CreateHTMLLogWriter(string filePath);
}

public interface ILogWriter : IDisposable
{
    void OpenLog();
    void WriteLog(string message);
    void CloseLog();
}

public class HtmlLogger : IHTMLLogger
{
    public class HTMLLogWriter : StreamWriter, ILogWriter
    {
        public HTMLLogWriter(string filePath)
            : base(filePath)
        {
        }
        public void WriteLog(string message)
        {
            base.Write(string.Format("<DIV>{0}</DIV>", message));
        }
        public void OpenLog()
        {
            base.Write("<HTML><BODY>");
        }
        public void CloseLog()
        {
            base.Write("</BODY></HTML>");
        }
    }

    public ILogWriter CreateHTMLLogWriter(string filePath)
    {
        return new HTMLLogWriter(filePath);
    }
}

EDIT: As you see, I can easily integrate new encoding logic to encode any string data:

public interface IHTMLEncoder
{
    string Encode(string message);
}

public interface IHTMLLogger
{
    ILogWriter CreateHTMLLogWriter(string filePath);
}

public interface ILogWriter : IDisposable
{
    void OpenLog();
    void WriteLog(string message);
    void CloseLog();
}

public class HtmlLogger : IHTMLLogger
{
    public class HTMLEncoder : IHTMLEncoder
    {
        public string Encode(string message)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(message.ToCharArray()));
        }
    }
    public class HTMLLogWriter : StreamWriter, ILogWriter
    {
        private IHTMLEncoder _encoder;
        public HTMLLogWriter(string filePath, IHTMLEncoder encoder)
            : base(filePath)
        {
            _encoder = encoder;
        }
        public void WriteLog(string message)
        {
            base.Write(string.Format("<DIV>{0}</DIV>", _encoder.Encode(message)));
        }
        public void OpenLog()
        {
            base.Write("<HTML><BODY>");
        }
        public void CloseLog()
        {
            base.Write("</BODY></HTML>");
        }
    }    
    public ILogWriter CreateHTMLLogWriter(string filePath)
    {
        return new HTMLLogWriter(filePath, new HTMLEncoder());
    }
}

Sample:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.IO;
using System.Diagnostics;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private static IHTMLLogger logger = new HtmlLogger();
        private ILogWriter logWriter;

        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            logWriter = logger.CreateHTMLLogWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "html.log"));
            logWriter.OpenLog();

            this.Location = UserSettings.Default.FormLocation;
            this.Size = UserSettings.Default.FormSize;
            this.textBox1.Text = UserSettings.Default.ProcessPath;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            UserSettings.Default.FormLocation = this.Location;
            UserSettings.Default.FormSize = this.Size;
            UserSettings.Default.ProcessPath = this.textBox1.Text;
            UserSettings.Default.Save();

            logWriter.CloseLog();
            logWriter.Dispose();

            MessageBox.Show(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "html.log")));
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            UserSettings.Default.ProcessPath = this.textBox1.Text;
            logWriter.WriteLog(this.textBox1.Text);
        }
    }

    [SettingsSerializeAs(SettingsSerializeAs.Xml)]
    [Serializable]
    public sealed class UserSettings : ApplicationSettingsBase
    {
        private const string FormLocationProperty = "FormLocation";
        private const string FormSizeProperty = "FormSize";
        private const string ProcessPathProperty = "ProcessPath";

        private UserSettings() { }

        private static UserSettings _defaultInstance = new UserSettings();

        public static UserSettings Default { get { return _defaultInstance; } }

        [UserScopedSetting()]
        [DefaultSettingValue("0, 0")]
        public Point FormLocation
        {
            get { return (Point)(this[FormLocationProperty]); }
            set { this[FormLocationProperty] = value; }
        }

        [UserScopedSetting()]
        [DefaultSettingValue("300, 300")]
        public Size FormSize
        {
            get { return (Size)this[FormSizeProperty]; }
            set { this[FormSizeProperty] = value; }
        }

        [UserScopedSetting]
        [DefaultSettingValue("")]
        public string ProcessPath
        {
            get { return (string)this[ProcessPathProperty]; }
            set { this[ProcessPathProperty] = value; }
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

In my opinion, you should do anything in Dispose except closing Objects. Maybe the user of your Library doesn't call Dispose and just sets the reference null. The closing Tags will never be written, but the starttags would. So it would be an invalid XML file, which is the bad behavior in my opinion.

I would implement a Method for an explicit save, so the class user can save when ever he wants, and can even continue logging after the save. Therefore I also would use XMLDocument, so I don't have to deal with the correctness of the XML and the File saving.

class HtmlLogger:ILogger,IDisposable 
{
    private System.IO.StreamWriter  _file;
    private bool _disposed;

    public HtmlLogger()
    {
        _disposed = false;
        _file = new StreamWriter(@"somepath");
        _file.Write("<HTML><BODY>");
    }

    ~HtmlLogger{
       Dispose(false);
    }

    public void Log(string message)
    {
        _file.Write("<DIV>{0}</DIV>", message);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    private void Dispose(bool disposing){
        if(_disposed)
           return;

        if(disposing){
            _disposed=true;
        }

        if(file != null)
        {
            _file.Flush();
            _file.Close();
            _file= null;
        }
    }

    protected virtual void Save()
    {
           if (_file != null)
           {
               _file.Write("</BODY></HTML>");
               _file.Flush();
           }
        }
    }
}
  1. I would add a destructor and flush/close the file in any way, just to be shure, that other Programs can open it again (if it is correct XML then) and no references to the file are stored.
  2. What happens to the XML if somebody calls:

    Log("<SuperMessage>");
    Log("Ü");
    
\$\endgroup\$
1
  • \$\begingroup\$ Agreed, you need to encode messages using HTMLEncode or some sort of message encoding to the HTML format i.e. encode <,>,/,",',& and related symbols \$\endgroup\$ Oct 4, 2011 at 2:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.