ASP.NET MVC 모호한 작업 방법


135

충돌하는 두 가지 조치 방법이 있습니다. 기본적으로 항목 ID 또는 항목 이름과 상위 항목에 따라 두 가지 경로를 사용하여 동일한 뷰를 얻을 수 있기를 원합니다 (항목은 다른 상위에서 동일한 이름을 가질 수 있음). 검색어를 사용하여 목록을 필터링 할 수 있습니다.

예를 들어 ...

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

여기 내 행동 방법이 있습니다 ( Remove활동 방법 도 있습니다) ...

// 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) { ... }

그리고 여기 경로가 있습니다 ...

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

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

page매개 변수가 null 일 수 있기 때문에 오류가 발생하는 이유를 이해하지만 오류 를 해결하는 가장 좋은 방법을 알 수 없습니다. 내 디자인은 처음부터 좋지 않습니까? Method #1검색 매개 변수를 포함하도록 서명을 확장 하고 논리를 Method #2둘 다 호출하는 개인 메소드로 옮기는 방법에 대해 생각했지만 실제로 모호성을 해결할 것이라고는 생각하지 않습니다.

도움을 주시면 감사하겠습니다.


실제 솔루션 (Levi의 답변을 기반으로 함)

다음 클래스를 추가했습니다 ...

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; }
}

그런 다음 액션 방법을 장식했습니다 ...

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

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

3
실제 구현을 게시 해 주셔서 감사합니다. 그것은 비슷한 문제를 가진 사람들을 확실히 도와줍니다. 오늘처럼 :-P
Paulo Santos

4
놀랄 만한! 사소한 변경 제안 : (정말 유용한) 1) params string [] valueName은 속성 선언을보다 간결하게하고 (기본 설정) 2) IsValidForRequest 메서드 본문을 다음과 같이 return ValueNames.All(v => controllerContext.RequestContext.RouteData.Values.ContainsKey(v));
바꿉니다.

2
동일한 querystring 매개 변수 문제가있었습니다. 요구 사항에 대해 고려 된 매개 변수가 필요한 경우 contains = ...다음과 같이 섹션을 교체 하십시오.contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value) || controllerContext.RequestContext.HttpContext.Request.Params.AllKeys.Contains(value);
patridge

3
경고에 대한 참고 사항 : 필수 매개 변수는 이름 그대로 정확하게 전송해야합니다. action 메소드 매개 변수가 이름별로 특성을 전달하여 복합 유형으로 채워진 (및 MVC가이를 복합 유형으로 마사지하도록 허용 한 경우) 이름이 querystring 키에 없기 때문에이 시스템이 실패합니다. 예를 들어 다음과 같이 작동하지 않습니다 : ActionResult DoSomething(Person p). 여기서 Person와 같이 다양한 간단한 속성 Name이 있으며 속성 이름으로 직접 요청합니다 (예 :) /dosomething/?name=joe+someone&other=properties.
patridge

4
MVC4 이상을 사용 controllerContext.HttpContext.Request[value] != null하는 경우 대신 controllerContext.RequestContext.RouteData.Values.ContainsKey(value);을 사용해야 합니다 . 그럼에도 불구하고 좋은 작품입니다.
Kevin Farrugia

답변:


180

MVC는 서명만을 기반으로 한 메소드 오버로드를 지원하지 않으므로 실패합니다.

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

그러나 않는 속성에 따라 지원 방법 오버로드 :

[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; }
}

위의 예에서 속성은 "키 xxx 가 요청에있는 경우이 메소드가 일치합니다"라고 간단히 말합니다 . 경로에 포함 된 정보 (controllerContext.RequestContext)로 목적에 더 잘 맞는 경우 필터링 할 수도 있습니다.


이것은 내가 필요한 것입니다. 제안한대로 controllerContext.RequestContext를 사용해야했습니다.
Jonathan Freeland 2016 년

4
좋은! RequireRequestValue 속성을 아직 보지 못했습니다. 그것은 좋은 것입니다.
CoderDennis 2016 년

1
valueprovider를 사용하여 다음과 같은 여러 소스에서 값을 가져올 수 있습니다. controllerContext.Controller.ValueProvider.GetValue (value);
Jone Polvora

나는 ...RouteData.Values대신에 갔다 . 그러나 이것은 "작동한다". 좋은 패턴인지 아닌지는 논쟁의 여지가 있습니다. :)
bambams

1
이전 편집 내용이 거부되었으므로 [AttributeUsage (AttributeTargets.All, AllowMultiple = true)]
Mzn

7

당신의 루트의 매개 변수 {roleId}, {applicationName}그리고 {roleName}당신의 액션 메소드의 매개 변수 이름과 일치하지 않습니다. 그게 중요한지 모르겠지만, 당신의 의도가 무엇인지 파악하기가 더 어렵습니다.

itemId가 정규식을 통해 일치시킬 수있는 패턴을 준수합니까? 그렇다면 패턴과 일치하는 URL 만 itemId를 포함하는 것으로 식별되도록 경로에 구속 조건을 추가 할 수 있습니다.

itemId에 숫자 만 포함 된 경우 다음과 같이 작동합니다.

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

편집 : 당신은 또한에 제약 조건을 추가 할 수 있습니다 AssignRemovePretty모두 있도록 경로 {parentName}와이 {itemName}필요합니다.

편집 2 : 또한 첫 번째 작업은 두 번째 작업으로 리디렉션되므로 첫 번째 작업의 이름을 바꾸면 모호성을 제거 할 수 있습니다.

// 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) { ... }

그런 다음 라우트에 조치 이름을 지정하여 적절한 메소드를 강제로 호출하십시오.

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
죄송합니다, 파라미터가 실제로 일치합니다. 나는 문제를 해결했다. 정규식 제한을 시도하고 다시 연락 드리겠습니다. 감사!
Jonathan Freeland 2016 년

두 번째 편집은 도움이되었지만 궁극적으로 거래를 봉인 한 것은 Levi의 제안이었습니다. 다시 감사합니다!
Jonathan Freeland 2016 년


3

최근에는 @Levi의 답변을 개선하여 여러 매개 변수 지원, 모든 매개 변수 지원 (모두 대신) 및 심지어는 일치하지 않는 것과 같은 광범위한 시나리오를 지원했습니다.

내가 지금 사용하고있는 속성은 다음과 같습니다.

/// <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
    }
}

추가 정보 및 방법 구현 샘플 은이 주제에 대해 쓴 이 블로그 게시물 을 확인하십시오 .


고마워, 큰 개선! 그러나 ParameterNames는 ctor
nvirth에

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

MVC Contribs 테스트 라우트 라이브러리를 사용하여 라우트를 테스트하십시오

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.