3

Detail(int id)对 a采取行动ProductController,我可以通过

/Product/Detail/32

但如果我这样做

Product/Detail

id我也没有通过访问同一个控制器。如何使参数成为必需,否则返回 404(根本不执行控制器操作,例如不匹配路由)

public ActionResult Detail(int id) {
    // some fancy code that get the product by id
    return View()
}

路线:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Application", action = "Index", id = UrlParameter.Optional } defaults);

我知道如果(未找到产品)返回HttpNotFound()并且适用于大多数情况,我可以做到这一点,但我想知道是否有一种方法可以在不传递参数的情况下甚至无法达到控制器操作

编辑:

/ <-- homepage
/Product/List <-- List of products
/Product/Detail <-- return 404
/Product/Detail/10 <-- Product Details id 10

现在,我想知道是否有任何方法可以支持这种“简单”场景。控制器上的操作是:

ApplicationController{
    public ActionResult Index() {}
}

ProductController {
    public ActionResult List(){}
    public ActionResult Detail(int id){}
}

当前路由只是默认的:

routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Application", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

添加建议的路由无法按预期工作,因为要么返回 a 404on/Product/Detail但也返回404on /Product/Listand /

或(其他建议)

ActionResult Detail(int id)在没有在请求中发送参数的情况下调用,这是这个问题的目的,以了解是否可以在/Product/Detail没有 id 参数的情况下不匹配 url。

4

2 回答 2

2

更改路由

只需为其提供路由并将id默认值删除为可选参数:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index" } // defaults
);

但这些默认值永远不会被使用,所以:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}" // URL with parameters
);

这是需要定义的实际路线。

你的controllerand actionare 永远不会根据需要从 URL 中省略,id并且是 URL 定义中的最后一个,这意味着第一对也必须在那里。

我不确定这是否正是您所需要的,但是根据您问题的当前状态,这应该可以为您解决问题。但是如果你需要你的 id 来获得一些预定义的值,你可以在你的路由定义中给它一个不同的值:

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = 1 } // defaults
);

这使得所有三个都可以从 URL 中省略,并且它们都将具有特定的值。

id定义格式的路由约束

您还可以使用路由约束来告诉路由 URL 参数的外观。由于您id似乎必须是数字,这也是一种可能性

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index" }, // defaults
    new { id = "\d+" } // constraints
);

编辑后

您的问题实际上有两种解决方案。

  1. 正确路由以定义id某些路由所需
  2. 动作方法选择器过滤器以声明方式标记它们需要某些参数的动作

解决方案 1:路由

这一个定义了几个路由定义,但硬编码需要id参数的操作:

routes.MapRoute(
    "RequiresId",
    "{controller}/{action}/{id}", // URL with parameters
    null,
    new { action = "Detail" }
);
routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}" // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { action = "(?!Detail).+" } // any action except "Detail"
);

第一条路线定义了所有具有操作方法的控制器Detail都需要id参数。这很简单,只要所有具有这些操作的控制器都具有相同的要求(可能是您的情况)。但是如果这不是真的,它会变得更加复杂,因为您必须为每个控制器提供约束。

解决方案 2:操作方法选择器过滤器

此解决方案仅需要带有可选的默认路由id。自定义操作方法选择器过滤器(鲜为人知且很少使用)将帮助您编写如下代码:

[RequiresRouteValues("id, name")]
public ActionResult Detail(int id, string name)
{
    ...
}

你会把它放在那些需要它的方法上。如果该特定参数不存在,控制器动作调用程序将无法找到合适的方法,因此返回 404。

我已经在我的博客上详细讨论过这个问题。它还包括过滤器的代码,如下所示:

/// <summary>
/// Represents an attribute that is used to restrict action method selection based on route values.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class RequiresRouteValuesAttribute : ActionMethodSelectorAttribute
{
    #region Properties
 
    /// <summary>
    /// Gets required route value names.
    /// </summary>
    public ReadOnlyCollection<string> Names { get; private set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether to include form fields in the check.
    /// </summary>
    /// <value><c>true</c> if form fields should be included; otherwise, <c>false</c>.</value>
    public bool IncludeFormFields { get; set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether to include query variables in the check.
    /// </summary>
    /// <value>
    ///     <c>true</c> if query variables should be included; otherwise, <c>false</c>.
    /// </value>
    public bool IncludeQueryVariables { get; set; }
 
    #endregion
 
    #region Constructors
 
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiresRouteValuesAttribute"/> class.
    /// </summary>
    private RequiresRouteValuesAttribute()
    {
        this.IncludeFormFields = true;
        this.IncludeQueryVariables = true;
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiresRouteValuesAttribute"/> class.
    /// </summary>
    /// <param name="commaSeparatedNames">Comma separated required route values names.</param>
    public RequiresRouteValuesAttribute(string commaSeparatedNames)
        : this((commaSeparatedNames ?? string.Empty).Split(','))
    {
        // does nothing
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiresRouteValuesAttribute"/> class.
    /// </summary>
    /// <param name="names">Required route value names.</param>
    public RequiresRouteValuesAttribute(IEnumerable<string> names)
        : this()
    {
        if (names == null || names.Count().Equals(0))
        {
            throw new ArgumentNullException("names");
        }
 
        // store names
        this.Names = new ReadOnlyCollection<string>(names.Select(val => val.Trim()).ToList());
    }
 
    #endregion
 
    #region ActionMethodSelectorAttribute implementation
 
    /// <summary>
    /// Determines whether the action method selection is valid for the specified controller context.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="methodInfo">Information about the action method.</param>
    /// <returns>
    /// true if the action method selection is valid for the specified controller context; otherwise, false.
    /// </returns>
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
 
        // always include route values
        HashSet<string> uniques = new HashSet<string>(controllerContext.RouteData.Values.Keys);
 
        // include form fields if required
        if (this.IncludeFormFields)
        {
            uniques.UnionWith(controllerContext.HttpContext.Request.Form.AllKeys);
        }
 
        // include query string variables if required
        if (this.IncludeQueryVariables)
        {
            uniques.UnionWith(controllerContext.HttpContext.Request.QueryString.AllKeys);
        }
 
        // determine whether all route values are present
        return this.Names.All(val => uniques.Contains(val));
    }
 
    #endregion
}

第一个在具有多个控制器和与之相关的不同约束的应用程序中使事情变得复杂。第二个是优雅的,适用于简单和复杂的场景。

我当然会选择解决方案 2。但在这种情况下,把我当作一个有偏见的开发人员。

于 2012-12-11T12:58:55.943 回答
0

不要将 Id 作为可选

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Application", action = "Index"} );

如果您想保持 id 可选,那么您可以为产品详细信息定义不同的路线

routes.MapRoute("ProductDetails", "product/details/{id}");

这需要一个 Id 并保持原来的默认路由不变。

于 2012-12-11T12:59:27.973 回答