In a default install of a MVC3 website tabs are created in the top left. I would like to hide/show these tabs based on whether the current user would have access to the index ViewResult. The allowed roles to the ViewResult are defined by attributes. Is there any way of getting the list of roles for a ViewResult?
If you are asking (sorry, wasn't clear exactly to me) about conditional showing of HTML elements based on roles, you could do something like this:
#if (User.IsInRole("Administrators"))
{
#Html.ActionLink("Do Some Action", "DoAction", "SomeController")
}
If that isn't what you're asking, let me know.
Follow-up based on your comment:
Your question interests me and I did a little poking around and found that Vivien Chevallier has an interesting idea here which essentially lets you write something like so:
#Html.ActionLinkAuthorized("The Privilege Zone", "ThePrivilegeZone", "Home", true)
in your View and then this check the controller action and either renders a link or doesn't.
In his controller example, you've got an action like this:
[Authorize(Roles = "Administrator")]
public ActionResult ThePrivilegeZone()
{
return View();
}
(I guess the key point here is that your View doesn't know squat about "Administrators" and relies upon the extension code to do the heavy lifting here:
public static MvcHtmlString ActionLinkAuthorized(
this HtmlHelper htmlHelper,
string linkText, string actionName, string controllerName,
RouteValueDictionary routeValues,
IDictionary<string, object> htmlAttributes, bool showActionLinkAsDisabled)
{
if (htmlHelper.ActionAuthorized(actionName, controllerName))
{
return htmlHelper.ActionLink(
linkText,
actionName, controllerName, routeValues, htmlAttributes);
}
else
{
if (showActionLinkAsDisabled)
{
TagBuilder tagBuilder = new TagBuilder("span");
tagBuilder.InnerHtml = linkText;
return MvcHtmlString.Create(tagBuilder.ToString());
}
else
{
return MvcHtmlString.Empty;
}
}
}
Rather than cut/paste all of that code here, you can take a look at it and see the example application he's got for that. I think what's particularly interesting to this approach is that the view could display that PrivilegeZone link but knows only that something else will determine whether that's the case. So, assuming that you got new requirements to only allow folks who were "Administrators" or "Owners" to have access to the link, you could modify the controller action accordingly and not touch the view code. Interesting idea, at least to me.
Related
What component(s) do I need to implement and how can I hook it into the framework in order to have urls where the query parameters with names containing 2 or more words separated by hyphens?
For example:
I would have this url:
www.mysite.com/resource/i-am-looking-for?parameterOne=foo¶meterTwo=bar
I would like to have it like this:
www.mysite.com/resource/i-am-looking-for?parameter-one=foo¶meter-two=bar
My action would be something like this:
ActionResult DoSomething(string parameterOne, string parameterTwo)
The reason: user friendly urls and consistency
I need to have:
the component to integrate seamlessly with the framework URL helpers (Url.Action, Url.RouteUrl etc)
the incoming data to be bound to the action parameters (or model)
Is there a diagram where I can see the framework extensibility point in this regard?
Thank you!
public ActionResult SomeAction( string Some-Var ) {} is invalid because in C# variable names can not contain hyphens. Underscores ARE allowed, however so this IS valid public ActionResult SomeAction( string Some_Var ) {}
Now, if you relax your need to bind to strongly typed input vars to the action, you can accomplish your goal with Request.QueryString["some-var"] but you will have to handle the type conversion and error handling associated with it.
You can add Custom Value Provider Factory as shown below,
public class MyQueryStringValueProvider : NameValuePairsValueProvider
{
public QueryStringValueProvider(
HttpActionContext actionContext,
CultureInfo culture)
: base(
() =>{
var request = actionContext.ControllerContext;
foreach(var pair in request
.GetQueryNameValuePairs()){
yield return new KeyValuePair<String,String)(
Transform(pair.Key), pair.Value
);
}, culture)
{
}
private String Transform(String key){
// just removing all - , as it is case insensitive
//
return key.Replace("-","");
}
}
And you have to register your provider as shown below,
ValueProviderFactories.Factories.Add(
new MyQueryStringValueProvider());
For safe side, you can remove existing QueryStringValueProvider to avoid name conflicts for keys that does not have dash.
For dashes in Action Name, you can refer https://github.com/AtaS/lowercase-dashed-route
How about url encoding on client's side?
This way you can call controllers using general way. Please have a look one of the answers regarding that: Encode URL in JavaScript?
for example: this is your action.
public ActionResult GetNew(int id)
{
return View(news.Where(i => i.Id == id).SingleOrDefault());
}
First Step
Open App_Start>RouteConfig.cs open in your project.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "GetNew",
url: "news/new-detail/{id}",
defaults: new { controller = "News", action = "GetNew", id = ""}
);
}
}
Now run your project and write your browser http://localhost:….(different for you)/news/new-detail/1
New ID No. 1 will open.
I hope it's been helpful
I know there are lots of (answered) questions relating to attribute-based routing, but I can't seem to find one which answers my particular scenario.
I've got a WebAPI 2 controller, with a few methods using the default routing:
public Dictionary<int, SensorModel> Get()
{
return SensorModel.List();
}
public SensorModel Get(int id)
{
return SensorModel.Get(id);
}
[HttpPost]
public SensorModel Post(SensorModel model)
{
if (model == null) throw new Exception("model cannot be null");
if (model.Id <= 0) throw new Exception("Id must be set");
return SensorModel.Update(model.Id, model);
}
These all work fine. I'm trying to create a nested route as below:
[Route("sensor/{id}/suspend")]
public SensorModel Suspend(int id, DateTime restartAt, EnSite site)
{
return SensorModel.Suspend(id, restartAt, site);
}
For which I would expect the URL to look like:
http://[application_root]/api/sensor/1/suspend?restartAt={someDateTime}&site={anInt}
Sorry, forgot to say that the actual issue is a 404!
Can anyone tell me what I'm doing wrong? I know that I can do it like this:
[Route("sensor/suspend")]
public SensorModel Suspend(int id, DateTime restartAt, EnSite site)
{
return SensorModel.Suspend(id, restartAt, site);
}
Which makes the URL:
http://[application_root]/api/sensor/suspend?id=1&restartAt={someDateTime}&site={anInt}
But a cleaner API design seems to be a nested route, I think.
Your assumption is wrong in this point:
For which I would expect the URL to look like:
http://[application_root]/api/sensor/1/suspend?restartAt={someDateTime}&site={anInt}
It should be something like below:
http://[application_root]/sensor/1/suspend?id=1&restartAt={someDateTime}&site={anInt}
When you specify an attribute based routing, it overrides the default routing architecture of ../api/.. (or whatever you specify in route.config file).
So, whenever you try to use attribute based routing, you should do something like /route_prefix_at_controller_level/route_prefix_at_method_level.
Trying to get UpdateModel to work for my User. The User class has basic string properties like CompanyName, FirstName, LastName, etc so nothing exotic.
Here is the header for my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Public.Master" Inherits="System.Web.Mvc.ViewPage<User>" %>
After they submit, in my controller, the code looks like this:
[HttpPost]
public ActionResult Edit(string id, FormCollection collection)
{
try
{
User myUser = db.Get<IUserRepository>().Get(id);
UpdateModel(myUser);
db.Update(myUser);
return RedirectToAction("Index", "Home");
}
catch
{
return View();
}
}
The values past into FormCollection have the values like:
[0] "FirstName" string
[1] "LastName" string
[2] "Email" string
Here is my UserModelBinder (took out some error checking code) which seems to be the source of the problem:
public class UserModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
IPrincipal p = controllerContext.HttpContext.User;
User u = db.Get(p.Identity.Name);
return u;
}
}
while the myUser I get from the database has all its original values, the UpdateModel for my controller never actually makes any changes. I have read problems people have with ViewModels and which prefix to use, but I am just passing in the regular database object.
The strange thing is that this User edit is for my "Public" area and I already have a User edit for the Admin area which lets the administrator change extra Properties. The "Admin" area User edit is working fine, but the "Public" area for User edit isn't even though the code is almost identical.
Update:
This turned out to be a custom ModelBinding problem and by changing my UserModelBinding to derive from DefaultModelBinder and adding into my BindModel method:
if (bindingContext.Model != null)
return base.BindModel(controllerContext, bindingContext);
Everything seems to work.
Try this instead
public ActionResult Edit(User thisUser)
Id will need to come from a hidden field maybe.
Also you will need to ensure your field names match the User property names.
You shouldn't need to do anything more as the object should have the values within it.
If this is not helpful then let me know and I'll remove this answer
edit
This is one of my update methods
[HttpPost]
public ActionResult EditCustomer(Customer customer)
{
//ensure that the model is valid and return the errors back to the view if not.
if (!ModelState.IsValid)
return View(customer);
//locate the customer in the database and update the model with the views model.
Customer thisCustomer = customerRepository.Single(x => x.CustomerID == customer.CustomerID);
if (TryUpdateModel<Customer>(thisCustomer))
customerRepository.SaveAll();
else
return View(customer);
//return to the index page if complete
return RedirectToAction("index");
}
edit 2
my custom model binder
public class CustomContactUsBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ContactFormViewModel contactFormViewModel = bindingContext.Model as ContactFormViewModel;
if (!String.IsNullOrEmpty(contactFormViewModel.Name))
if (contactFormViewModel.Name.Length > 10)
bindingContext.ModelState.AddModelError("Name", "Name is too long. Must be less than 10 characters.");
base.OnModelUpdated(controllerContext, bindingContext);
}
}
This blog post sounds like it solves your exact problem:
UpdateModel(user, "User");
Since it seems the data you want to bind to is prefixed by the viewmodel name.
I have a number of permissions, and based on a set of conditions these permission determine if a user can see certain features. I have written a helper function for this as the logic in the view became quite extensive.
Essentially I'm looking for a function the same as Html.ActionLink that I can access from a class file (Ideally if I can access the Helper that would be great) So I can do somthing like so,
public static string GetAdminLinks()
{
if(PermCheck)
{
return(Html.ActionLink(...));
}
}
Any sugestions?
in controller:
Url.Action("Index", "Home", null, Request.Url.Scheme);
It largely depends on how your permission check is implemented (and of which information it needs to determine the user's permissions). Anyhow, I'd implement it as an extension to the HtmlHelper class.
Somewhere in your App_Code:
using System.Web.Mvc.Html;
public static class HtmlHelperExtensions {
public static string SecureActionLink(this HtmlHelper htmlHelper, string action, string controller){
if(PermCheck)
return htmlHelper.ActionLink(action, controller);
else
return string.Empty;
}
//add other ActionLink overrides if you like...
}
Then you'll be able to call the extension method from anywhere in your ViewPages without any code behind.
I have a fairly large ASP MVC application. Instead of having many controllers all in the controller directory I would rather create some hierarchy. So I might have something like
~\Controllers\Security\
~\Controllers\Maintenance\
~\Controllers\Reports\
I would also like to be able to do similar with Views
~\Views\Security\Users\
~\Views\Security\Roles\
~\Views\Maintenance\Customer\
~\Views\Maintenance\Product\
Is this easily done?
I think you're looking for something like what the "master" says in this post:
http://haacked.com/archive/0001/01/01/areas-in-aspnetmvc.aspx
Basically you have to create a ViewEngine to specify where to look for the views. It's a fairly simple code, just don't forget to register it in the global.asax! As for the controller part you'll have to register new routes also in the global.asax.
The concept you're searching for is called "areas", as outlined by Phil Haack here:
http://haacked.com/archive/2008/11/04/areas-in-aspnetmvc.aspx
I would think you'd need to write your own RouteHandler, which shouldn't be too tough.
A quick google search turned up: This blog post detailing it
Have you considered to switch to Features Folder. I've tried it (with little modifications) and it works pretty good.
Described in this post
http://timgthomas.com/2013/10/feature-folders-in-asp-net-mvc/
Code examples are in Jimmiy Bogard's repo
https://github.com/jbogard/presentations/tree/master/putyourcontrollersonadietv2
What you really want here is for your Views folder hierarchy to match the namespace hierarchy of your controllers. You can write a custom ViewEngine to do this fairly easily - see my ControllerPathViewEngine project on GitHub for details.
I've included a snippet of the ControllerPathRazorViewEngine class to outline how it works. By intercepting the FindView / FindPartialView methods and replacing the controller name with a folder path (based on controller namespace and name), we can get it to load views from nested folders within the main Views folder.
public class ControllerPathRazorViewEngine : RazorViewEngine
{
//... constructors etc.
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
return FindUsingControllerPath(controllerContext, () => base.FindView(controllerContext, viewName, masterName, useCache));
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
return FindUsingControllerPath(controllerContext, () => base.FindPartialView(controllerContext, partialViewName, useCache));
}
private ViewEngineResult FindUsingControllerPath(ControllerContext controllerContext, Func<ViewEngineResult> func)
{
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string controllerPath = controllerPathResolver.GetPath(controllerContext.Controller.GetType());
controllerContext.RouteData.Values["controller"] = controllerPath;
var result = func();
controllerContext.RouteData.Values["controller"] = controllerName;
return result;
}
}