3

I want to create and AMP version of my website in ASP.NET MVC using .NET Core 2.0. Previously I had done some work with DisplayModeProvider instances in tha past on .Net framework, but that does not seem to be an option in .NET Core.

What I want to be able to do is alter the view names to be index.amp.cshtml rather than index.cshtml when my URL starts iwth /amp. What's the best way to achieve this in .NET Core?

kieszen
  • 33
  • 7

2 Answers2

4

You can do something like this using IViewLocationExpander. As it happens, I was playing with this a few days ago so I have some code samples to hand. If you create something like this:

public class AmpViewLocationExpander : IViewLocationExpander
{
    public void PopulateValues(ViewLocationExpanderContext context)
    {
        var contains = context.ActionContext.HttpContext.Request.Query.ContainsKey("amp");
        context.Values.Add("AmpKey", contains.ToString());

        var containsStem = context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp");
        context.Values.Add("AmpStem", containsStem.ToString());
    }

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        if (!(context.ActionContext.ActionDescriptor is ControllerActionDescriptor descriptor)) { return viewLocations; }

        if (context.ActionContext.HttpContext.Request.Query.ContainsKey("amp")
            || context.ActionContext.HttpContext.Request.Path.StartsWithSegments("/amp")
        )
        {
            return viewLocations.Select(x => x.Replace("{0}", "{0}.amp"));
        }

        return viewLocations;
    }
}

iViewLocationExpander can be found in Microsoft.AspNetCore.Mvc.Razor

Then in your Configure Services method in Startup.cs, add the following:

  services.Configure<RazorViewEngineOptions>(options =>
        {
            options.ViewLocationExpanders.Add(new AmpViewLocationExtender());
        });

What this will do is update the view locations per request to insert .amp before .cshtml any time the URL either starts with /amp or there is a query string key of amp. If your AMP views don't exist, it might blow-up a little, I've not fully tested it, but it should get you started.

mnield
  • 1,859
  • 1
  • 20
  • 34
  • 1
    Thanks @mnield! Is there a way to limit this to exclude layout pages and partial views? Not for AMP specifically, but I think it's a nice way to provide alternate views. – kieszen Mar 13 '18 at 09:50
  • Hi @kieszen, yes - of course. you can add `if (!context.IsMainPage) return viewLocations;` to the start of `ExpandViewLocations`. This will exclude layout pages and partials. I'm not entirely sure if you can do it more granularly than that. – mnield Mar 13 '18 at 10:41
  • @mnield I was thinking it would be beneficial to redirect all mobile traffic to the amp version of the website, do you agree? – prospector May 21 '18 at 20:51
  • @prospector, in a way, this does that. In the implementation I was experimenting with, the views used for navigation etc. referred to the AMP version of the site. So once `amp` is in the URL, you always get those links. What I wouldn't want to do is force it to always be AMP. I find nothing more frustrating that - for example - being forced on to a mobile view of a site when it is less functional than the desktop one. – mnield May 22 '18 at 05:10
  • @mnield I can see your point. Most of my websites I develop are for lead generation and loading time is money so for my work I just recently a few hours ago force redirect to AMP but for more informational sites I agree, I'd just use the responsive design. – prospector May 22 '18 at 05:28
0

You can define this Middleware :

public class AmpMiddleware
{
    private RequestDelegate _next;

    public AmpMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public Task Invoke(HttpContext context)
    {
        const string ampTag = "/amp";

        var path = context.Request.Path;
        if (path.HasValue)
        {
            var ampPos = path.Value.IndexOf(ampTag);
            if (ampPos >= 0)
            {
                context.Request.Path = new PathString(path.Value.Remove(ampPos, ampTag.Length));
                context.Items.Add("amp", "true");
            }
        }
        return _next(context);
    }
}

public static class BuilderExtensions
{
    public static IApplicationBuilder UseAmpMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<AmpMiddleware>();
    }
}

And call it in Startup:

app.UseAmpMiddleware();

Then can check in page and simple set another layout or limit some code, in his way no need to create separate page for amp version:

@if (HttpContext.Items.ContainsKey("amp"))
{
    <b>Request AMP Version</b>
}
MohsenB
  • 1,669
  • 18
  • 29