I'm using MVC 5 and I'm calling an Action from a View via Html.RenderAction(). I want to pass an ID parameter to the Action and I do it like so:
Html.RenderAction("MyAction", "MyController", new { id = resourceID });
My Action is something like this: public ActionResult MyAction(int id)
This works fine. However, I want to decorate the Action with my own custom AuthorizeAttribute in order to check if the current user has access to the resource with this specific ID. In my custom AuthorizeAttribute class I have something like this:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!base.AuthorizeCore(httpContext))
{
return false;
}
RouteData rd = httpContext.Request.RequestContext.RouteData;
var resourceID = rd.Values["id"];
SessionInfo sessionInfo = HttpContext.Current.Session["SessionInfo"] as SessionInfo;
int userID = sessionInfo.UserInfo.UserID;
return AuthorizationBusiness.DoesUserHaveAccessToResource(userID, int.Parse(resourceID.ToString()));
}
The problem is that the original Http request does not contain the id parameter, so resourceID will be null here. In order to workaround this problem, I added this parameter to the Request before calling RenderAction, like this:
Context.Request.RequestContext.RouteData.Values["id"] = resourceID;
Html.RenderAction("MyAction", "MyController", new { id = resourceID });
Now I can retrieve the ID within the AuthorizeAttribute, however, this doesn't feel like the right way to do it. What's the best way to achieve this? And can you tell me if there are any downsides in the way I'm doing it?
Thanks in advance!
If I understand correctly:
You are calling Html.RenderAction("MyAction", "MyController", new { id = resourceID });
This return a view on which Html.Action method is called
Action which is called by Html.Action is decorated with custom authorization attribute in order to check if the user is authorized to access it
If my understanding is correct then I would not implement additional authorize attribute but in the action which is called by Html.RenderAction("MyAction", "MyController", new { id = resourceID }) check if the user is authorized and if not modify the returned view so that the Html.Action won't be called.
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.
I have an action filter that redirects to route when certain criteria is met.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (someValue)
{
filterContext.Result = new RedirectToRouteResult("MyCustomRoute", new RouteValueDictionary());
}
base.OnActionExecuting(filterContext);
}
and this is my custom route:
routes.MapRoute("MyCustomRoute",
"customRoute/",
new { controller = "Custom", action = "CustomAction" },
new[] { "CustomControllerNamespace" });
This is triggered when a user navigates to a route for example: /someRoute?param1=1¶m2=2¶m3=3
When I debug the action filter I can see that inside filterContext.ActionParameters I have 2 models containing the parameters passed in the url.
This is my action:
public ActionResult CustomAction(CustomModel model, CustomModel2 model2)
{// do something}
This action has the same parameters as the original action.
The problem is that when I redirect to this action from the action filter the parameters in model and model 2 are null.
If I remove the original route from the RouteTable and create a route with the same name and url but with different controller, the action filter is triggered and when I redirect to the CustomAction the models are populated with the values.
Do you know how I can pass the ActionParameters when I redirect to another route? I don't want to pass them inside the RouteValueDictionary.
Do you have any idea why this is happening?
EDIT:
After some tweaking I found that I can pass the parameters from the 2 models by manually passing each parameter to a RouteValueDictionary, that I use in RedirectToRouteResult.
var model = filterContext.ActionParameters["model"] as CustomModel;
var model2= filterContext.ActionParameters["model2"] as CustomModel2;
var routeValues = new RouteValueDictionary();
if (model != null && model2!= null)
{
routeValues = new RouteValueDictionary(new
{
Param1 = model.Param1,
Param2 = model.Param2,
Param3 = model2.Param3
});
}
filterContext.Result = new RedirectToRouteResult("MyCustomRoute", routeValues);
Is there any other way to do this? I don't think that this is a good practice and if a new property is added or changed I'll have to change this code.
As far as I know, you can't pass ViewModels (complex objects) to methods such as RedirectToAction and RedirectToRoute.
There are various methods to overcome this, one of them being TempData as shown here.
My existing MVC code contains an action routine something like this:
[HttpPost]
public ActionResult Register1(SomeViewModel model)
{
return RedirectToAction("Register", new { p = model.PageNumber - 1 });
}
I want to move this code to a library routine:
public static ActionResult ProcessPost(Controller controller, string action,
int pageNumber)
{
// Redirect to the specified action on the specified controller
return new RedirectToRouteResult( <something here> );
}
and call it from the action routine like this:
return ProcessPost(this, "register", model.PageNumber);
Can some kind person give me the <something here> code that yields an ActionResult that redirects to the specified action (specified by the string argument) on the specified Controller (specified by the Controller argument?
Taking a look at the documentation on RedirectToRouteResult seems pretty straight forward:
var routeValues = new RouteValueDictionary();
routeValues.Add("Action", action);
routeValues.Add("Controller", controller.GetType().Name);
routeValues.Add("PageNumber", pageNumber);
var result = new (RedirectToRouteResult(routeValues);
After some experimentation, this appears to be a simple solution:
return new RedirectResult(controller.Url.RouteUrl(
new { action = action, p = pageNumber }
));
Apparently, the Url method on a specific Controller instance is smart enough to use that instance to get the controller name part of the full URL.
After reviewing A LOT of questions and Internet data, I've solved a problem of mine with getting URL parameter from MVC3 application correctly.
Thing is that there wasn't a fault in coding, but in routing (I'm not so good with routing...).
Here's the current issue.
http://localhost:51561/Report/Details/1
This is the way my application presents Report details, which is good. But when it does it like this, I can't get value from URL parameter, like this
Request.QueryString["id"]
But, when I manually type in URL http://localhost:51561/Report/Details?id=1 it works...
Thing is i like the first URL type, but I don't know how to get parameter from it...
Help, please...
Update:
My Controller actions:
public ViewResult Details(int id)
{
Report report = db.Reports.Find(id);
ViewBag.TestID = Request.QueryString["id"].ToString();
return View(report);
}
public ActionResult Show(int id)
{
Report report = db.Reports.Find(id);
var imageData = report.Image;
return (File(imageData, "image/jpg"));
}
My View:
<div class="display-label">Picture</div>
<div class="display-field">
<img alt="image" src="<%=Url.Action("Show", "Report", new { id = ViewBag.TestID })%>" width="200px" />
</div>
First of all, you shouldn't use Request.QueryString in your application. Apart from that, in the first URL, you don't have a query string, and thus you can't access it (also read this article on msdn about Request.QueryString).
I also would like to suggest you to go through the basic tutorial of ASP.NET MVC3, to be found here. Many things like your question are thoroughly explained there.
To answer your question now, in your first URL example, the 1 in the URL is a parameter of your action (the Details action). You have to add this parameter to your method (action):
public ActionResult Details(int id)
UPDATE:
You have apparently the right action (method) declaration. Now, you can just use the parameter id. So change the Request.QueryString["id"] just by the variable (parameter) id.
public ViewResult Details(int id)
{
Report report = db.Reports.Find(id);
ViewBag.TestID = id;
return View(report);
}
There is no need to apply ToString() on the id, you shouldn't make it when it isn't necessary (you might need it somewhere else, later or so). Just put it in the ViewBag as the original type.
Your Show() method is good :). You have now the id parameter as you needed. (Try to avoid too many parentheses, it makes it look messy and now so clear.)
public ActionResult Show(int id)
{
Report report = db.Reports.Find(id);
var imageData = report.Image;
return File(imageData, "image/jpg");
}
You're not supposed to use Request.QueryString["id"] in MVC
Just add id parameter to your ReportController.Details action:
public ActionResult Details (int id)
The above is assuming you have a default route setup in Global.asax:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I'm trying to pass a complex object (that can be serialized, if that helps) to another view.
Currently this is the code i have, in some controller method :-
User user = New User { Name = "Fred, Email = "xxxx" };
return RedirectToAction("Foo", user);
now, i have the following action in the same controller ...
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Foo(User user)
{
...
}
When i set a breakpoint in there, the code does stop there, but the value of user is null.
What do i need to do? Am i missing something in the global.asax?
cheers :)
Put your User object in TempData. You can't pass it as a parameter.
TempData["User"] = new User { Name = "Fred", Email = "xxxx" };
return RedirectToAction("Foo");
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Foo()
{
User user = (User)TempData["User"];
...
}
Similar to How can I maintain ModelState with RedirectToAction?