Skip to content

Instantly share code, notes, and snippets.

@olsonjeffery
Created September 14, 2010 20:35
Show Gist options
  • Save olsonjeffery/579729 to your computer and use it in GitHub Desktop.
Save olsonjeffery/579729 to your computer and use it in GitHub Desktop.
Example of a RequireQueryStringValuesAttribute for ASP.NET MVC
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);
}
}
}
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