I am creating a custom HtmlHelper extension for Html.RenderAction. My Parent view will contain many different partial views which will be rendered byt calling Html.Renderaction. But the the admin can sort off switch of a partial view for a role or he can completely deactivate the action for entire application So i am planning to have an extension method for Html.RenderAction which will in turn check for the role and see if the role has access to a particular action. This role to action mapping is dine in xml and I am planning to load this xml as in memory data structure only once. And have the html helper extension look into that data structure. Is that a good way to go? Any better ways?
#section column2 {
#{Html.RenderActionIfIfAllowed("DashboardItem_Users", "DashBoard",User);}
}
#section column3 {
#{Html.RenderActionIfIfAllowed("DashboardItem_Orders", "DashBoard", User);}
}
I have to render the above partialviews. So i have created a html helper extension called Html.RenderActionIfIfAllowed.
public static class HtmlHelperExtensions
{
public static void RenderActionIfIfAllowed<TModel>(this HtmlHelper<TModel> htmlHelper, string actionName, string controllerName, IPrincipal user)
{
//We can use the layour manager class to check if a particular role has access to an action and also if the action is active.
//Hard coding here just for demo purpose
if (user.IsInRole("Admin") && actionName != "DashboardItem_Users")
{
System.Web.Mvc.Html.ChildActionExtensions.RenderAction(htmlHelper, actionName, controllerName);
}
else if (user.Identity.IsAuthenticated && !user.IsInRole("Admin"))
{
System.Web.Mvc.Html.ChildActionExtensions.RenderAction(htmlHelper, actionName, controllerName);
}
}
}
The reason for doing that way is because we would like to dynamically show or not show a aprtial view to user based on whether the view is active or not. We will read an xml file that will say whether the view is active not for a user and render it accordingly
I used to create ViewModel for this and set bool properties
public class DashBoardViewModel{
public DashBoard dashBoard{get;set;}
bool showItemDashBoard{get;set;}
bool showOrderDashBoard{get;set;}
}
In Controller I validate user role and set those boolean properties.
In View
if(Model.showItemDashBoard){
#Html.RenderAction("Action","Controller")
}
Related
I have an app with many widgets and their content depends on the user requesting specific route. Simply put: if widget action is requested, its content must be rendered, otherwise it's empty. Consider routes/actions like this:
~/MyApp/Index -> without model; app HTML, without any widgets
~/MyApp/Foo/{id} -> uses FooModel; if ModelState is valid, returns
Index HTML with injected partial view of Foo's widget to div#foo;
otherwise redirects to Index.
~/MyApp/Bar/{id} -> same as Foo, but different model and widget
My foo action :
public ActionResult Foo(string id) {
if (ModelState.IsValid) {
var response = FooService.GetData(id);
// Inject Foo widget to Index
}
return RedirectToAction("Index");
}
I know that it is possible to use ViewBag or other means to send variables and using the condition to decide whether to render partial view or not. But... there should be a better way to do this, right?
I use MVC's Html.RenderActionResult when I want to build shared views with non-trivial binding logic (calling the database, composing complex objects, etc). The binding logic for each widget is contained in a PartialViewResult method, which is called from the *.cshtml file using Html.RenderAction().
ContentController:
public ActionResult Index(int id)
{
var indexViewModel = new IndexViewModel
{
Id = id,
Title = "My Title",
SubHeader = "Wow its 2016"
};
return View(indexViewModel);
}
public PartialViewResult PopularContent(int id)
{
var popularContentViewModel = new List<PopularContentViewModel>();
// query by id to get popular content items
return PartialView("_PopularContent", popularContentViewModel);
}
public PartialViewResult Widget2(int id)
{
return PartialView("_Widget2Partial");
}
Index.cshtml:
#model StackOverflow.RenderAction.ViewModels.IndexViewModel
<h1>#Model.Title</h1>
<h2>#Model.SubHeader</h2>
--RenderAction will call out to the specified route.
--Note the use of the Id parameter from the viewmodel.
#{Html.RenderAction("PopularContent", "Content", new {Model.Id});}
ASP.NET MVC Attribute Routing could a be a nice solution for this:
In your controller:
public class WidgetController : Controller
{
[Route("myapp/foowidget", Name = "FooWidget")]
public ActionResult FooWidget()
{
//create any model and return any view or partial or redirect
}
[Route("myapp/boowidget/{id:int}", Name = "BooWidget")]
public ActionResult BooWidget(int id)
{
//create any model and return any view or partial or redirect
}
}
And then in a View, you can call the Route by name:
#Url.RouteUrl("FooWidget")
or
#Url.RouteUrl("BooWidget")
or
#Html.RenderPartial("FooWidget")
#Url.RouteUrl("BooWidget") will render or concatenate the id that is in current url, if url is /myapp/something/id, because of your Route attribute definition: "myapp/boowidget/{id:int}". In fact #Url.RouteUrl("BooWidget") might extract the id from any current url of the format /controllerName/action/id, though you will have to test for sure.
And notice how you can have a separation of concerns with your WidgetController and your url Routes are not dependent on that controller's name in any way. That is a nice feature of Attribute Routing, you can declare custom routes as well as organize your controllers and break from nameing convention dependency of a controllerName being part of the url controllerName/action a user sees in their browser.
In regards to Html.RenderPartial, I am not sure if RenderPartial "connects" or will be able to route to your RouteName like "FooWidget". If it does great.
If not your solution is this:
public class WidgetController : Controller
{
public ActionResult FooWidget()
{
//model, you choose, return a partial
}
public ActionResult RedirectUser()
{
//do a redirect
}
public ActionResult BooWidget()
{
//any model, any partial
}
public ActionResult BooWidget(int id)
{
//any model, any partial
}
}
Each method in your controller is single purpose, has a distinct signature and does one thing, no conditions to pass in and no decisions required.
In my _layout.cshtml page, I've got some elements that need to be hidden on some pages. I know the pages on which we won't display some parts. For a single page, I could just do this:
#if (ViewContext.RouteData.Values["action"].ToString() != "LogIn") {
<div> .... <div>
}
But that gets messy and long with multiple pages. Someplace, ideally not in the _Layout page, I could build a list of actions, and if the current action is any of them, set a boolean variable (ShowStuff) to false. Then just do this on _Layout:
#if (ShowStuff== true) {
<div> .... <div>
}
I'm just not sure where would be the best-practice way to examine that list of actions and set the boolean. Can the _Layout page have it's own model and controller like a normal view?
Similarly to MikeSW answer, I'd use an action filter, but I would populate ViewData with a specific ViewModel. When you want to display it simply DisplayFor the value, if it's populated the template is used by whatever type the model is, if it's null nothing is displayed. (examples below from memory, may not be exactly correct.)
public BlahModelAttribute : ActionFilterAttribute
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
BlahModel model = Db.GetModel();
filterContext.Controller.ViewData.Set(model);
}
}
ViewData extensions:
public static ViewDataExtensions
{
private static string GetName<T>()
: where T : class
{
return typeof(T).FullName;
}
public static void Set<T>(this ViewDataDictionary viewData, T value)
: where T : class
{
var name = GetName<T>();
viewData[name] = value;
}
public static T Get<T>(this ViewDataDictionary viewData)
: where T : class
{
var name = GetName<T>();
return viewData[name] as T;
}
}
In your view:
#{var blahModel = ViewData.Get<BlahModel>() }
#Html.DisplayFor(m => blahModel)
If devs would stop looking for the 'best way' for every problem they have, that would be great. No best way here, just opinionated solutions. Here's mine: You can create an action filter [ShowNav] and decorate any controller/action you need. That filter will put a boolean into HttpContext.Items . Create then a HtmlHelper which checks for the boolean. Then in _layout, if (Html.CanShowNavig()) { <nav> } . That's the easiest solution that comes to my mind.
Every page in my site should have some of the same data, similar to how in SO every page displays info about the current user at the top of the page. The way I implemented this was to have a base controller class that all my controllers derive from. In that base controller's constructor I put my model in the ViewBag, and then my layout page uses that.
I'm running into problems with this because my layouts aren't strongly typed. For example, I have to construct new HtmlHelpers in the layout page:
#{var layoutHtml = new HtmlHelper<LayoutModel>(Html.ViewContext, Html.ViewDataContainer); }
#* OK, now we can use the html helper... *#
#layoutHtml.TextAreaFor(model => model.Feedback)
I really don't want to have to have my models subclass from a layout model, since that would force each action to fill out the shared model data individually, but manually creating HtmlHelpers also seems like a bad idea.
Any thoughts?
I might be wrong, but shouldn't you use partial views for this?
Your solution looks way too complicated.
A base controller is a great way to start. I would also introduce a base viewmodel. The base viewmodel would contain user specific information.
public abstract class BaseController : Controller
{
public string CurrentAccountName
{
get
{
return (HttpContext.User == null) ? null : HttpContext.User.Identity.Name;
}
}
public T CreateViewModel<T>() where T : BaseViewModel, new()
{
T viewModel = new T
{
UserName = CurrentAccountName
};
return viewModel;
}
}
public abstract class BaseViewModel
{
public string UserName { get; set; }
}
Then on each request you would populate your viewmodel with user specific information and whatever information is required for the view. MyViewModel is just a viewmodel that inherits from BaseViewModel.
public class MyController : BaseController
{
public ActionResult Index()
{
MyViewModel viewModel = CreateViewModel<MyViewModel>();
return View(viewModel);
}
}
In the master view I would pass in the BaseViewModel and the View I would pass in the inherited MyViewModel.
Now you have access to your user information in your master view and can pass it to a partial or render it directly to the page.
you can define multiple Layouts which you can use in your appropiate views! Just include them like so:
#{
Layout = "~/Views/Shared/_MySubLayout.cshtml";
}
i believe you can use RenderAction to solve this problem. Because information this action will display is common on all pages, you can put it in BaseController and call it from your site master. it will compute its own model and return that model to partial view which can be strongly typed and you don't have to instantiate htmlHelper the way you are doing now.
So I was reading another question regarding login loop when you have a user logging in, set to return to a URL which they might not have access to after logging in (ie. an admin page, and the user logs in with a normal account).
The solution under WebForms seems to be to utilize the UrlAuthorizationModule.CheckUrlAccessForPrincipal method. However that does not work for URLs going to Action Methods secured with the Authorize Attribute. I figured I could work out which method the URL was pointing at and reflect over it to solve my problem - but I can't seem to work out how I get this information out of the routing table.
Anyone ever worked with this, or have a solution for this? If I can just get hold of the route information from a URL I think I could work the rest out, but if anyone has a generic solution - ie. some hidden method akin to the before mentioned one for MVC, then that would be totally awesome as well.
I'm not asking how to check if the User has acces to a specified Controller/Action pair. I first and foremost need to work out how to get the Controller/Action pair from the RouteTable based off the URL. The reason for all the background story, is in case that there does indeed exist an equivalent to UrlAuthorizationModule.CheckUrlAccessForPrincipal for MVC.
John Farrell (jfar)'s answer (SecurityTrimmingExtensions class) updated for MVC 4:
public static class SecurityCheck
{
public static bool ActionIsAuthorized(string actionName, string controllerName)
{
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
ControllerBase controller = factory.CreateController(HttpContext.Current.Request.RequestContext, controllerName) as ControllerBase;
var controllerContext = new ControllerContext(HttpContext.Current.Request.RequestContext, controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
foreach (var authAttribute in actionDescriptor.GetFilterAttributes(true).Where(a => a is AuthorizeAttribute).Select(a => a as AuthorizeAttribute))
{
authAttribute.OnAuthorization(authContext);
if (authContext.Result != null)
return false;
}
return true;
}
}
I ported and hacked this code from the MvcSitemap:
public static class SecurityTrimmingExtensions
{
/// <summary>
/// Returns true if a specific controller action exists and
/// the user has the ability to access it.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <returns></returns>
public static bool HasActionPermission( this HtmlHelper htmlHelper, string actionName, string controllerName )
{
//if the controller name is empty the ASP.NET convention is:
//"we are linking to a different controller
ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName)
? htmlHelper.ViewContext.Controller
: GetControllerByName(htmlHelper, controllerName);
var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo);
var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
return ActionIsAuthorized(controllerContext, actionDescriptor);
}
private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false; // action does not exist so say yes - should we authorise this?!
AuthorizationContext authContext = new AuthorizationContext(controllerContext);
// run each auth filter until on fails
// performance could be improved by some caching
foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters)
{
authFilter.OnAuthorization(authContext);
if (authContext.Result != null)
return false;
}
return true;
}
private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName)
{
// Instantiate the controller and call Execute
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName);
if (controller == null)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
"Controller factory {0} controller {1} returned null",
factory.GetType(),
controllerName));
}
return (ControllerBase)controller;
}
It could use some caching but for my case that was a premature optimization.
What is the problem you are trying to solve? It sounds like you may be headed down a path to a complex solution that could use a simple solution instead.
If a user doesn't have permissions to access the page after login, are you wanting non-logged in users to go to one page, while logged in users go to a different page?
If that's the case I might be tempted to create another controller for just such scenarios and redirect to that controller anywhere the user doesn't have access. Or if you are using your own base Controller I would put the functionality there.
Then the controller could present the desired view. For example if a non-logged in user tries to access a page they could get redirected to a generic error page. If the user is logged in, they could get redirected to a not authorized page.
This is very similar to Robert's answer.
Here's a basic skeleton for a base controller.
public BaseController: Controller
{
... // Some code
public ActionResult DisplayErrorPage()
{
// Assumes you have a User object with a IsLoggedIn property
if (User.IsLoggedIn())
return View("NotAuthorized");
// Redirect user to login page
return RedirectToAction("Logon", "Account");
}
}
Then in lets say a AdminController (that inherits from BaseController) action
public ActionResult HighlyRestrictedAction()
{
// Assumes there is a User object with a HasAccess property
if (User.HasAccess("HighlyRestrictedAction") == false)
return DisplayErrorPage();
// At this point the user is logged in and has permissions
...
}
This is probably going to sound controversial, but I check security at the beginning of each controller method, inside the method:
public class ProductController : Controller
{
IProductRepository _repository
public ActionResult Details(int id)
{
if(!_repository.UserHasAccess(id))
return View("NotAuthorized");
var item = _repository.GetProduct(id);
if (item == null)
return View("NotFound");
return View(item);
}
}
The reason I don't use the [Authorize] attributes for this is that you cannot pass an id, or any other identifying information, to the attribute at runtime.
In my application I have created a custom filter derived from AuthorizeAttribute, so any unauthorized access will simply go to AccessDenied page. For links, I replace Html.ActionLink with a custom helper Html.SecureLink. In this helper extension, I check for this user's roles access to controller/action against database. If he/she have the authorization, return link otherwise return the link text with special remarks (could be image/ coloring/ js)
Why not attribute your controller methods with the security requirement.
I wrote an attribute to do this as follows:
public class RequiresRoleAttribute : ActionFilterAttribute
{
public string Role { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (string.IsNullOrEmpty(Role))
{
throw new InvalidOperationException("No role specified.");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
else
{
bool isAuthorised = filterContext.HttpContext.User.IsInRole(this.Role);
<< Complete Logic Here >>
}
}
}
i just spent some time implementing #jfar's solution (updating it for non-deprecated GetFilters() version), and then i realized that i can skip this whole thing.
in my case (and i assume most cases) I have a custom AuthorizationAttribute to implement site authorization which in turn calls my authorization service to make the actual access level determinenation.
So in my html helper for generating menu links, i skipped right to the auth service:
#Html.MenuItem(#Url, "icon-whatever", "TargetController", "TargetAction")
public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, UrlHelper url,string iconCss, string targetController, string targetAction)
{
var auth = IoC.Resolve<IClientAuthorizationService>().Authorize(targetController, targetAction);
if (auth == AccessLevel.None)
return MvcHtmlString.Create("");
*user is determined within the client auth service
public string GetUser() {
return HttpContext.Current.User.Identity.Name;
}
*could also add some behavior for read-only access. it's nice because my auth service takes care of caching so i don't have to worry about performance.
I want to pass some parameters to my MVC UserControl like ShowTitle(bool) and the ViewData.Model.Row . How I define my usercontrol and pass them to it?
Tanx
You can use the RenderAction HtmlHelper, found in the MVC futures dll available at codeplex. In your main page
...
You need an action method on the controller with the parameters. The action then creates a ViewData for the usercontrol. The usercontrol is returned as a view with
return View("usercontrolname", model);
The .ascx file then uses the model for just the user control. The resulting HTML is rendered into the calling page.
You can define your control as
public partial class MyUserControl : System.Web.Mvc.ViewUserControl<MyUserControlViewData> {
}
public class MyUserControlViewData {
public IList<MyData> MyData { get; set; }
public string SomethingElse { get; set; }
}
After that you can create an instance of MyUserControlViewData classin your controller, populate with data and pass it to the view. Is that what you're looking for?