Token replacement aka WordPress style shortcodes in ASP.Net MVC

For a digital publishing platform, token replacement can be a good way to allow users to easily insert special types of content into their posts.
Wordpress have their square bracket syntax ‘shortcodes’, so plugin developers can enable users to insert say, a contact form into a blog post with code as simple as [contactform].
I recently implemented something similar in an ASP.Net MVC project; here’s how it can be done in an MVC way, using a PartialView to contain the markup of the form.

I’m using XML syntax for my shortode:
<contactform requireemail=true requirephone=true />

The first thing we need is a Regex that can identify the shortcode in a string. In this case it’s just (<contactform.*?/>)

I’ve chosen valid XML syntax for my shortcode so that I can use built in XML parsing tools to read the data from a token string.

Here’s my model:

[Serializable]
[XmlRoot(ElementName = "contactform")]
ContactForm 
{
	[XmlAttribute("requireemail")]
	bool RequireEmail {get;set;}

	[XmlAttribute("requirephone")]
	bool RequirePhone {get;set;}
} 

And the code to deserialise a valid token:

public ContactForm Deserialize(string data)
{
	try
	{
		var doc = XDocument.Parse(data).Root;
		if (doc == null)
			return null;

		var ser = new XmlSerializer(typeof(ContactForm));
			return (ContactForm)ser.Deserialize(doc.CreateReader());
	}
	catch
	{
		return null;
	}
}

As mentioned, I have the markup for my contact form in a Partial View. I also have a string of content from the database possibly containing
a contact form shortcode.

Using my Regex and Deserialize method, one way to render the output including the contact form is to break up the content string into a list of objects.

What I mean is, turn a string like

“Contact me by filling out my form Thank you!”

into a List with 3 objects,
1. the string “Contact me by filling out my form ”
2. a ContactForm model
3. the string ” Thank you!”

Then in my View, I can loop through the List and call my “_ContactForm.cshtml” partial where needed:

@foreach(var contentItem in Model.contentList)
{
	if (contentItem is ContactForm)
	{
		var form = (ContactForm)contentItem;
		@Html.Partial("_ContactForm.cshtml", form)
	}
	else
	{
		@Html.Raw(contentItem.ToString())
	}
}

Here’s the code to turn a string into a List of objects, possibly containing a ContactForm model:

public List<object> InsertContactForms(string content){

	var items = Regex.Split(content, "(<contactform.*?/>)"); 
	var contactFormRegex = new Regex("(<contactform.*?/>)");
	
	var returnList = new List<object&;gt;();
	
	foreach (var item in items){
		if (contactFormRegex.IsMatch(item)){
			ContactForm formVm = Deserialize(item);
			if (formVm != null){
				returnList.Add(formVm);				
			}			
		}			
		else {		
			returnList.Add(item);			
		}
	}		
	
	return returnList;
}

This method works pretty nicely if you just need to pass a view model to an MVC view, but in some instances you might need to output a string already containing the form HTML. This can be achieved by rendering the partial view on the server and outputting the result.To render a partial view from within my app, I need a ViewContext. I can new one of these up using a ControllerContext.
The code looks like this

public string RenderPartial(ControllerContext context, string partialName, object model){

	var view = ViewEngines.Engines.FindPartialView(context, partialName).View;
	
	using (StringWriter sw = new StringWriter()){
		var viewContext =
				new ViewContext(context, 
						view,
						new ViewDataDictionary {Model = model},
						new TempDataDictionary(),sw);	
		view.Render(viewContext, sw);		
		return sw.GetStringBuilder().ToString();
	}
}

Now from within a Controller, I can use this to replace instances of with the full markup contained in my Partial, using the following method:

public string InsertContactForms(string content){

	Regex contactformRegex = new Regex("(<contactform.*?/>)", RegexOptions.Compiled);
	Match m = contactformRegex.Match(content);
	
	while (m.Success){
		ContactForm contactformVm = Deserialize(m.Value);
		
		if (contactformVm != null){
		
			content = contactformRegex.Replace(content, RenderPartial(ControllerContext, "_ContactForm.cshtml", contactformVm), 1);	
		}			
		
		m = m.NextMatch();			
	}
	
	return content;
}

If this gets output to an MVC View, it’s as simple as calling @Html.Raw(Model.content), or if retrieved via aJax could be appended to a page using Javascript.