Reputation: 305
I inherited a WCF project containing some badly constructed routes (which work) that I need to migrate into a new NancyFx project. I need to be able to define routes in my Nancy app that respond to the same GET requests. I cannot change the routes for the time being.
In the WCF project, a GET request to ...
http://localhost:12345/webapi/GetUsers?UserId=567&Language=en
matches this UriTemplate:
UriTemplate = "GetUsers?UserId={userId}&Language={language}
I was hoping this would be the equivalent in Nancy
Get["/GetUsers?UserId={userId}&Language={language}"] = p => { ... }
but the same GET request results in a 404.
Is there a way to structure my Nancy route to respond to this GET request? If not, any work-arounds?
I know this is horrible but its temporary until I can schedule time with our UI team to rewrite the front-end to call proper rest-full URLs.
Upvotes: 2
Views: 2282
Reputation: 19153
Following on from @TheCodeJunkie's answer, I got somewhat overexcited by his idea of extending the NancyContext
to provide route conditions that pre-check the Query
values to ensure the Request is valid. I have further extended my NancyContext
to provide the same pre-checks for the Form
values, Header
collection and even the Files
collection, like so:
public static bool HasQuery(this NancyContext context, params string[] values)
{
var query = context.Request.Query;
return values.All(value => query[value].HasValue);
}
public static bool HasForm(this NancyContext context, params string[] values)
{
var form = context.Request.Form;
return values.All(value => form[value].HasValue);
}
public static bool HasHeader(this NancyContext context, params string[] values)
{
var headers = context.Request.Headers;
return values.All(value => !headers[value].FirstOrDefault().IsEmpty());
}
public static bool HasFile(this NancyContext context, params string[] values)
{
var files = context.Request.Files;
return values.All(value => files.Any(file => file.Key == value));
}
This has allowed me to remove the ceremonial code that was validating the Query and Form values (when I remembered to do this, which wasn't every time as it turned out). So you get cleaner code that is easier to read and easier to remember to do the checks in the first place, for example:
Before
Post["/"]=_=>
{
var form = Request.Form;
if (
!form.username.HasValue ||
!form.password.HasValue ||
!form.email.HasValue ||
!form.claim.HasValue)
{
return HttpStatusCode.UnprocessableEntity;
}
//process form values with confidence
}
After
Post["/", ctx => ctx.HasForm("username", "password", "email", "claim")]=_=>
{
//process form values with confidence
};
Upvotes: 5
Reputation: 9616
John,
Your route would be /GetUsers
(how are you handling the /webapi
part by the way? Is that the base url of your application or are you setting up a module path?) and you would read the query-string using the Request.Query
member.
The Query property returns a DynamicDictionary
which enables you to access the values as either properties Request.Query.UserId
or as a dictionary Request.Query["UserId"]
You cannot make the query-string part of pattern that needs to be matched in order for the routes to be invoked. What you can do, if you really want to, is to use the route condition, which is the second parameter on the route declaration. This gives you control over a predicate which determines if the route is OK to be used or not. So you could do something like this
Get["/GetUsers", ctx => ctx.Request.Query.UserId.HasValue && ctx.Request.Query.Language.HasValue] = p {... }
You could then refactor it all to an extension method, on NancyContext, that makes it a bit tidier
Get["/GetUsers", ctx => ctx.HasQueryValues("UserId", "Language")] = p {... }
and make the extension something like
public static bool HasQueryValues(this NancyContext context, params string[] values)
{
return values.All(x => context.Request.Query[x].HasValue);
}
Hope this helps!
Upvotes: 8