Feature toggling MVC Web Api - asp.net-mvc

Is it possible to use Feature Toggling in MVC Web APIs?
I want to restrict certain controller actions to be called until the API functionality is complete.

A suggestion could be to create a custom Feature actionfilter. Maybe like this:
public class FeatureAttribute : ActionFilterAttribute
{
public string RequiredFeature { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!Settings.Default.SomeFeature)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary {{ "Controller", "Home" }, { "Action", "Index" } });
}
base.OnActionExecuting(filterContext);
}
}
And on your controller add the attribute:
[Feature(RequiredFeature = "Somefeature")]
public ActionResult ActionNotYetReady()
{
return View();
}
This is a simple example, but would redirect users to a specific controller-action whenever the feature toggle/config setting for a given feature is turned off.

Related

Restrict MVC action to a contentType

Is where any attribute in MVC that restricts an action method so that the method handles only accept contentType: 'application/json' requests?
[HttpPost]
public JsonResult GetLastestPosts(int categoryID, int lastPostID)
{
var list = Posts.GetPostsByRootCategoryIDForAjax(categoryID, lastPostID);
return new JsonResult()
{
Data = list
};
}
There isn't any out of box feature which can restrict requests based on ContentType.But you can always write custom Action Filter and make the necessary restrictions there.
public class RestrictionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//Check for the content type take decision based on that.
}
}

ASP.NET MVC 5 jQuery Mobile web page shows "undefined" after session time out

I am developing Asp.Net MVC 5 jQuery Mobile application. I am facing weird error. When the session is expired and user clicks on any link. A new white page comes with text "undefined". How can I redirect user to Login page.
I tried this, but did not work.
If you put the [Authorize] Atttribute on your Action it should go directly to your Login page, otherwise check the html link rendered on the client.
Bye
You could create an action filter for this scenario.
public class SessionExpiredRedirect : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.Controller.ControllerContext.HttpContext.Session == null) // Or however you want to check for expired session.
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "Controller", "YourController" },
{ "Action", "YourAction" }
});
}
}
}
To use the filter you can either place the attribute specifically on the action method you want to redirect if the session has expired:
public class HomeController : Controller
{
[SessionExpiredRedirect]
public ActionResult Index()
{
}
}
Or in the FilterConfig.cs file:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new SessionExpiredRedirect());
}
}
In which case the attribute will be executed for all action methods.

mvc controller default action

Say I have a controller with multiple actions, is there an override to make the controller return a default action if a condition is met?
Example:
I have a checkoutcontroller and I want each action to return HttpNotFound() if e-commerce is disabled on the site, is there a better way of doing it than just doing the following:
public class CheckoutController
{
[HttpGet]
public ActionResult ViewBasket()
{
if (AppSettings.EcommerceEnabled)
{
return View();
}
else
{
return HttpNotFound();
}
}
[HttpGet]
public ActionResult DeliveryAddress()
{
if (AppSettings.EcommerceEnabled)
{
return View();
}
else
{
return HttpNotFound();
}
}
}
You can create a custom action filter that will be used for action methods inside CheckoutController.
public class CommercePermissionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (AppSettings.EcommerceEnabled)
{
base.OnActionExecuting(filterContext);
}
else
{
// Example for redirection
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "Controller", "Error" },
{ "Action", "AccessDenied" }
});
}
}
}
Then you can use this filter on each action method by
[HttpGet]
[CommercePermission]
public ActionResult ViewBasket()
Or if you want whole controller actions to have this filter,
[CommercePermission]
public class CheckoutController
You can even apply filters globally to all actions in your project.
You can find more info here.
You could create a custom action filter. This would intercept the routing to that action, apply its internal logic, and either continue to the action or interrupt it as you define in the filter. Filters can be applied to individual actions, an entire controller class, or even globally for the whole application.

MVC Authorization - multiple login pages

I have a the following methods in an MVC Controller which redirect to the login page when a user is not logged in.
[Authorize]
public ActionResult Search() {
return View();
}
[Authorize]
public ActionResult Edit() {
return View();
}
Is there a quick/easy/standard way to redirect the second action to a different login page other than the page defined in the web.config file?
Or do I have to do something like
public ActionResult Edit() {
if (IsUserLoggedIn)
return View();
else
return ReturnRedirect("/Login2");
}
I think it is possible by creating a custom authorization filter:
public class CustomAuthorization : AuthorizeAttribute
{
public string LoginPage { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect(LoginPage);
}
base.OnAuthorization(filterContext);
}
}
In your action:
[CustomAuthorization(LoginPage="~/Home/Login1")]
public ActionResult Search()
{
return View();
}
[CustomAuthorization(LoginPage="~/Home/Login2")]
public ActionResult Edit()
{
return View();
}
Web.config based forms authentication does not have such a functionality built-in (this applies to both WinForms and MVC). You have to handle it yourself (either through an HttpModule or ActionFilter, the method you mentioned or any other method)
I implemented the accepted answer by user434917 and even though I was being redirected correctly, I was also receiving the error "Server cannot set status after HTTP headers have been sent." in the server log. After searching, I found this post (answer by Mattias Jakobsson) that solved the problem. I combined the answers to get this solution.
Create a custom authorization filter:
using System.Web.Mvc;
using System.Web.Routing;
namespace SomeNamespace.CustomFilters
{
public class CustomAuthorization : AuthorizeAttribute
{
public string ActionValue { get; set; }
public string AreaValue { get; set; }
public string ControllerValue { get; set; }
public override void OnAuthorization(AuthorizationContext context)
{
base.OnAuthorization(context);
if (context.HttpContext.User.Identity.IsAuthenticated == false)
{
var routeValues = new RouteValueDictionary();
routeValues["area"] = AreaValue;
routeValues["controller"] = ControllerValue;
routeValues["action"] = ActionValue;
context.Result = new System.Web.Mvc.RedirectToRouteResult(routeValues);
}
}
}
}
Then on your controller, use the customer attribute.
[CustomAuthorization(ActionValue = "actionName", AreaValue = "areaName", ControllerValue = "controllerName")]
public class SomeControllerController : Controller
{
//DO WHATEVER
}
Yeah pretty easy! Lets say you have 2 different type of users. First typenormal users, the other one is administrators. You would like to make them login from different pages. You also want them to be able to access different ActionResults.
First you have add two different schemes. In these schemes you will define your different login pages and other options you want.
in startup.cs
services.AddAuthentication("UserSceheme").AddCookie("UserScheme", config =>
{
config.LoginPath = "/UsersLogin/Login/";
config.Cookie.Name = "UsersCookie";
});
services.AddAuthentication("AdminScheme").AddCookie("AdminScheme", config =>
{
config.LoginPath = "/AdminLogin/Login/";
config.Cookie.Name = "AdminsCookie";
});
Then you will define two policies. Here I called them UserAccess and AdminAccess Each policy will use the sceheme that I point.
In startup.cs just after schemes add those below.
services.AddAuthorization(options =>
{
options.AddPolicy("UserAccess", policy =>
{
policy.AuthenticationSchemes.Add("UserScheme");
policy.RequireAuthenticatedUser();
});
options.AddPolicy("AdminAccess", policy =>
{
policy.AuthenticationSchemes.Add("AdminScheme");
policy.RequireAuthenticatedUser();
});
});
The last thing we have to do is again telling the scheme we want to use when login.
var userPrincipal = new ClaimsPrincipal(new[] {
new ClaimsIdentity(loginClaims, "ServiceCenter")
});
HttpContext.SignInAsync("AdminScheme",userPrincipal);
Thats it! Now we can use these just like this; This will redirect you to the users login page.
[Authorize(Policy = "UserAccess")]
public IActionResult Index()
{
return View();
}
If you have some places that you want both user types to be able to access all you have to do;
[Authorize(Policy = "AdminAccess")]
[Authorize(Policy = "UserAccess")]
public IActionResult Index()
{
return View();
}
And finally for log-out you also have to point the scheme
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync("AdminScheme");
return View();
}
Thats it!

ASP.NET MVC redirect to an access denied page using a custom role provider

I'm creating a custom role provider and I set a Authorize attribute specifying a role in my controller and it's working just fine, like this:
[Authorize(Roles="SuperAdmin")]
public class SuperAdminController : Controller
...
But when an user doens't have access to this controller, he's redirected to login page.
How can I redirect him to a "AcessDenied.aspx" page?
[AccessDeniedAuthorize(Roles="SuperAdmin")]
public class SuperAdminController : Controller
AccessDeniedAuthorizeAttribute.cs:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if(filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Here's my solution, based on eu-ge-ne's answer.
Mine correctly redirects the user to the Login page if they are not logged in, but to an Access Denied page if they are logged in but are unauthorized to view that page.
[AccessDeniedAuthorize(Roles="SuperAdmin")]
public class SuperAdminController : Controller
AccessDeniedAuthorizeAttribute.cs:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("~/Account/Logon");
return;
}
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new RedirectResult("~/Account/Denied");
}
}
}
AccountController.cs:
public ActionResult Denied()
{
return View();
}
Views/Account/Denied.cshtml: (Razor syntax)
#{
ViewBag.Title = "Access Denied";
}
<h2>#ViewBag.Title</h2>
Sorry, but you don't have access to that page.
Take a look at tvanfosson's Answer from this very similar question, This is what I am doing(Thanks to tvanfosson), so now I just have to say:
[MyAuthorize(Roles="SuperAdmin",ViewName="AccessDenied")]
public class SuperAdminController : Controller
...
If the user is not in the role, they will get thew view specified by ViewName.
A slight improvement to Matt's answer by avoiding the need to hard-code the Logon page and optionally setting the access denied view within the attribute:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public string AccessDeniedViewName { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated &&
filterContext.Result is HttpUnauthorizedResult)
{
if (string.IsNullOrWhiteSpace(AccessDeniedViewName))
AccessDeniedViewName = "~/Account/AccessDenied";
filterContext.Result = new RedirectResult(AccessDeniedViewName);
}
}
}
Redirect is not always the best solution
Use standard http code 403:
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
I had similar issue. No matter what role I had, I was always redirected to LogIn page instead of AccessDenied.
The fix was unbelievably easy, but it might not work in all cases. So it turned out, that I had wrong order in Startup.cs of these two lines:
app.UseAuthentication();
app.UseAuthorization();
Make sure if app.UseAuthentication(); is BEFORE app.UseAuthorization();
In other words, ask "Who are you?" first, and then "Are you allowed here?", not the other way.
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult && WebSecurity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("~/Account/AccessDenied");
}
}
}
I've built on Vic's answer to allow me to have a different Access Denied page for each of the application's areas. Did it by returning a RedirectToRouteResult instead, which instead of redirecting to a URL relative to the root of the application it redirects to the current area's controller and action:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public string AccessDeniedController { get; set; }
public string AccessDeniedAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated &&
filterContext.Result is HttpUnauthorizedResult)
{
if (String.IsNullOrWhiteSpace(AccessDeniedController) || String.IsNullOrWhiteSpace(AccessDeniedAction))
{
AccessDeniedController = "Home";
AccessDeniedAction = "AccessDenied";
}
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { Controller = AccessDeniedController, Action = AccessDeniedAction }));
}
}
}
Just a small update to Vic Alcazar,
Added details of the request url in redirect
So that can log the details of the access denied and by who if want
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public string AccessDeniedViewName { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated &&
filterContext.Result is HttpUnauthorizedResult)
{
if (string.IsNullOrWhiteSpace(AccessDeniedViewName))
AccessDeniedViewName = "~/Account/AccessDenied";
var requestUrl = filterContext.HttpContext.Request.Url;
filterContext.Result = new RedirectResult(String.Format("{0}?RequestUrl={1}", AccessDeniedViewName, requestUrl));
}
}
}

Resources