«

»

Oct 05

ViewModel composition and using Partial views with Partials

Slightly confused by the title of this topic? I can see why. Even I didn’t know what to give the name to the problem a recent problem I encountered and I ended up resolving it. Instead I’ll try and state the problem in my definition below:

Please note. There might be better ways of doing this, or perhaps I’m doing it all wrong. Take this with a grain of salt and if it works for you, that’s great. Otherwise that’s great too…

History

Whilst being new to MVC .NET I did alot of reading on best practices, any practices and different methods of using the concept to achieve my desired solutions. After all this reading I decided that actually using the MVVM was an approach I wanted to go down and it seemed to offer a good seperation of the concerns and enforce that seperation more explicitly between the model and the view.

So, upon deciding on using this approach working in a small teams became easy to break out the work. One of us was tasked with primarily the view and presentation. Another was tasked with the controllers mapping of domain model to viewmodels, and another was tasked with the data model and repository layer (the last two actually also jointly worked on the domain model as well in this setup).

Approach

The problem we started experiencing was that we wanted to re-use ViewModels as much as we could and our view designer wanted to re-use view code as much as he could. Hence he had lots of partial views that utilised specific ViewModels and we had lots of ViewModels that ended up being at times a composition of a bunch of different sub ViewModels.

i.e.

public class ViewModel1
{
   public string SomeOtherProperty;

   public ViewModel2 VM;
   public ViewModel3 VM3
}

This was great! It meant that alot of our views got shared around the place and our mapping layer made use of these shared ViewModels as well (At this stage we weren’t using any mapping systems such as AutoMapper. Not only that. Our views stayed pretty simple and compact with handling just the code for that ViewModel. If it contained other ViewModels it would delegate off to the necessary partial.

Problem

The problem came when we had to start posting back our data and how the control id and name elements were written out. In order for the standard binding to work the controls needed to be output showing the layer in which the properties exist on our viewModels.

So in our example above, it would have to be something like


not


However this is exactly what was happening. Because we were passing the ViewModels into the partials the controls did not contain the necessary prefix required.

@Html.Partial("MyPartial", Model.VM); // this was producing the wrong controls

Solution

What I ended up doing was relatively simple and works well (well for non-list objects anyway). The solution was to create a extension method on the Partial routine and pass a prefix or expression into this method. The method itself then would be responsible for building the prefix required.

public static class PartialViewHelper
{
	public static void RenderPartial(this HtmlHelper helper, string partialViewName, object model, string prefix)
	{
		string oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix;

		helper.ViewData.TemplateInfo.HtmlFieldPrefix += string.Format("{0}{1}", !string.IsNullOrWhiteSpace(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ? "." : "", prefix);
		helper.RenderPartial(partialViewName, model);

		helper.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
	}

	public static void RenderPartial(this HtmlHelper helper, string partialViewName, ViewDataDictionary viewData, string prefix)
	{
		string oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix;

		viewData.TemplateInfo.HtmlFieldPrefix += string.Format("{0}{1}", !string.IsNullOrWhiteSpace(viewData.TemplateInfo.HtmlFieldPrefix) ? "." : "", prefix);
		helper.RenderPartial(partialViewName, viewData);

		viewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
	}

	public static MvcHtmlString Partial(this HtmlHelper helper, string partialViewName, object model, string prefix)
	{
		string oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix;

		// This partial is used when adding a new row to a grid via javascript
		// I have to create an empty view data dictionary in case this is after an unsuccessful (because of validation) post
		// and the modelstate is populated using the first grid row's data. If I use the current view data, the new row will
		// appear and the render partial would have populated it with the first row's data.
		string newPrefix = string.Format("{0}{1}", !string.IsNullOrWhiteSpace(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ? "." : "", prefix);
		ViewDataDictionary viewData = new ViewDataDictionary() { Model = model, TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = newPrefix } };

		var encodedHtml = helper.Partial(partialViewName, viewData);
		
		helper.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

		return encodedHtml;
	}

	public static MvcHtmlString Partial(this HtmlHelper helper, string partialViewName, ViewDataDictionary viewData, string prefix)
	{
		string oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix;

		viewData.TemplateInfo.HtmlFieldPrefix += string.Format("{0}{1}", !string.IsNullOrWhiteSpace(viewData.TemplateInfo.HtmlFieldPrefix) ? "." : "", prefix);
		var encodedHtml = helper.Partial(partialViewName, viewData);

		viewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

		return encodedHtml;
	}

	public static string RenderPartialViewToString(ViewDataDictionary ViewData, TempDataDictionary TempData, ControllerContext ControllerContext, string viewName, object model)
	{
		return RenderPartialViewToString(ViewData, TempData, ControllerContext, viewName, model, null);
	}

	public static string RenderPartialViewToString(ViewDataDictionary ViewData, TempDataDictionary TempData, ControllerContext ControllerContext, string viewName, object model, string prefix)
	{
		string oldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;

		if (!string.IsNullOrWhiteSpace(prefix))
			ViewData.TemplateInfo.HtmlFieldPrefix += string.Format("{0}{1}", !string.IsNullOrWhiteSpace(ViewData.TemplateInfo.HtmlFieldPrefix) ? "." : "", prefix);

		ViewData.Model = model;

		using (System.IO.StringWriter sw = new System.IO.StringWriter())
		{
			ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);

			if (viewResult.View == null)
			{
				throw new NullReferenceException("The view was not found");
			}

			ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
			viewResult.View.Render(viewContext, sw);

			ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

			return sw.GetStringBuilder().ToString();
		}
	}
}

Why can’t I just pass the model and have code build up the prefix? Well, later I discovered I could and created another extensions method overload to do just that.

public static MvcHtmlString Partial(this HtmlHelper helper, string partialViewName, Expression> expression)
	 where TProperty : new()
{
	var prefix = (expression.Body as MemberExpression).Member.Name;

	var func = expression.Compile();
	TProperty property = func((TModel)helper.ViewContext.ViewData.Model);

	return Partial(helper, partialViewName, property, prefix);
}

Note: I noticed the other day that this doesn’t account for lists. I don’t think it would be too much of a major to do another extension method that took a list object but that’s a work on I’ll think about later. Feel free to comment on any of this or if you have found better ways let me know. I’m always up for learning new and better methodologies.

Leave a Reply

WordPress spam blocked by CleanTalk.