草庐IT

c# - 生成传出 url 时选择了意外的路由

coder 2024-05-23 原文

请考虑以下路线:

routes.MapRoute(
    "route1",
    "{controller}/{month}-{year}/{action}/{user}"
);
routes.MapRoute(
    "route2",
     "{controller}/{month}-{year}/{action}"
);

以及以下测试:

测试 1
[TestMethod]
public void Test1()
{
    RouteCollection routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    RequestContext context = new RequestContext(CreateHttpContext(), 
                                                new RouteData());

    DateTime now = DateTime.Now;
    string result;

    context.RouteData.Values.Add("controller", "Home");
    context.RouteData.Values.Add("action", "Index");
    context.RouteData.Values.Add("user", "user1");
    result = UrlHelper.GenerateUrl(null, "Index", null,
                                    new RouteValueDictionary(
                                        new
                                        {
                                            month = now.Month,
                                            year = now.Year
                                        }),
                                    routes, context, true);
    //OK, result == /Home/10-2012/Index/user1
    Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year), 
                    result);
}

测试 2
[TestMethod]
public void Test2()
{
    RouteCollection routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    RequestContext context = new RequestContext(CreateHttpContext(), 
                                                new RouteData());

    DateTime now = DateTime.Now;
    string result;

    context.RouteData.Values.Add("controller", "Home");
    context.RouteData.Values.Add("action", "Index");
    context.RouteData.Values.Add("user", "user1");
    context.RouteData.Values.Add("month", now.Month + 1);
    context.RouteData.Values.Add("year", now.Year);
    result = UrlHelper.GenerateUrl(null, "Index", null,
                                    new RouteValueDictionary(
                                        new
                                        {
                                            month = now.Month,
                                            year = now.Year
                                        }),
                                    routes, context, true);
    //Error because result == /Home/10-2012/Index
    Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year), 
    result);
}

当我在请求上下文中已经有路由值并尝试使用 UrlHelper 生成传出 url 时,此测试模拟了一种情况。

问题是(在测试 2 中提出),如果我有来自预期路线的所有段的值(这里是 route1 )并尝试通过 routeValues 替换其中的一些参数,需要的路由被省略,并使用下一个合适的路由。

因此测试 1 运行良好,因为请求上下文已经具有路由 1 的 5 个段中的 3 个的值,并且缺少的两个段(即 yearmonth)的值通过 routeValues 传递范围。

测试 2 具有请求上下文中所有 5 个段的值。我想通过 routeValues 用其他值替换其中的一些(即月和年)。 .但是路线 1 似乎不合适,因此使用了路线 2。

为什么?我的路线有什么问题?

在这种情况下,我是否需要手动清除请求上下文?

编辑
[TestMethod]
public void Test3()
{
    RouteCollection routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    RequestContext context = new RequestContext(CreateHttpContext(), 
                                                new RouteData());

    DateTime now = DateTime.Now;
    string result;

    context.RouteData.Values.Add("controller", "Home");
    context.RouteData.Values.Add("action", "Index");
    context.RouteData.Values.Add("month", now.Month.ToString());
    context.RouteData.Values.Add("year", now.Year.ToString());
    result = UrlHelper.GenerateUrl(null, "Index", null,
                                    new RouteValueDictionary(
                                        new
                                        {
                                            month = now.Month + 1,
                                            year = now.Year + 1
                                        }),
                                    routes, context, true);
    Assert.AreEqual(string.Format("/Home/{0}-{1}/Index", now.Month + 1, now.Year + 1), 
                    result);
}

这个测试让事情变得更加困惑。在这里,我正在测试 route2。它有效!我有请求上下文中所有 4 个段的值,通过 routeValues 传递其他值,生成的传出url就OK了。

所以,问题出在 route1 上。我错过了什么?

编辑

来自 Sanderson S. Freeman A. - Pro ASP.NET MVC 3 Framework 第三版:

The routing system processes the routes in the order that they were added to the RouteCollection object passed to the RegisterRoutes method. Each route is inspected to see if it is a match, which requires three conditions to be met:

  1. A value must be available for every segment variable defined in the URL pattern. To find values for each segment variable, the routing system looks first at the values we have provided (using the properties of anonymous type), then the variable values for the current request, and finally at the default values defined in the route.
  2. None of the values we provided for the segment variables may disagree with the default-only variables defined in the route. I don't have default values in these routes
  3. The values for all of the segment variables must satisfy the route constraints. I don't have constraints in these routes


因此,根据我在匿名类型中指定值的第一条规则,我没有默认值。 当前请求的变量值 - 我想这是来自请求上下文的值。

这些对 route2 的推理有什么问题,而它们对 route1 运行良好?

编辑

实际上,一切都不是从单元测试开始的,而是从一个真正的 mvc 应用程序开始的。我在那里用过 UrlHelper.Action Method (String, Object)生成传出网址。由于此方法在布局 View 中使用(大多数 View 的父 View ),我已将其带入我的扩展助手方法(以从 View 中排除额外的逻辑),此扩展方法从请求中提取操作名称上下文,作为参数传递给它。我知道我可以通过请求上下文提取所有当前路由值并替换那些年和月(或者我可以创建一个匿名路由值集合,包含上下文中的所有值),但我认为这是多余的,因为 mvc 自动考虑了请求上下文中包含的值。所以,我只提取了 Action 名称,因为没有没有 Action 名称的 UrlHelper.Action 重载(或者我什至也希望“不指定” Action 名称),并通过匿名路由值对象添加了新的月份和年份。

这是一个扩展方法:
public static MvcHtmlString GetPeriodLink(this HtmlHelper html, 
                                          RequestContext context, 
                                          DateTime date)
{
    UrlHelper urlHelper = new UrlHelper(context);
    return MvcHtmlString.Create(
              urlHelper.Action(
                 (string)context.RouteData.Values["action"], 
                 new { year = date.Year, month = date.Month }));
}

正如我在上面的测试中所描述的,它适用于较短的路由(当请求上下文仅包含 Controller 、年和月以及操作时),但在较长的路由中失败(当请求上下文包含 Controller 、年和月、操作和用户时) )。

我已经发布了一个解决方法,我用来使路由按照我需要的方式工作。

虽然我确实很想知道,为什么我必须在这种情况下采取任何解决方法,这两条路线之间的主要区别是什么? route1从工作方式route2做。

编辑

再说一句。就请求上下文中的值而言,类型为 string ,我决定尝试将它们作为字符串设置到上下文中,以确保没有类型混淆(int vs string)。我不明白,这方面发生了什么变化,但一些路线开始正确生成。但并非全部......这更没有意义。我已经在实际应用程序中而不是测试中更改了它,因为测试具有 int在上下文中,而不是字符串。

好吧,我找到了使用 route1 的条件 - 它仅在 month 的值时使用和 year在上下文中等于匿名对象中给出的那些。如果它们不同(在测试中对 intstring 都是如此),则使用 route2。但为什么?

这证实了我在实际应用程序中的内容:
  • 我有 string s 在上下文中,但提供 int通过匿名对象,它以某种方式混淆了 mvc 并且无法使用 route1 .
  • 我改了int匿名对象中的 s 到 string s,以及 month 所在的网址和 year在上下文中等于匿名对象中的那些,开始正确生成;而所有其他人都没有。

  • 所以,我看到了一个规则:匿名对象的属性应该是 string 类型的。匹配请求上下文中路由值的类型。

    但是这个规则好像是非强制性 ,就像在 Test3 中一样,我更改了类型(您现在可以在上面看到它),但它仍然可以正常工作。 MVC 设法正确地转换类型本身。

    最后我找到了对所有这些行为的解释。请看下面我的回答。

    最佳答案

    这是我用来让它工作的快速解决方法:

    public static MvcHtmlString GetPeriodLink(this HtmlHelper html, 
                                              RequestContext context, 
                                              DateTime date)
    {
        UrlHelper urlHelper = new UrlHelper(context);
    
        context.RouteData.Values["month"] = date.Month;
        context.RouteData.Values["year"] = date.Year;
    
        return MvcHtmlString.Create(
                  urlHelper.Action(
                     (string)context.RouteData.Values["action"]));
    }
    

    我只是删除了 monthyear来自 context.RouteData.Values 的条目.我干脆更换了monthyear请求上下文中的条目。如果从上下文中删除它们(就像我一开始所做的那样),它们将无法用于在此之后调用的助手方法。

    这种方法使我的扩展方法按测试 1 中描述的场景工作(请参阅问题)。

    最后

    仔细重读 Sanderson S., Freeman A. - 专业 ASP.NET MVC 3 框架(第 3 版)我至少找到了所有这些东西的解释:

    第 2 部分 ASP.NET MVC 详细

    第 11 章 URL、路由和区域

    生成传出 URL

    在第 节了解段变量重用 :

    The routing system will reuse values only for segment variables that occur earlier in the URL pattern than any parameters that are supplied to the Html.ActionLink method.



    至于我的 month-year段在 controller 之后立即满足我确实为 month 指定了值和 year ,所有尾随段( actionuser )都不会重用。就我没有在匿名对象中指定它们而言,它们似乎无法用于该路线。所以,route1 不能匹配。

    书中甚至有警告:

    The best way to deal with this behavior is to prevent it from happening. We strongly recommend that you do not rely on this behavior, and that you supply values for all of the segment variables in a URL pattern. Relying on this behavior will not only make your code harder to read, but you end up making assumptions about the order in which your users make requests, which is something that will ultimately bite you as your application enters maintenance.



    好吧,它咬了我)))

    值得失去 100 rep 来记住(我什至会在这里再重复一遍)规则:路由系统将仅重用在 URL 模式中出现的比提供的任何参数更早的段变量的值。

    关于c# - 生成传出 url 时选择了意外的路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13081884/

    有关c# - 生成传出 url 时选择了意外的路由的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    3. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

      为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

    4. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

      在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

    5. ruby-on-rails - rails : save file from URL and save it to Amazon S3 - 2

      从给定URL下载文件并立即将其上传到AmazonS3的更直接的方法是什么(+将有关文件的一些信息保存到数据库中,例如名称、大小等)?现在,我既不使用Paperclip,也不使用Carrierwave。谢谢 最佳答案 简单明了:require'open-uri'require's3'amazon=S3::Service.new(access_key_id:'KEY',secret_access_key:'KEY')bucket=amazon.buckets.find('image_storage')url='http://www.ex

    6. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

      我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

    7. ruby-on-rails - Ruby url 到 html 链接转换 - 2

      我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.

    8. c# - 如何在 ruby​​ 中调用 C# dll? - 2

      如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

    9. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

      我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

    10. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    随机推荐