A Very Simple ViewModel Example

I'm currently working on a blog website app in ASP.NET MVC as a pet project. I'm not intending to use this new app instead of Blogger, but rather am doing it as a creative exercise.

During the course of this project, I found the need to make use of the MVVM (Model View ViewModel) pattern. In this particular scenario, my implementation of MVVM was very simple, but extremely useful. In this blog post, I'll explain why I needed it, and how it came in handy.

The App
As I mentioned, my blog app is being written in ASP.NET MVC. I have a BlogPostController class that is used to coordinate the creation, editing, deleting, and retrieving of blog posts. The actual data operations are performed by a repository class that the controller calls. And, of course, the data is displayed and collected via views.

The Data Model
I'm using a SQL Server database with an Entity Framework data model. Among the tables/entities in my data model are BlogPosts and Tags. Tags are descriptive labels that can be assigned to BlogPosts in a manner typical to blogs. Because I want to keep the data normalized, each Tag is represented only once in the data, but can be related to one or more BlogPosts by way of a cross-reference table. Here's a picture of the part of the data model that pertains to this (click for a larger view):


The Problem
When creating or editing a blog post, I wanted to allow the user to be able to enter a string of comma-separated tags (again, as is typical of blog sites), but of course, this isn't the way the model is implemented. To require the user to choose from a list of available tags, as well as add functionality for them to explicitly create a new tag, seemed clunky, and was never an option as far as I was concerned. The best user experience would be the tried-and-true comma-separated string.

MVVM To The Rescue
Here's where MVVM came in handy. My plan was to create a ViewModel that would include the aforementioned comma-separated string of tags, and have the controller take that information to either display or persist via the repository.

The ViewModel class I created (BlogPostViewModel) is extremely simple: it just contains an instance of the BlogPost class, as well as a string to store the tags.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace BlogMVC.Models
{
    public class BlogPostViewModel
    {
        public BlogPost Post { getset; }
        public string TagsString { getset; }
 
        public BlogPostViewModel()
        {
        }
 
        public BlogPostViewModel(BlogPost post, string tagsString)
        {
            Post = post;
            TagsString = tagsString;
        }
    }
}
 
To reflect this, I updated my views to use this new ViewModel class instead of the BlogPost class they were originally created to work with. For example, here is a PartialView I use when creating and editing BlogPosts -- notice the first line:

@model BlogMVC.Models.BlogPostViewModel
           
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>BlogPost<legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Post.Title)
        <div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Post.Title)
            @Html.ValidationMessageFor(model => model.Post.Title)
        <div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Post.Body)
        <div>
        <div class="editor-field">
            @Html.TextAreaFor(
                  model => model.Post.Body, 
                  new { @class = "BlogPostBodyEdit" })
 
            @Html.ValidationMessageFor(model => model.Post.Body)
        <div>
        <div class="editor-label">
            @Html.LabelFor(model => model.TagsString, "Tags")
        <div>
        <div class="editor-field">
            @Html.TextBoxFor(model => model.TagsString)
            @Html.ValidationMessageFor(model => model.TagsString)
        <div>
        <p>
            <input type="submit" value="Save" />
        <p>
    <fieldset>
}
 
I also updated my controller methods to used this new class as well. For example, the Create() method now accepts a BlogPostViewModel object -- and uses the string that contains the tags data to pass this data to the repository class. The repository then creates new Tags for any that are specified in the string but don't currently exist in the repository. The final step is to create new BlogPostTagsXRefs to related each Tag to the BlogPost. Here's the BlogPostController.Create() method:

//
// POST: /BlogPost/Create
 
[HttpPost]
public ActionResult Create(BlogPostViewModel blogPostViewModel)
{
    blogPostViewModel.Post.AuthorUserId = UserId;
    blogPostViewModel.Post.CreatedDate = DateTime.Now;
 
    if (ModelState.IsValid)
    {
        //Persist new blog post
        _repo.AddNewBlogPost(blogPostViewModel.Post);
                
        //If this blog post has tags, persist any that don't exist, 
        //and relate each tag to the new blog post
        if (!String.IsNullOrWhiteSpace(blogPostViewModel.TagsString))
        {
            IEnumerable<string> tags = 
                blogPostViewModel.TagsString.Split(',');
 
            if (tags != null && tags.Count() > 0)
                _repo.PersistTagsForBlogPost(blogPostViewModel.Post, tags);
        }
 
        return RedirectToAction("Index");  
    }
 
    return View(blogPostViewModel);
} 
 
That's all there was to it. The term "Model-View-ViewModel" can sometimes bring to mind thoughts of complex mappings and adaptations between the view and the model, but as this example shows, sometimes a ViewModel can be something very simple, yet at the same time very useful.

(Please note: In some of the sample code above, Blogger has inserted extra line breaks for some reason. The real code does not have these extra breaks -- I'm not CR/LF happy ;) .)

Comments

Charlie said…
Kool article, I am working on something similar. How did you go about displaying all the tags per post within a view for display? Like for a viewable post page?
Alan said…
Sorry for the delay in responding. For displaying the tags per post, the plan is (I'm still coding it) to parse those tags into a comma-delimited string and use that same view model. The view would then iterate through each tag in the string and display it as a link to show all posts that have that tag (paginated of course -- it would start with, say, the first 10, and give the user the option to page through the rest).

Popular posts from this blog

How To Mock Out Child Components In Unit Tests of Angular 2 Code

A Generic Method Using HttpClient to Make a Synchronous Get Request

The Cause and Solution for the "System.Runtime.Serialization.InvalidDataContractException: Type 'System.Threading.Tasks.Task`1[YourTypeHere]' cannot be serialized." Exception