4

In my ASP .NET MVC 3 web application, I am using a lot of partial views. I am using these partial views in some cases through normal render calls

<div id="attributes">
   @Html.Partial("_DeviceAttributesPartial", Model.DeviceAttributes)
</div>

and in other cases using AJAX:

$.ajax({
   url: '@Url.Action("GetDeviceAttributes")',
   type: 'POST',
   data: { deviceID: device, deviceTypeID: devicetype, deviceModelID: devicemodel },
   success: function (result) {
      // when the AJAX succeeds refresh the device model drop down holder
      $('#attributes').html(result);
   }
});

I was trying to find a way to stop users from going directly to my partial view ActionResults such as this one:

public ActionResult GetDeviceModelList(int deviceTypeID)
{
   var model = new EditDeviceViewModel();

   var deviceType = _db.DeviceTypes.Single(t => t.ID == deviceTypeID);
   model.DeviceModelList = new SelectList(_db.DeviceModels.Where(m => m.DeviceType.ID == deviceType.ID), "ID", "Model");

   return PartialView("_DeviceModelListPartial", model);
}

I stumbled up on this answer to simply make the action private. I gave it a try and it seems to work, however I feel uneasy about doing that, not knowing what other side effects might happen.

So my questions are:

  • Is setting actions to private a sensible thing to do?
  • What other side effects might occur from doing this?
  • How about actions that are only available through a POST?

NB: Most of the partial action result functions are [HttpPost] so I don't believe they are accessible anyway.

Community
  • 1
  • 1
dnatoli
  • 6,972
  • 9
  • 57
  • 96
  • The function in my question is actually already a `POST`, I was just using it as an example and knew that if I included the `[HttpPost]' it would ruin the example. But thanks for your input! :) – dnatoli Sep 13 '11 at 06:11
  • So just to confirm, are you calling these partial views from AJAX (jQuery) or are you using them to build another ActionResult? – Alastair Pitts Sep 13 '11 at 06:16
  • @Alastair - Updated question with the answer to your question. – dnatoli Sep 13 '11 at 06:29

3 Answers3

3

Reducing an action visibility will prevent that action from being available outside the controller. Actions that are decorated by the [HttpPost] attribute are available to everybody but only via a POST http request, which is a little beyond the average user to do.

Consider decorating the actions with the [ChildActionOnly] attribute that you won't be accessing via a POST, instead decorate these actions with [HttpPost].

Adrian Toman
  • 11,316
  • 5
  • 48
  • 62
  • 1
    Using the [ChildActionOnly] attribute returns a HTTP 500 error when I'm using AJAX. – dnatoli Sep 13 '11 at 07:27
  • My answer didn't keep pace with the question. Use [ChildActionOnly] on any action that you never want users to directly request and are never called via an AJAX POST request. Remove [ChildActionOnly] from any action that also has [HttpPost] as the [ChildActionOnly] is preventing the AJAX POST request. – Adrian Toman Sep 13 '11 at 08:08
  • As for the ones that are called by an AJAX POST request? Is there any way I can stop the routes being created for those actions? – dnatoli Sep 13 '11 at 23:37
  • No. You need the routes so you can call the action from javascript as per your example. There will be no GET routes for these actions, therefore a user can't invoke an action by typing a URL into a browser. – Adrian Toman Sep 14 '11 at 04:47
  • So in other words, use it if the action is a GET and I don't want users to access it directly, otherwise using the `HttpPostAttribute` will be sufficient enough for what I want. – dnatoli Sep 14 '11 at 05:06
  • Yes :) I wished I put it that succinctly to start with. – Adrian Toman Sep 14 '11 at 05:25
2

You could create a filter that checks if the request is an ajax request and if not returns a 404

public class AjaxResult : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new HttpNotFoundResult();
        }

        base.OnActionExecuting(filterContext);
    }
}

You can then decorate your controller method with

[AjaxResult]

Depending on your circumstances, you could also

filterContext.Result = new HttpUnauthorizedResult();

This will return a 401 instead of a 404.

You will still be able to use RenderPartial and block any non-ajax requests to the action result.

After attempting an implementation of this myself, it is important to note that you must ensure the HTTP header "X-Requested-With" is set to "XMLHttpRequest". With Ajax requests, this obviously is not an issue, but with angular you will need to tag this on the end of your module declaration:

.run(function ($http) {
       $http.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
 });
Joel Clark
  • 43
  • 1
  • 5
1

Marking an action private makes this action inaccessible. It's like as if the action no longer existed. When you used partials with the RenderPartial method you are not invoking the corresponding controller action. If you invoke the controller action with Html.RenderAction then you could decorate this action with the [ChildActionOnly] attribute. You may take a look at the following blog post to better understand the difference between the two.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Using the [ChildActionOnly] attribute return a HTTP 500 error when I'm using AJAX. – dnatoli Sep 13 '11 at 07:28
  • 1
    @link664, that's normal. Child actions are not supposed to be invoked independently. If you want to invoke the action with AJAX you need to use a standard action as you did. Of course making this action private will no longer work with your AJAX calls. So if an action should be invoked with AJAX it could also be invoked directly. Now if the action is marked with HttpPost then it cannot be invoked by typing the url in the address bar. But do not make it private. – Darin Dimitrov Sep 13 '11 at 07:56