MVC, Building and persisting view models.

On my recent projects there has been a higher demand for richer UI with pages containing a number of smaller steps (page updates) before the final submission.

In order to do this you will probably need to persist the collection of data between posts, here is one method of tackling this whilst keeping to the Post-Redirect-Get pattern.

To persist the data add these

using System.Collections.Generic;
using System.Web;

namespace MappingUIModels.Extensions
{
    /// <summary>Creation, access and deletions of objects passed in by session key and id.</summary>
    public static class SessionExtensions
    {
        /// <summary>Saves the object into a dictionary in session by key and a default dictionary id of object type name.</summary>
        public static T Save<T>(this T model, string key) where T : class
        {
            return model.Save(key,typeof(T).Name);
        }
        /// <summary>Saves the object into a dictionary in session using the key and id.</summary>
        public static T Save<T>(this T model, string key, string id) where T : class
        {
            var dictionary = FindOrCreateDictionary(key);
            if (dictionary.ContainsKey(id))
                dictionary[id] = model;
            else
                dictionary.Add(id, model);
            return model;
        }

        /// <summary>Retreives the object from the dictionary in session by key and a default dictionary id of object type name.</summary>
        public static T Find<T>(this T model, string key) where T : class
        {
            return model.Find<T>(key, typeof(T).Name);
        }
        /// <summary>Retreives the object from the dictionary in session by key and id.</summary>
        public static T Find<T>(this T model, string key, string id) where T : class
        {
            var dictionary = FindOrCreateDictionary(key);

            if (dictionary.ContainsKey(id))
                return dictionary[id] as T;

            return null;
        }
        /// <summary>Flush session by key.</summary>
        public static T Flush<T>(this T model, string key)
        {
            HttpContext.Current.Session[key] = null;
            return model;
        }
        /// <summary>Retreive or create dictionary in sesssion.</summary>
        private static Dictionary<string,object> FindOrCreateDictionary(string key)
        {
            Dictionary<string,object> dictionary = HttpContext.Current.Session[key] as Dictionary<string,object>;
            if (dictionary == null)
            {
             dictionary = new Dictionary<string, object>();
             HttpContext.Current.ReadValue(x => x.Session)[key] = dictionary;
            }
            return dictionary;
        }
    }
}

You may want to abstract this further so you can inject the persistence implementation and remove the coupling.

And here is a

using System.Web.Mvc;
using MappingUIModels.Models;
using MappingUIModels.Extensions;

namespace MappingUIModels.Controllers
{
    [HandleError]
    public class MyController : Controller
    {
        /// <summary>Post-Redirect-Get pattern example.</summary>
        private const string Key = "MyKey123";

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Index()
        {
            return View(new myViewModel().FindAndBuild(Key));          //Build/Show
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Index(myViewModel model)
        {
            if (ModelState.IsValid) model.Description = "Well done.";  //Update
            model.Save(Key);                                           //Save
            return RedirectToAction("Index");                          //Redirect
        }
    }
}

Take note of the method on the view model FindAndBuild(Key). The view models are now responsible for knowing how to create themselves simular to MVVM. Again I’ve used extension methods in this simple example and you may want to abstract this further.

And here is a

using MappingUIModels.Extensions;

namespace MappingUIModels.Models
{
    public class myViewModel
    {
        public string Name { get; set; }
        public string Description { get; set; }
    }

    public static class MyViewModelExtensions
    {
        // Returns the model in session if it exists or builds a new one.
        public static myViewModel FindAndBuild(this myViewModel model, string key)
        {
            return model.Find(key).Flush(key) ?? model.Build();
        }

        // Builds the model, can use Services, repositories and mapping extensions here.
        public static myViewModel Build(this myViewModel model)
        {
            model.Name = "Tom & Jerry";
            model.Description = "Cartoon";
            return model;
        }
    }
}