How to add model attributes to the default error page - spring-mvc

What is the best way to handle default page not found error when a user requests a url that doesn't have any mapping in the application (e.g. a url like /aaa/bbb that there is no mapping for it in the application) while be able to add model attributes to the page?

There is some anserws in SO but those have caused me other problems and more importantly they don't state how to add model attributes to the error page.
The best solution I have found is this:
Create a controller that implements ErrorController and overrides
its getErrorPath() method.
In that controller create a handler method annotated with
#RequestMapping("/error")
It's in this method that you can add whatever model attributes you want and return whatever view name you want:
#Controller
public class ExceptionController implements ErrorController {
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping("/error")
public String handleError(Model model) {
model.addAttribute("message", "An exception occurred in the program");
return "myError";
}
}
Now if you want to handle other specific exceptions you can create #ExceptionHandler methods for them:
#ExceptionHandler(InvalidUsernameException.class)
public String handleUsernameError(Exception exception, Model model) {
model.addAttribute("message", "Invalid username");
return "usernameError";
}
Notes:
If you add specific exception handlers in the class, don't forget to annotate the controller with #ControllerAdvice (along with the #Controller)
The overridden getErrorPath() method can return any path you want as well as /error.

Related

Set ViewBag property in the constructor of a ASP.NET MVC Core controller

My theme has some sort of breadcrumb. The controller is always the category. To avoid repeat myself, I want to set it in the constructor of the controller for all actions like this:
class MyController:Controller{
public MyController() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
When I access ViewBag.BreadcrumbCategory in the layout-view, its null. In a Action it works:
class MyController:Controller{
public IActionResult DoSomething() {
ViewBag.BreadcrumbCategory = "MyCategory";
}
}
I'm wondering that setting a ViewBag property is not possible in a constructor? It would be annoying and no good practice to have a function called on every action which do this work. In another question using the constructor was an accepted answear, but as I said this doesn't work, at least for ASP.NET Core.
There is an GitHub issue about it and it's stated that this is by design. The answer you linked is about ASP.NET MVC3, the old legacy ASP.NET stack.
ASP.NET Core is written from scratch and uses different concepts, designed for both portability (multiple platforms) as well as for performance and modern practices like built-in support for Dependency Injection.
The last one makes it impossible to set ViewBag in the constructor, because certain properties of the Constructor base class must be injected via Property Injection as you may have noticed that you don't have to pass these dependencies in your derived controllers.
This means, when the Controller's constructor is called, the properties for HttpContext, ControllerContext etc. are not set. They are only set after the constructor is called and there is a valid instance/reference to this object.
And as pointed in the GitHub issues, it won't be fixed because this is by design.
As you can see here, ViewBag has a dependency on ViewData and ViewData is populated after the controller is initialized. If you call ViewBag.Something = "something", then you it will create a new instance of the DynamicViewData class, which will be replaced by the one after the constructor gets initialized.
As #SLaks pointed out, you can use an action filter which you configure per controller.
The following example assumes that you always derive your controllers from Controller base class.
public class BreadCrumbAttribute : IActionFilter
{
private readonly string _name;
public BreadCrumbAttribute(string name)
{
_name = name;
}
public void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
var controller = context.Controller as Controller;
if (controller != null)
{
controller.ViewBag.BreadcrumbCategory = _name;
}
}
}
Now you should be able to decorate your controller with it.
[BreadCrumb("MyCategory")]
class MyController:Controller
{
}
I have the same issue and solve it overriding the OnActionExecuted method of the controller:
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
ViewBag.Module = "Production";
}
Here is a better way to do this for .NET Core 3.x, use the ResultFilterAttribute:
Create your own custom filter attribute that inherits from ResultFilterAttribute as shown below:
public class PopulateViewBagAttribute : ResultFilterAttribute
{
public PopulateViewBagAttribute()
{
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
(context.Controller as MyController).SetViewBagItems();
base.OnResultExecuting(context);
}
}
You'll need to implement the method SetViewBagItems to populate your ViewBag
public void SetViewBagItems()
{
ViewBag.Orders = Orders;
}
Then Decorate your Controller class with the new attribute:
[PopulateViewBag]
public class ShippingManifestController : Controller
That's all there is to it! If you are populating ViewBags all over the place from your constructor, then you may consider creating a controller base class with the abstract method SetViewBagItems. Then you only need one ResultFilterAttribute class to do all the work.

Accessing AuthorizationAttribute within Controller

I have a custom AuthorizeAttribute written in MVC. I have it applied to a controller for security. In that AuthorizeAttribute class I have written are several variables I gathered from a web service call I would like to access inside the controller to prevent having to call the web service again. Is this possible?
Your best approach would be to use HttpContext.Current.Items for storing those variables because that data will only be valid for a single http request. Something like this:
public class CustomAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity == null) return false;
if (!httpContext.Request.IsAuthenticated) return false;
var user = new WSUser(); //get this from your webservice
if(user == null) return false;
httpContext.Items.Add("prop", user.Property);
return user.Authorized;
}
}
public class HomeController : Controller
{
[CustomAuthorize]
public ActionResult Index()
{
var property = (string) HttpContext.Items["prop"];
return View();
}
}
You would also want to encapsulate logic for storing and retrieving items from HttpContext.Current into a separate class to keep the code clean and to follow Single responsibility principle
You could save these variables in a static class to store it. But, a elegant solution would be to have a modelbinder object that you call like parameter in your controller and that read the static class and return the properties that you need.
Perhaps, if you are applying security, the best will be that call the webservices each once.
Reference for your custom model binder

Configure multiple controllers in Spring MVC, and call one controller's method from another controller

I am trying to configure multiple controller in my application and also trying to redirect from one controller to other.
Error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'nc' bean method.
EDIT :
First Controller
#Controller
#RequestMapping(value = "/nc")
public class StockController {
#RequestMapping(value = "/testMap", method = RequestMethod.GET)
public String redirectToStockList(#RequestParam(value = "testInput") String testInput) {
System.out.println("In StockController..!!");
return "SampleTamplate";
}
}
Second Controller
#Controller
public class WelcomeController {
#Autowired
private UsersServiceImpl serviceImpl;
private String redirectedURL;
private static final Logger logger = LoggerFactory
.getLogger(WelcomeController.class);
#RequestMapping(value = { "/", "/login" }, method = RequestMethod.GET)
public String login(#RequestParam(value = "username") String username) {
logger.debug("In login() method.");
System.out.println("In WelcomeController..!!");
return "Login";
}
}
jsp:
First Form:
<form action="testMap" method="post">
<input type="text" class="form-control" name="testInput"/>
</form>
Second Form:
<form action="login" method="post">
<input type="text" class="form-control" name="username"/>
</form>
When I submit both forms one by one, control goes to 'WelcomeController' every time. And for first form, It gives 'resources not found' error that's OK because there is no mapping present as "/testMap" in welcome controller.
So what I want is, to call specific controller on my form submission and also call one controller's method from another controller.
Any help would be appreciated.
I will try to answer this question in both Grails and Spring way as this is the best time to introduce Grails here.
In spring when call is leaving from controller then RequestDispatcher actually helps to catch the call or to check the exact view resolver. Now, as you want to transfer call to another controller here sping provides API (http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#mvc-redirecting) Inshort you have to use view name like "forward:controllerName" like
#RequestMapping({"/someurl"})
public String execute(Model model) {
if (someCondition) {
return "forward:/someUrlA";
} else {
return "forward:/someUrlB";
}
In grails there is forward method you can find in controller API which does things for you (http://grails.github.io/grails-doc/2.0.4/api/org/codehaus/groovy/grails/plugins/web/api/ControllersApi.html). It actually passes the same session to next requested controller.
Request you to please try to use it. Hope this help.
In the below example the LoginController redirects to the /menu URL if there are validation errors upon submitting a login form by calling the menuMapping() method that resides within the MenuController class.
(NOTE: I have included the use of the BindingResult class and a hypothetical form as this would be a valid reason for wanting to redirect to another controller. However, below solution would still work as well without the BindingResult and if statement without the use of a form).
#Controller
public class LoginController {
MenuController menuController;
#RequestMapping(value = "/login")
public String loginMapping(BindingResult result){
if(result.hasErrors) {
return "login";
}
else {
return menuController.menuMapping();
}
}
With your MenuController in another class like so:
#Controller
public class MenuController {
#RequestMapping(value = "/menu")
public String menuMapping(){
return "menu";
}
}
(EDIT: if you wanted to apply the redirect and the controller methods were within the same class then the loginMapping return statement would simply be return menuMapping(); rather than return menuController.menuMapping();

How to make #PreAuthorize having higher precedence than #Valid or #Validated

I am using spring boot, and I have enabled the global method security in WebSecurityConfigurerAdapter by
#EnableGlobalMethodSecurity(prePostEnabled = true, order = Ordered.HIGHEST_PRECEDENCE)
And Below is my controller code
#PreAuthorize("hasAnyRole('admin') or principal.id == id")
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public User updateUser(#PathVariable("id") String id, #Valid #RequestBody UserDto userDto)
{ ....}
However, when a non-admin user try to do a PUT request, the JSR303 validator will kick in before #PreAuthorize.
For example, non-admin user ended up getting something like "first name is required" instead of "access denied". But after user supplied the first name variable to pass the validator, access denied was returned.
Does anyone know how to enforce the #PreAuthorize get checked before #Valid or #Validated?
And I have to use this kind of method-level authorization instead of url-based authorization in order to perform some complex rule checking.
I had the same issue and I found this post. The comment of M. Deinum helps me to understand what was going wrong
Here is what I did :
The public method has the #PreAuthorize and do the check
There is NO #Valid on the #RequestBody parameter
I create a second method, private, where I do the DTO validation. Using the #Valid annotation
The public methods delegates the call to the private one. The private method is called only is the public method is authorized
Example :
#RequestMapping(method = RequestMethod.POST)
#PreAuthorize("hasRole('MY_ROLE')")
public ResponseEntity createNewMessage(#RequestBody CreateMessageDTO createMessageDTO) {
// The user is authorized
return createNewMessageWithValidation(createMessageDTO);
}
private ResponseEntity createNewMessageWithValidation(#Valid CreateMessageDTO createMessageDTO) {
// The DTO is valid
return ...
}
For the same scenario, I have found reccomendations to implement security via spring filters.
Here is similar post : How to check security acess (#Secured or #PreAuthorize) before validation (#Valid) in my Controller?
Also, maybe a different approach - try using validation via registering a custom validator in an #InitBinder (thus skip the #valid annotation).
To access principal object in filter class:
SecurityContextImpl sci = (SecurityContextImpl)
session().getAttribute("SPRING_SECURITY_CONTEXT");
if (sci != null) {
UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();
}
In this case /{id} is a path param in the URL. To access path params in filter or interceptor class:
String[] requestMappingParams = ((HandlerMethod)handler).getMethodAnnotation(RequestMapping.class).params()
for (String value : requestMappingParams) {.
Use WebSecurityConfigurerAdapter.configure(HttpSecurity http) instead of #PreAuthorize
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers( "/path/**").hasRole("admin");
}
}

ASP.NET MVC: Action Filter to set up controller variables?

I have a scenario whereby with every page request I must check the session of the presence of a particular ID. If this is found I must grab a related object from the database and make it available to the controller. If no session ID is found I need to redirect the user (session expired).
At the moment I have a custom chunk of code (couple of lines) that does this at the start of every action method within my controller - which seems like unnecessary repetition.
Is this scenario worthy of an Action Filter?
Thanks
UPDATE
Some great info here guys. Thank you
Yes, this sounds like a good application of an action filter, as you can apply it at the controller level to operate on all actions. You could also make it part of a controller base class, if you didn't want to add it to all controllers manually, or write your own controller factory which automatically applies this action filter to each controller.
See ASP.NET MVC Pass object from Custom Action Filter to Action for passing data from an action filter to an action.
Create a base controller like this
public class MyContollerController : Controller
{
public DataEntity userData;
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
var customId = requestContext.HttpContext.Session["key"];
if(customId!=null)
{
userData=getDataGromDataBase(customId);
}
else
{
//redirect User
}
}
}
Now Create ur controllers like this
public class MyDemoController : MyContollerController
{
public ActionResult Action1()
{
//access your data
this.userData
}
public ActionResult Action2()
{
//access your data
this.userData
}
}
Another way is to do that with Model Binders. Suppose that object is ShoppingCart
//Custom Model Binder
public class ShoppingCarModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//TODO: retrieve model or return null;
}
}
//register that binder in global.asax in application start
ModelBinders.Binders.Add(typeof(ShoppingCart), new ShoppingCartBinder());
// controller action
public ActionResult DoStuff(ShoppingCart cart)
{
if(cart == null)
{
//whatever you do when cart is null, redirect. etc
}
else
{
// do stuff with cart
}
}
Moreover, this is more unit testable and clear way, as this way action relies on parameters supplied from outside

Resources