27

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

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

4

5 回答 5

21

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

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

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

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

由 roufamatic 编辑 (2010-11-17)


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

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

    • 移动的
      • 苹果手机
        • 索引.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方法以确定哪个设备正在查看您的站点。您可以查看此博客文章了解如何操作。这篇文章还有一些很好的链接,指向一些关于这个主题的 Mix 视频。

于 2009-09-07T04:29:52.253 回答
2

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

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

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

于 2009-09-07T02:47:58.193 回答
2

这是一个实际工作的版本,无论是在 T4MVC 还是在发布模式(启用视图缓存)。它也处理用户控件和绝对/相对 url。它需要移动设备浏览器文件

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;
    }
}
于 2010-09-29T08:26:35.377 回答
2

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

Scott Hanselman 建议的更新解决方案无法正常工作。您可以在此处找到此方法的示例实现。检查描述如何重复错误行为的自述文件。

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

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

一些 MobileViewEngine 功能:

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

希望,这会有所帮助

于 2011-02-07T15:17:19.327 回答
0

您的核心逻辑在控制器中应该是相同的,并且只有您需要的视图才会更改,因此控制器是您确实需要 if/else 语句为每个控制器操作提供正确视图的位置,如您所述。

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

于 2009-09-07T02:48:34.853 回答