Asp.net MVC3: is really based on roles #Html.Action() - c#

My View uses #Html.Action(…) to reflect various functional blocks. In opening the page, the site shows Authorization dialog box for users that do not have role pointed in controller method. (e.g. “manager” or “caller”) On pressing “cancel” get:
401 - Unauthorized: Access is denied due to invalid credentials.
May I achieve changes of my application behavior in case a user has no required roles, #Html.Action is ignored or nothing is shown?
My VIew:
#model InApp.ViewModel.ListViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div id="CallList">
#Html.Action("List","Call",new {id=Model.id})
</div>
<div class="Order">
#Html.Action("Create","Order",new {id=Model.id})
</div>
Controllers:
[Authorize(Roles = "manager, caller")] //if a user is not 'manager' or 'caller'
public PartialViewResult List() // nothing is shown
{
//...private
return PartialView();
}
[Authorize(Roles = "manager, admin")]
public PartialViewResultCreate()
{
//...private
return PartialView();
}
Trying to find the correct solution I have found similar questions:
Ignore #Html.Action() if user not in Role
and asp.net MVC3 razor: display actionlink based on user role
But I do not like “if” condition in my View. I am looking for a complex solution to hide and show separate parts using only AuthorizeAttribute and to avoid if – else in View or Controller. Thanks for any help!

I can suggest using this extension method:
This is a wrapper for #Html.Action which checks the user rights by using reflection.
public static MvcHtmlString ActionBaseRole(this HtmlHelper value, string actionName, string controllerName, object routeValues , IPrincipal user)
{
bool userHasRequeredRole = false;
Type t = Type.GetType((string.Format("MyProject.Controllers.{0}Controller",controllerName))); // MyProject.Controllers... replace on you namespace
MethodInfo method = t.GetMethod(actionName);
var attr = (method.GetCustomAttribute(typeof(AuthorizeAttribute), true) as AuthorizeAttribute);
if (attr != null)
{
string[] methodRequeredRoles = attr.Roles.Split(',');
userHasRequeredRole = methodRequeredRoles.Any(r => user.IsInRole(r.Trim())); // user roles check in depends on implementation authorization in you site
// In a simple version that might look like
}
else userHasRequeredRole = true; //method don't have Authorize Attribute
return userHasRequeredRole ? value.Action(actionName, controllerName, routeValues) : MvcHtmlString.Empty;
}
Using in view:
#Html.ActionBaseRole("List","Call",new {id=Model.id},User)

If you do not like this logic in the view - move it somewhere else. For example you can make your own extension method and use it like #Html.ActionOrNothing(...).
And the implementation should check if user has permissing to view something and return an empty string/view otherwise.

Related

RenderAction on layout page throwing error

I placed a RenderAction inside my layout page:
#{Html.RenderAction( "LoginPartial", "Account" );}
The controller method looks like this but never seems to be called. The AccountController on the other hand does:
public ActionResult LoginPartial()
{
ApplicationUser user = this.userRepository.GetByID( User.Identity.GetUserId() );
return PartialView( "_LoginPartial", user );
}
The view looks like this:
#model Test.Domain.Entities.ApplicationUser
#using Microsoft.AspNet.Identity
#{
Layout = "";
}
...
When i run the application, I get:
HTTP Error 404.15 - Not Found
The request filtering module is configured to deny a request where the query string is too long.
And that's because the URL has come out as:
http://localhost:53744/Account/Login?ReturnUrl=%2FAccount%2FLogin%3FReturnUrl%3D%252FAccount%252FLogin%253FReturnUrl%253D%25252FAccount%25252FLogin%25253FReturnUrl%25253D%2525252FAccount%2525252FLogin%2525253FReturnUrl%2525253D%252525252FAccount%252525252FLogin%252525253FReturnUrl%252525253D%25252525252FAccount%25252525252FLogin%25252525253FReturnUrl%25252525253D%2525252525252FAccount%2525252525252FLogin%2525252525253FReturnUrl%2525252525253D%252525252525252FAccount%252525252525252FLogin%252525252525253FReturnUrl%252525252525253D%25252525252525252FAccount%25252525252525252FLogin%25252525252525253FReturnUrl%25252525252525253D%2525252525252525252FAccount%2525252525252525252FLogin%2525252525252525253FReturnUrl%2525252525252525253D%252525252525252525252FAccount%252525252525252525252FLogin%252525252525252525253FReturnUrl%252525252525252525253D%25252525252525252525252FAccount%25252525252525252525252FLogin%25252525252525252525253FReturnUrl%25252525252525252525253D%2525252525252525252525252FAccount%2525252525252525252525252FLogin%2525252525252525252525253FReturnUrl%2525252525252525252525253D%252525252525252525252525252FAccount%252525252525252525252525252FLogin%252525252525252525252525253FReturnUrl%252525252525252525252525253D%25252525252525252525252525252FAccount%25252525252525252525252525252FLogin%25252525252525252525252525253FReturnUrl%25252525252525252525252525253D%2525252525252525252525252525252FAccount%2525252525252525252525252525252FLogin%2525252525252525252525252525253FReturnUrl%2525252525252525252525252525253D%252525252525252525252525252525252FAccount%252525252525252525252525252525252FLogin%252525252525252525252525252525253FReturnUrl%252525252525252525252525252525253D%25252525252525252525252525252525252FAccount%25252525252525252525252525252525252FLogin%25252525252525252525252525252525253FReturnUrl%25252525252525252525252525252525253D%2525252525252525252525252525252525252FAccount%2525252525252525252525252525252525252FLogin%2525252525252525252525252525252525253FReturnUrl%2525252525252525252525252525252525253D%252525252525252525252525252525252525252F
Any ideas what i have done wrong?
You have to use [AllowAonymuous] on the partial view returning method in the controller
I.e.LoginPartial
Or try to add location section and set allow=* in web.config

Redirecting from cshtml page

I want to redirect to a different view depending on the result of a dataset, but I keep getting returned to the page I am currently on, and can't work out why. I drop into the if statement the action gets called but once i return the view to the new page, it returns me back to the current page.
CSHTML page
#{
ViewBag.Title = "Search Results";
EnumerableRowCollection<DataRow> custs = ViewBag.Customers;
bool anyRows = custs.Any();
if(anyRows == false)
{
Html.Action("NoResults","Home");
}
// redirect to no search results view
}
Controller
public ActionResult NoResults()
{
return View("NoResults");
}
View I cant get too..
#{
ViewBag.Title = "NoResults";
}
<h2>NoResults</h2>
Change to this:
#{ Response.Redirect("~/HOME/NoResults");}
Would be safer to do this.
#{ Response.Redirect("~/Account/LogIn?returnUrl=Products");}
So the controller for that action runs as well, to populate any model the view needs.
Source
Redirect from a view to another view
Although as #Satpal mentioned, I do recommend you do the redirecting on your controller.
This clearly is a bad case of controller logic in a view. It would be better to do this in a controller and return the desired view.
[ChildActionOnly]
public ActionResult Results()
{
EnumerableRowCollection<DataRow> custs = ViewBag.Customers;
bool anyRows = custs.Any();
if(anyRows == false)
{
return View("NoResults");
}
else
{
return View("OtherView");
}
}
Modify NoResults.cshtml to a Partial.
And call this as a Partial view in the parent view
#Html.Partial("Results")
You might have to pass the Customer collection as a model to the Result action or in a ViewDataDictionary due to reasons explained here: Can't access ViewBag in a partial view in ASP.NET MVC3
The ChildActionOnly attribute will make sure you cannot go to this page by navigating and that this view must be rendered as a partial, thus by a parent view. cfr: Using ChildActionOnly in MVC
You can go to method of same controller..using this line , and if you want to pass some parameters to that action it can be done by writing inside ( new { } )..
Note:- you can add as many parameter as required.
#Html.ActionLink("MethodName", new { parameter = Model.parameter })

Determine if a user will have access to a given Controller Action based on Role

I am trying to build a dynamic menu for my ASP.NET MVC4 web application. As I am constructing the menu I want to make sure that menu items for which a user should not have access are not displayed in the menu.
I am using forms authentication and the [Authorize] attribute with each page requiring a given a role.
Given two strings (Controller and Action), and a logged in user, how can I determine if a user will have access to that Controller Action?
All of my menu data is stored in a database. My plan to render the menu is to construct a JSON object of the menu data and embed that into the View. Then client side I will use Handlebars.js and plug the menu JSON object into a template.
What I am trying to do is check permissions on a given Controller/Action for a user as I am rendering the menu data. My initial thought was to use reflection and look up the controller action method and check for the existence of an Authorize attribute and check to see if the current logged in user has the necessary role access that page. If not, then the menu item would not be rendered.
I am always reluctant to use reflection however, there usually tends to be an easier way of doing things.
public static IEnumerable<MethodInfo> GetActions(string controller, string action)
{
return Assembly.GetExecutingAssembly().GetTypes()
.Where(t =>(t.Name == controller && typeof(Controller).IsAssignableFrom(t)))
.SelectMany(
type =>
type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(a => a.Name == action && a.ReturnType == typeof(ActionResult))
);
}
then
var roles = ((AuthorizeAttribute) (GetActions("ControllerName" + "Controller", "ActionName").First().GetCustomAttributes(typeof (AuthorizeAttribute), false)[0])).Roles;
if(roles.Contains("admin or smth"))
{
doSomsing();
}
I have implemented a very similar scenario.
The only difference is that my menus are stored in XML files.
Since you have your menu data stored in a database, I suggest you either add an xml field that contains security info to each menu record; or, have a new table that maps menu items to users.
The table can look like this:
MenuItemName (id) User
---------------------------------------
ViewVacationHistory (12) firstuser
ViewVacationHistory (12) seconduser
ApproveVacationRequest (10) seconduser
Now when your controller receives the request that would result in displaying menu items, and since your controller is decorated with the Authorize attribute, it will receive the user in the HttpContext. Here you simply query the database for menu items that match, then render the menu accordingly.
Use ActionFilter Attribute to filter the users based on Role
http://www.asp.net/mvc/tutorials/hands-on-labs/aspnet-mvc-4-custom-action-filters
create the class called Rolevalidation and add the code as below
public class AuthorizeRoles : AuthorizeAttribute
{
List<string> roles = new List<string>(“your list of roles”);
bool isAuthenticated = false;
for (int i = 0; i < roles.Count(); i++)
{
if (u.Role.Name == roles[i])
{
isAuthenticated = true;
break;
}
}
if (isAuthenticated)
{
SetCachePolicy(filterContext);
}
else
{
filterContext.Result = new RedirectResult("~/Error");
}
}
Add this code in every controller’s begining* [AuthorizeRoles(Roles = "SuperAdmin")]
Assuming you have a view that contains all the menu html code, probably will be better just using:
<ul class="menu">
<li>
#Html.ActionLink("Home", "Index", "Home")
</li>
#if (System.Web.Security.Roles.IsUserInRole(User.Identity.Name, "Administrator"))
{
<li>
#Html.ActionLink("Statistics", "Index", "Stats")
</li>
}
</ul>
Hope this helps!
The links will be generated by the json object which comes from an action and controller.
The json object should have a list of links(or what ever required to implement the menu item), the list should be generated by sort of settings stored in the database, tells each user what links to show.
Beside that what if the user has the URL so in this case you need to use
https://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.users(v=vs.118).aspx
You can use an Authorize attribute with which to decorate an action from the controller [Authorize(AuthorizationContext)] or you can extend the authorize attribute for custom authorization;
You can also define some conventions in a dictionary in which you define that a Create action of a controller needs a create type authorization a.s.o.
Dictionary<string, Right> actionConventions = new Dictionary<string, Right>
{
{ "Index", Right.View },
{ "List", Right.View },
{ "Open", Right.View },
{ "Create", Right.Create},
{ "Edit", Right.Edit },
{ "Delete", Right.Delete }
}
and override OnAuthorization and Authorize methods of AuthorizeAttribute and either check the convention if the action sticks to the conventions defined in the dictionary or check a specific condition where you either Authorize(AuthorizationContext) for the action or HandleUnauthorizedRequest(AuthorizationContext).
Handling the menu you can create an authorization service in which you return the rights of the current user and add a class containing the rights of the user and you check the Model when rendering the model, if a menu item should be rendered or not.
Authorize Attribute MSDN

ASP MVC Redirect without changing URL(routing)

Goal:
I want to be able to type URL: www.mysite.com/NewYork OR www.mysite.com/name-of-business
Depending on the string I want to route to different actions without changing the URL.
So far I have:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"UrlRouter", // Route name
"{query}", // URL with parameters
new { controller = "Routing", action = "TestRouting" } // Parameter defaults
);
}
In the controller I have:
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
return RedirectToAction("Index", "Availability"); // <--------- not sure
else if (query == "name-of-business")
return Redirect("nameofbusines.aspx?id=2731"); // <--------- not sure
else
return RedirectToAction("TestTabs", "Test"); // <--------- not sure
}
I have pretty much tried everything to redirect/transfer to the page without
changing the URL, but everything I've tried changes the URL or gives me an error.
Basically I'm looking for the equivalent of server.transfer where I can keep the URL but send info to the action and have it display its result.
I'm with Nick on this one, though I think you could just use regular views instead of having to do partials. You may need to implement them as shared views if they are not in the views corresponding to the controller (since it will only look in the associated and shared views).
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
{
var model = ...somehow get "New York" model
return View("Index", model );
}
else if (query == "name-of-business")
{
var model = ...get "nameofbusiness" model
return View("Details", model );
}
else
{
return View("TestTabs");
}
}
Each view would then take a particular instance of the model and render it's contents using the model. The URL will not change.
Anytime that you use a RedirectResult, you will actually be sending an HTTP redirect to the browser and that will force a URL change.
Im not sure if you tried this way or if this way has any drawbacks..
Add a global.asax file to your project. In that add the following method:
void Application_BeginRequest(object sender, EventArgs e)
{
// Handles all incoming requests
string strURLrequested = Context.Request.Url.ToString();
GetURLToRedirect objUrlToRedirect = new GetURLToRedirect(strURLrequested);
Context.RewritePath(objUrlToRedirect.RedirectURL);
}
GetURLToRedirect can be a class that has the logic to find the actual URL based on the URL typed in. The [RedirectURL] property will be set with the url to redirect to beneath the sheets.
Hope that helps...
You can change your controller like this:
public ActionResult TestRouting(string query)
{
string controller,action;
if (query == "NewYork")
{
controller = "Availability";
action = "Index";
}
else
{
controller = "Test";
action = "TestTabs";
}
ViewBag.controller = controller;
ViewBag.action = action;
return View();
}
Then you can use these ViewBags in your view like this:
#{
Layout = null;
Html.RenderAction(ViewBag.action, ViewBag.controller);
}
That's it. And you can improve this example with use a class and some functions.
Are you saying you want to go to "www.mysite.com/NewYork" and then "really" go "somewhere else" but leave the url alone? Perhaps what you would want to do then is use partial views to implement this? That way, your base page would be what gets routed to, and then inside of that page you do your condition testing to bring up different partial views? I've done that in my application for viewing either a read-only version of a grid or an editable grid. It worked very nicely.
I'm not sure what you can do about the redirect to the .aspx page, but you should be able to replace the RedirectToAction(...)s with something like this:
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
{
var controller = new AvailabilityController();
return controller.Index();
}
else if (query == "name-of-business")
return Redirect("nameofbusines.aspx?id=2731"); <--------- not sure
else
{
var controller = new TestController();
return controller.TestTabs();
}
}

ASP.NET MVC Standard Link/Href As Save Button And Model IS NUll

Okay so, i am totally new to MVC and I'm trying to wrap my head around a few of the concepts. I've created a small application...
This application has a view for creating a new Individual record. The view is bound to a model ViewPage... And I have a associated IndividualController which has a New method...
The New method of the IndividualController looks like this...
public ActionResult New()
{
var i = new Individual();
this.Title = "Create new individual...";
i.Id = Guid.NewGuid();
this.ViewData.Model = new Individual();
return View();
}
Now, the above all seems to be working. When the view loads I am able to retrieve the data from the Individual object. The issue comes into play when I try and save the data back through the controller...
In my IndividualController I also have a Save method which accepts an incoming parameter of type Individual. The method looks like...
public ActionResult Save(IndividualService.Individual Individual)
{
return RedirectToAction("New");
}
Now, on my view I wanted to use a standard html link/href to be used as the "Save" button so I defined an ActionLink like so...
<%=Html.ActionLink("Save", "Save") %>
Also, defined in my view I have created a single textbox to hold the first name as a test like so...
<% using (Html.BeginForm()) { %>
<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>
<% } %>
So, if I put a break point in the Save method and click the "Save" link in my view the break point is hit within my controller. The issue is that the input parameter of the Save method is null; even if I type a value into the first name textbox...
Obviously I am doing something completely wrong. Can someone set me straight...
Thanks in advance...
Your New controller method doesn't need to create an individual, you probably just want it to set the title and return the view, although you may need to do some authorization processing. Here's an example from one of my projects:
[AcceptVerbs( HttpVerbs.Get )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult New()
{
ViewData["Title"] = "New Event";
if (this.IsMasterEditAllowed())
{
ViewData["ShowNewMaster"] = "true";
}
return View();
}
Your Save action should take the inputs from the form and create a new model instance and persist it. My example is a little more complex than what I'd like to post here so I'll try and simplify it. Note that I'm using a FormCollection rather than using model binding, but you should be able to get that to work, too.
[AcceptVerbs( HttpVerbs.Post )]
[Authorization( Roles = "SuperUser, EditEvent, EditMasterEvent")]
public ActionResult Save( FormCollection form )
{
using (DataContext context = ...)
{
Event evt = new Event();
if (!TryUpdateModel( evt, new [] { "EventName", "CategoryID", ... }))
{
this.ModelState.AddModelError( "Could not update model..." );
return View("New"); // back to display errors...
}
context.InsertOnSubmit( evt );
context.SubmitChanges();
return RedirectToAction( "Show", "Event", new { id = evt.EventID } );
}
}
If I don't create a new Indvidual object in the New method then when my view tries to bind the textbox to the associated model I get a NullReferenceException on the below line in my view...
`<%=Html.TextBox("FirstName", ViewData.Model.FirstName)%>`
With regards to the Save method. From what I understand since my view is strongly typed shouldn't I be able to have a method signature like...
`public ActionResult New(IndividualService.Individual ind)
{
return View();
}`
I thought that was the purpose behind model binding..?
I would strongly recommend that you take a step back from what you are doing and run through some of the Tutorials/Videos here http://www.asp.net/learn/
If you have a strongly typed View it means that when the Controller picks that view to generate the output the view has better access to the Model.
However the View is not responsible for what comes back from the client subsequently such as when a form is posted or a URL is otherwise navigated to.
ASP.NET-MVC uses information in the URL to determine which Controller to hand the request off to. After that it's the controller's responsibility to resolve the various other elements in the request into instance(s) of Model classes.
The relationship between the incoming request and the controller is clouded by the significant assistance the ASP.NET-MVC routing gives the controller. For example a route can be defined to supply parameters to the controller method and thats all the controller needs and hence you don't see any code in the method relating to the http request. However it should be understood that the contoller method is simply processing a http request.
I hope you can see from the above that it would be too early in a requests life-cycle for an instance of a class from the model to passed to a public method on a controller. Its up to the controller to determine which model classes if any need instancing.

Categories

Resources