5
\$\begingroup\$

I've got a quite simple WPF application, trying to implement a correct MVVM pattern.

The application in itself is simple: one listItem and one textbox + button for searching. On init, the entire content of a "large" (~50k to ~1million lines) file is loaded into the listItem. When the users enters text in textbox and clicks on the button, I go through the entire file and get the corresponding elements and refresh the listItem.

Right now, everything is fairly dirty, as you can see in the code below. I'm wondering how I can deal with this file. Should I keep everything in memory like I'm doing right now? Or should I reparse the file every time I need to? And how would all of this fit in MVVM pattern? I was thinking of a "Messages" class as a Model.

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.LoadConversation();
    }

    private MessagesProcessor _messageProcessor = new MessagesProcessor("c.txt");

    private void LoadConversation()
    {
        foreach (var msg in _messageProcessor.Messages)
        {
            ListBoxConversation.Items.Add(msg.ToString());
        }
    }

    private void onSearchBtnClick(object sender, RoutedEventArgs e)
    {
         var results = new List<Message>();
         results.AddRange(this._messageProcessor.GetSearchResults(word));
         foreach (var result in results.OrderBy(p => p.Date))
         {
                ListBoxConversation.Items.Add(result.ToString());
         }      
    }
}

MessageProcessor.cs

 public class MessagesProcessor
 {
    private List<Message> _messages;

    public List<Message> Messages
    {
        get
        {
            return _messages;
        }
    }

    public MessagesProcessor(string pathOfBaseFile)
    {
        this._messages = ParseBaseMessageFile(pathOfBaseFile); 
    }

    public IEnumerable<Message> GetSearchResults(string word)
    {
        return this._messages.Where(msg => msg.Content.Contains(word, StringComparison.OrdinalIgnoreCase));
    }
 }

Message.cs

public class Message
{
    public DateTime Date { get; set; }
    public string Content { get; set; }
    public string Author { get; set; }

    public override string ToString()
    {
        return "[" + this.Date.ToString("d") + " " + this.Date.ToString("t") + "]" + this.Author + " : " + this.Content;
    }
}
\$\endgroup\$
  • \$\begingroup\$ Is your code already working? You seem unsure. \$\endgroup\$ – RubberDuck Apr 23 '15 at 14:53
  • 1
    \$\begingroup\$ @RubberDuck It's all working fine. \$\endgroup\$ – user3744187 Apr 23 '15 at 14:55
  • \$\begingroup\$ Have you tried the app with a large file? Are you only concerned with code style/approach or do you have additional performance concerns too? \$\endgroup\$ – RobH Apr 23 '15 at 15:22
  • \$\begingroup\$ @RobH Right now, I'm concerned with code style/approach. But after testing performances, 300000 lines seems to be the threshold. Average file should be around 100k-200k lines though. \$\endgroup\$ – user3744187 Apr 23 '15 at 15:37
4
\$\begingroup\$

This is actually a nice example of how re-writing an application using MVVM can make life easier. It looks like you have the wrong idea about MVVM (your code should not be in the code behind). As a starting point, you would need an empty MVVM application with a main viewmodel and view, and know how to set up data binding and property changed events. If you haven't done this before, I would recommend taking a look at MVVM light which is a small framework that can get you up and running quickly.

The idea is that your view and viewmodel should not care where the messages are coming from - that is not their concern. This is in line with SRP and means that you can change how you read from the files (or read from somewhere else entirely) without touching any code in your V and VM layers. I would suggest starting with something like this:

ViewModel

public class MainViewModel : ViewModelBase
{
    private IMessageRepository _messageRepository;

    public MainViewModel(IMessageRepository messageRepository)
    {
        _messageRepository = messageRepository;
        FilteredMessages = _messageRepository.GetMessages();
    }

    // This will be bound to the search Button via a command
    public void FilterMessages(string searchString)
    {
        var messages = _messageRepository.GetMessages();
        FilteredMessages = messages.Where(m => m.Contains(searchString, StringComparison.OrdinalIgnoreCase))
    }

    // This will need to be bound to the ListBox and raise PropertyChangedEvents when set
    public IEnumerable<Message> FilteredMessages { get; private set; }
}

Model

public interface IMessageRepository
{
    IEnumerable<Message> GetMessages();
}

public abstract class FileMessageRepository : IMessageRepository
{
    protected readonly string FilePath;

    protected FileMessageRepository(string filePath)
    {
        FilePath = filePath;
    }

    public abstract IEnumerable<Message> GetMessages();

    protected Message BuildMessage(string line)
    {
        ...
    }
}

public class LazyFileMessageRepository : FileMessageRepository
{
    public LazyFileMessageRepository(string filePath) : base(filePath)
    {
    }

    public override IEnumerable<Message> GetMessages()
    {
        // This will read from the file as it is enumerated
        return File.ReadLines(FilePath).Select(BuildMessage);
    }
}

public class EagerFileMessageRepository : FileMessageRepository
{
    private Message[] _cachedMessages;

    public EagerFileMessageRepository(string filePath) : base(filePath)
    {
    }

    public override IEnumerable<Message> GetMessages()
    {
        // This will read from the file once (slow) and keep the result in memory
        return _cachedMessages ?? (_cachedMessages = File.ReadAllLines(FilePath).Select(BuildMessage).ToArray());
    }
}

With this implementation you are free to choose which IMessageRepository you use, and can carry out performance/profiling tests as required.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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