Web Api - how to stop the web pipeline directly from an OnActionExecuting Filter - asp.net

I have a pre-action web api hook that will check ModelState.IsValid. If the ModelState is not valid I do not want to execute the action and just return my message immediately. How exactly do I do this?
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) {
if (!actionContext.ModelState.IsValid)
{
var msg = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
// Now What?
}
base.OnActionExecuting(actionContext);
}
}

set the Response.Result. If the result is not null it will not execute the action. the exact syntax is escaping me right now, but it's as simple as
if(actionContext.ModelState.IsValid == false)
{
var response = actionContext.Request.CreateErrorResponse(...);
actionContext.Response = response;
}

Have you actually seen the example on the ASP.NET WebApi page?
Looks very much like what you're trying to achieve and all they do is setting the Response of the Context object:
If model validation fails, this filter returns an HTTP response that contains the validation errors. In that case, the controller action is not invoked.
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
see: Handling Validation Errors

My guess is that you should throw a HttpResponseException

Related

How to catch or intercept ODataException to return custom error payload

I am refactoring an ASP.NET WEB API solution that uses Odata.
When dealing with errors I would like to provide a custom error payload which is defined in my CustomException class.
The issue is that when I make a bad request the generated response is the ODataException error payload which contains some confidential information that I don't want exposed and also the stack trace.
I need to modify this Odata payload and replace it with my own.
So far what I've tried is to use Exception Filters applied on Controller level and also tried to register an Exception Handler on global level. None of these worked.
Any help would be greatly appreciated.
I was able to resolve it with Exception Filter, I was using the wrong method before:
public class CustomExceptionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ErrorContentResponse error = new ErrorContentResponse();
var response = new HttpResponseMessage(actionExecutedContext.Response.StatusCode)
{
Content = new ObjectContent<ErrorContentResponse>(error, new JsonMediaTypeFormatter())
};
actionExecutedContext.Response = response;
}
}

Accessing actual request in Message Handlers and Action filters in Web API

This is more of a design related question, and any help/pointers in the right direction is highly appreciated.
I am working on a ASP.NET Web API2 application, and have an Authorization filter and other Action filters. Within these filters, I need to access the Request object that comes as a part of the HttpPost request body.
I use the following code to read the request body content and deserialize into the desired object. It works fine.
//actionContext is HttpActionContext
var requestContent = actionContext.Request.Content.ReadAsStringAsync();
var request = JsonSerializer.GetObject<BaseOTARequestModel>(requestContent.Result);
To serve a particular request, I am deserializing the request content twice (I have 2 filters). Once the request reaches the controller action, it is deserialized again by the Web API framework. I feel guilty that every request is deserialized 3 times, and have a feeling there is a better way to handle this.
How can I avoid deserializing the request multiple times in a request?
I took this on as a challenge and came up with this solution. Here's a base filter attribute class:
public abstract class BaseOTARequestFilterAttribute : ActionFilterAttribute
{
private HttpActionContext _actionContext;
protected BaseOTARequestModel RequestModel
{
get
{
if (_actionContext.Request.Properties.ContainsKey("RequestModel"))
{
return _actionContext.Request.Properties["RequestModel"] as BaseOTARequestModel;
}
return null;
}
set
{
_actionContext.Request.Properties["RequestModel"] = value;
}
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_actionContext = actionContext;
if (RequestModel == null)
{
//actionContext is HttpActionContext
var requestContent = actionContext.Request.Content.ReadAsStringAsync();
RequestModel = JsonSerializer.GetObject<BaseOTARequestModel>(requestContent.Result);
}
}
}
This base class handles your deserialization and uses the Request.Properties collection to store it. (OK, I know a Web API is stateless but this state only exists during the execution of the request so fine imho.)
Your various attributes should all inherit this base class and can use the derialized object as follows:
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var data = RequestModel;
// etc.
}
This may not be the most elegant solution, but I believe it works. Interested to hear the views of others.

Angularjs - Spring MVC integration

I am trying to integrate AngularJS with Spring MVC; but I am not able to post parameters to spring controller as RequestBody. Can some one help me to achieve the same. Below is brief flow of my program.
After doing data entry TodoNewController gets executed. From here I am calling user-defined method "create" which I have defined in services.js. As per the flow after this it should call create method of TodoController.java along with input params; but it is not happening. Can some one let me know what is wrong with the code. Below is the code for same.
controller.js
function TodoNewController($scope, $location, Todo) {
$scope.submit = function () {
Todo.create($scope.todo, function (todo) {
$location.path('/');
});
};
$scope.gotoTodoListPage = function () {
$location.path("/")
};
}
services.js
angular.module('todoService', ['ngResource']).
factory('Todo', function ($resource) {
return $resource('rest/todo/:id', {}, {
'create': {method:'PUT'}
});
});
TodoController.java
#Controller
public class TodoController {
private static final AtomicLong todoIdGenerator = new AtomicLong(0);
private static final ConcurrentSkipListMap<Long, Todo> todoRepository = new ConcurrentSkipListMap<Long, Todo>();
#RequestMapping(value = "/todo", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void create(#RequestBody Todo todo) {
long id = todoIdGenerator.incrementAndGet();
todo.setId(id);
todoRepository.put(id, todo);
}
}
Spring expects application/x-www-form-urlencoded as the Content-Type of the request. You may try inject $http into your service and invoke $http.defaults.headers.put["Content-Type"] = "application/x-www-form-urlencoded"; at the beginning of it.
Modify the request mapping to match the actual mapping /rest/todo and change the databinding to use #ModelAttribute.
#RequestMapping(value = "/rest/todo", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void create(#ModelAttribute Todo todo) {
long id = todoIdGenerator.incrementAndGet();
todo.setId(id);
todoRepository.put(id, todo);
}
Isolate the problem first. Is it Spring or Angular that's causing the issue? I suggest you install a Rest client plugin either in Chrome or FireFox. Then create a PUT request and enter the correct endpoint URL. If you're able to receive the correct response, then it means your Angular request is constructed incorrectly.
Now, run your Angular-based client. Make a PUT request. Inspect the parameters and request sent (in Chrome, you can use Developer tools) and see if it matches the request you sent earlier. If it does, then it should work. If not, then you know the problem.
Also, your Angular resource:
$resource('rest/todo/:id')
has a different URL than what you have in your Spring controller
#RequestMapping(value = "/todo", method = RequestMethod.PUT)
So the first one is like 'rest/todo/1' and the latter is '/todo'. I don't think those would match.

Spring MVC Validation - Avoiding POST-back

I'd like to validate a Spring 3 MVC form. When an element is invalid, I want to re-display the form with a validation message. This is pretty simple so far. The rub is, when the user hits refresh after an invalid submission, I don't want them to POST, I want them to GET. This means I need to do a redirect from the form POST (submission) to re-display the form with validation messages (the form is submitted via a POST).
I'm thinking the best way to do this is to use SessionAttributeStore.retrieveAttribute to test if the form is already in the user's session. If it is, use the store form, otherwise create a new form.
Does this sound right? Is there a better way to do this?
To solve this problem, I store the Errors object in the session after a redirect on a POST. Then, on a GET, I put it back in the model. There are some holes here, but it should work 99.999% of the time.
public class ErrorsRedirectInterceptor extends HandlerInterceptorAdapter {
private final static Logger log = Logger.getLogger(ErrorsRedirectInterceptor.class);
private final static String ERRORS_MAP_KEY = ErrorsRedirectInterceptor.class.getName()
+ "-errorsMapKey";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView mav)
throws Exception
{
if (mav == null) { return; }
if (request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
// POST
if (log.isDebugEnabled()) { log.debug("Processing POST request"); }
if (SpringUtils.isRedirect(mav)) {
Map<String, Errors> sessionErrorsMap = new HashMap<String, Errors>();
// If there are any Errors in the model, store them in the session
for (Map.Entry<String, Object> entry : mav.getModel().entrySet()) {
Object obj = entry.getValue();
if (obj instanceof Errors) {
if (log.isDebugEnabled()) { log.debug("Adding errors to session errors map"); }
Errors errors = (Errors) obj;
sessionErrorsMap.put(entry.getKey(), errors);
}
}
if (!sessionErrorsMap.isEmpty()) {
request.getSession().setAttribute(ERRORS_MAP_KEY, sessionErrorsMap);
}
}
} else if (request.getMethod().equalsIgnoreCase(HttpMethod.GET.toString())) {
// GET
if (log.isDebugEnabled()) { log.debug("Processing GET request"); }
Map<String, Errors> sessionErrorsMap =
(Map<String, Errors>) request.getSession().getAttribute(ERRORS_MAP_KEY);
if (sessionErrorsMap != null) {
if (log.isDebugEnabled()) { log.debug("Adding all session errors to model"); }
mav.addAllObjects(sessionErrorsMap);
request.getSession().removeAttribute(ERRORS_MAP_KEY);
}
}
}
}
It's not clear from your question but it sounds like your GET and POST actions are mapped to the same handler. In that case you can do something like:
if ("POST".equalsIgnoreCase(request.getMethod())) {
// validate form
model.addAttribute(form);
return "redirect:/me.html";
}
model.addAttribute(new MyForm());
return "/me.html";
In the JSP check if there are any error on the form and display as needed.
Such approach is called PRG (POST/REdirect/GET) design pattern I explained it few days ago as one of the answers:
Spring MVC Simple Redirect Controller Example
Hope it helps :)

ASP.NET MVC 2.0 JsonRequestBehavior Global Setting

ASP.NET MVC 2.0 will now, by default, throw an exception when an action attempts to return JSON in response to a GET request. I know this can be overridden on a method by method basis by using JsonRequestBehavior.AllowGet, but is it possible to set on a controller or higher basis (possibly the web.config)?
Update: Per Levi's comment, this is what I ended up using-
protected override JsonResult Json(object data, string contentType, System.Text.Encoding contentEncoding)
{
return Json(data, contentType, JsonRequestBehavior.AllowGet);
}
This, like other MVC-specific settings, is not settable via Web.config. But you have two options:
Override the Controller.Json(object, string, Encoding) overload to call Json(object, string, Encoding, JsonRequestBehavior), passing JsonRequestBehavior.AllowGet as the last argument. If you want this to apply to all controllers, then do this inside an abstract base controller class, then have all your controllers subclass that abstract class.
Make an extension method MyJson(this Controller, ...) which creates a JsonResult and sets the appropriate properties, then call it from your controller via this.MyJson(...).
There's another option. Use Action Filters.
Create a new ActionFilterAttribute, apply it to your controller or a specific action (depending on your needs). This should suffice:
public class JsonRequestBehaviorAttribute : ActionFilterAttribute
{
private JsonRequestBehavior Behavior { get; set; }
public JsonRequestBehaviorAttribute()
{
Behavior = JsonRequestBehavior.AllowGet;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var result = filterContext.Result as JsonResult;
if (result != null)
{
result.JsonRequestBehavior = Behavior;
}
}
}
Then apply it like this:
[JsonRequestBehavior]
public class Upload2Controller : Controller
MVC 2 block Json for GET requests for security reasons. If you want to override that behavior, check out the overload for Json that accepts a JsonRequestBehavior parameter.
public ActionResult Index()
{
return Json(data, JsonRequestBehavior.AllowGet)
}
I also got this error when I first use MVC 2.0 using my old code in MVC 1.0. I use fiddler to identify the cause of the error. See the steps on how to troubleshoot it using Fidder -
http://www.rodcerrada.com/post/2011/07/11/jQuery-getJSON()-does-not-tirgger-the-callback-in-ASPNET-MVC-2.aspx
Is this is the security issue MVC2 was trying to address?
http://haacked.com/archive/2009/06/25/json-hijacking.aspx
If so, it seems like the vulnerability is only an issue if you are trying to do a json call to an outside website. If your MVC2 app is only making json calls to your own website (to fill jqgrids for example), shouldn't you be able to safely override the Json call in your base controller to always allow get?
Just change JSON code from :
$.getJson("methodname/" + ID, null, function (data, textStatus)
to:
$.post("methodname/" + ID, null, function (data, textStatus)

Resources