Mapping _escaped_fragment_ to controller actions

February 3, 2014

If you have a website which loads content dynamically using AJAX, you may be using hashbangs (#!) to indicate state. This method is popular due to the specification put together by google which translates the url fragment into a querystring parameter, and issues a request to the server to get an html snapshot of the state.

This is well documented by google, here: https://developers.google.com/webmasters/ajax-crawling/docs/specification.

In order to return the various states of the page, you need to respond to different values of the _escaped_fragment_ querystring parameter sent as part of a GET request to this page. Most implementations I’ve seen to deal with this end up switching on the value of this querystring parameter inside an Action method. Here is an example of what i’m talking about http://stackoverflow.com/questions/5643257/implementing-googles-hashbang-ajax-crawl-with-asp-net-mvc.

Below is an implementation of an EscapedFragmentRoute which allows you to map routes which include hashbangs to actions using the standard MVC routing infrastructure. Just include the hashbang in your route patterns, and this will match it, working off the assumption that the url fragment can be found in the _escaped_fragment_ querystring parameter.

    public class EscapedFragmentRoute : Route
    {
        private const string EscapedFragmentQsParameter = "_escaped_fragment_";

        public EscapedFragmentRoute(Route baseRoute)
            : base(baseRoute.Url, baseRoute.Defaults, baseRoute.Constraints, baseRoute.DataTokens, baseRoute.RouteHandler)
        {
        }

        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            var fragment = httpContext.Request.QueryString[EscapedFragmentQsParameter];

            if (fragment != null)
                return base.GetRouteData(new HttpContextBaseWrapper(httpContext, fragment));

            return base.GetRouteData(httpContext);
        }

        public class HttpContextBaseWrapper : HttpContextBase
        {
            private readonly HttpContextBase httpContextBase;
            private readonly HttpRequestWrapper httpRequestWrapper;

            public HttpContextBaseWrapper(HttpContextBase httpContextBase, string fragment)
            {
                this.httpContextBase = httpContextBase;
                this.httpRequestWrapper = new HttpRequestWrapper(httpContextBase.Request, fragment);
            }

            public override HttpRequestBase Request
            {
                get { return this.httpRequestWrapper; }
            }

            public override HttpResponseBase Response
            {
                get { return this.httpContextBase.Response; }
            }

            public override IDictionary Items
            {
                get { return this.httpContextBase.Items; }
            }

            public override IPrincipal User
            {
                get
                {
                    return this.httpContextBase.User;
                }

                set
                {
                    this.httpContextBase.User = value;
                }
            }
        }

        public class HttpRequestWrapper : HttpRequestBase
        {
            private readonly HttpRequestBase request;

            private readonly string fragment;

            public HttpRequestWrapper(HttpRequestBase request, string fragment)
            {
                this.request = request;
                this.fragment = fragment;
            }

            public override string AppRelativeCurrentExecutionFilePath
            {
                get
                {
                    return this.request.AppRelativeCurrentExecutionFilePath;
                }
            }

            public override string PathInfo
            {
                get
                {
                    return this.request.PathInfo + "#!" + this.fragment;
                }
            }
        }
    }

I am using attribute routing with MVC 5.1 which allows you to write your own route generation attributes. The following attribute creates an EscapedFragmentRoute instead of the standard one. Note, if you go down this path, then you need to use this attribute on the main page too, otherwise you’ll find that multiple controllers/actions may match the url, and cause an error

    public class EscapedFragmentRouteAttribute : Attribute, IDirectRouteFactory
    {
        public EscapedFragmentRouteAttribute(string template)
        {
            if (template == null)
                throw new ArgumentNullException("template");

            this.Template = template;
        }

        public string Name { get; set; }

        public int Order { get; set; }

        public string Template { get; private set; }

        public RouteEntry CreateRoute(DirectRouteFactoryContext context)
        {
            var builder = context.CreateBuilder(this.Template);

            builder.Name = this.Name;
            builder.Order = this.Order;
            var routeEntry = builder.Build();

            return new RouteEntry(routeEntry.Name, new EscapedFragmentRoute(routeEntry.Route));
        }
    }

Finally, a quick example of how to use this:

    [RoutePrefix("#!/recipe")]
    public class RecipeController : AsyncController
    {
        [EscapedFragmentRoute("{recipeId}")]
        public async Task<ActionResult> Details(int recipeId)
        {
            ...
        }
    }

Rendering scripts from partial views at the end of the page in MVC (Razor)

February 16, 2011

Everyone knows scripts should be placed at the bottom of the page.

If you have a partial view in MVC, it may be necessary to render scripts specific to this view, and a common approach is to include the scripts (not necessarily inline) in the html mark up of the view.

The problem with this is that you may be depending on library scripts which are not loaded yet because they are the last thing in your Layout/Master.

One option is to change the design so that rather than calling Html.Partial to render a partial view, the partial view becomes the view, and the view becomes a master/layout. You can now use DefineSection, RenderBody, RenderSection etc. This however was not an option for me for several reasons.

Here is the solution I came up with which will only work for rendering content at the end of the document. It won’t work for injecting content into the head (e.g. css) because it runs in the order you expect. The scripts are registered in during the call to Html.Partial, which (usually) comes after the head section, meaning the head section is already done and its too late. In fact, I have even named the functions BeginScripts and Html.PageScripts instead of something more generic to make it clear it is only really applicabale for scripts at the end of the page.

On the partial views:

@using (Html.BeginScripts())
{
    <script src="@Url.Content("~/Scripts/script.js")" type="text/javascript"></script>
}

And (pretty much) last thing in the Layout/Master:

@Html.PageScripts()

And the helpers themselves:

    public static class HtmlHelpers
    {
        private class ScriptBlock : IDisposable
        {
            private const string scriptsKey = "scripts";
            public static List<string> pageScripts
            {
                get
                {
                    if (HttpContext.Current.Items[scriptsKey] == null)
                        HttpContext.Current.Items[scriptsKey] = new List<string>();
                    return (List<string>)HttpContext.Current.Items[scriptsKey];
                }
            }

            WebViewPage webPageBase;

            public ScriptBlock(WebViewPage webPageBase)
            {
                this.webPageBase = webPageBase;
                this.webPageBase.OutputStack.Push(new StringWriter());
            }

            public void Dispose()
            {
                pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString());
            }
        }

        public static IDisposable BeginScripts(this HtmlHelper helper)
        {
            return new ScriptBlock((WebViewPage)helper.ViewDataContainer);
        }

        public static MvcHtmlString PageScripts(this HtmlHelper helper)
        {
            return MvcHtmlString.Create(string.Join(Environment.NewLine, ScriptBlock.pageScripts.Select(s => s.ToString())));
        }
    }

It shouldn’t take much work to improve the above code to operate more like the ClientScriptManager of asp.net… use a dictionary instead of a list, and require a key to verify the same script can’t be registered twice. Alternativly (what i’ve done in the project I’m working on), you could accept an optional list of library dependencies in BeginScripts, and simply make sure these have been rendered before outputting the scripts themseleves

Simples


Implementing IDisposable

June 22, 2010

There’s a bit more to implementing IDisposable than closing a resource in the Dispose method. I can never remember the pattern exactly as Microsoft recommends, so this is a link to Microsoft’s example: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx


Coverting text sid to binary in T-SQL

January 14, 2010

If you want to convert a text sid in the form:

S-1-5-21-2127521184-1604012920-1887927527-72713

to something like

010500000000000515000000A065CF7E784B9B5FE77C8770091C0100

in SQL Server, the following code will do it:

CREATE FUNCTION SidToHex (@textsid nvarchar(max))
RETURNS varbinary(256)
AS
BEGIN
	DECLARE @pos int,
	@currentBlock nvarchar(max),
	@blockno nvarchar(max),
	@nextHyphen int,
	@revision int,
	@binarysid varbinary(256),
	@blockval bigint;

	SET @pos = 1
	SET @blockno = 0
	SET @binarysid = 0x

	WHILE @pos < LEN(@textsid)
	BEGIN
		SET @nextHyphen = CHARINDEX('-', @textsid, @pos)
		SET @nextHyphen = CASE @nextHyphen WHEN 0 THEN LEN(@textsid) + 1 ELSE @nextHyphen END
		SET @currentBlock = SUBSTRING(@textsid, @pos, @nextHyphen - @pos)
		SET @blockno = @blockno + 1
		SET @pos = @nextHyphen + 1

		IF @blockno = 1
		BEGIN
			IF @currentBlock <> 'S'
			RETURN -1
		END
		ELSE IF @blockno = 2
			SET @revision = CONVERT(int, @currentBlock)
		ELSE IF @blockno = 3
			SET @binarysid = @binarysid + CAST(CONVERT(int, @currentBlock) as binary(6))
		ELSE
		BEGIN
			SET @blockVal = CONVERT(int, @currentBlock)
			SET @blockVal =
			((@blockVal & 0x000000FF) * 0x1000000) |
			((@blockVal & 0x0000FF00) * 0x100) |
			((@blockVal & 0x00FF0000) / 0x100) |
			((@blockVal & 0xFF000000) / 0x1000000)
			SET @binarysid = @binarysid + CAST(@blockVal as binary(4))
		END
	END

	RETURN CAST(@revision as binary(1)) + CAST(@blockno - 3 as binary(1)) + @binarysid;
END
GO

Stream.ReadByte

January 3, 2010

Why does the ReadByte¬†method of a stream return an int instead of a byte? According to reflector it is returning the first element of a byte array, implicitly casted to an int. I’m then casting this straight back to a byte… I’m not sure what the benefit is of this!


Follow

Get every new post delivered to your Inbox.