ASP.NET MVC 自定义路由约束、依赖注入和单元测试

2024-01-22

关于这个话题,我又问了一个问题:

ASP.NET MVC 自定义路由约束和依赖注入 https://stackoverflow.com/questions/8308642/asp-net-mvc-custom-route-constraints-and-dependency-injection

目前的情况是:在我的 ASP.NET MVC 3 应用程序上,我定义了一个路由约束,如下所示:

public class CountryRouteConstraint : IRouteConstraint {

    private readonly ICountryRepository<Country> _countryRepo;

    public CountryRouteConstraint(ICountryRepository<Country> countryRepo) {
        _countryRepo = countryRepo;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {

        //do the database look-up here

        //return the result according the value you got from DB
        return true;
    }
}

我正在使用它,如下所示:

routes.MapRoute(
    "Countries",
    "countries/{country}",
    new { 
        controller = "Countries", 
        action = "Index" 
    },
    new { 
        country = new CountryRouteConstraint(
            DependencyResolver.Current.GetService<ICountryRepository<Country>>()
        ) 
    }
);

在单元测试部分,我使用了以下代码:

[Fact]
public void country_route_should_pass() {

    var mockContext = new Mock<HttpContextBase>();
    mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns("~/countries/italy");

    var routes = new RouteCollection();
    TugberkUgurlu.ReservationHub.Web.Routes.RegisterRoutes(routes);

    RouteData routeData = routes.GetRouteData(mockContext.Object);

    Assert.NotNull(routeData);
    Assert.Equal("Countries", routeData.Values["controller"]);
    Assert.Equal("Index", routeData.Values["action"]);
    Assert.Equal("italy", routeData.Values["country"]);
}

在这里,我不知道如何传递依赖关系。任何想法?


就我个人而言,我尝试避免在路由约束内执行此类验证,因为以这种方式表达您的意图要困难得多。相反,我使用约束来确保参数采用正确的格式/类型,并将此类逻辑放入我的控制器中。

在您的示例中,我假设如果该国家/地区无效,那么您将退回到其他路线(例如“未找到国家/地区”页面)。与接受所有国家/地区参数并在控制器中检查它们相比,依赖路由配置的可靠性要低得多(并且更有可能被破坏):

    public ActionResult Country(string country)
    {
        if (country == "france") // lookup to db here
        {
            // valid
            return View();
        }

        // invalid 
        return RedirectToAction("NotFound");
    }

除此之外,您在这里试图实现的目标(正如已经提到的)实际上是一个集成测试。当您发现框架的某些部分妨碍了您的测试时,那么可能是时候进行重构了。在你的例子中我想测试

  1. 国家/地区已正确验证
  2. 我的路由配置。

我们能做的第一件事是将国家/地区验证移到一个单独的类中:

public interface ICountryValidator
{
    bool IsValid(string country);
}

public class CountryValidator : ICountryValidator
{
    public bool IsValid(string country)
    {
        // you'll probably want to access your db here
        return true;
    }
}

然后我们可以将其作为一个单元进行测试:

    [Test]
    public void Country_validator_test()
    {
        var validator = new CountryValidator();

        // Valid Country
        Assert.IsTrue(validator.IsValid("france"));

        // Invalid Country
        Assert.IsFalse(validator.IsValid("england"));
    }

Our CountryRouteConstraint然后更改为:

public class CountryRouteConstraint : IRouteConstraint
{
    private readonly ICountryValidator countryValidator;

    public CountryRouteConstraint(ICountryValidator countryValidator)
    {
        this.countryValidator = countryValidator;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        object country = null;

        values.TryGetValue("country", out country);

        return countryValidator.IsValid(country as string);
    }
}

我们像这样绘制我们的路线:

routes.MapRoute(
    "Valid Country Route", 
    "countries/{country}", 
    new { controller = "Home", action = "Country" },
    new { country = new CountryRouteConstraint(new CountryValidator()) 
});

现在,如果您确实觉得有必要测试 RouteConstraint,您可以独立测试:

    [Test]
    public void RouteContraint_test()
    {
        var constraint = new CountryRouteConstraint(new CountryValidator());

        var testRoute = new Route("countries/{country}",
            new RouteValueDictionary(new { controller = "Home", action = "Country" }),
            new RouteValueDictionary(new { country = constraint }),
            new MvcRouteHandler());

        var match = constraint.Match(GetTestContext(), testRoute, "country", 
            new RouteValueDictionary(new { country = "france" }), RouteDirection.IncomingRequest);

        Assert.IsTrue(match);
    }

就我个人而言,我不会费心执行此测试,因为我们已经抽象了验证代码,所以实际上这只是测试框架。

为了测试路由映射,我们可以使用 MvcContrib测试助手 http://nuget.org/packages/MvcContrib.Mvc3.TestHelper-ci.

    [Test]
    public void Valid_country_maps_to_country_route()
    {
        "~/countries/france".ShouldMapTo<HomeController>(x => x.Country("france"));
    }

    [Test]
    public void Invalid_country_falls_back_to_default_route()
    {
        "~/countries/england".ShouldMapTo<HomeController>(x => x.Index());
    }

根据我们的路由配置,我们可以验证有效的国家/地区映射到国家/地区路由,无效的国家/地区映射到后备路由。

但是,您问题的要点是如何处理路线约束的依赖性。上面的测试实际上是在测试很多东西——我们的路由配置、路由约束、验证器以及可能对存储库/数据库的访问。

如果您依赖 IoC 工具为您注入这些内容,则必须模拟您的验证器和存储库/数据库,并在测试设置中使用 IoC 工具注册它们。

如果我们能够控制约束的创建方式,那就更好了:

public interface IRouteConstraintFactory
{
    IRouteConstraint Create<TRouteConstraint>() 
        where TRouteConstraint : IRouteConstraint;
}

您的“真正”实现只需使用 IoC 工具来创建IRouteConstraint实例。

我喜欢将路由配置放在一个单独的类中,如下所示:

public interface IRouteRegistry
{
    void RegisterRoutes(RouteCollection routes);
}

public class MyRouteRegistry : IRouteRegistry
{
    private readonly IRouteConstraintFactory routeConstraintFactory;

    public MyRouteRegistry(IRouteConstraintFactory routeConstraintFactory)
    {
        this.routeConstraintFactory = routeConstraintFactory;
    }

    public void RegisterRoutes(RouteCollection routes)
    {
        routes.MapRoute(
            "Valid Country", 
            "countries/{country}", 
            new { controller = "Home", action = "Country" },
            new { country = routeConstraintFactory.Create<CountryRouteConstraint>() });

        routes.MapRoute("Invalid Country", 
            "countries/{country}", 
            new { controller = "Home", action = "index" });
    }
}

可以使用工厂创建具有外部依赖关系的约束。

这使得测试变得更加容易。由于我们只对测试国家/地区路线感兴趣,因此我们可以创建一个仅执行我们需要的操作的测试工厂:

    private class TestRouteConstraintFactory : IRouteConstraintFactory
    {
        public IRouteConstraint Create<TRouteConstraint>() where TRouteConstraint : IRouteConstraint
        {
            return new CountryRouteConstraint(new FakeCountryValidator());
        }
    }

请注意,这次我们使用的是FakeCountryValidator其中包含足够的逻辑供我们测试我们的路线:

public class FakeCountryValidator : ICountryValidator
{
    public bool IsValid(string country)
    {
        return country.Equals("france", StringComparison.InvariantCultureIgnoreCase);
    }
}

当我们设置测试时,我们通过了TestRouteFactoryConstraint到我们的路线注册表:

    [SetUp]
    public void SetUp()
    {
        new MyRouteRegistry(new TestRouteConstraintFactory()).RegisterRoutes(RouteTable.Routes);
    }

这次当我们运行路由测试时not测试我们的验证逻辑或数据库访问。相反,当提供有效或无效的国家/地区时,我们将对路由配置进行单元测试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ASP.NET MVC 自定义路由约束、依赖注入和单元测试 的相关文章

随机推荐

  • Java从构造函数调用构造函数

    我有一个构造函数 private Double mA private Double mB Foo Double a mA a mB a 10 Foo Double a Double b mA a mB b some logic here 如
  • update-alternatives:错误:arptables 的替代 /usr/sbin/arptables-legacy 未注册;不设置

    我在 Buster 主机上有 Debian 10 Buster KVM 来宾计算机 尝试切换到旧版iptables在以下虚拟机上Debian 维基 https wiki debian org iptables update alternat
  • 在 python 中使用鼻子进行并行化测试

    我有一个包含大量 py 文件的目录 例如 test 1 py test 2 py 等 每个文件都经过正确编写 可以与鼻子一起使用 因此 当我运行 notests 脚本时 它会找到所有 py 文件中的所有测试并执行它们 我现在想要并行化它们
  • 更改 svg 组元素中心

    我想将 svg 组的中心平移到其左上角 代替 x x gt
  • 如何在 OS X 中为我的文档创建图标

    我有一个基于文档的应用程序 因此 我需要为我的应用程序提供一个图标 也为我的文档提供一个图标 在 Xcode Images xcassets 中有一个名为 AppIcon 的项目 我可以用它来设置应用程序的图标 但是如何为我的文档制作图标呢
  • 为什么 jQuery 会抛出错误“fadeOut is not a function”?

    我正在使用 jQuery 并将以下代码放入我的 javascript 中 function HideMe itemID var myDiv item itemID myDiv fadeOut slow 但它给了我这个错误 fadeOut不是
  • Node/Express文件上传

    我正在使用 Node v0 10 26 和 Express v4 2 0 而且我对 Node 还很陌生 在过去的三个小时左右的时间里 我一直在用头撞桌子 试图让文件上传表单与节点一起使用 此时我只是试图让 req files 不返回未定义
  • Java 中 LTRIM 和 RTRIM 的良好替代品是什么?

    JavaScript 的一个好的替代品是什么ltrim and rtrim Java 中的函数 使用正则表达式你可以写 String s String ltrim s replaceAll s String rtrim s replaceA
  • 在Vba中从多个工作表中收集平均值

    我需要将多个工作表中某个范围 C2 C11 的平均值计算到一个新工作表中 如果添加新工作表并将数据输入到指定范围内 该功能应该仍然有效 到目前为止我有这个 Sheets Add Dim myavg As Collection Set mya
  • php彩票问题多赢家问题

    我用 php 创建了一个彩票脚本 我现在的问题是选择不止一名获胜者 因为玩家的门票上有可能有相同的号码 这里我提供两个表结构和源代码 lotto game id int jackpot int status varchar10 pick 1
  • 如何使用Java JSch库逐行读取远程文件?

    我正在尝试使用Java逐行读取文件 这非常简单 stackoverflow com上有多种解决方案 但这里需要注意的是该文件位于远程服务器上 并且它不是可以获取本地副本 它是单个 txt 文件中数百万条亚马逊评论的大量集合 JSch 附带了
  • 毛伊岛:在 Android 上使用 Acrobat Reader 打开捆绑(资产)pdf 文件

    我的应用程序在下面有一个 PDF 帮助文件Resources Raw helpfile pdf 在 Android 上 应通过将其 发送 到 Acrobat Reader 来打开它 前提是它已安装在设备上 我尝试了像 从捆绑文件写入应用程序
  • 如何以 Symfony 形式制作 RadioButton?

    我有必须用 Symfony Form 重做的表单 但我坚持在这个地方 div class currency label div
  • 从 C# 打开新的 Outlook 邮件

    我希望从我的程序中生成 Outlook 消息 我能够从程序中构建和发送或构建并保存 我想要的是构建然后显示以允许用户从 AD 列表中手动选择收件人 下面的代码是此处示例和其他教程站点的混合 但是我找不到一个只是构建然后 显示 电子邮件而不保
  • Xcode 4 使用不同的配置设置构建静态库依赖项

    我想知道是否可以使用不同的配置设置编译依赖的静态库 我的情况是我有一个项目 project pbxproj 它依赖于静态库 cocoa touch 项目 libStatic pbxproj 我的项目有 3 个配置设置 例如调试 发布和临时配
  • 将列表转换为 pandas 中的日期时间

    我有福尔 熊猫列表 str jan 1 jan 15 feb 1 feb 15 mar 1 mar 15 apr 1 apr 15 may 1 may 15 jun 1 jun 15 jul 1 jul 15 aug 1 aug 15 se
  • 如何在 Android 上停止 HttpURLConnection 连接

    我使用 AsyncTask 连接 URLPath 如下代码所示 Override public void onCreate Bundle savedInstanceState super onCreate savedInstanceStat
  • 从任何活动中打开侧栏应用程序启动器

    我是 Android 开发新手 我正在考虑创建一个示例启动器应用程序来替换 Android 默认启动器 我已经创建了这个启动器 但我面临几个问题 1 单击 应用程序 按钮后 我将显示设备上安装的所有应用程序 但应用程序图标的大小不同 我尝试
  • 如何将遗传算法与一些启发式算法相结合

    我正在研究大学调度问题并为此使用简单的遗传算法 实际上它效果很好 可以在 1 小时内将目标函数值从 0 优化到 90 大约 但随后这个过程会急剧减慢 需要几天时间才能得到最佳解决方案 我看到很多论文认为将其他算法与遗传算法混合是合理的 请您
  • ASP.NET MVC 自定义路由约束、依赖注入和单元测试

    关于这个话题 我又问了一个问题 ASP NET MVC 自定义路由约束和依赖注入 https stackoverflow com questions 8308642 asp net mvc custom route constraints