This issue is caused by how Glass Mapper generates URLs with its UrlBuilder:
public override string ToString()
{
StringBuilder sb = new StringBuilder();
// [get the first part of the URL] (omitted for brevity)
foreach (var keyValuePair in QueryString)
{
if (keyValuePair.Key.HasValue())
{
sb.AppendFormat("{0}={1}{2}", keyValuePair.Key ?? string.Empty, keyValuePair.Value ?? string.Empty,QuerySeparator);
}
}
// [remove the last separator] (omitted for brevity)
return sb.ToString();
}
Note the line where it appends the next query string parameter concatenated with an = and its value, even if the value is empty.
The fix
I didn't want to change this functionality in Glass Mapper, because I figured it was actually MORE likely to be what people want. But for our case I needed a solution, so here's what I came up with:
Create a custom GlassHtml
utility
Override the default BeginRenderLink
behavior by replacing any offending contents of the generated URL.
public class CustomGlassHtml : GlassHtml
{
public CustomGlassHtml(ISitecoreContext sitecoreContext) : base(sitecoreContext)
{
}
public override RenderingResult BeginRenderLink<T>(T model, Expression<Func<T, object>> field, TextWriter writer, object parameters = null, bool isEditable = false, bool alwaysRender = false)
{
// We use a StringWriter to capture the rendering output from Glass so we can
// prevent the case where Glass' UrlBuilder adds a = after a valueless parameter.
// For example:
// http://website.com/page?param1¶m2=very_yes
// becomes
// http://website.com/page?param1=¶m2=very_yes
var glassRenderedLink = new StringBuilder();
var stringWriter = new StringWriter(glassRenderedLink);
// Call the standard BeginRenderLink. When the RenderingResult is created, it immediately writes the "first part" to the passed in writer
RenderingResult renderingResult = base.BeginRenderLink(model, field, stringWriter, parameters, isEditable, alwaysRender);
string firstPart = glassRenderedLink.ToString();
glassRenderedLink.Clear();
// When the RenderingResult is disposed, it writes the "last part" to its writer
renderingResult.Dispose();
string lastPart = glassRenderedLink.ToString();
// Then we fix the URL problem
firstPart = firstPart.Replace("=&", "&");
// And return our own RenderingResult
return new RenderingResult(writer, firstPart, lastPart);
}
}
Create a custom GlassHtmlFactory
to return your CustomGlassHtml
public class CustomGlassHtmlFactory : IGlassHtmlFactory
{
public IGlassHtml GetGlassHtml(ISitecoreContext sitecoreContext)
{
return new CustomGlassHtml(sitecoreContext);
}
}
Set your CustomGlassHtmlFactory
as the GlassHtmlFactory
in the GlassMapperScCustom
class
public static IDependencyResolver CreateResolver()
{
var config = new Glass.Mapper.Sc.Config();
var dependencyResolver = new DependencyResolver(config);
// add any changes to the standard resolver here
// Register our custom GlassHtml class
dependencyResolver.GlassHtmlFactory = new CustomGlassHtmlFactory();
dependencyResolver.Finalise();
return dependencyResolver;
}