我正在阅读一些ASP.NET MVC的内容,我将把工作中的WebForms迁移到MVC。在这个过程中,我希望会有一个功能请求,即如果用户来自移动设备,则返回简化的视图。
我还不确定最佳实现该逻辑的位置。我相信有更好的方法,而不是在每个返回视图的操作中添加一个针对Browser.IsMobileDevice的if/else语句。我可以采用哪些选项来实现这一点?
我正在阅读一些ASP.NET MVC的内容,我将把工作中的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)
首先,你需要引入Mobile Device Browser File到你的项目中。使用此文件,您可以针对任何想要支持的设备,而无需知道这些设备发送的特定信息。这个文件已经为你完成了这项工作。然后,你使用Request.Browser属性来定制你想要返回的视图。
接下来,考虑一下你想要如何组织Views文件夹中的视图。我喜欢将桌面版本保留在根目录下,然后有一个Mobile文件夹。例如,Home视图文件夹看起来像这样:
我不同意@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视频的有用链接。
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;
}
}
MobileViewEngine
类和单元测试。Mobile/Platform/Index
- 如果视图存在并且在支持列表中列出了移动设备平台(IPhone、Android等)。Mobile/Index
- 所有其他移动设备的视图。如果该视图不存在,您可以选择回退到桌面视图版本。Index
- 适用于桌面视图版本。MobileDeviceRule
和PlatformSpecificRule
)来自定义移动视图层次结构(例如Mobile/ Platform/Manufacturer
)或自定义移动视图路径解析。希望这可以帮助您
你的核心逻辑应该在控制器中保持一致,只有需要的视图会发生变化,因此控制器是需要 if/else 语句来为每个控制器操作提供正确视图的地方,就像你所说的那样。
另一种方法是将控制器逻辑封装在单独的 dll 中,然后为移动版本设置不同的控制器/路径。如果常规控制器从移动设备接收到请求,则可以将它们重定向到包含所有移动控制器的移动区域,这些控制器使用共享控制器逻辑。这种解决方案还允许您进行针对移动控制器的特定“微调”,而不会影响常规控制器。