ASP.NET MVC belirsiz eylem yöntemleri


135

Çakışan iki eylem yöntemim var. Temel olarak, bir öğenin kimliği veya öğenin adı ve ebeveyninin (öğeler farklı ebeveynler arasında aynı ada sahip olabilir) iki farklı yolu kullanarak aynı görünüme ulaşmak istiyorum. Listeye filtre uygulamak için bir arama terimi kullanılabilir.

Örneğin...

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

İşte benim eylem yöntemleri ( Removeeylem yöntemleri de vardır ) ...

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

Ve işte yollar ...

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

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

pageParametre boş olabileceğinden , hatanın neden oluştuğunu anlıyorum , ancak sorunu çözmek için en iyi yolu bulamıyorum. Tasarımım başlangıçta zayıf mı? Method #1Arama parametrelerini eklemek ve mantığı Method #2her ikisi de arayacakları özel bir yönteme taşımak için imzayı genişletmeyi düşündüm , ancak bunun belirsizliği çözeceğine inanmıyorum.

Herhangi bir yardım büyük mutluluk duyacağız.


Gerçek Çözüm (Levi'nin cevabına dayanarak)

Aşağıdaki sınıfı ekledim ...

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

Ve sonra eylem yöntemlerini dekore ettiniz ...

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

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

3
Gerçek uygulamayı gönderdiğiniz için teşekkür ederiz. Kesinlikle benzer sorunları olan insanlara yardımcı olur. Bugünkü gibi. :-P
Paulo Santos

4
İnanılmaz! Küçük değişiklik önerisi: (imo gerçekten kullanışlı) 1) nitelik bildirimini daha özlü hale getirmek için dize [] valueNames parametrelerini ve (tercih) 2) IsValidForRequest yöntem gövdesinireturn ValueNames.All(v => controllerContext.RequestContext.RouteData.Values.ContainsKey(v));
Benjamin Podszun

2
Ben aynı querystring parametre sorunu vardı. Gereksinim için dikkate alınan bu parametrelere ihtiyacınız varsa, aşağıdaki contains = ...gibi bir şey için bölümü değiştirin :contains = controllerContext.RequestContext.RouteData.Values.ContainsKey(value) || controllerContext.RequestContext.HttpContext.Request.Params.AllKeys.Contains(value);
patridge

3
Bununla ilgili uyarı notu: gerekli parametreler tam olarak adlandırıldığı şekilde gönderilmelidir. İşlem yöntemi parametreniz, özelliklerini ada göre geçirerek (ve MVC'nin karmaşık türe masaj yapmasına izin vererek) doldurulmuş karmaşık bir türse, ad sorgu dizesi anahtarlarında olmadığından bu sistem başarısız olur. Örneğin, bu işi olmaz: ActionResult DoSomething(Person p)nerede Personolduğu değişik basit gibi özellikler Nameve buna istekleri doğrudan özellik adları ile yapılır (örneğin /dosomething/?name=joe+someone&other=properties).
patridge

4
Eğer itibaren MVC4 kullanıyorsanız, kullanmak gerekir controllerContext.HttpContext.Request[value] != nullyerine controllerContext.RequestContext.RouteData.Values.ContainsKey(value); ama yine de güzel bir çalışma.
Kevin Farrugia

Yanıtlar:


180

MVC yalnızca imzaya dayalı yöntem aşırı yüklemesini desteklemediğinden, bu başarısız olur:

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

Ancak, does niteliğe dayalı olarak destek yöntemi aşırı yüklenmesini:

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

Yukarıdaki örnekte, öznitelik basitçe " istekte xxx anahtarının bulunması durumunda bu yöntem eşleşir" der . Ayrıca, amaçlarınıza daha uygunsa, rotadaki bilgilere (controllerContext.RequestContext) göre de filtreleyebilirsiniz.


Bu sadece ihtiyacım olan şey oldu. Önerdiğin gibi controllerContext.RequestContext kullanmam gerekiyordu.
Jonathan Freeland

4
Güzel! Henüz RequireRequestValue özniteliğini görmemiştim. Bunu bilmek iyi bir şey.
CoderDennis

1
valueeprovider'ı aşağıdaki gibi çeşitli kaynaklardan değerler almak için kullanabiliriz: controllerContext.Controller.ValueProvider.GetValue (value);
Jone Polvora

Bunun ...RouteData.Valuesyerine gittim , ama bu "çalışıyor". İyi bir kalıp olup olmadığı tartışmaya açıktır. :)
bambams

1
Önceki düzenlememi reddettim, bu yüzden sadece yorum yapacağım: [AttributeUsage (AttributeTargets.All, AllowMultiple = true)]
Mzn

7

Rotanıza parametreler {roleId}, {applicationName}ve {roleName}eylem yöntemleri parametre adları aynı değil. Bunun önemli olup olmadığını bilmiyorum, ama niyetinizin ne olduğunu anlamak zorlaşıyor.

İtemId'leriniz normal ifade ile eşleştirilebilecek bir desene uygun mu? Öyleyse, rotanıza yalnızca kalıpla eşleşen URL'lerin bir itemId içeriyor olarak tanımlanması için bir kısıtlama ekleyebilirsiniz.

İtemId yalnızca rakam içeriyorsa, bu işe yarar:

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

Düzenleme: AssignRemovePrettyRotaya bir sınırlama ekleyebilirsiniz, böylece hem {parentName}ve hem {itemName}de gereklidir.

Düzenleme 2: Ayrıca, ilk işleminiz yalnızca 2. işleminize yönlendirildiğinden, ilk işlemi yeniden adlandırarak bazı belirsizlikleri kaldırabilirsiniz.

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

Ardından, uygun yöntemi çağrılmaya zorlamak için rotalarınızdaki Eylem adlarını belirtin:

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
Üzgünüm Dennis, parametreler gerçekten uyuşuyor. Soruyu çözdüm. Normal ifade kısıtlamasını deneyeceğim ve size geri döneceğim. Teşekkürler!
Jonathan Freeland

İkinci düzenlemeniz bana yardımcı oldu, ancak nihayetinde Levi'nin anlaşmayı mühürleyen önerisi oldu. Tekrar teşekkürler!
Jonathan Freeland


3

Son zamanlarda, çoklu parametre desteği, bunlardan herhangi biriyle (hepsinin yerine) eşleşmesi ve hatta hiçbiriyle eşleşmemesi gibi @ Levi'nin ele almam gereken çok çeşitli senaryoları desteklemek için cevabını geliştirme şansını yakaladım.

İşte şimdi kullanıyorum özniteliği:

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

Daha fazla bilgi ve nasıl yapılır örnekleri için , bu konuda yazdığım bu blog gönderisine göz atın.


Teşekkürler, büyük gelişme! Ancak ParameterNames,
ctor'da

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

rotalarınızı test etmek için MVC Contribs test rotaları kütüphanesini kullanmayı düşünün

"Items/parentName/itemName".Route().ShouldMapTo<Items>(x => x.Assign("parentName", itemName));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.