Created
September 14, 2010 20:35
-
-
Save olsonjeffery/579729 to your computer and use it in GitHub Desktop.
Example of a RequireQueryStringValuesAttribute for ASP.NET MVC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.Mvc; | |
using System.Reflection; | |
// Examples.cs | |
// based on the RequireRouteValuesAttribute found on SO at: | |
// http://stackoverflow.com/questions/1045316/asp-net-mvc-ambiguous-action-methods | |
// | |
// (c) 2010, Jeff Olson, http://olsonjeffery.github.com | |
namespace Example | |
{ | |
// This should give you an idea of how the RequireQueryStringValuesAttribute | |
// can be used. I, personally, don't really feel like this is a Good | |
// Thing to do in general, as the usual arguments about | |
// pervasive method overloading apply here, more than ever. | |
// My specific use case that lead me in this direction was | |
// the need to provide a local implementation of an existant | |
// web service which engaged in major endpoint-overloading | |
// abuse. So this was a technical solution to that problem | |
// (the alternative to which was a huge nested if-structure | |
// or switch-statement hell) | |
// With the above in mind, I feel like this is a Here Be Dragons | |
// sort of thing if applied generally as a pattern to create | |
// your services (although I still don't think that's a good | |
// reason for the ASP.NET team to not have included a similar | |
// option in their core distro) | |
public class ExampleController : Controller | |
{ | |
public ExampleController() { } | |
// default endpoint with no query parameters | |
[RequireQueryStringValues(new string[0])] | |
public ContentResult Foo() { | |
return Content("<blah />", "application/xml", Encoding.Default); | |
} | |
// invoked for calls to /example/foo?bar=whatever | |
[RequireQueryStringValues(new[] {"bar")] | |
public ContentResult Foo(string bar) { | |
return Content("<blah />", "application/xml", Encoding.Default); | |
} | |
// invoked for calls to /example/foo?bar=whatever&baz=blah | |
[RequireQueryStringValues(new[] {"bar", "baz")] | |
public ContentResult Foo(string bar, string baz) { | |
return Content("<blah />", "application/xml", Encoding.Default); | |
} | |
// A BRIEF DISTRACTION ON THE TOPIC OF TYPE/ARITY OVERLOADING IN C# | |
// | |
// Do note that, in the example shown above, there are no type-related test cases, | |
// so we can't demonstrate a way to overcome arity-related overloading problems for | |
// method (that is, conflicts related to the number of parameters for a method). | |
// for example: | |
public class AnotherExampleController : Controller | |
{ | |
public AnotherExampleController() { } | |
// WRONG! won't compile -- sadly: jerks do design web services with endpoints like this | |
[RequireQueryStringValues(new[] {"bar")] | |
public ContentResult Foo(string bar) { return Content("", "application/xml", Decoding.Default); } | |
[RequireQueryStringValues(new[] {"somethingElse")] | |
public ContentResult Foo(string somethingElse) { return Content("", "application/xml", Decoding.Default); } | |
// This is kosher, on the other hand... | |
// (in this case, the second overload has an integer as its sole parameter .. thus, | |
// the fiendish compiler is defeated, if you have parameters in the query that you can | |
// safely cast to an int, of course) | |
[RequireQueryStringValues(new[] {"bar")] | |
public ContentResult Foo(string bar) { return Content("", "application/xml", Decoding.Default); } | |
[RequireQueryStringValues(new[] {"somethingElse")] | |
public ContentResult Foo(int somethingElse) { return Content("", "application/xml", Decoding.Default); } | |
} | |
// It is important to know that the attribute is going to help sort out | |
// issues related whether the provided parameters to the attribute's ctor are | |
// present in the query string in their entire majesty and not whether they | |
// map cleanly to the method signature.. this gives you, gentle reader, the | |
// opportunity for some naughtiness if you run up against a wall with | |
// overload signatures | |
public class YetAnotherExampleController : Controller | |
{ | |
public YetAnotherExampleController() { } | |
// invoked for calls to /yetanotherexample/foo?bar=whatever | |
[RequireQueryStringValues(new[] {"bar")] | |
public ContentResult Foo(string bar) { return Content("", "application/xml", Decoding.Default); } | |
// invoked for calls to /yetanotherexample/foo?somethingElse=whatever | |
// | |
// Ho! What trickery is this mine eyes spies atop yon' method signature? | |
// since the "placeholder" param doesn't appear in the attribute, it'll be NULL, | |
// yet you can still have these two overloaded uses of a single endpoint cleanly | |
// abstracted into two methods, instead of together in one method within | |
// an if/switch/etc .. anywys, evil. yeah. | |
[RequireQueryStringValues(new[] {"somethingElse")] | |
public ContentResult Foo(string somethingElse, string placeholder) | |
{ | |
return Content("we're tricky!", | |
"application/xml", Decoding.Default); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.Mvc; | |
using System.Reflection; | |
// RequireQueryStringValuesAttribute.cs | |
// based on the RequireRouteValuesAttribute found on SO at: | |
// http://stackoverflow.com/questions/1045316/asp-net-mvc-ambiguous-action-methods | |
// | |
// (c) 2010, Jeff Olson, http://olsonjeffery.github.com | |
// This is an example for an Attribute class that can be decorated on methods | |
// in an ASP.NET MVC Controller class. | |
// | |
// By default, ASP.NET MVC has no logic set up to disambiguate requests for | |
// actions associated with multiple implementation methods in a controller | |
// (think method overloading). So, if you had a FooController and two implementations | |
// of the Bar action, you would need a way to disambiguate them. You do this by | |
// decorating the action(s) with attributes that inherit from | |
// System.Web.Mvc.ActionMethodSelectorAttribute . One example of this in the default | |
// distro is the AcceptVerbsAttribute, which allows you to indicate that specific | |
// actions should only be invoked when called with certain HTTP Methods. You can also | |
// use to it "hint" the MVC engine on how it should deal with two overloads of the | |
// same action (one for GET, one for PUT, etc). | |
// See Examples.cs for an idea of how this attribute can be used in your ASP.NET MVC | |
// controllers | |
namespace Example | |
{ | |
public class RequireQueryStringValuesAttribute : ActionMethodSelectorAttribute | |
{ | |
public RequireQueryStringValuesAttribute(string[] valueNames) | |
{ | |
ValueNames = valueNames; | |
} | |
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) | |
{ | |
bool contains = false; | |
var queryKeys = controllerContext.RequestContext.HttpContext.Request.QueryString.AllKeys; | |
if (ValueNames.Length > 0) | |
{ | |
foreach (var key in queryKeys) | |
{ | |
contains = ValueNames.Contains(key); | |
if (!contains) break; | |
} | |
} | |
else | |
{ | |
contains = queryKeys.Count() == 0; | |
} | |
var paramNumMatch = queryKeys.Count() == ValueNames.Count(); | |
return contains && paramNumMatch; | |
} | |
public string[] ValueNames { get; private set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment