John L
John L

Reputation: 305

Nancy Routes resulting in 404

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

Answers (2)

biofractal
biofractal

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

TheCodeJunkie
TheCodeJunkie

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

Related Questions