Redirect to a certain view in AuthorizeAttribute? - asp.net-mvc

In my AdministratorController I have an action with custom attribute:
[AuthorizedOnly (Roles = "admin, superadmin")]
public ActionResult Index()
{...}
The attribute is:
class AuthorizedOnlyAttribute : AuthorizeAttribute
{
public AuthorizedOnlyAttribute()
{
View = "~/Views/Main/Index.cshtml";
Master = String.Empty;
}
public String View { get; set; }
public String Master { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
CheckIfUserIsAuthenticated(filterContext);
}
private void CheckIfUserIsAuthenticated(AuthorizationContext filterContext)
{
if(filterContext.Result == null)
return;
if(filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if(String.IsNullOrEmpty(View))
return;
var result = new ViewResult{ViewName = View, MasterName = Master};
filterContext.Result = result;
}
}
It correctly shows me the view that I need: ~/Views/Main/Index.cshtml
But in my browser URL is still from Administrator controller: .../Administrator/Index
How can I redirect to the View that I need, so that URL would also change?
Thanks a lot in advance!

Try this
string retUrl = filterContext.HttpContext.Request.RawUrl;
filterContext.Result =
new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary
{{ "controller", "Main" },
{ "action", "Home" },
{ "returnUrl", retUrl } });

Related

Pass value from ActionFilterAttribute to controller

I have the following base controller with a string variable
public abstract class BaseController:Controller
{
string encryptedSessionGuid;
}
All other controller derives from base controller and ActionMethod has a custom ActionFilterAttribute CheckQueryString-
public class SampleController : BaseController
{
[CheckQueryString(new string[] {"sid"})]
public ActionResult SampleMethod()
{
return View();
}
}
Here is my custom attribute. It sends query string value to view. But I would like to send it base controller variable encryptedSessionGuid also.
public class CheckQueryString : ActionFilterAttribute
{
string[] keys;
public CheckQueryString(string[] Keys) { keys = Keys; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
foreach (var key in keys)
{
if (ctx.Request.QueryString[key] == null)
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
string value = ctx.Request.QueryString[key];
if (string.IsNullOrEmpty(value))
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
var viewBag = filterContext.Controller.ViewData;
viewBag[key] = value;
}
}
}
base.OnActionExecuting(filterContext);
}
}
How can it be done?

Unit testing logic in controller OnActionExecuting event

There is a custom logic to set the page title in my controller's OnActionExecuting event which sets default value for title if it is not set using an attribute on action methods:
[PageTitle("Overriden page title")]
public ActionResult About()
{
return View();
}
public ActionResult Error()
{
return View();
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Page title
var title = filterContext.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false);
if (title.Length == 1)
ViewBag.Title = ((PageTitleAttribute)(title[0])).Parameter;
else
ViewBag.Title = "Default Website Title";
}
How can I unit test this functionality?
This is what I ended up doing (might be helpful for anyone facing similar problem).
1) I split up the code in the controller to below:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
SetupMetadata(filterContext);
base.OnActionExecuting(filterContext);
}
public virtual void SetupMetadata(ActionExecutingContext filterContext)
{
//Page title
var title = filterContext.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false);
if (title.Length == 1)
ViewBag.Title = ((PageTitleAttribute)(title[0])).Parameter;
else
ViewBag.Title = "Default Page Title";
}
}
2) Deriving my HomeController from the basecontroller.
3) And then unit tested it using:
[TestClass]
public class BaseControllerTests
{
[TestMethod]
public void OnActionExecuting_should_return_attribute_value_when_set()
{
var ctx = new Mock<ActionExecutingContext>();
var controller = new HomeController();
ctx.Setup(c => c.Controller).Returns(controller);
ctx.Setup(c => c.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false)).Returns(new object[] { new PageTitleAttribute("Overriden Title") });
controller.SetupMetadata(ctx.Object);
Assert.AreEqual("Overriden Title", controller.ViewBag.Title);
}
[TestMethod]
public void OnActionExecuting_should_return_default_attribute_values_if_attributes_are_missing()
{
var ctx = new Mock<ActionExecutingContext>();
var controller = new HomeController();
ctx.Setup(c => c.Controller).Returns(controller);
ctx.Setup(c => c.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false)).Returns(new object[0]);
controller.SetupMetadata(ctx.Object);
Assert.AreEqual("Default Page Title", controller.ViewBag.Title);
}
}

Preventing OnActionExecuting and filtering

I have this filter class.
public class Sessional : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpSessionStateBase session = filterContext.HttpContext.Session;
LoggedUserInfo user = (LoggedUserInfo)session["User"];
if ((user == null && !session.IsNewSession) || (session.IsNewSession))
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Account/LogOut");
FAuth.AbandonSession();
FormsAuthentication.SignOut();
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}
}
When I apply it on the Controller, all the action will logout user if session is unavailable.
But I want to write attribute that allow the only action to do its work without loggin out, for example UnSessional.
[Authorize]
[Sessional]
public class ReportController : Controller
{
[HttpGet]
[UnSessional]
public ActionResult GetReport() //unsessional action
{
return View();
}
[HttpPost]
public ActionResult GetReport(GetReportModel model) //sessional action
{
if (!ModelState.IsValid)
{
return View();
}
return View();
}
}
You can check for the "UnSessionAttribute" existance for the current Action, here is the sample code:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.ActionDescriptor.GetCustomAttributes(typeof(UnSessionAttribute), true).Length > 0)
{
return;
}
HttpSessionStateBase session = filterContext.HttpContext.Session;
LoggedUserInfo user = (LoggedUserInfo)session["User"];
if ((user == null && !session.IsNewSession) || (session.IsNewSession))
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Account/LogOut");
FAuth.AbandonSession();
FormsAuthentication.SignOut();
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}

How can I use an action filter in ASP.NET MVC to route to a different view but using the same URL?

Is it possible to make a filter that, after a controller action has been (mostly) processed, checks for a certain test condition and routes to a different view transparently to the user (i.e., no change in the URL)?
Here would be my best guess at some pseudocode:
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// If some condition is true
// Change the resulting view resolution to XYZ
base.OnResultExecuting(filterContext);
}
filterContext.Result = new ViewResult
{
ViewName = "~/Views/SomeController/SomeView.cshtml"
};
This will short-circuit the execution of the action.
also you can return view as from your action
public ActionResult Index()
{
return View(#"~/Views/SomeView.aspx");
}
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I have extended the AuthorizeAttribute of ASP.NET MVC action filter as DCIMAuthorize, in which I perform some security checks and if user is not authenticated or authorized then action filter will take user to access denied page. My implementation is as below:
public class DCIMAuthorize : AuthorizeAttribute
{
public string BusinessComponent { get; set; }
public string Action { get; set; }
public bool ResturnJsonResponse { get; set; }
public bool Authorize { get; set; }
public DCIMAuthorize()
{
ResturnJsonResponse = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
//to check whether user is authenticated
if (!httpContext.User.Identity.IsAuthenticated)
return false;
//to check site level access
if (HttpContext.Current.Session["UserSites"] != null)
{
var allSites = (VList<VSiteList>)HttpContext.Current.Session["UserSites"];
if (allSites.Count <= 0)
return false;
}
else
return false;
// use Authorize for authorization
Authorize = false;
string[] roles = null;
//get roles for currently login user
if (HttpContext.Current.Session["Roles"] != null)
{
roles = (string[])HttpContext.Current.Session["Roles"];
}
if (roles != null)
{
//for multiple roles
string[] keys = new string[roles.Length];
int index = 0;
// for each role, there is separate key
foreach (string role in roles)
{
keys[index] = role + "-" + BusinessComponent + "-" + Action;
index++;
}
//access Authorization Details and compare with keys
if (HttpContext.Current.Application["AuthorizationDetails"] != null)
{
Hashtable authorizationDetails = (Hashtable)HttpContext.Current.Application["AuthorizationDetails"];
bool hasKey = false;
foreach (var item in keys)
{
hasKey = authorizationDetails.ContainsKey(item);
if (hasKey)
{
Authorize = hasKey;
break;
}
}
}
}
return base.AuthorizeCore(httpContext);
}
catch (Exception)
{
throw;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
filterContext.Controller.ViewData["ResturnJsonResponse"] = ResturnJsonResponse;
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (!Authorize)
{
//Authorization failed, redirect to Access Denied Page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "Base" },
{ "action", "AccessDenied" }
//{ "returnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
catch (Exception)
{
throw;
}
}
}
You Can Also Save All Route File Path in a Static And Use it Like This :
public static class ViewPath
{
public const string SomeViewName = "~/Views/SomeViewName.cshtml";
//...
}
And into Your ActionFilter :
context.Result = new ViewResult()
{
ViewName = ViewPath.SomeViewName /*"~/Views/SomeViewName.cshtml"*/
};

Ignore url values with Outputcache

When I use outputcaching on an action method it is cached by full url, what i would like to do is cache the page but ignore some parts of the url.
Custom route defined in Global.asax:
routes.MapRoute(
"Template",
"Report/{reportid}/{reportname}/Template/{templateid}/{action}",
new { controller = "Template", action = "Index" }
);
My template controller
public class TemplateController : Controller
{
[OutputCache(Duration=60*60*2)]
public ActionResult Index(Template template)
{
/* some code */
}
}
For example when I go to the following URL's:
http://mywebsite.com/Report/789/cacheme/Template/5
-> cached for 2 hours based on the url
http://mywebsite.com/Report/777/anothercacheme/Template/5
-> also gets cached for 2 hours based on that url
What I would like is that the OutputCache ignores the reportname and reportid values so when I go to the above url's it returns the same cached version. Is this possible with the OutputCache attribute or will I have to write my custom OutputCache FilterAttribute?
Ended up with the following (inspired by http://blog.stevensanderson.com/2008/10/15/partial-output-caching-in-aspnet-mvc/):
public class ResultCacheAttribute : ActionFilterAttribute
{
public ResultCacheAttribute()
{
}
public string CacheKey
{
get;
private set;
}
public bool AddUserCacheKey { get; set; }
public bool IgnoreReport { get; set; }
/// <summary>
/// Duration in seconds of the cached values before expiring.
/// </summary>
public int Duration
{
get;
set;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string url = "";
foreach (var item in filterContext.RouteData.Values)
{
if (IgnoreReport)
if (item.Key == "reportid" || item.Key == "reportname")
continue;
url += "." + item.Value;
}
if (AddUserCacheKey)
url += "." + filterContext.HttpContext.User.Identity.Name;
this.CacheKey = "ResultCache-" + url;
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
this.CacheKey += "-ajax";
if (filterContext.HttpContext.Cache[this.CacheKey] != null)
{
filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[this.CacheKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;
filterContext.HttpContext.Cache.Add(this.CacheKey, filterContext.Result, null, DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null);
base.OnActionExecuted(filterContext);
}
}

Resources