我正在阅读一些 ASP.NET MVC,并且我有一个正在工作的 Web 应用程序,我将从 WebForms 迁移到 MVC。我希望在此过程中获得的功能请求之一是,如果用户来自移动设备,则返回一个简化的视图。
我不太清楚实现这种逻辑的最佳位置在哪里。我确信有比在返回视图的每个操作中为 Browser.IsMobileDevice 添加 if/else 更好的方法。我必须做什么样的选择?
我正在阅读一些 ASP.NET MVC,并且我有一个正在工作的 Web 应用程序,我将从 WebForms 迁移到 MVC。我希望在此过程中获得的功能请求之一是,如果用户来自移动设备,则返回一个简化的视图。
我不太清楚实现这种逻辑的最佳位置在哪里。我确信有比在返回视图的每个操作中为 Browser.IsMobileDevice 添加 if/else 更好的方法。我必须做什么样的选择?
更新:这个解决方案有一个微妙的错误。MVC 框架将调用FindView
/FindPartialView
两次:一次使用useCache=true
,如果没有返回结果,一次使用useCache=false
. 由于所有类型的视图只有一个缓存,如果桌面浏览器最先出现,移动用户最终可能会看到桌面视图。
对于那些有兴趣使用自定义视图引擎来解决这个问题的人,Scott Hanselman 在这里更新了他的解决方案:
http://www.hanselman.com/blog/ABetterASPNETMVCMobileDeviceCapabilitiesViewEngine.aspx
(为答案劫持道歉,我只是不希望其他人不得不经历这个!)
由 roufamatic 编辑 (2010-11-17)
您要做的第一件事是将移动设备浏览器文件引入您的项目。使用此文件,您可以定位您想要支持的任何设备,而无需了解这些设备在其标头中发送的具体内容。该文件已经为您完成了工作。然后,您使用 Request.Browser 属性来定制您想要返回的视图。
接下来,想出一个关于如何在 Views 文件夹下组织视图的策略。我更喜欢将桌面版本保留在根目录,然后有一个 Mobile 文件夹。例如,主视图文件夹如下所示:
我不得不不同意@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 视频。
在 Model-View-Controller 模式中,选择视图的是控制器,因此,添加if
语句并返回适当的视图并没有那么糟糕。您可以将语句封装if
在一个方法中并调用它:
return AdaptedView(Browser.IsMobileDevice, "MyView.aspx", model);
或者,您可以创建一个视图引擎,根据它是否移动来动态执行视图。我不喜欢这种方法,因为我认为控制器应该负责。例如,如果您在 iPhone 上浏览,您可能希望查看完整的桌面版本。在前一种方法中,您将传递适当的布尔标志,但在后一种方法中,事情变得更加复杂。
这是一个实际工作的版本,无论是在 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;
}
}
我认为插入此功能的正确位置是自定义 ViewEngine。但是您应该知道IViewEngine.FindView
方法是如何被调用的(在此处ViewEngineCollection
找到更多信息)。
Scott Hanselman 建议的更新解决方案无法正常工作。您可以在此处找到此方法的示例实现。检查描述如何重复错误行为的自述文件。
我建议另一种方法检查原始 ViewEngine 是否未找到视图,如果useCache
参数是true
,它会检查原始 ViewEngine 中是否存在带有参数的视图useCache=false
。
将所有代码都放在这里太复杂了,但是您可以在这里找到在我的开源游乐场中实施的建议方法。检查MobileViewEngine
类和单元测试。
一些 MobileViewEngine 功能:
Mobile/Platform/Index
– 如果视图存在并且移动设备平台(iPhone、Android 等)被列入支持列表。Mobile/Index
- 查看所有其他移动设备。如果视图不存在,您可以选择回退到桌面视图版本。Index
– 用于桌面视图版本。Mobile/ Platform/Manufacturer
)或自定义移动视图路径分辨率(参见MobileDeviceRule
和PlatformSpecificRule
)。希望,这会有所帮助
您的核心逻辑在控制器中应该是相同的,并且只有您需要的视图才会更改,因此控制器是您确实需要 if/else 语句为每个控制器操作提供正确视图的位置,如您所述。
另一种方法是将控制器逻辑包装在单独的 dll 中,然后为移动版本设置不同的控制器/路径。如果常规控制器收到来自移动设备的请求,您可以将它们重定向到您的移动区域,其中包含使用共享控制器逻辑的所有移动控制器。此解决方案还允许您执行特定于移动控制器的“tweeks”,并且不会影响您的常规控制器。