Phương pháp hành động mơ hồ ASP.NET MVC


135

Tôi có hai phương pháp hành động mâu thuẫn. Về cơ bản, tôi muốn có thể có cùng một chế độ xem bằng hai tuyến đường khác nhau, bằng ID của một mặt hàng hoặc theo tên của mặt hàng đó và cha mẹ của nó (các mặt hàng có thể có cùng tên giữa các cha mẹ khác nhau). Một thuật ngữ tìm kiếm có thể được sử dụng để lọc danh sách.

Ví dụ...

Items/{action}/ParentName/ItemName
Items/{action}/1234-4321-1234-4321

Đây là phương thức hành động của tôi (cũng có Removephương thức hành động) ...

// Method #1
public ActionResult Assign(string parentName, string itemName) { 
    // Logic to retrieve item's ID here...
    string itemId = ...;
    return RedirectToAction("Assign", "Items", new { itemId });
}

// Method #2
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... }

Và đây là các tuyến đường ...

routes.MapRoute("AssignRemove",
                "Items/{action}/{itemId}",
                new { controller = "Items" }
                );

routes.MapRoute("AssignRemovePretty",
                "Items/{action}/{parentName}/{itemName}",
                new { controller = "Items" }
                );

Tôi hiểu tại sao xảy ra lỗi, vì pagetham số có thể là null, nhưng tôi không thể tìm ra cách tốt nhất để giải quyết nó. Là thiết kế của tôi kém để bắt đầu? Tôi đã nghĩ về việc mở rộng Method #1chữ ký của mình để bao gồm các tham số tìm kiếm và chuyển logic Method #2ra một phương thức riêng mà cả hai sẽ gọi, nhưng tôi không tin rằng điều đó thực sự sẽ giải quyết sự mơ hồ.

Mọi sự trợ giúp sẽ rất được trân trọng.


Giải pháp thực tế (dựa trên câu trả lời của Levi)

Tôi đã thêm lớp sau ...

public class RequireRouteValuesAttribute : ActionMethodSelectorAttribute {
    public RequireRouteValuesAttribute(string[] valueNames) {
        ValueNames = valueNames;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
        bool contains = false;
        foreach (var value in ValueNames) {
            contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value);
            if (!contains) break;
        }
        return contains;
    }

    public string[] ValueNames { get; private set; }
}

Và sau đó trang trí các phương thức hành động ...

[RequireRouteValues(new[] { "parentName", "itemName" })]
public ActionResult Assign(string parentName, string itemName) { ... }

[RequireRouteValues(new[] { "itemId" })]
public ActionResult Assign(string itemId) { ... }

3
Cảm ơn đã đăng lên thực hiện thực tế. Nó chắc chắn giúp những người có vấn đề tương tự. Như tôi đã có ngày hôm nay. :-P
Paulo Santos

4
Kinh ngạc! Đề xuất thay đổi nhỏ: (imo thực sự hữu ích) 1) chuỗi params [] valueNames để khai báo thuộc tính ngắn gọn hơn và (ưu tiên) 2) thay thế phần thân phương thức IsValidForRequest bằngreturn ValueNames.All(v => controllerContext.RequestContext.RouteData.Values.ContainsKey(v));
Benjamin Podszun

2
Tôi đã có cùng một vấn đề tham số chuỗi truy vấn. Nếu bạn cần những tham số được xem xét cho yêu cầu, hãy trao đổi contains = ...phần đó cho một cái gì đó như thế này:contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value) || controllerContext.RequestContext.HttpContext.Request.Params.AllKeys.Contains(value);
patridge

3
Lưu ý cảnh báo về điều đó: các tham số bắt buộc phải được gửi chính xác như được đặt tên. Nếu tham số phương thức hành động của bạn là một loại phức tạp được điền bằng cách chuyển các thuộc tính của nó theo tên (và để MVC xoa bóp chúng thành loại phức tạp), hệ thống này sẽ thất bại vì tên không nằm trong các khóa chuỗi truy vấn. Ví dụ: điều này sẽ không hoạt động : ActionResult DoSomething(Person p), nơi Personcó các thuộc tính đơn giản khác nhau Namevà các yêu cầu đối với nó được thực hiện với tên thuộc tính trực tiếp (ví dụ /dosomething/?name=joe+someone&other=properties:).
patridge

4
Nếu bạn đang sử dụng MVC4 trở đi, bạn nên sử dụng controllerContext.HttpContext.Request[value] != nullthay vì controllerContext.RequestContext.RouteData.Values.ContainsKey(value); nhưng dù sao cũng là một tác phẩm hay
Kevin Farrugia

Câu trả lời:


180

MVC không hỗ trợ nạp chồng phương thức chỉ dựa trên chữ ký, vì vậy điều này sẽ thất bại:

public ActionResult MyMethod(int someInt) { /* ... */ }
public ActionResult MyMethod(string someString) { /* ... */ }

Tuy nhiên, nó không hỗ trợ nạp chồng phương thức dựa trên thuộc tính:

[RequireRequestValue("someInt")]
public ActionResult MyMethod(int someInt) { /* ... */ }

[RequireRequestValue("someString")]
public ActionResult MyMethod(string someString) { /* ... */ }

public class RequireRequestValueAttribute : ActionMethodSelectorAttribute {
    public RequireRequestValueAttribute(string valueName) {
        ValueName = valueName;
    }
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
        return (controllerContext.HttpContext.Request[ValueName] != null);
    }
    public string ValueName { get; private set; }
}

Trong ví dụ trên, thuộc tính chỉ đơn giản nói "phương thức này khớp nếu khóa xxx có mặt trong yêu cầu." Bạn cũng có thể lọc theo thông tin có trong tuyến đường (controlContext.RequestContext) nếu điều đó phù hợp hơn với mục đích của bạn.


Điều này cuối cùng chỉ là những gì tôi cần. Như bạn đề xuất, tôi cần sử dụng controlContext.RequestContext.
Jonathan Freeland

4
Đẹp! Tôi chưa thấy thuộc tính RequireRequestValue. Đó là một điều tốt để biết.
CoderDennis

1
chúng ta có thể sử dụng valueprovider để lấy các giá trị từ một số nguồn như: controlContext.Controll.ValueProvider.GetValue (value);
Jone Polvora

Tôi đã đi sau ...RouteData.Valuesthay thế, nhưng "công trình" này. Có hay không một mô hình tốt được mở để tranh luận. :)
bambams

1
Tôi đã bị từ chối chỉnh sửa trước đó vì vậy tôi sẽ bình luận: [AttributionUsage (AttributionTarget.All, AllowMult Môn = true)]
Mzn

7

Các thông số trong tuyến đường của bạn {roleId}, {applicationName}{roleName}không phù hợp với tên tham số trong phương pháp hành động của bạn. Tôi không biết điều đó có quan trọng không, nhưng nó khiến cho việc tìm hiểu ý định của bạn là khó khăn hơn.

ItemId của bạn có phù hợp với một mẫu có thể được khớp qua regex không? Nếu vậy, thì bạn có thể thêm một hạn chế vào tuyến đường của mình để chỉ các url phù hợp với mẫu được xác định là có chứa một itemId.

Nếu itemId của bạn chỉ chứa các chữ số, thì điều này sẽ hoạt động:

routes.MapRoute("AssignRemove",
                "Items/{action}/{itemId}",
                new { controller = "Items" },
                new { itemId = "\d+" }
                );

Chỉnh sửa: Bạn cũng có thể thêm một ràng buộc cho AssignRemovePrettytuyến đường sao cho cả hai {parentName}{itemName}được yêu cầu.

Chỉnh sửa 2: Ngoài ra, vì hành động đầu tiên của bạn chỉ chuyển hướng đến hành động thứ 2 của bạn, bạn có thể xóa một số sự mơ hồ bằng cách đổi tên hành động đầu tiên.

// Method #1
public ActionResult AssignRemovePretty(string parentName, string itemName) { 
    // Logic to retrieve item's ID here...
    string itemId = ...;
    return RedirectToAction("Assign", itemId);
}

// Method #2
public ActionResult Assign(string itemId, string searchTerm, int? page) { ... }

Sau đó chỉ định tên Hành động trong các tuyến của bạn để buộc phương thức thích hợp được gọi:

routes.MapRoute("AssignRemove",
                "Items/Assign/{itemId}",
                new { controller = "Items", action = "Assign" },
                new { itemId = "\d+" }
                );

routes.MapRoute("AssignRemovePretty",
                "Items/Assign/{parentName}/{itemName}",
                new { controller = "Items", action = "AssignRemovePretty" },
                new { parentName = "\w+", itemName = "\w+" }
                );

1
Xin lỗi Dennis, các thông số thực sự phù hợp. Tôi đã sửa câu hỏi. Tôi sẽ thử kiềm chế regex và lấy lại cho bạn. Cảm ơn!
Jonathan Freeland

Chỉnh sửa thứ hai của bạn đã giúp tôi ra ngoài, nhưng cuối cùng, đó là đề nghị của Levi đã niêm phong thỏa thuận. Cảm ơn một lần nữa!
Jonathan Freeland


3

Gần đây, tôi đã có cơ hội cải thiện câu trả lời của @ Levi để hỗ trợ một loạt các kịch bản mà tôi phải giải quyết, chẳng hạn như: hỗ trợ nhiều tham số, khớp với bất kỳ trong số chúng (thay vì tất cả chúng) và thậm chí không khớp với bất kỳ kịch bản nào.

Đây là thuộc tính tôi đang sử dụng bây giờ:

/// <summary>
/// Flags an Action Method valid for any incoming request only if all, any or none of the given HTTP parameter(s) are set,
/// enabling the use of multiple Action Methods with the same name (and different signatures) within the same MVC Controller.
/// </summary>
public class RequireParameterAttribute : ActionMethodSelectorAttribute
{
    public RequireParameterAttribute(string parameterName) : this(new[] { parameterName })
    {
    }

    public RequireParameterAttribute(params string[] parameterNames)
    {
        IncludeGET = true;
        IncludePOST = true;
        IncludeCookies = false;
        Mode = MatchMode.All;
    }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        switch (Mode)
        {
            case MatchMode.All:
            default:
                return (
                    (IncludeGET && ParameterNames.All(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    || (IncludePOST && ParameterNames.All(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    || (IncludeCookies && ParameterNames.All(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
            case MatchMode.Any:
                return (
                    (IncludeGET && ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    || (IncludePOST && ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    || (IncludeCookies && ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
            case MatchMode.None:
                return (
                    (!IncludeGET || !ParameterNames.Any(p => controllerContext.HttpContext.Request.QueryString.AllKeys.Contains(p)))
                    && (!IncludePOST || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Form.AllKeys.Contains(p)))
                    && (!IncludeCookies || !ParameterNames.Any(p => controllerContext.HttpContext.Request.Cookies.AllKeys.Contains(p)))
                    );
        }
    }

    public string[] ParameterNames { get; private set; }

    /// <summary>
    /// Set it to TRUE to include GET (QueryStirng) parameters, FALSE to exclude them:
    /// default is TRUE.
    /// </summary>
    public bool IncludeGET { get; set; }

    /// <summary>
    /// Set it to TRUE to include POST (Form) parameters, FALSE to exclude them:
    /// default is TRUE.
    /// </summary>
    public bool IncludePOST { get; set; }

    /// <summary>
    /// Set it to TRUE to include parameters from Cookies, FALSE to exclude them:
    /// default is FALSE.
    /// </summary>
    public bool IncludeCookies { get; set; }

    /// <summary>
    /// Use MatchMode.All to invalidate the method unless all the given parameters are set (default).
    /// Use MatchMode.Any to invalidate the method unless any of the given parameters is set.
    /// Use MatchMode.None to invalidate the method unless none of the given parameters is set.
    /// </summary>
    public MatchMode Mode { get; set; }

    public enum MatchMode : int
    {
        All,
        Any,
        None
    }
}

Để biết thêm thông tin và cách thực hiện các mẫu hãy xem bài đăng trên blog này mà tôi đã viết về chủ đề này.


Cảm ơn, cải thiện tuyệt vời! Nhưng ParameterNames không được đặt trong ctor
nvirth

0
routes.MapRoute("AssignRemove",
                "Items/{parentName}/{itemName}",
                new { controller = "Items", action = "Assign" }
                );

xem xét sử dụng thư viện thử nghiệm MVC Contribs để kiểm tra tuyến đường của bạn

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.