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

发布于 2024-08-04 11:03:09 字数 215 浏览 5 评论 0原文

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

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

I'm working my way through some ASP.NET MVC reading and I have a web app at work that I'll be migrating from WebForms to MVC. One of the feature requests I expect to get in the process is to have a simplified view returned if the user is coming from a mobile device.

I can't quite see where the best place is to implement that type of logic. I'm sure there's a better way than adding an if/else for Browser.IsMobileDevice in every action that returns a view. What kind of options would I have to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

金兰素衣 2024-08-11 11:03:09

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

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

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

(对答案劫持表示歉意,我只是不想让其他人经历这个!)

由 roufamatic 编辑(2010-11) -17)


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

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

  • 主页
    • 手机
      • 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 方法来确定哪个设备正在查看您的网站。您可以查看这篇博客文章了解如何操作。这篇文章还提供了一些关于这个主题的 Mix 视频的一些不错的链接。

Update: This solution has a subtle bug. The MVC framework will call into FindView/FindPartialView twice: once with useCache=true, and if that doesn't return a result, once with useCache=false. Since there's only one cache for all types of views, mobile users may end up seeing desktop views if a desktop browser was first to arrive.

For those interested in using custom view engines to solve this problem, Scott Hanselman has updated his solution here:

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

(Apologies for the answer hijack, I just don't want anyone else to have to go through this!)

Edited by roufamatic (2010-11-17)


The first thing you want to do is introduce the Mobile Device Browser File to your project. Using this file you can target what ever device you want to support without having to know the specifics of what those devices send in their headers. This file has already done the work for you. You then use the Request.Browser property to tailor which view you want to return.

Next, come up with a strategy on how you want to organize your views under the Views folder. I prefer to leave the desktop version at the root and then have a Mobile folder. For instance the Home view folder would look like this:

  • Home
    • Mobile
      • iPhone
        • Index.aspx
      • BlackBerry
        • Index.aspx
    • Index.aspx

I have to disagree with @Mehrdad about using a custom view engine. The view engine serves more than one purpose and one of those purposes is finding views for the controller. You do this by overriding the FindView method. In this method, you can do your checks on where to find the view. After you know which device is using your site, you can use the strategy you came up with for organizing your views to return the view for that device.

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;
    }
}

The above code allows you set the view based on your strategy. The fall back is the desktop view, if no view was found for the device or if there isn't a default mobile view.

If you decide to put the logic in your controller's instead of creating a view engine. The best approach would be to create a custom ActionFilterAttribute that you can decorate your controller's with. Then override the OnActionExecuted method to determine which device is viewing your site. You can check this blog post out on how to. The post also has some nice links to some Mix videos on this very subject.

暮凉 2024-08-11 11:03:09

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

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

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

In the Model-View-Controller pattern, it's the controller that chooses view, so, it's not that bad to add an if statement and return an appropriate view. You can encapsulate the if statement in a method and call it:

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

Alternatively, you can create a view engine that dynamically executes a view based on whether it's mobile or not. I'm not a fan of this approach since I believe the controller should be in charge. For instance, if you're browsing on iPhone, you might want to see the full desktop version instead. In the former approach, you'd pass the appropriate boolean flag but in the latter, things become more complicated.

池予 2024-08-11 11:03:09

这是一个实际可以工作的版本,无论是在 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;
    }
}

This is a version which actually works, both with T4MVC and in release mode (where caching of views is enabled). It does take care of usercontrols and absolute/relative urls as well. It requires the 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;
    }
}
记忆之渊 2024-08-11 11:03:09

我认为插入此功能的正确位置是自定义 ViewEngine。但您应该了解 ViewEngineCollection 如何调用 IViewEngine.FindView 方法(有关此内容的更多信息此处)。

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

我建议使用另一种方法来检查原始 ViewEngine 是否未找到视图,并且如果 useCache 参数为 true,则使用参数 useCache 检查原始 ViewEngine 中是否存在视图=假。

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

一些 MobileViewEngine 功能:

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

希望这会有所帮助

I think that the right place to plug this functionality is custom ViewEngine. But you should be aware of how IViewEngine.FindView method is called by the ViewEngineCollection (find more on this here).

Updated solution suggested by Scott Hanselman does not work correctly. You can find my sample implementation of this approach here. Check readme file that describes how you can repeat incorrect behavior.

I suggest another approach that checks if a view was not found by original ViewEngine and if useCache parameter is true, it checks if view exist in original ViewEngine with parameter useCache=false.

It is too complex to put all code here but you can find the suggested approach implemented in my open-source playground here. Check MobileViewEngine class and unit-tests.

Some MobileViewEngine features:

  • Works correctly with view caching and uses original view engine cache.
  • Supports both: shot view names and relative view paths (~/Views/Index) used by MvcContrib T4 template.
  • Resolves "Index" view as following:
    • Mobile/Platform/Index – if view exists and a mobile device platform (IPhone, Android etc.) is enlisted in supported list.
    • Mobile/Index - view for all other mobile devices. If the view does not exists you can optionally fallback to desktop view version.
    • Index – for desktop view version.
  • You can customize mobile view hierarchy (e.g. Mobile/ Platform/Manufacturer) or customize mobile view path resolution by adding/changing device rules (see MobileDeviceRule and PlatformSpecificRule).

Hope, this will help

注定孤独终老 2024-08-11 11:03:09

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

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

Your core logic should be the same in the controllers and only the view you need will change so the controller is where the you do need the if/else statement to serve up the correct view for each controller action as you stated.

An alternative would be to wrap you controller logic in a seperate dll and then have different controllers / paths for the mobile version. If a regular controller receives a request from a mobile device you can redirect them to your mobile area which contains all your mobile controllers that use the shared controller logic. This solution would also allow you to do 'tweeks' that are specific to the mobile controllers and not have it impact your regular controllers.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文