【问题标题】:How Would I Change ASP.NET MVC Views Based on Device Type?如何根据设备类型更改 ASP.NET MVC 视图?
【发布时间】:2010-11-26 02:51:53
【问题描述】:

我正在阅读一些 ASP.NET MVC,并且我有一个正在工作的 Web 应用程序,我将从 WebForms 迁移到 MVC。我希望在此过程中获得的功能请求之一是,如果用户来自移动设备,则返回一个简化的视图。

我不太明白实现这种逻辑的最佳位置在哪里。我确信有比在返回视图的每个操作中为 Browser.IsMobileDevice 添加 if/else 更好的方法。我需要什么样的选择?

【问题讨论】:

    标签: asp.net-mvc views mobile-devices


    【解决方案1】:

    在模型-视图-控制器模式中,选择视图的是控制器,因此,添加if 语句并返回适当的视图并没有那么糟糕。您可以将if 语句封装在一个方法中并调用它:

    return AdaptedView(Browser.IsMobileDevice, "MyView.aspx", model);
    

    或者,您可以创建一个视图引擎,根据它是否移动来动态执行视图。我不喜欢这种方法,因为我认为控制器应该负责。例如,如果您在 iPhone 上浏览,您可能希望查看完整的桌面版本。在前一种方法中,您需要传递适当的布尔标志,但在后一种方法中,事情变得更加复杂。

    【讨论】:

      【解决方案2】:

      你的核心逻辑在控制器中应该是相同的,只有你需要的视图会改变,所以控制器是你确实需要 if/else 语句为每个控制器操作提供正确视图的地方。

      另一种方法是将控制器逻辑包装在单独的 dll 中,然后为移动版本设置不同的控制器/路径。如果常规控制器收到来自移动设备的请求,您可以将它们重定向到您的移动区域,其中包含使用共享控制器逻辑的所有移动控制器。此解决方案还允许您执行特定于移动控制器的“tweeks”,而不会影响您的常规控制器。

      【讨论】:

        【解决方案3】:

        更新:这个解决方案有一个微妙的错误。 MVC 框架将调用FindView/FindPartialView 两次:一次调用useCache=true,如果没有返回结果,调用一次useCache=false。由于所有类型的视图只有一个缓存,因此如果桌面浏览器最先出现,移动用户最终可能会看到桌面视图。

        对于那些有兴趣使用自定义视图引擎来解决这个问题的人,Scott Hanselman 在这里更新了他的解决方案:

        http://www.hanselman.com/blog/ABetterASPNETMVCMobileDeviceCapabilitiesViewEngine.aspx

        (为答案劫持道歉,我只是不希望其他人不得不经历这个!)

        由 roufamatic (2010-11-17) 编辑


        您要做的第一件事是将Mobile Device Browser File 引入您的项目。使用此文件,您可以定位您想要支持的任何设备,而无需了解这些设备在其标头中发送的具体内容。该文件已经为您完成了工作。然后,您可以使用 Request.Browser 属性来定制要返回的视图。

        接下来,就如何在 Views 文件夹下组织视图提出一个策略。我更喜欢将桌面版本保留在根目录,然后有一个 Mobile 文件夹。例如,主视图文件夹如下所示:

        • 首页
          • 手机
            • iPhone
              • 索引.aspx
            • 黑莓
              • 索引.aspx
          • 索引.aspx

        我不同意@Mehrdad 关于使用自定义视图引擎的观点。视图引擎有不止一个用途,其中之一就是为控制器寻找视图。您可以通过覆盖 FindView 方法来做到这一点。在这种方法中,您可以检查在哪里可以找到视图。在您知道哪个设备正在使用您的网站后,您可以使用您想出的组织视图的策略来返回该设备的视图。

        public class CustomViewEngine : WebFormViewEngine
        {
            public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
            {
                // Logic for finding views in your project using your strategy for organizing your views under the Views folder.
                ViewEngineResult result = null;
                var request = controllerContext.HttpContext.Request;
        
                // iPhone Detection
                if (request.UserAgent.IndexOf("iPhone",
           StringComparison.OrdinalIgnoreCase) > 0)
                {
                    result = base.FindView(controllerContext, "Mobile/iPhone/" + viewName, masterName, useCache);
                }
        
                // Blackberry Detection
                if (request.UserAgent.IndexOf("BlackBerry",
           StringComparison.OrdinalIgnoreCase) > 0)
                {
                    result = base.FindView(controllerContext, "Mobile/BlackBerry/" + viewName, masterName, useCache);
                }
        
                // Default Mobile
                if (request.Browser.IsMobileDevice)
                {
                    result = base.FindView(controllerContext, "Mobile/" + viewName, masterName, useCache);
                }
        
                // Desktop
                if (result == null || result.View == null)
                {
                    result = base.FindView(controllerContext, viewName, masterName, useCache);
                }
        
                return result;
            }
        }
        

        上面的代码允许你根据你的策略设置视图。如果没有为设备找到视图或没有默认的移动视图,则回退是桌面视图。

        如果您决定将逻辑放在控制器中而不是创建视图引擎。最好的方法是创建一个自定义的ActionFilterAttribute,你可以用它来装饰你的控制器。然后覆盖OnActionExecuted 方法以确定哪个设备正在查看您的站点。您可以查看此blog post 了解如何操作。这篇文章还有一些不错的链接,指向一些关于这个主题的 Mix 视频。

        【讨论】:

        • 不适用于 T4MVC,您假设 viewname 只是一个名称而不是路径。由于缓存,也无法在发布模式下工作。
        • 自从 T4MVC 发布以来还没有尝试过。这是在可用之前。当我有时间时,我会更新答案以反映我在针对 T4MVC 进行测试时的结果。
        • 卡尔,你说的缓存是什么意思?这种方法与 Scott Hanselman 在这里描述的相同:hanselman.com/blog/…,但他没有提及。
        • 这个解决方案有一个错误!事实证明,Carl 是正确的,缓存是一个问题,但不是他描述的方式。问题在于失败的逻辑。如果直接通过结果被缓存,但特定于设备的视图没有被缓存,那么即使特定于设备的视图可用,浏览器也会收到失败。
        • 汉塞尔曼回应。 hanselman.com/blog/…
        【解决方案4】:

        这是一个实际工作的版本,在 T4MVC 和发布模式下(启用视图缓存)都有效。它也处理用户控件和绝对/相对 url。它需要Mobile Device Browser File

        public class MobileCapableWebFormViewEngine : WebFormViewEngine
        {
        
            protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
            {
                if (viewPath.EndsWith(".ascx"))
                    masterPath = "";
                return base.CreateView(controllerContext, viewPath, masterPath);
            }
            public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
            {
                useCache = false;
                ViewEngineResult result = null;
                var request = controllerContext.HttpContext.Request;
        
                if (request.Browser.IsMobileDevice || request["mobile"] != null || request.Url.Host.StartsWith("m."))
                {
                    var mobileViewName = GetMobileViewName(viewName);
        
                    result = base.FindView(controllerContext, mobileViewName, masterName, useCache);
                    if (result == null || result.View == null)
                    {
                        result = base.FindView(controllerContext, viewName, "Mobile", useCache);
                    }
                }
        
                if (result == null || result.View == null)
                {
                    result = base.FindView(controllerContext, viewName, masterName, useCache);
                }
        
                return result;
            }
        
            private static string GetMobileViewName(string partialViewName)
            {
                var i = partialViewName.LastIndexOf('/');
                return i > 0
                           ? partialViewName.Remove(i) + "/Mobile" + partialViewName.Substring(i)
                           : "Mobile/" + partialViewName;
            }
        }
        

        【讨论】:

        • 感谢您的跟进,但我仍然不明白您为什么不信任缓存。允许缓存的 AFAICT 应该加快对 base.FindView() 的调用。这些调用是在选择了正确的视图之后发生的。用反射器检查确认。如果您期望这些视图会改变,我可以理解禁用缓存,但这不是这里发生的事情。视图是相同的,我们只是添加了如何选择它们的逻辑。我会坚持使用缓存。
        【解决方案5】:

        我认为插入此功能的正确位置是自定义 ViewEngine。但是您应该知道IViewEngine.FindView 方法是如何被ViewEngineCollection 调用的(在此here 上查找更多信息)。

        Scott Hanselman 建议的更新 solution 无法正常工作。你可以找到我的这种方法的示例实现here。检查描述如何重复错误行为的自述文件。

        我建议另一种方法检查原始 ViewEngine 是否未找到视图,如果 useCache 参数为 true,它会检查原始 ViewEngine 中是否存在带有参数 useCache=false 的视图。

        将所有代码都放在这里太复杂了,但是您可以在我的开源游乐场here 中找到建议的方法。检查MobileViewEngine 类和单元测试。

        一些 MobileViewEngine 功能:

        • 与视图缓存一起正常工作并使用原始视图引擎缓存。
        • 同时支持:MvcContrib T4 模板使用的镜头视图名称和相对视图路径 (~/Views/Index)。
        • 将“索引”视图解析如下:
          • Mobile/Platform/Index – 如果视图存在并且移动设备平台(iPhone、Android 等)被列入支持列表。
          • Mobile/Index - 查看所有其他移动设备。如果视图不存在,您可以选择回退到桌面视图版本。
          • Index – 用于桌面视图版本。
        • 您可以自定义移动视图层次结构(例如Mobile/ Platform/Manufacturer)或通过添加/更改设备规则自定义移动视图路径分辨率(请参阅MobileDeviceRulePlatformSpecificRule)。

        希望,这会有所帮助

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-03-04
          • 2012-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-15
          • 1970-01-01
          相关资源
          最近更新 更多