Skip to content

Files

Latest commit

678890d · Jan 8, 2022

History

History
285 lines (231 loc) · 11.8 KB

File metadata and controls

285 lines (231 loc) · 11.8 KB

十一、还在用 MVC 3?

“我要吃她吃的!” 哈利遇见莎莉时的匿名顾客

有很多项目是用 MVC 3 开发的,如果你还在用 MVC 3 工作,你现在可能会觉得有点被冷落了,想要一些其他人都有的东西。别担心——你可以使用现有的 MVC 3 技术实现同样的移动友好的效果,并在没有大的中断的情况下将自己定位到 MVC 4。

减速带:MVC 3 和 MVC 4 并列

你可以很容易地在 MVC 3 旁边安装 MVC 4,打破任何东西都不是假设的。然而,有一个小减速带,你可能会遇到。

安装 MVC 4 后,它会在C:\ Program Files(x86)\ Microsoft ASP 中为您的系统安装它的新文件。. NET\ASP。NET MVC 4 文件夹,所以它确实将新的 MVC 文件与它旁边的 ASP.NET MVC 3 文件夹分开。但是,它也会在ASP.NET 网页文件夹内 v1.0 文件夹旁安装一个新的 v2.0 文件夹:C:\ Program Files(x86)\ Microsoft ASP。. NET\ASP。NET 网页\v2.0

这个文件夹恰好包含了一些 MVC 3 使用的同名但不同版本的文件。

当您在安装 MVC 4 后返回并构建现有的 MVC 3 项目时,您可能会得到以下编译错误:

      c:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1360,9): warning MSB3247: Found conflicts between different versions of the same dependent assembly.

如果你仔细研究一下,你会发现你的 MVC 3 项目引用了系统。网页系统。网络助手dll,但没有指定版本。最快的解决方法是在记事本中打开您的项目文件,并进行一些快速编辑,将所有内容放回正确的位置,您的项目将再次正常工作。

之前的项目定义文件:(*)。csproj)

      <Reference Include="System.Web.WebPages">
        <Private>True</Private>
      </Reference>
      <Reference Include="System.Web.Helpers">

项目定义文件在:(*)之后。csproj)

      <Reference Include="System.Web.WebPages, Version=1.0.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
        <SpecificVersion>True</SpecificVersion>
      </Reference>
      <Reference Include="System.Web.Helpers, Version=1.0.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
        <Private>True</Private>
      </Reference>

一旦你修复了这个小错误,你应该能够再次正常运行你的 MVC 3 项目,并且一切都并行工作。但是,您必须将此修复应用于您打开的每个 MVC 3 项目。

回到 MVC 3 项目

让我们回到我们手头的主题:如何将您的 MVC 3 项目转换为使用我们在花哨的新 MVC 4 项目中使用的移动友好技术?事实证明,在 MVC 3 中,我们不需要添加太多东西来实现这一点。

首先要做的是安装 jQuery。使用 NuGet 的移动包,就像我们在 MVC 4 中做的那样。使用包管理器控制台命令行,运行命令安装-包 jQueryMobile

由于DisplayModeProvider代码是 MVC 4 中的新特性,我们将不得不在我们的 MVC 3 项目中复制该功能。在项目的根目录下,创建一个名为mobilecapablerazoverwingine . cs的新类文件,并将以下代码放入该类中:

      using System;
      using System.IO;
      using System.Web;
      using System.Web.Mvc;

      namespace YourApplicationNameSpace
      {
        // In Global.asax.cs Application_Start you can insert these
        // into the ViewEngine chain like so:
        //
        // ViewEngines.Engines.Insert(0, new
        //   MobileCapableRazorViewEngine());
        //
        // or
        //
        // ViewEngines.Engines.Insert(0,
        //  new MobileCapableRazorViewEngine("iPhone")
        //  {
        //    ContextCondition = (ctx =>
        //     ctx.Request.UserAgent.IndexOf(
        //      "iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
        //  });

        public class MobileCapableRazorViewEngine : RazorViewEngine
        {
          public string ViewModifier { get; set; }
          public Func<HttpContextBase, bool> ContextCondition
            { get; set; }

          public MobileCapableRazorViewEngine()
            : this("Mobile", context =>
              context.Request.Browser.IsMobileDevice)
          {
          }

          public MobileCapableRazorViewEngine(string viewModifier)
            : this(viewModifier,
              context => context.Request.Browser.IsMobileDevice)
          {
          }

          public MobileCapableRazorViewEngine(string viewModifier,
            Func<HttpContextBase, bool> contextCondition)
          {
            this.ViewModifier = viewModifier;
            this.ContextCondition = contextCondition;
          }

          public override ViewEngineResult FindView(
           ControllerContext controllerContext,
           string viewName, string masterName, bool useCache)
          {
            return NewFindView(controllerContext, viewName, 
              null, useCache, false);
          }

          public override ViewEngineResult FindPartialView(
            ControllerContext controllerContext,
            string partialViewName, bool useCache)
          {
            return NewFindView(controllerContext, partialViewName,
              null, useCache, true);
          }

          private ViewEngineResult NewFindView(
            ControllerContext controllerContext,
            string viewName, string masterName, bool useCache,
            bool isPartialView)
          {
            if (!ContextCondition(controllerContext.HttpContext))
            {
              // We found nothing and we pretend we looked nowhere.
              return new ViewEngineResult(new string[] { });
            }

            // Get the name of the controller from the path.
            string controller = controllerContext.RouteData
              .Values["controller"].ToString();
            string area = "";
            try
            {
              area = controllerContext.RouteData.DataTokens["area"]
               .ToString();
            }
            catch
            {
            }

            // Apply the view modifier.
            var newViewName = string.Format("{0}.{1}", viewName,
              ViewModifier);

            // Create the key for caching purposes.         
            string keyPath = Path.Combine(area, controller,
              newViewName);

            string cacheLocation =
              ViewLocationCache
                .GetViewLocation(controllerContext.HttpContext,
                keyPath);

            // Try the cache.         
            if (useCache)
            {
              //If using the cache, check to see if the location
              //is cached.                             
              if (!string.IsNullOrWhiteSpace(cacheLocation))
              {
                if (isPartialView)
                {
                  return new ViewEngineResult(CreatePartialView(
                    controllerContext, cacheLocation), this);
                }
                else
                {
                  return new ViewEngineResult(
                    CreateView(controllerContext, cacheLocation,
                     masterName),
                      this);
                }
              }
            }
            string[] locationFormats = string.IsNullOrEmpty(area) ?
              ViewLocationFormats : AreaViewLocationFormats;

            // For each of the paths defined, format the string and
            // see if that path exists. When found, cache it.         
            foreach (string rootPath in locationFormats)
            {
              string currentPath = string.IsNullOrEmpty(area)
                ? string.Format(rootPath, newViewName, controller)
                : string.Format(rootPath, newViewName, controller,
                  area);
              if (FileExists(controllerContext, currentPath))
              {
                ViewLocationCache.InsertViewLocation(
                  controllerContext.HttpContext,
                  keyPath, currentPath);
                if (isPartialView)
                {
                  return new ViewEngineResult(CreatePartialView(
                    controllerContext, currentPath), this);
                }
                else
                {
                  return new ViewEngineResult(CreateView(
                    controllerContext, currentPath, masterName),
                      this);
                }
              }
            }
            // We found nothing and we pretend we looked nowhere.
            return new ViewEngineResult(new string[] { });
          }
        }
      }

通过运行以下命令,可以在 NuGet 上获得此代码:

      PM> Install-Package MobileViewEngines

如果你安装了 NuGet 包,它不会自动将代码包含在你的项目中,但是代码会在你的项目旁边的 Packages 文件夹中,所以你可以从那里复制它。


斯科特·汉斯曼(Scott Hanselman)和彼得·摩尔菲尔德(Peter Mourfield)为这段代码赢得了赞誉,前者在博客中介绍了这种移动视图方法 5 ,后者贡献了这段版本的移动视图引擎代码!


现在您已经有了可以使用的视图引擎,编辑 Global.asax.cs 文件并用下面的代码更新Application_Start函数(它看起来与我们为 MVC 4 所做的非常相似!):

      protected void Application_Start()
      {
        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        ViewEngines.Engines.Insert(0,
        new MobileCapableRazorViewEngine("Phone")
        {
          ContextCondition = (ctx =>
            ctx.Request.UserAgent.IndexOf("iPhone",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("iPod",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("Droid",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("Blackberry",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.StartsWith("Blackberry",
                StringComparison.OrdinalIgnoreCase))
        });
        ViewEngines.Engines.Insert(0,
        new MobileCapableRazorViewEngine("Tablet")
        {
          ContextCondition = (ctx =>
            ctx.Request.UserAgent.IndexOf("iPad",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("Playbook",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("Transformer",
                StringComparison.OrdinalIgnoreCase) >= 0 ||
            ctx.Request.UserAgent.IndexOf("Xoom",
                StringComparison.OrdinalIgnoreCase) >= 0)
        });
      }

就是这样——您现在有了一个代码库,它在功能上几乎等同于我们使用 MVC 4 移动功能和DisplayModeProvider创建的代码库。有一些不同之处,但是您应该能够开始使用这个代码库创建一个非常移动友好的网站。jQueryMobile 功能应该都一样,大部分布局文件也应该一样。有一些类似捆绑技术的东西在 MVC 3 中不可用,所以您必须在布局文件中列出您的每个样式表和 JavaScript 文件(或者自己缩小并连接它们)。

下图是一个 MVC 3 应用程序的示例,其中包含布局页面代码的屏幕截图、生成的熟悉的蓝色选项卡式桌面默认布局的屏幕截图,以及标题更改为电话主页的更新后的电话布局。

具有桌面和电话布局的 MVC 3 应用程序

当您将这种技术与您在本书前面所学的内容相结合时,您应该能够开始让您的 MVC 3 应用程序几乎像 MVC 4 应用程序一样对移动友好!