Nancy navigation to URL without trailing slash? - url

We are using Nancy framework for our application which is self-hosted in console application.
The problem appears when loading the URL without trailing slash.
Let's say we are hosting the page in
http://host.com:8081/module/
It then serves us am html page which has resources with a relative path like this:
content/scripts.js
Everything works well when you enter a url such as
// Generates a resource url 'http://host.com:8081/module/content/scripts.js'
// which is good
http://host.com:8081/module/
But when we leave out a trailing slash, the resource url is
// Generates a resource url 'http://host.com:8081/content/scripts.js'
// which is bad
http://host.com:8081/module
Is there any way to make redirection to trailing slash version? Or at least detect if trailing slash exists or not.
Thanks!

This feels a bit hacky but it works:
Get["/module/"] = o =>
{
if (!Context.Request.Url.Path.EndsWith("/"))
return Response.AsRedirect("/module/" + Context.Request.Url.Query, RedirectResponse.RedirectType.Permanent);
return View["module"];
};
The Request accessible from Context lets you see whether the path has the trailing slash or not and redirect to a 'slashed' version.
I wrapped this up as an extension method (which works for my very simple use case):
public static class NancyModuleExtensions
{
public static void NewGetRouteForceTrailingSlash(this NancyModule module, string routeName)
{
var routeUrl = string.Concat("/", routeName, "/");
module.Get[routeUrl] = o =>
{
if (!module.Context.Request.Url.Path.EndsWith("/"))
return module.Response.AsRedirect(routeUrl + module.Request.Url.Query, RedirectResponse.RedirectType.Permanent);
return module.View[routeName];
};
}
}
To use in a module:
// returns view "module" to client at "/module/" location
// for either "/module/" or "/module" requests
this.NewGetRouteForceTrailingSlash("module");
This is worth reading though before going with a solution such as this

Related

Owin/NancyFx Trailing Slash on Root Path

I have an Owin/NancyFx single-page application using AngularJs and UI Router.
Its hosted in IIS7 and for the most part everything is working. However there is one annoying issue with the root path that I can't seem to solve.
I would like a trailing slash on the root path, something like:
http://myserver.internaldomain.com/myapp/
This way when UI Router goes to handle the hashbang routing, all urls will look like:
http://myserver.internaldomain.com/myapp/#/mySpaRoute
However, I can't seem to get a trailing slash to append, so instead my URL looks like:
http://myserver.internaldomain.com/myapp#/mySpaRoute
I have tried to create an Owin middleware the looks at the URL and redirects if there's a missing / at the end. This works for all routes that are handled by the WebApi but not NancyFx. That seems reasonable since NancyFx takes over routing early to handle rendering its views.
Next I tried a NancyFx BeforeRequest pipeline lambda to do the same thing, interrogate the URL and append a / as needed. This however resulted in a redirect loop. The request would come in to the pipeline as: http://example.com/app, and then redirect to: http://example.com/app/, however at the next pipeline execution, the trailing / would be stripped and the pipeline handler would redirect again -- this is where the loop occured.
So I guess simply, how do I make NancyFx add a trailing / to the end of my routes?
Update:
Went to lunch, talked to the duck a bit, updated all the assemblies, then decided that its just the root get path that I really need to append the / to make hashbang routing look decent:
public class HomeModule : NancyModule
{
// note this works fine when running from localhost, but when running
// as an application in IIS, a redirect loop occurs
public HomeModule()
{
Get["/"] = _ =>
{
var requestUri = new Uri(Request.Url);
if (!requestUri.AbsoluteUri.EndsWith("/"))
{
var targetUri = requestUri.ToString() + "/";
return Response.AsRedirect(targetUri);
}
const string view = "views/home.cshtml";
var model = new { Title = Constants.ApplicationTitle };
return View[view, model];
}
}
}
Annnnnnd Redirect loop.
Ultimately this appears to have been caused by the Uri class. The Uri class does a very good job of removing trailing slashes in many cases. This means that I was, essentially, fixing any "malformed" urls by creating a new Uri out of them. Then I was breaking these nice Uri's by appending a / to them. On redirect the newly cast Uri would remove my extraneous /, then fail the if statement and the process would begin again, hence by redirect loop.
To fix the issue, I instead used the System.Web.HttpContextBase property provided in the owin environment context and checked the Request.Url property which seems to be the original requested Url with little or no post-processing.
These changes were made in my EnforceTrailingSlashMiddleware that I had written earlier. Here is the invoke method:
public override async Task Invoke(IOwinContext context)
{
var httpContext = context.Environment["System.Web.HttpContextBase"] as System.Web.HttpContextBase;
if (httpContext != null && httpContext.Request != null && httpContext.Request.Url != null)
{
var path = httpContext.Request.Url.ToString();
/*
formatter is a class ("SlashFormatter") with two methods:
"ShouldAppendSlash" which takes a path string and returns a boolean
(whether or not a slash should be appended)
"AppendSlash" which takes a string, safely appends a slash and
then returns the modified string.
*/
if (formatter.ShouldAppendSlash(path))
{
var url = formatter.AppendSlash(path);
context.Response.Redirect(url);
}
}
await Next.Invoke(context);
}

HttpContext AbsolutePath goes to wrong URL for aliased pages - C#

On my website each page has links that are created in the codebehind, where the links are the current URL with one query parameter changed. To do this, I've been using this method (this specific example is for the pagination):
var queryValues = HttpUtility.ParseQueryString(HttpContext.Current.Request.QueryString.ToString());
queryValues.Set("page", num);
string url = HttpContext.Current.Request.Url.AbsolutePath;
string updatedQueryString = "?" + queryValues.ToString();
string newUrl = url + updatedQueryString;
return newUrl;
This worked on my local version fine. However, when I created each page in Ektron and added a manual alias, the URLs generated still went to the file location in the solution. For example, my original page was /WebAssets/Templates/EventListView.aspx. I created the page in Ektron as /Alumni/Events/List. I can go to /Alumni/Events/List, but then when I click on a page button the page that loads is /WebAssets/Templates/EventListView.aspx?page=2 instead of /Alumni/Events/List/?page=2
I found one solution:
var rawUrl = HttpContext.Current.Request.RawUrl;
var url = rawUrl.Split('?')[0];
string newUrl = url + updatedQueryString;
Use the QuickLink property of the primary contentblock for /Alumni/Events/List, this will be the alias use want to use for your page links or for redirects to the same page. This is probably ContentData.QuickLink if you're already loading the ContentData at some point in the code.
Notes:
Aliasing may remove your "page" querystring parameter by default, to resolve this issue, edit your alias in the Workarea to have a "Query String Action" of "Append".
Make sure you preprend a "/" to the QuickLink value (if it's not absolute and not prepended already) if using it on the frontend, otherwise your links will bring you to something like /Alumni/Events/List/Alumni/Events/List?page=2, which is no good.

ASP.net MVC SPA routing

I'm planning to build a SPA with asp.net MVC4 but I'm not quite sure how I have to Setup my Project because of the Routing. Most SPA's work with hashrouting like this mypage/#/News/today for instance.
What would happen if the browses directly to mypage/News/today if I haven't specified a Controller named News with an action today?
The App should handle both types of Routing, how can I achieve this?
Do I have to build my App in a classic way, like Adding several Controllers with appropriate Actions and views and also build a clientside MVC structure with knockout, jquery etc?
You'll have to let all routes to "pages" fall through to let your SPA handle them (including essentially fake 404s if it's not to a real page in your SPA), but at the same time, need to make sure that you get the correct responses for API calls and/or file requests.
Below is the setup I have (I am using Vue as the js framework but that doesn't matter much for this, and not at all for the server-side piece).
First, add this to your Startup.cs, in addition to your default route setup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.Use(async (context, next) =>
{
await next();
var path = context.Request.Path.Value;
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !Path.HasExtension(path) && !path.StartsWith("/api"))
{
context.Request.Path = "/Home/SpaRedirect"; // attempts to redirect to the URL within the SPA
context.Response.StatusCode = (int)HttpStatusCode.OK; // Make sure we update the status code, otherwise it returns 404
await next();
}
});
...
}
So the newly added SpaRedirect to HomeController looks like this, and just stores the requested URL in ViewData...
public IActionResult SpaRedirect()
{
ViewData["RequestUrl"] = HttpContext.Request.Path;
return View("Index");
}
Then in Index.cshtml, just capture that requested url in session storage so we have it available on the client-side:
<script src="~/dist/main.js" asp-append-version="true">
sessionStorage.setItem("redirectAttempt", #ViewData["RequestUrl"]);
</script>
Then in your boot script file (the entry-point for your SPA), add something like:
let redirectAttemptUrl = sessionStorage.getItem("redirectAttempt");
if (redirectAttemptUrl) {
router.push(redirectAttemptUrl);
sessionStorage.removeItem("redirectAttempt");
}
Which just checks for the presence of a requested url, and then the SPA's router attempts to navigate to it (in the example above it is a vue-router), then removes it from storage.
So this way, if a user attempts to navigate directly to a URL by entering it in the url bar (or via a bookmark) the app will load and take them to the right place, IF it exists... which takes us to the last piece...
Finally, you have to handle "404s" within your SPA, which is done by adding a catch-all route to your routes defs that takes user to a 404 component page you set up, which for Vue would look like this:
// adding an explicit 404 path as well for programmatically handling when something is not found within the app, i.e. return this.$router.push('/404')
{ path: '/404', component: NotFound, name: '404', alias: '*' }, // remove alias to not show the actual url that resulted in our little 404 here
{ path: '*', redirect: '/404' }, // this is the catch-all path to take us to our 404 page
Caveat: I'm no expert so could be missing something, would love additional comments on how to improve this. One thing that this doesn't handle is if the user is ALREADY in the SPA and for some reason edits the URL directly to navigate to someplace else, it would still trigger a server call and full reload, which ideally wouldn't be the case, but this is a pretty trivial issue I'd say.

asp.net mvc output cache, problem in trailing "/" character

Iam using to cache my asp.net mvc pages
[OutputCache(VaryByParam = "something", Duration = 1800, Location = System.Web.UI.OutputCacheLocation.Server)]
public ActionResult MyPage()
{
}
problem is cached page is not picked up when some one adds a trailing slash character("/")
eg:
//cache entry 1
mySite/MyPage
//cache entry2
mySite/MyPage/
To my knowledge it's not good to have one page accessible via many different URL's. So you could make you application to redirect when it encounters trailing slash.

What's the best way to get the current URL in Spring MVC?

I'd like to create URLs based on the URL used by the client for the active request. Is there anything smarter than taking the current HttpServletRequest object and it's getParameter...() methods to rebuilt the complete URL including (and only) it's GET parameters.
Clarification: If possible I want to resign from using a HttpServletRequest object.
Well there are two methods to access this data easier, but the interface doesn't offer the possibility to get the whole URL with one call. You have to build it manually:
public static String makeUrl(HttpServletRequest request)
{
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
I don't know about a way to do this with any Spring MVC facilities.
If you want to access the current Request without passing it everywhere you will have to add a listener in the web.xml:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
And then use this to get the request bound to the current Thread:
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
Instead of using RequestContextHolder directly, you can also use ServletUriComponentsBuilder and its static methods:
ServletUriComponentsBuilder.fromCurrentContextPath()
ServletUriComponentsBuilder.fromCurrentServletMapping()
ServletUriComponentsBuilder.fromCurrentRequestUri()
ServletUriComponentsBuilder.fromCurrentRequest()
They use RequestContextHolder under the hood, but provide additional flexibility to build new URLs using the capabilities of UriComponentsBuilder.
Example:
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentRequestUri();
builder.scheme("https");
builder.replaceQueryParam("someBoolean", false);
URI newUri = builder.build().toUri();
Java's URI Class can help you out of this:
public static String getCurrentUrl(HttpServletRequest request){
URL url = new URL(request.getRequestURL().toString());
String host = url.getHost();
String userInfo = url.getUserInfo();
String scheme = url.getProtocol();
String port = url.getPort();
String path = request.getAttribute("javax.servlet.forward.request_uri");
String query = request.getAttribute("javax.servlet.forward.query_string");
URI uri = new URI(scheme,userInfo,host,port,path,query,null)
return uri.toString();
}
in jsp file:
request.getAttribute("javax.servlet.forward.request_uri")
You can also add a UriComponentsBuilder to the method signature of your controller method. Spring will inject an instance of the builder created from the current request.
#GetMapping
public ResponseEntity<MyResponse> doSomething(UriComponentsBuilder uriComponentsBuilder) {
URI someNewUriBasedOnCurrentRequest = uriComponentsBuilder
.replacePath(null)
.replaceQuery(null)
.pathSegment("some", "new", "path")
.build().toUri();
//...
}
Using the builder you can directly start creating URIs based on the current request e.g. modify path segments.
See also UriComponentsBuilderMethodArgumentResolver
If you need the URL till hostname and not the path use Apache's Common Lib StringUtil, and from URL extract the substring till third indexOf /.
public static String getURL(HttpServletRequest request){
String fullURL = request.getRequestURL().toString();
return fullURL.substring(0,StringUtils.ordinalIndexOf(fullURL, "/", 3));
}
Example: If fullURL is https://example.com/path/after/url/ then
Output will be https://example.com
System.out.println(((HttpServletRequest)request).getRequestURI());
I used it. hope it's useful.

Resources