0

I'm trying to implement device specific views in a ASP.NET MVC application, as described e.g. here: https://www.simple-talk.com/dotnet/asp-net/multiple-views-and-displaymode-providers-in-asp-net-mvc-4/ or here: https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/aspnet-mvc-4-mobile-features

Although the articles above are geared towards ASP.NET MVC4, their content is relevant for later versions of the framework (my app is using ASP.NET MVC 5.2).

I've stumbled on an issue. I have the following controller:

public class TestController: Controller
{
    public ActionResult Test()
    {
        return View(new TestModel());
    }
    public ActionResult Test2()
    {
        return View("~/Views/Test/Test.cshtml", new TestModel());
    }
}

The Test model is really basic:

public class TestModel
{
    public string TheDate
    {
        get
        {
            return DateTime.Now.ToString();
        }
    }
}

And I have two views in the "~/Views/Test" folder:

Test.cshtml

@model MyNamespace.Models.TestModel
<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<h1>This is the desktop view</h1>
<p>model data: @Model.TheDate</p>
</body>
</html>

Test.Mobile.cshtml

@model MyNamespace.Models.TestModel
<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<h1>This is the mobile view</h1>
<p>model data: @Model.TheDate</p>
</body>
</html>

I've implemented the solution described in the links above.

When asking for /test/test, I get the proper view (Test.cshtml when requesting it through my desktop browser and Test.Mobile.cshtml when requesting it from a mobile emulator). However, when asking for /test/test2, i get the desktop view all the time.

I've searched for a solution to my problem, but everyone seems to be reproducing the same scenario over and over (i.e. the "/test/test" scenario) and nobody seems to have tried to do the "/test/test2" scenario. Is it even possible to override that functionality? I'm not afraid to get my hands dirty with overriding default razor/MVC functionality, but I don't really know where to start with this.

Any help is appreciated.

Yiangos
  • 267
  • 2
  • 10

1 Answers1

1

I am not sure about overriding this functionality, but you can use custom RazorViewEngine class and override FindView method, and in it detect mobile device using Request.Browser.IsMobileDevice as a workaround like this:

public class CustomViewEngine : RazorViewEngine
{
      public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
      {
           var viewPath = controllerContext.HttpContext.Request.Browser.IsMobileDevice ? "MobilePath" : "defaultPath";

           return base.FindView(controllerContext, viewPath, "MasterName", useCache);
      }
}

Do not forget to register your custom view engine in Application_Start like this:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine())
Ognjen Babic
  • 727
  • 1
  • 4
  • 14
  • I was hoping for something that didn't touch the controllers' codebase. Plus, it's not really as generic as the DisplayMode solution - what if I want to throw in a "Tablet" mode, then a "LegacyPhone" mode, etc? The return statement for these types of actions will get quite cumbersome pretty soon. – Yiangos Mar 23 '17 at 12:25
  • For a more generic solution you could create your own custom view engine by inheriting RazorViewEngine class and set up paths you want to specific views by using base.FindView method. This way all your checks for devices would be at one place. – Ognjen Babic Mar 23 '17 at 12:52
  • That doesn't sound right either. My issue is when the returned View object already has a viewpath declared in the constructor arguments. It is my understanding that the FindView method is not called in this case - the engine already knows where to search for the view. However I'll try and see if it does get called, and if so, I'll cram some device detection logic in there as well. I'll let you know of the results, though I'm a bit pessimistic at the moment. – Yiangos Mar 23 '17 at 12:59
  • 1
    Well, it seems to be working but it needs A LOT of work, though I'm not complaining. The FindView method is called for each and every view-based result, however there are quite a few caveats: the method accepts a string that may or may not be the view virtual path (I have to check for that) and also another string called "Master". I assume the latter is the content of the @Layout directive, if there is one. I need to investigate, however it seems like this is the way to go. If you make your comment into a proper answer, I'll accept it. – Yiangos Mar 23 '17 at 15:22
  • Edited the answer. I'm glad that I was able to help :) – Ognjen Babic Mar 23 '17 at 15:45