conditional url routing with asp.net 4 webforms - asp.net

I want to server mobile and web version of pages on my site without any redirection so that if visitor browse them with PC they would see web version and vice versa.
I can do some media queries and reduce stuff on the page but that is not ideal.
I know i can do it with asp.net mvc, but, project is already half finished and I don't have time to rewrite it.
I thought about using conditional routing but as routes register on application start it didn't look possible. Is there anyway using conditional roting?
I am open to suggestions too.

This isn't an MVC solution, but I know you can do this with the IIS7 rewrite module.
<rewrite>
<rules>
<rule name="Mobile" stopProcessing="true">
<match url="^(.*)$" />
<conditions logicalGrouping="MatchAny">
<add input="{USER_AGENT}" pattern="iPhone" />
</conditions>
<action type="Rewrite" url="Mobile/{R:1}" />
</rule>
</rules>
</rewrite>
You could certainly also do this with a custom conditional route in MVC.
public class MobileConstraint : IRouteConstraint
{
public MobileConstraint() { }
public bool Match(HttpContextBase httpContext, Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
// add null checking etc
return httpContext.Request.UserAgent.Contains("iPhone")
}
}
context.MapRoute(
"mobile",
"Mobile/{controller}/{action}/{id}",
new { action = "Index", controller = "Home", id = UrlParameter.Optional },
new { controller = new MobileConstraint() }
);

Related

MVC: Serve file with http header

I am trying to follow this guide and store files in a sub-folder.
However, the OneSignal guide asks to serve these files with additional HTTP header Service-Worker-Allowed: /. How do I do that in Asp.Net MVC?
You can use in your controller's action :
this.Response.Headers.Add("Service-Worker-Allowed","/");
Hope this help :-)
Edit :
A better way to do this is to create an action filter to automatically add this header :
public class WorkerAllowedAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Headers.Add("Service-Worker-Allowed", "/");
base.OnActionExecuted(filterContext);
}
}
And you can put it on your action (or your controller) :
public partial class HomeController
{
[WorkerAllowed]
public ActionResult Index()
{
return View();
}
}
No matter what I tried, I could not get the scope allowed even though I could see it in the response header. I figured out a solution based on this article: https://weblog.west-wind.com/posts/2015/Nov/13/Serving-URLs-with-File-Extensions-in-an-ASPNET-MVC-Application
Basically, we are making the service worker js file appear that it 'lives' in the root directory to avoid scope issues. You can leave your service worker js file in whatever directory you want (probably /Scripts). Then you set up an IIS rewrite rule in web.config (or in IIS) that re-routes any request that includes the service worker file name to an action (be sure you specify the exact path to the action). The action then returns the file as type javascript/application.
<system.webServer>
<rewrite>
<rules>
<rule name="Service worker fix">
<match url="pwa-sw.js"/>
<action type="Rewrite" url="/home/serviceworker"/>
</rule>
</rules>
</rewrite>
</system.webServer>
Then the controller
[AllowAnonymous]
[Route("serviceworker")]
public ActionResult serviceworker()
{
return File("/Scripts/pwa-sw.js", "application/javascript");
}

Preventing CSRF with the same-site cookie attribute

I was surfing the web and found article Preventing CSRF with the same-site cookie attribute.
As on link maintain We need to add Set-Cookie header.
Set-Cookie: key=value; HttpOnly; SameSite=strict
Now My Question is, I want to set this in my ASP.NET site in all Cookies and Authentication Cookie.
I tried to set this using header from IIS but someone says this is wrong way implementation.
I have also tried below.
HttpCookie newAuthenticationCookie = new HttpCookie(FormsAuthentication.FormsCookieName
, FormsAuthentication.Encrypt(newAuthenticationTicket))
{
HttpOnly = true
};
newAuthenticationCookie.Values.Add("SameSite", "strict");
But it seems like not helping me.
Please suggest me a better way to do this.
Thanks.
After Deep review on HttpCookie Source it's confirm that we cannot do this with the code, as there is no way to add extra attribute on Cookie and class is marked as sealed.
But still anyhow I manage solution by modifying web.config as below.
<rewrite>
<outboundRules>
<rule name="Add SameSite" preCondition="No SameSite">
<match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
<action type="Rewrite" value="{R:0}; SameSite=strict" />
<conditions>
</conditions>
</rule>
<preConditions>
<preCondition name="No SameSite">
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=strict" negate="true" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
This add SameSite=strict on each Set-Cookie.
You can also set this in code when creating a cookie:
var httpCookie = new HttpCookie("mycookie", "myvalue");
httpCookie.Path += ";SameSite=Strict";
Response.SetCookie(httpCookie);
This will give you the following header:
Set-Cookie:mycookie=myvalue; path=/;SameSite=Strict
bit of a hack until it's pushed in to the framework.
Just adding my answer to systematize all the info found here and in other places.
1. To secure custom cookies under 4.7.2 and later
var c = new HttpCookie("test");
c.SameSite = SameSiteMode.Lax;
2. To secure Forms authentication cookie
In web.config
<authentication mode="Forms">
<forms ..... cookieSameSite="Lax" />
</authentication>
3. To secure ASP.NET Session cookie
In Global.asax
void Session_Start(Object sender, EventArgs e)
{
Response.Cookies["ASP.NET_SessionId"].SameSite = SameSiteMode.Lax;
//while we're at it lets also make it secure
if (Request.IsSecureConnection)
Response.Cookies["ASP.NET_SessionId"].Secure = true;
}
Fun fact: even if you set <httpCookies requireSSL="true" /> the ASP.NET session cookie will still be non-secure for some reason.
3(a). UPDATE 01.2020: .NET 4.8 Session cookie is now "SameSite" by default
Installing the latest Windows Update will make your session cookies Lax by default. You can control it here:
<sessionState cookieSameSite="Lax" /> <!-- in system.web -->
4. <httpCookies samesite=xxx> does not exist?
Adding <httpCookies sameSite="Strict" /> like suggested in the comment above in web.config didn't work, I was getting the error.
Unrecognized attribute 'samesite'
Even though I'm targeting 4.7.2. Tested on multiple project and multiple machines, also VS2019 does not show this in intellisense and MS docs do not mention it anywhere.
.NET 4.7.2 has now built-in support for SameSite property. The HttpCookie has now a property called SameSite. See more info here from Microsoft.
No need anymore to hack this through the config file.
In order to have SameSite defined to ASP.NET_SessionId cookie I had to set the web.config under system.web section:
<sessionState cookieSameSite="Lax" />
Because in this day and age we use owin to fix the same silly webapi cookie bug...
public class CookieSameSiteMiddleware : OwinMiddleware
{
public CookieSameSiteMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
var url = context.Request.Path.Value.ToLowerInvariant();
if (url.Contains("/api/mylogin"))
{
context.Response.OnSendingHeaders(x =>
{
var scv = context.Response.Headers.FirstOrDefault(h => h.Key == "Set-Cookie");
if (!scv.Equals(default(KeyValuePair<string, string[]>)))
{
//context.Response.Headers.Remove("Set-Cookie");
context.Response.Headers.Set("Set-Cookie", scv.Value[0] + "; SameSite=strict");
}
}, null);
}
await this.Next.Invoke(context);
}
}
Make sure the middle-ware is registered before .UseWebApi()
Pre 4.7.2 you can just append the string to the cookie path.
FormsAuthentication.SetAuthCookie(username, false, FormsAuthentication.FormsCookiePath + "; SameSite=Lax");
https://www.nuget.org/packages/Microsoft.Owin.Security.Cookies/4.1.0 now supports SameSite.
That is very good news because the other solutions here doesn't work that brilliantly:
Implementing OwinMiddleware: Works great, except for performance. This might be something specific for our environment but that solution was about 10% of our CPU.
<outboundRules>: Probably possible to get working. But all solutions I've seen so far and we tested, including the one in this thread, had some issues when multiple cookies where set in the same response.

Url routing is not working?

web.config for url rewrite is
<rewrite>
<rules>
<rule name="Mobile Portal">
<match url="^(code)(/)?([^']*)" />
<action type="Redirect" url="Code.aspx?id={R:3}" />
</rule>
</rules>
</rewrite>
input: www.abc.com/Code.aspx?id=123abcdef
Required output: www.abc.com/code/123abcdef
current output: http://www.abc.com/Code.aspx?id=.aspx
The valid page url is www.abc.com/code/123abc . I need "123abc" . When I am accessing the page by www.abc.com/code/123abc, the url is converted to "http://www.abc.com/Code.aspx?id=.aspx ".I am using IIS 7. So how can I fix this issue?
Thanks a lot in advance.
You can use url routing instead of url rewriting. for this purpose go to Global.asax file and write RegisterRoutes method like this :
void RegisterRoutes(RouteCollection routes)
{
routes.MapPageRoute("my_abc_page",
"code/{id}",
"~/Code.aspx");
}
with above command, any request like www.abc.com/code/123abcdef interpret to www.abc.com/Code.aspx?id=123abcdef and you can access to this id in code of your page :
theId = Page.RouteData.Values["id"] as string;

Setting up redirect in web.config file

I'm trying to redirect some unfriendly URLs with more descriptive ones. These URLs end in .aspx?cid=3916 with the last digits being different for each category name page. I want it to instead redirect to Category/CategoryName/3916. I tried this in the web.config file:
<location path="Category.aspx?cid=3916">
<system.webServer>
<httpRedirect enabled="true" destination="http://www.example.com/Category/CategoryName/3916" httpResponseStatus="Permanent" />
</system.webServer>
</location>
but since it didn't end with just the extension, it didn't work. Is there an easy way to get this to work? I'm using IIS 7.5.
Open web.config in the directory where the old pages reside
Then add code for the old location path and new destination as follows:
<configuration>
<location path="services.htm">
<system.webServer>
<httpRedirect enabled="true" destination="http://example.com/services" httpResponseStatus="Permanent" />
</system.webServer>
</location>
<location path="products.htm">
<system.webServer>
<httpRedirect enabled="true" destination="http://example.com/products" httpResponseStatus="Permanent" />
</system.webServer>
</location>
</configuration>
You may add as many location paths as necessary.
You probably want to look at something like URL Rewrite to rewrite URLs to more user friendly ones rather than using a simple httpRedirect. You could then make a rule like this:
<system.webServer>
<rewrite>
<rules>
<rule name="Rewrite to Category">
<match url="^Category/([_0-9a-z-]+)/([_0-9a-z-]+)" />
<action type="Rewrite" url="category.aspx?cid={R:2}" />
</rule>
</rules>
</rewrite>
</system.webServer>
In case that you need to add the http redirect in many sites, you could use it as a c# console program:
class Program
{
static int Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("Please enter an argument: for example insert-redirect ./web.config http://stackoverflow.com");
return 1;
}
if (args.Length == 3)
{
if (args[0].ToLower() == "-insert-redirect")
{
var path = args[1];
var value = args[2];
if (InsertRedirect(path, value))
Console.WriteLine("Redirect added.");
return 0;
}
}
Console.WriteLine("Wrong parameters.");
return 1;
}
static bool InsertRedirect(string path, string value)
{
try
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
// This should find the appSettings node (should be only one):
XmlNode nodeAppSettings = doc.SelectSingleNode("//system.webServer");
var existNode = nodeAppSettings.SelectSingleNode("httpRedirect");
if (existNode != null)
return false;
// Create new <add> node
XmlNode nodeNewKey = doc.CreateElement("httpRedirect");
XmlAttribute attributeEnable = doc.CreateAttribute("enabled");
XmlAttribute attributeDestination = doc.CreateAttribute("destination");
//XmlAttribute attributeResponseStatus = doc.CreateAttribute("httpResponseStatus");
// Assign values to both - the key and the value attributes:
attributeEnable.Value = "true";
attributeDestination.Value = value;
//attributeResponseStatus.Value = "Permanent";
// Add both attributes to the newly created node:
nodeNewKey.Attributes.Append(attributeEnable);
nodeNewKey.Attributes.Append(attributeDestination);
//nodeNewKey.Attributes.Append(attributeResponseStatus);
// Add the node under the
nodeAppSettings.AppendChild(nodeNewKey);
doc.Save(path);
return true;
}
catch (Exception e)
{
Console.WriteLine($"Exception adding redirect: {e.Message}");
return false;
}
}
}

Using ASP.NET routing to serve static files

Can ASP.Net routing (not MVC) be used to serve static files?
Say I want to route
http://domain.tld/static/picture.jpg
to
http://domain.tld/a/b/c/picture.jpg
and I want to do it dynamically in the sense that the rewritten URL is computed on the fly. I cannot set up a static route once and for all.
Anyway, I can create a route like this:
routes.Add(
"StaticRoute", new Route("static/{file}", new FileRouteHandler())
);
In the FileRouteHandler.ProcessRequest method I can rewrite the path from /static/picture.jpg to /a/b/c/picture.jpg. I then want to create a handler for static files. ASP.NET uses the StaticFileHandler for this purpose. Unfortunately, this class is internal. I have tried to create the handler using reflection and it actually works:
Assembly assembly = Assembly.GetAssembly(typeof(IHttpHandler));
Type staticFileHandlerType = assembly.GetType("System.Web.StaticFileHandler");
ConstructorInfo constructorInfo = staticFileHandlerType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
return (IHttpHandler) constructorInfo.Invoke(null);
But using internal types doesn't seem to be the proper solution. Another option is to implement my own StaticFileHandler, but doing so properly (supporting HTTP stuff like ranges and etags) is non-trivial.
How should I approach routing of static files in ASP.NET?
Why not use IIS to do this? You could create a redirect rule to point any requests from the first route to the second one before it even gets to your application. Because of this, it would be a quicker method for redirecting requests.
Assuming you have IIS7+, you would do something like...
<rule name="Redirect Static Images" stopProcessing="true">
<match url="^static/?(.*)$" />
<action type="Redirect" url="/a/b/c/{R:1}" redirectType="Permanent" />
</rule>
Or, if you don't need to redirect, as suggested by #ni5ni6:
<rule name="Rewrite Static Images" stopProcessing="true">
<match url="^static/?(.*)$" />
<action type="Rewrite" url="/a/b/c/{R:1}" />
</rule>
Edit 2015-06-17 for #RyanDawkins:
And if you're wondering where the rewrite rule goes, here is a map of its location in the web.config file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<!-- rules go below -->
<rule name="Redirect Static Images" stopProcessing="true">
<match url="^static/?(.*)$" />
<action type="Redirect" url="/a/b/c/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
After digging through this problem for a few hours, I found that simply adding ignore rules will get your static files served.
In RegisterRoutes(RouteCollection routes), add the following ignore rules:
routes.IgnoreRoute("{file}.js");
routes.IgnoreRoute("{file}.html");
I've had a similar problem. I ended up using HttpContext.RewritePath:
public class MyApplication : HttpApplication
{
private readonly Regex r = new Regex("^/static/(.*)$", RegexOptions.IgnoreCase);
public override void Init()
{
BeginRequest += OnBeginRequest;
}
protected void OnBeginRequest(object sender, EventArgs e)
{
var match = r.Match(Request.Url.AbsolutePath);
if (match.Success)
{
var fileName = match.Groups[1].Value;
Context.RewritePath(string.Format("/a/b/c/{0}", fileName));
}
}
}
I came up with an alternative to using the internal StaticFileHandler. In the IRouteHandler I call HttpServerUtility.Transfer:
public class FileRouteHandler : IRouteHandler {
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
String fileName = (String) requestContext.RouteData.Values["file"];
// Contrived example of mapping.
String routedPath = String.Format("/a/b/c/{0}", fileName);
HttpContext.Current.Server.Transfer(routedPath);
return null; // Never reached.
}
}
This is a hack. The IRouteHandler is supposed to return an IHttpHandler and not abort and transfer the current request. However, it does actually achieve what I want.
Using the internal StaticFileHandler is also somewhat a hack since I need reflection to get access to it, but at least there is some documentation on StaticFileHandler on MSDN making it a slightly more "official" class. Unfortunately I don't think it is possible to reflect on internal classes in a partial trust environment.
I will stick to using StaticFileHandler as I don't think it will get removed from ASP.NET in the foreseeable future.
You need to add TransferRequestHandler for handling your static files.Please see following answer
https://stackoverflow.com/a/21724783/22858

Resources