What is Model in ModelAndView from Spring MVC? - spring

Having this basic function
protected ModelAndView handleRequestInternal(...) {
...
return new ModelAndView("welcomePage", "WelcomeMessage", message);
}
I know that this will return modelandView.
I know that welcomePage is my viewname so that means something like welcomepage.jsp will get called.
But I am confused with what is Model part.
What is WelcomeMessage and message and how Model work in that scenario?

The model presents a placeholder to hold the information you want to display on the view. It could be a string, which is in your above example, or it could be an object containing bunch of properties.
Example 1
If you have...
return new ModelAndView("welcomePage","WelcomeMessage","Welcome!");
... then in your jsp, to display the message, you will do:-
Hello Stranger! ${WelcomeMessage} // displays Hello Stranger! Welcome!
Example 2
If you have...
MyBean bean = new MyBean();
bean.setName("Mike!");
bean.setMessage("Meow!");
return new ModelAndView("welcomePage","model",bean);
... then in your jsp, you can do:-
Hello ${model.name}! {model.message} // displays Hello Mike! Meow!

new ModelAndView("welcomePage", "WelcomeMessage", message);
is shorthand for
ModelAndView mav = new ModelAndView();
mav.setViewName("welcomePage");
mav.addObject("WelcomeMessage", message);
Looking at the code above, you can see the view name is "welcomePage". Your ViewResolver (usually setup in .../WEB-INF/spring-servlet.xml) will translate this into a View. The last line of the code sets an attribute in your model (addObject("WelcomeMessage", message)). That's where the model comes into play.

It is all explained by the javadoc for the constructor. It is a convenience constructor that populates the model with one attribute / value pair.
So ...
new ModelAndView(view, name, value);
is equivalent to:
Map model = ...
model.put(name, value);
new ModelAndView(view, model);

Here in this case,
we are having 3 parameter's in the Method namely ModelandView.
According to this question, the first parameter is easily understood. It represents the View which will be displayed to the client.
The other two parameters are just like The Pointer and The Holder
Hence you can sum it up like this
ModelAndView(View, Pointer, Holder);
The Pointer just points the information in the The Holder
When the Controller binds the View with this information, then in the said process, you can use The Pointer in the JSP page to access the information stored in The Holder to display that respected information to the client. Here is the visual depiction of the respected process.
return new ModelAndView("welcomePage", "WelcomeMessage", message);

Well, WelcomeMessage is just a variable name for message (actual model with data). Basically, you are binding the model with the welcomePage here. The Model (message) will be available in welcomePage.jsp as WelcomeMessage.
Here is a simpler example:
ModelAndView("hello","myVar", "Hello World!");
In this case, my model is a simple string (In applications this will be a POJO with data fetched for DB or other sources.). I am assigning it to myVar and my view is hello.jsp. Now, myVar is available for me in hello.jsp and I can use it for display.
In the view, you can access the data though:
${myVar}
Similarly, You will be able to access the model through WelcomeMessage variable.

ModelAndView: The name itself explains it is data structure which contains Model and View data.
Map() model=new HashMap();
model.put("key.name", "key.value");
new ModelAndView("view.name", model);
// or as follows
ModelAndView mav = new ModelAndView();
mav.setViewName("view.name");
mav.addObject("key.name", "key.value");
if model contains only single value, we can write as follows:
ModelAndView("view.name","key.name", "key.value");

#RequestMapping(value="/register",method=RequestMethod.POST)
public ModelAndView postRegisterPage(HttpServletRequest request,HttpServletResponse response,
#ModelAttribute("bean")RegisterModel bean)
{
RegisterService service = new RegisterService();
boolean b = service.saveUser(bean);
if(b)
{
return new ModelAndView("registerPage","errorMessage","Registered Successfully!");
}
else
{
return new ModelAndView("registerPage","errorMessage","ERROR!!");
}
}
/* "registerPage" is the .jsp page -> which will viewed.
/* "errorMessage" is the variable that could be displayed in page using -> **${errorMessage}**
/* "Registered Successfully!" or "ERROR!!" is the message will be printed based on **if-else condition**

Related

How do I get the MethodInfo of an action, given action, controller and area names?

What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.

How to use different type of parameter without creating new view?

I would like to create a list with a string and an int value at the same time like follows:
#Html.ActionLink("Back to List", "IndexEvent", new { location = "location" })
and
#Html.ActionLink("Back to List", "IndexEvent", new { locationID = 1 })
It didn't work. I guess MVC controller didn't get the type difference of parameter. So, I had to make a new Action as "IndexEvenyByID" but it requires to have a new view. Since I wanted to keep it simple, is there any way to use same view with respect to different parameters?
Try adding two optional parameters to the IndexEvent action like this:
public ActionResult IndexEvent(string location = "", int? locationID = null)
This should not require a new view or view model. You should have two actions as you have described, but the code could be as follows:
Controller
public ActionResult GetEvents(string location){
var model = service.GetEventsByLocation(location);
return View("Events", model);
}
public ActionResult GetEventsById(int id){
var model = service.GetEventsById(id);
return View("Events", model);
}
Service
public MyViewModel GetEventsByLocation(string location){
//do stuff to populate a view model of type MyViewModel using a string
}
public MyViewModel GetEventsById(int id){
//do stuff to populate a view model of type MyViewModel using an id
}
Basically, if your View is going to use the same view model and the only thing that is changing is how you get that data, you can completely reuse the View.
If you really want to stick to a single action and multiple type, you could use a object parameter.
public ActionResult GetEvents(object location)
{
int locationID;
if(int.TryParse(location, out locationID))
var model = service.GetEventsByID(locationID);
else
var model = service.GetEventsByLocation(location as string);
return View("Events", model);
}
Something like that (Not completly right but it gives you an idea). This, however, wouldn't really be a "clean" way to do it IMO.
(Edit)
But the 2 actions method is still by far preferable (eg. What happens if we're able to parse a location name into a int?)

How to pass class via RedirectToAction

I have the following code:
public ActionResult Index()
{
AdminPreRegUploadModel model = new AdminPreRegUploadModel()
{
SuccessCount = successAddedCount,
FailureCount = failedAddedCount,
AddedFailure = addedFailure,
AddedSuccess = addedSuccess
};
return RedirectToAction("PreRegExceUpload", new { model = model });
}
public ActionResult PreRegExceUpload(AdminPreRegUploadModel model)
{
return View(model);
}
but model is null when I breakpoint on PreRegExcelUpload. Why?
Instead of using the Session object in Evgeny Levin's answer I would suggest to use TempData. See http://rachelappel.com/when-to-use-viewbag-viewdata-or-tempdata-in-asp.net-mvc-3-applications about TempData.
You could also fix this by calling return PreRegExceUpload(model); instead of return RedirectToAction(..) in you Index function.
TempData is just a "smart" wrapper for the Session, under the hood it still acts the same way.
Since it's only 4 fields, i would pass them via querystring.
Always try and avoid session/tempdata where possible, for which in this scenario it certainly is.
Are you sure that's your full code? As it doesn't make sense.
If your POST'ing some data and saving it to the database (for example), usually you redirect to another action passing the unique identifier (which is usually generated after the save), fetch it back from the DB and return the view.
That is much better practice.
If you explain your scenario a bit more, and show the proper code your using, i can help further.
Use session to pass model to method:
public ActionResult Index()
{
AdminPreRegUploadModel model = new AdminPreRegUploadModel()
{
SuccessCount = successAddedCount,
FailureCount = failedAddedCount,
AddedFailure = addedFailure,
AddedSuccess = addedSuccess
};
Session["someKey"] = model;
return RedirectToAction("PreRegExceUpload");
}
public ActionResult PreRegExceUpload()
{
var model = (AdminPreRegUploadModel) Session["someKey"];
Session["someKey"] = null;
return View(model);
}
Method RedirectToAction() can't take non primitive types as parameters, because url parameters is string.

ID in Spring-MVC 2.5 edit form using #Controller

I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious

How do I test the default view when using T4MVC with MvcContrib.TestHelper AssertViewRendered

I am using T4MVC in my ASP.NET MVC 3 project. I have the following basic test:
[TestMethod]
public void IndexReturnsIndexView()
{
var controller = new HomeController();
var result = controller.Index();
result.AssertViewRendered().ForView(MVC.Home.Views.Index);
}
The test fails if the controller method returns the default View:
public virtual ActionResult Index()
{
return View();
}
The error given is:
MvcContrib.TestHelper.ActionResultAssertionException: Expected view name '~/Views/Home/Index.cshtml', actual was ''
But the test passes if I override the View to specify which viewName to return:
public virtual ActionResult Index()
{
return View(MVC.Home.Views.Index);
}
I tried using the following assertion, but still not luck:
result.AssertViewRendered().ForView(MVC.Home.Index().GetT4MVCResult().Action);
The following error is raised:
MvcContrib.TestHelper.ActionResultAssertionException: Expected view name 'Index', actual was ''
I then realized that I had misread the assertion failure, so I changed the test to this:
result.AssertViewRendered().ForView(String.Empty);
The test passes, but the test itself seems to be useless.
Preferably I don't want to have to specify all views by name, so how do I test this? To clarify, I am using the MvcContrib.Mvc3.TestHelper-ci 3.0.96.0, which I installed today from NuGet.
UPDATE
This isn't an answer to the question, but I have started doing the following instead, which provides more value as a test case:
using (var controller = new FeatureController(mockGateway))
{
// Act
var result = controller.Index();
var model = result.ViewData.Model as MyModel;
// Assert
Assert.IsNotNull(model, "Model is null or wrong type");
result.AssertViewRendered().WithViewData<MyModel>();
// Alternative Assert for model data
Assert.IsTrue(model.Items.Count > 0);
}
I left this open for a considerable amount of time, so that anyone else could answer if they chose to. I'll now answer this myself.
The following checks to make sure that the view returned contains the expected model, and more appropriately data within that model. This is a much better unit test for the controller in question.
using (var controller = new FeatureController(mockGateway))
{
// Act
var result = controller.Index();
var model = result.ViewData.Model as MyModel;
// Assert
Assert.IsNotNull(model, "Model is null or wrong type");
result.AssertViewRendered().WithViewData<MyModel>();
// Alternative Assert for model data
Assert.IsTrue(model.Items.Count > 0);
}
AssertViewRendered().ForView() tests against the view name that you explicitly pass to View() is your controller action. If, as you're doing, you don't specify a view name in your action, then .ForView() will be testing against an empty string, as you've seen.
If you called View("Index"); in your action, you could call .ForView("Index") in your test.
I would think that this assertion would be most useful if your action could return different possible views.

Resources