I am working on a large web application which I have recently shelved tons of .aspx pages from the project.
To avoid page not found error, I added these entities in the xml which came around 300+ in count. I wrote a http module that checks the request url in the xml entities and if they are found, my module is going to redirect the request to respective new pages.
Everything works great, but my collection is getting iterated for all the requests, I mean for each and every .jpg, .css, .js, .ico, .pdf etc.
Is there any object or property in .net that can tell the type of request that user requested for like HttpContext.request.type. So that I can avoid checking the request for all unwanted file types.
Yet another approach is to use ASP.NET Routing (from .NET 3.5) to create routes that map each of the old pages onto a handler for the new page. ASP.NET routing makes it easy to have multiple Urls for a single ASPX page and you can in fact hide .ASPX completely from the end-user and have SEO friendly Urls for all your pages instead. If you map multiple URLs onto one page you'll want to put the canonical URL tag on the page.
Alternatively
If you want redirects you can register routes with a simple redirect route handler like this:-
routes.Add(new Route("sample.aspx", new RedirectRouteHandler("/home/newsample.aspx")));
And the RedirectRouteHandler might look something like this:-
/// <summary>
/// Redirect Route Handler
/// </summary>
public class RedirectRouteHandler : IRouteHandler
{
private string newUrl;
public RedirectRouteHandler(string newUrl)
{
this.newUrl = newUrl;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new RedirectHandler(newUrl);
}
}
/// <summary>
/// <para>Redirecting MVC handler</para>
/// </summary>
public class RedirectHandler : IHttpHandler
{
private string newUrl;
public RedirectHandler(string newUrl)
{
this.newUrl = newUrl;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext httpContext)
{
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.StatusCode = 301;
httpContext.Response.AppendHeader("Location", newUrl);
return;
}
}
You could use HttpContext.Current.Request.FilePath property which gets the virtual path of the current request.
For example, for the URL http://www.contoso.com/virdir/page.html/tail, the FilePath value is /virdir/page.html.
The second step is to get the extension itself. For example, you may do it using System.IO.Path.GetExtension method. For the /virdir/page.html path it'll return .html extension.
You could instead catch just the 404 errors to avoid intercepting every page request. Add an Application_Error method to your global.asax like below. This will allow you to also redirect to a special error page if the page isn't one you need to redirect according to your XML file.
protected void Application_Error(object sender, EventArgs e)
{
Log.Error("*** Application_Error ***");
Exception baseException = Server.GetLastError().GetBaseException();
HttpException httpException = baseException as HttpException;
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
Log.ErrorFormat("HTTPEXCEPTION: {0} : {1}", httpCode, HttpContext.Current.Request.RawUrl);
if (httpCode == 404)
{
...
Related
I need to create a single page that is going to be a part of an existing ASP.NET MVC website that is only open or accessible if came from allowed websites (Referrer). Our partners are planning
to load this page using an iFrame. This page allows any user to quickly enter the details and save into
our database.
example: www.mysecuredwebsite.com/publicpage
Since this is going to be public single page only and no logins, the only security is
only allow if the referrer is in the list of allowed sites.
Example:
www.abc.com
www.randomwebsite.com
www.amazingsite.com
Any request from any other sites will be redirected to an error page.
What is the best approach to this problem? I'm thinking of creating a custom attribute that will be used
to decorate the controller which then reads
a list of allowed sites from the app.config orany type of config.
Or maybe from the SQLDB since the site is using SQLDB? The list I think will be frequently change
depending on the growing number of clients. It should be easily configurable and does not require
redeployment.
Any thoughts? Thanks!
You can use two methods
Method 1: Use ActionFilterAttribute.
Method 2: Use the Application_BeginRequest method in the Global.asax file.
Note: It is better to put the allowed urls in the database so that it can be easily changed.
I will explain the first method
The following filter checks whether UrlReferrer is allowed. If it
is not allowed, it will be referred to the error page.
add UrlReferrerFilterAttribute
using System.Web.Mvc;
using System.Web.Routing;
namespace yournamespace
{
public class UrlReferrerFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string urlReferrer = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
urlReferrer = filterContext.HttpContext.Request.UrlReferrer.AbsoluteUri;
if(!db.UrlReferrerTable.Any(a => a.Url == urlReferrer))
{
var values = new RouteValueDictionary(new
{
action = "ErrorPage",
controller = "Home"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
base.OnActionExecuting(filterContext);
}
}
}
now use
[UrlReferrerFilter]
public ActionResult YourAction()
{
//..................
//..................
}
I have an old Controller class in my company which inherits ApiController.
One of the actions are a POST endpoint that receives data from browser.
Since I'm in the ApiController, I don't have the built-in property IsMobile to detect.
So, How do I Detect if Request is from Mobile in ApiController ?
Any alternatives to suggests ?
You might look at the User-Agent header, and look for hints of mobile client:
var userAgent = Request.Headers.UserAgent.ToString();
You might be able to look for certain strings like 'mobile' or 'mobi' (or search for multiple alternative keywords).
Here's a page that lists mobile client user agents:
https://developers.whatismybrowser.com/useragents/explore/hardware_type_specific/mobile/7
Update:
Here's a library that's already doing something similar with the user-agent header:
https://github.com/wangkanai/Detection
As suggested by the initial answers, the key is using UserAgent.
I found this answer which I wrapped up in an ActionFilter in case I would like to use it thou out multiple actions
/// <summary>
/// This action filter sets Request's properties with a "BrowserCapabilitiesFactory" object. <br/>
/// To get it, use <b>Request.Properties.TryGetValue("UserBrowser", out yourObjectToSet);</b>. <br/>
/// With that, you can check browser details of the request. Use this for <b>ApiController</b> actions. <br/>
/// If browser was not found or any exception occured in filter, then the value of the key will be set to null,
/// By doing that , ensures that the Properties will always have a key-value of UserBrowser
/// </summary>
public class GetUserBrowserActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
try
{
// ============================== Get User agent and parse it to a strongly type object ============================== //
var userAgent = HttpContext.Current.Request.UserAgent;
var userBrowser = new HttpBrowserCapabilities { Capabilities = new Hashtable { { string.Empty, userAgent } } };
var factory = new BrowserCapabilitiesFactory();
factory.ConfigureBrowserCapabilities(new NameValueCollection(), userBrowser);
actionContext.Request.Properties.Add("UserBrowser", userBrowser);
}
catch (Exception ex)
{
actionContext.Request.Properties.Add("UserBrowser", null);
}
}
}
The EU General Data Protection Regulation (GDPR) will come into effect from 25th May 2018. One can read in detail here. This time it has to be all opt-in and they have very heavy fine (€20 million or 4% of global earning!).
Since, it has to be all opt-in(at least in our case), we have decided user accepts our cookies to receive our services.
We will not be logging out current users to give us concept, however, we will present them consent page when they come into one of our sites. If they say yes then we will save an "accept-cookie" or else they won't be able to come into our sites. Afterwards, whenever a use logs into our site, we check the existence of this cookie.
My idea in implementing this solution is to intercept the user request and check the existence of accept-cookie and redirect to the requested resource or controller in our case as we will asp.net mvc accordingly.
My question is can I do this using RegisterRoutes to route request to a controller and if yes, redirect to the requested controller?
What about this solution? Though, the solution is for different aspect. I have modified the variables name from language to consent to make it more meaningful(not trying to copy):
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var euCookie = filterContext.HttpContext.Request.Cookies["ConsentCookies"];
if (euCookie == null)
{
// cookie doesn't exist, redirect use to a View showing
//all the cookies being saved in client machine
// and to take user consent(accept or deny)
}
// do something with euCookie
base.OnActionExecuting(filterContext);
}
}
As this rule comes into effect on 25th May 2018, it would be nice to hear your idea regarding different kind of implementation.
Finally, I came up with something that I wanted--intercepting user request and redirecting based upon a certain cookie. This can be used as a nuget as we have multiple applications and saving cookies could be done from one of the application. As it is made as an action filter attribute, it can be place above controller:
[MyAcceptCookieCheck]
public class HomeController : Controller
This makes it easy to implement across all application and operations regarding saving cookies will be done from the one of the application so that it will be easy to make any changes i.e., only from one place.
public class MyAcceptCookieCheck : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookies = filterContext.HttpContext.Request.Cookies["OurAcceptCookie"];
var values = filterContext.RouteData.Values.Values;
originalRequest = filterContext.HttpContext.Request.Url.AbsoluteUri;
RouteValueDictionary requestOrigin = new RouteValueDictionary { {
"url", originalRequest } };
if (cookies == null && !values.Contains("CookieConsent")) //so that it won't loop endlessly
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
//filterContext.Result = new RedirectResult(urlHelper.Action("CookieConsent", "Home"));
filterContext.Result = new RedirectResult(urlHelper.Action("CookieConsent","Cookie",requestOrigin ,"https","www.my-domain.com/mysitename"));
}
else if(cookies != null)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
string actionName = filterContext.RouteData.Values["action"].ToString();
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
filterContext.Result = new RedirectResult(urlHelper.AbsolutePath(actionName, controllerName));
}
}
}
Code for AbsolutePath (courtesy):
public static string AbsolutePath(this UrlHelper url, string actionName, string controllerName, object routeValues = null)
{
string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
return url.Action(actionName, controllerName, routeValues, scheme);
}
Now, I can redirect all requests without having that particular cookie to a cookie consent page and show user all the details about cookies being used and ask for permission to save "ConsentCookie".
I have overridden the HandleUnauthorizedRequest method in my asp.net mvc application to ensure it sends a 401 response to unauthorized ajax calls instead of redirecting to login page. This works perfectly fine when I run it locally, but my overridden method doesn't get called once I deploy to IIS. The debug point doesn't hit my method at all and straight away gets redirected to the login page.
This is my code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
resultMessage = "Errors"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
filterContext.Result = new RedirectResult(ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url);
}
}
}
and I have the attribute [AjaxAuthorize] declared on top of my controller. What could be different once it's deployed to IIS?
Update:
Here's how I'm testing, it's very simple, doesn't even matter whether it's an ajax request or a simple page refresh after the login session has expired -
I deploy the site onto my local IIS
Login to the website, go to the home page - "/Home"
Right click on the "Logout" link, "Open in a new tab" - This ensures that the home page is still open on the current tab while
the session is logged out.
Refresh Home page. Now here, the debug point should hit my overridden HandleUnauthorizedRequest method and go through the
if/else condition and then redirect me to login page. But it
doesn't! it just simply redirects to login page straight away. I'm
thinking it's not even considering my custom authorize attribute.
When I run the site from visual studio however, everything works fine, the control enters the debug point in my overridden method and goes through the if/else condition.
When you deploy your web site to IIS, it will run under IIS integrated mode by default. This is usually the best option. But it also means that the HTTP request/response model isn't completely initialized during the authorization check. I suspect this is causing IsAjaxRequest() to always return false when your application is hosted on IIS.
Also, the default HandleUnauthorizedRequest implementation looks like this:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
Effectively, by calling base.HandleUnauthorizedRequest(context) you are overwriting the JsonResult instance that you are setting with the default HttpUnauthorizedResult instance.
There is a reason why these are called filters. They are meant for filtering requests that go into a piece of logic, not for actually executing that piece of logic. The handler (ActionResult derived class) is supposed to do the work.
To accomplish this, you need to build a separate handler so the logic that the filter executes waits until after HttpContext is fully initialized.
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new AjaxHandler();
}
}
public class AjaxHandler : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest())
{
response.StatusCode = (int)HttpStatusCode.Unauthorized;
this.Data = new
{
success = false,
resultMessage = "Errors"
};
this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
base.ExecuteResult(context);
}
else
{
var url = request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
url = ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url;
var redirectResult = new RedirectResult(url);
redirectResult.ExecuteResult(context);
}
}
}
NOTE: The above code is untested. But this should get you moving in the right direction.
I have a custom authorize attribute used for Ajax requests:
public class AjaxAuthorize : AuthorizeAttribute {
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
UrlHelper urlHelper;
if (filterContext.HttpContext.Request.IsAjaxRequest()) {
urlHelper = new UrlHelper(filterContext.RequestContext);
filterContext.HttpContext.Response.StatusCode = 401;
//Return JSON which tells the client where the login page exists if the user wants to authenticate.
filterContext.HttpContext.Response.Write(new JavaScriptSerializer().Serialize(
new {
LoginUrl = string.Format("{0}?ReturnURL={1}", FormsAuthentication.LoginUrl, urlHelper.Encode(filterContext.HttpContext.Request.Url.PathAndQuery))
}
));
filterContext.HttpContext.Response.End();
} else {
base.HandleUnauthorizedRequest(filterContext);
}
}
}
When I run the application locally I get the JSON result back from the Ajax request. However, when I put the code on my beta server I end up getting the IIS 401 HTML response.
Does anyone see something wrong with my code that would make this work only on localhost? Additionally, if anyone has a better idea for returning the JSON result I am open to that as well.
There is some strange power of StackOverflow that results in the OP thinking through the question differently after posting. I'll leave my answer here in hopes that it might benefit someone else.
It just occurred to me that IIS7 was getting in the way. I fixed this by adding one line of code:
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;