Does NancyFX supports ASP.NET MVC like 'Catch All' route? I need one, that basically match every URL. This is very handy for building up Single Page applications.
Is that possible?
Tested in Nancy version 0.23.2
Get[#"/(.*)"] did not work for me as a catch-all route. The routes "/", "/foo/bar", and longer routes would not catch. It seems like there's no getting around having to define a Get["/"] route for the root URL. Nothing else seems to catch it (tried Get["{uri*}"]). Here's how I ended up defining my routes (keep in mind I'm doing this for an Angular application):
Get["/views/{uri*}"] = _ => { return "A partial view..."; };
Get["/"] =
Get["/{uri*}"] = _ =>
{
var uri = (string)_.uri;// The captured route
// If you're using OWIN, you can also get a reference to the captured route with:
var environment = this.Context.GetOwinEnvironment();// GetOwinEnvironment is in the 'Nancy.Owin' namespace
var requestPath = (string)environment["owin.RequestPath"];
return View["views/defaultLayout.html"];
};
It's important to understand Pattern Scoring. The route patterns are weighted, if two routes match the same url segment, the higher score wins. The catch-all pattern is weighted 0 and even though the /views/{uri*} route pattern is also a catch-all, it starts with a literal, which is weighted 10000, so it will win out on all routes that start with /views.
Here's more info on Accessing Owin's Environment Variables. Note that the captured uri variable and requestPath will be slightly different. The requestPath will start with a / where as the uri variable will not. Also, if the matched route pattern is Get["/"], uri will be null and requestPath will be "/".
The Views route will return a partial html file, based on the url path, and all other routes will return the default Layout page that will bootstrap the SPA.
Yes, using Regex
Get[#"/(.*)"] = parameters => {
return View["viewname", parameters];
};
But you don't really need it for building a Single Page Application with NancyFX - you can just use Get and Post with all your routing logic and still have a single page app.
An updated answer for whom #synhershko 's solution does not work. Try:
Get[#"^(.*)$"] = parameters => {
return "hi";
};
This will capture all paths except for the index page. I am not sure if this will work in the context of Angular, but this worked for me when trying to hack together a simple server with just one handler.
Answer provided by #synhershko does not work for me. It does not handle /users/2 or any other route containing more segements.
Below code works on my machine ;) :
public class IndexModule : NancyModule
{
dynamic IndexPage() { return View["Index"]; }
public IndexModule()
{
Get["/"] = _ => { return IndexPage(); };
Get["/(.*)"] = _ => { return IndexPage(); };
Get["/(.*)/(.*)"] = _ => { return IndexPage(); };
Get["/(.*)/(.*)/(.*)"] = _ => { return IndexPage(); };
}
}
My solution is not perfect, cause it does not match everything. I repeated as many '/(.*)' as in my longest Angular route.
Related
So, currently, I have a routing component:
<Route path="/lists/:query" component={Lists} />
I get a call like:
http://localhost:4567/lists/page=17&city_codes=2567
In my Lists component, I handle this query in this way:
componentDidMount() {
const query = match.params.query;
const cleanQueryString = query.replace(/[|;$%#"<>()+,]/g, '');
// break up the string using '&' and '=' into an object
const properties = this.queryURL(cleanQueryString);
const cleanQueryObj = _.pick(Object.assign({}, ...properties), [
'page',
'city_codes',
'min_monthly_fee',
'max_monthly_fee',
'order_by',
]);
// update the query object based on component state
this.setState({ query: cleanQueryObj }, () => {
cleanQueryObj.page && this.updateIndex(parseInt(cleanQueryObj.page, 10));
});
// call axios request and update redux
dispatch(handleLists(cleanQueryObj));
// update browser url
this.props.history.push(cleanQueryObj);
Now, I see a lot of major sites using ?q= before the query and I'm wondering what I'm missing or what could be improved?
Thoughts?
While what you are doing is technically valid, it is a bit non-standard. The way you use the router :query param and the way it is formatted, reaaaaly looks like an actual location.search parameter format, and not a path parameter.
A more standard way to do it, would be with the following URL:
http://localhost:4567/lists?page=17&city_codes=2567
And code as follow:
// In your routes, just a simple route with no path params
<Route path="/lists" component={Lists} />
// In your component
import queryString from 'query-string'
[...]
componentDidMount() {
// Use location object from react-router
const { search } = this.props.location
// Parse it using a npm dedicated module
const { page, city_codes } = queryString.parse(search)
// Now you can use those params
]);
Edit: and now an actual answer to the question:
?q=blah is usually used in a search context, with q parameter being a string used to search something. There can be other parameters following for example ?q=blah&ext=txt.
It is hence different from your :query path param, which is encoded to contain multiple parameters, while q here is a single ready-to-use parameter.
I've read this post, but I don't like having browserHistory.push('/some/path') in one of my components while I have <Route path="/some/path" component={SomePage} /> in my router file since the path is duplicated. What if I want to change that path to /another/path? Now I need to remember to update it in the router file and also my component.
Is there a better way around this? I was thinking that I could have "/some/path" and all my other paths defined in some constants file that gets imported and referenced in my router and my component. Example:
paths.js
var Paths = {
myPath: "/some/path",
...
}
module.exports = Paths
router.jsx
var Paths = require('constants/paths');
...
<Route path={Paths.myPath} component={SomePage} />
component.jsx
var Paths = require('constants/paths');
...
browserhistory.push(Paths.myPath)
This seems like it could get a little messy when dealing with URL parameters like /some/path/:id, so I was hoping there might be a better way.
This is what I have done in the past for routing to make it simpler / more streamlined.
(as a side note i used lodash here so if you aren't you can use native functions to do basically the same thing. lodash just adds a bunch of nice features / functions that you dont need to go write yourself)
in my routes.jsx file you should create functions that convert any parameters into a url with a default path for this answer lets just make one for a profile route
export function pathToProfile(userName, params) {
return path(Paths.PROFILE, _.assign({userName}, params));
}
the path() function is just a simple helper utility function for generating a path.
path(url, params, urlMap) {
if(!url) {
console.error("URL is not defined for action: ", params);
}
if(!params)
return url;
params = _.merge({}, params);
if(urlMap) {
_.keys(urlMap).forEach((k) => {
params[urlMap[k]] = params[k];
delete params[k];
});
}
if(url.indexOf(":") !== -1) {
url.match(/:([0-9_a-z]+)/gi).forEach((match)=>{
var key = match.replace(":", "");
url = url.replace(match, params[key]);
params = _.omit(params, key);
});
}
if(_.keys(params).length > 0) {
url = url + "?" + this.paramsToString(params);
}
return url;
}
now looking at the constants file
Paths {
PROFILE: '/user/:username',
}
Finally usage.
I wouldn't recommend broswerHistory.push() when you can have an onClick handler. Yes it works and will redirect, but is it the best thing to use? react-router also has a Link that should be used wherever possible. you get some nice additional features one for example would be an active route for whatever page you're on.
browserHistory.push() is a good way to handle redirecting when you do things like an auth login redirect or if you are responding to data from a request and conditionally taking them to a page.
<Route path={Paths.PROFILE} component={Profile} />
<Link to={pathToProfile(this.props.user.username, {myParams: 'here'})></Link>
what that would be translated into if you wanted to see the url from that exact link it would be /user/someUsername?myParams=here
I'm actually getting the params like that:
http://localhost:9080/app?{%22address%22:%22rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep%22}
with this route
routes: {
"app":"app"
}
As you can see it's really ugly and I would like to have this simple:
http://localhost:9080/app/rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep
Or something like that
http://localhost:9080/app/#/rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep
You see the idea...
But when I try app/*address or app/:address it's not working , meaning I can't catch my params or even the page.
So basically /app is server, how to get the param without the ugly way I use ?
Edit: Here is a pretty good example I found in a tutorial:
routes: {
"posts/:id": "getPost",
// Example
"download/*path": "downloadFile",
// Download
":route/:action": "loadView",
// Load Route/Action View
},
So here the question would be how to get something like that
http://example.com/app/#/myparam
The good rule is to separate your routes. In your situation I'd do something like this:
routes: {
"p/:param_name": "getSomething"
};
...
getSomething: function(param_name) {
...
}
But if really don't need to use specific area in the router you should add your target route to the end of your routes.
routes: {
"p/:param_name": "getSomething",
...
":param": "getSomething"
};
So I turned on network capture in my web browser to have a look at my ajax calls coming out of AngularJS. For every call made, there seems to be two results:
URL|Protocol|Method|Result|Type|Received|Taken
/p/jobs/03512dc8-6f25-49ea-bdff-0028ac2023cb|HTTP|GET|301|text/html|408 B|< 1 ms
/p/jobs/03512dc8-6f25-49ea-bdff-0028ac2023cb|HTTP|GET|200|application/json|0.79 KB|15 ms
It looks like it's attempting to request HTML first, getting a 301 and then requesting the JSON. How can I eliminate the request for HTML? I'm using $resource to do this but I'd also like to see an example for $http.
Also, the receiving framework is NancyFX for .NET. Perhaps there's a header I need to specify to make sure it always returns JSON? I've tried the "Accept" header but it seems to make no difference. It's as if Nancy is always trying to return a View first before switching to JSON.
Javascript code (translated slightly from TypeScript):
$resource(jobUrl, {}, { get: {method: "GET", isArray: false }});
Nancy code:
public class JobService : NancyModule
{
public static readonly string Prefix = "/p/jobs";
WebLogger logger;
public JobService(WebLogger logger)
: base(Prefix)
{
this.logger = logger;
Get[""] = _ => GetJobs();
Get["/{id}"] = _ => GetJob(_.id);
Get["/{id}/nodes"] = _ => GetNodes(_.id);
Get["/{id}/faults"] = _ => GetFaults(_.id);
}
Job GetJob(string id)
{
lock (logger)
{
if (logger.JobGuid != id)
{
Context.Response.StatusCode = HttpStatusCode.NotFound;
return null;
}
return MakeJob();
}
}
Some part of your code will be helpful, but lets try - you have put character / at the end of $resource definition. Angular has asked server for content for directory, server response with header 301 - redirect to file as Angular expects some data response than directory listing.
I couldn't think of a reasonable title for this post.
The problem I'm having is that I have an SQL database attached to my MVC website. Within the website I have a news/blog system that I have worked on which stores the data in the database and pulls the information on request.
The problem is the routing. I currently have it set up to pull the information about the routing of each individual page as this:
var newsr = new NewsResources();
foreach (var item in newsr.GetAllNewsItems())
{
item.Title = item.Title.Replace(' ', '-').ToLower();
routes.MapRoute(item.Title, "News/" + item.Title,
new {controller = "News", action = "Post", id = item.ID});}
When I add a new news item however, this doesn't go into the routing system which is proving to be a right pain. I've had a google search for dynamically adding url routing but I can't seem to find a solution.
What I want to know is, is it possible to add a page to the routing system via the page controller once I have saved the post into the database?
I do not think you need to Do a For Each loop across all your POst item and add routes for that. You may do it like this
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("SingleItem", "News/{title}",
new { controller = "Items", action = "PostFromTitle" });
// and the generic route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
This will route ../News/SomeTitle request to your PostFromTitle action method of NewsController. Read the title there and get the post from there.
public ActionResult PostFromTitle(string title)
{
var post=repo.GetPostFromTitle(title);
return View("~/Views/News/Post.cshtml",post);
}
It may be better to an action that takes a title as a parameter.
routes.MapRoute("NewsItem", "News/{title}",
new { controller = "News", action = "ShowNews" }