我如何根据设备类型更改ASP.NET MVC视图?

时间:2009-09-07 02:40:37

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

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

我无法确定实现这种逻辑的最佳位置。我确信有一种比在每个返回视图的操作中为Browser.IsMobileDevice添加if / else更好的方法。我必须做什么样的选择?

5 个答案:

答案 0 :(得分:21)

更新:此解决方案有一个微妙的错误。 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文件夹。例如,Home视图文件夹如下所示:

  • 主页
    • 移动
      • iPhone
        • Index.aspx
      • 黑莓
        • Index.aspx
    • 的Index.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如何查看。该帖子还有一些关于这个主题的一些混音视频的很好的链接。

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:2)

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

Scott Hanselman建议的更新solution无效。您可以找到此方法的示例实现here。检查自述文件,该文件描述了如何重复不正确的行为。

我建议另一种检查原始ViewEngine是否找不到视图的方法,如果useCache参数是true,它会检查原始ViewEngine中是否存在参数useCache=false。< / p>

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

一些MobileViewEngine功能:

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

希望,这会有所帮助

答案 3 :(得分:1)

这是一个实际工作的版本,包括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;
    }
}

答案 4 :(得分:0)

您的核心逻辑在控制器中应该是相同的,只有您需要的视图才会发生变化,因此控制器就是您需要if / else语句为您所说的每个控制器操作提供正确视图的地方。 / p>

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