Can I use a hybrid approach that passes information in the ViewBag (C#) / ViewData (VB) but also includes a model
For example, I started passing a simple title through an error controller to an error view:
Function NotFound() As ActionResult
Response.StatusCode = 404
ViewData.Add("Title", "404 Error")
Return View("Error")
End Function
This worked fine and the View had access to the property #ViewData("Title")
Then I wanted to include some of the exception information that is automatically routed by Custom Errors in the HandleErrorInfo object like this:
Function NotFound(exception As HandleErrorInfo) As ActionResult
Response.StatusCode = 404
ViewData.Add("Title", "404 Error")
ViewData.Add("Message", "Sorry, the requested page could not be found.")
Return View("Error", exception)
End Function
Then, on the View, I declared a Model like this:
#ModelType System.Web.Mvc.HandleErrorInfo
Now I can access properties like #Model.Exception.Message but my #ViewData("Title") has disappeared.
The WebViewPage(Of TModel) still has a ViewData property, but it looks like I can't pass anything to it from the controller unless it's explicitly in the model.
Of course, as a workaround, I could create a base class that can strongly type storage for each object, but that seems a little heavy handed when all I want to pass in through the ViewBag is the title.
Is there a way to use both ViewBag AND the Model at the same time?
What was happening in this case was that when the CustomErrors tried to route to the NotFound action on the Error controller, it could only supply the default constructor. Not finding one, it bypassed the controller altogether and went directly to the error page which already had the model it wanted to pass ready.
As a small Proof of Concept, passing both parameters is definitely possible as evidenced by this example
TestModel.vb:
Public Class TestModel
Public Property ModelInfo As String
End Class
TestController.vb:
Public Class TestController : Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Dim model As New TestModel With {.ModelInfo = "Hello"}
ViewData("ViewDataInfo") = "World"
Return View("Index", model)
End Function
End Class
Test\Index.vbhtml:
#ModelType TestModel
#Model.ModelInfo #ViewData("ViewDataInfo")
Related
Hello I use Thymealf and want to do a validirung against my database before I subbmitte.
When I call the controller I get the error:
Error resolving template [], template might not exist or might not be accessible by any of the configured Template Resolvers
I think the error comes from the fact that I don't returne a modal.
Is there a way to make a request without thymleaf ?
Controller
#GetMapping("/getByKey/{key}")
public KeyValuePair keyExists(#PathVariable("key") String key){
return ssdbService.getByKey(key);
}
or do I understand here something completely wrong ?
Use a #RestController instead of a #Controller if you want to return data.
You can also just annotate your method with #ResponseBody if you just want one method to return data instead of the entire controller.
#ResponseBody
#GetMapping("/getByKey/{key}")
public KeyValuePair keyExists(#PathVariable("key") String key){
return ssdbService.getByKey(key);
}
I'm new to ASP.Net MVC. In PHP, I always use the PRG pattern even when the post request was invalid. It was pretty easy with session flashes (also user friendly).
In ASP.Net MVC, however, I don't see an easy way to do PRG when the request is invalid. I could think of some ways, but I don't think they are good practices and put some extra unnecessary work.
Moreover, from a couple of articles that I've read, a PRG when the request was invalid was discouraged. If it's a bad practice, then what's the better way to handle unsuccessful post requests? Is it really better off without the PRG? And should I just let the rather annoying browser warnings when a user tries to refresh the page?
In Mvc, it's normal practice to handle your Post Actions as it follows:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult LoginForm(LoginViewModel loginViewModel)
{
if (!ModelState.IsValid)
return View("Login", loginViewModel);
return Redirect("/");
}
As you can see, the property ModelState.IsValid will tell you if the request is invalid, therefore giving you the ability to return the same view and display the error messages in the ValidationSummary when the Post request contains an error. This is the code for the View:
#using (Html.BeginForm("LoginForm", "Account"}))
{
#Html.ValidationSummary() // THIS WILL SHOW THE ERROR MESSAGES
#Html.AntiForgeryToken()
#Html.TextBoxFor(x => x.Email)
#Html.PasswordFor(x => x.Password)
<button type="submit">Submit</button>
}
We have been using PRG pattern in our asp.net mvc web apps for about 5 years. The main reason we adopted PRG was to support browser navigation (eg back, forward). Our web apps are used by customer and for front/back office operations. Our typical web page flow is starts with a login, then progresses via many list/detail view. We also incorporate partial views which also have their own viewmodel. List views will have links (GETS) for navigation. Detail views will have forms (POSTS) for navigation.
Keys aspects of our PRG:
We incorporate viewmodels so each view has a viewmodel (all data access is done in the viewmodel).
Each viewmodel has a set() & get() method to maintain the key data field values associated with the most recent instance of the view. The set/get values are persisted in sessionstate.
The set method has a parameter for each value that needs to be set. The get method is just called from the viewmodel constructor to populate the viewmodel's public "key" values.
The viewmodel will also have a public load() method that get all neccessary data for its view.
Our PRG pattern overview:
In controllers we have a separate GET method and a POST method for each action. The GET only displays a view; the POST processes the posted data.
For list (menu) views, the controller GET method calls the target view's set('item key values here') method, then invokes a RedirectToAction to to the target view's controller GET action.
The controller GET method will instantiate the viewmodel (thus causing get of set values), call its load method which uses the set/get key values to get it data, and returns the view/viewmodel.
The controller POST method will either have the viewmodel save the valid posted data then redirect to the next desired page (probably the previous list menu) -OR- if redisplay the current view if the data is invalid.
I have not documented all the PRG flow senarios that we implemented, but the above is the basic flow.
SAMPLE VIEWMODEL SET/GET METHODS
private void GetKeys() {
Hashtable viewModelKeys;
if (SdsuSessionState.Exists("RosterDetail"))
{
viewModelKeys = (Hashtable)SdsuSessionState.Get("RosterDetail");
EventId = (int)viewModelKeys["EventId"];
SessionNo = (int)viewModelKeys["SessionNo"];
viewModelKeys = null;
}
}
public static void SetKeys(int eventId, int sessionNo) {
Hashtable viewModelKeys = new Hashtable();
viewModelKeys.Add("EventId",eventId);
viewModelKeys.Add("SessionNo",sessionNo);
SdsuSessionState.Set("RosterDetail",viewModelKeys);
viewModelKeys = null;
}
SAMPLE CONTROLLER
[AcceptVerbs("Get")]
public ActionResult MenuLink(int eventId, int sessionNo, string submitButton) {
if (submitButton == RosterMenu.Button.PrintPreview) {
// P-R-G: set called viewmodel keys.
RosterDetail.SetKeys(eventId,sessionNo);
// Display page.
return RedirectToAction("Detail","Roster");
}
if (submitButton == RosterMenu.Button.Export) { etc ...}
}
Hi I getting this error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[DBModel.Telemarketing]', but this dictionary requires a model item of type 'TWeb.Models.LoginModel'
In _Layout.cshtml file i have
#Html.Partial("_LoginPartial")
this partial login view is rendered in div on _layout page (it`s hides/shows with javaScripts )
#model TWeb.Models.LoginModel
Then I have "Telemarketings" controller having view:
public class TelemarketingController : Controller
{
private Entities db = new Entities();
//
// GET: /Telemarketing/
public ActionResult Index()
{
return View(db.Telemarketings.ToList());
}
When I click link in _Layout page
#Html.ActionLink("Telemarketingas", "Index", "Telemarketing", new{area="" },new{ })
It throws an error written in top of the post.
I am new in MVC, please help me.
problem 1) Your Partial requires a model, and you're not passing one.
proper syntax: #Html.Partial("_LoginPartial", Model.LoginModel)
problem 2) _layout, as far as I know, can't have a Model passed
Solution 1:
Use an ActionPartial. AcionPartials are called similarly,
#Html.Action("/Tools/_LoginPartial").
The difference is they have an ActionMethod Associated which can return a Model
public ActionResult _LoginPartial()
{
LoginModel Model= new LoginModel();
//populate Model from whatever
return View(Model);
}
Option 2:
Pass a LoginModel object to a Viewbag
Viewbag.LoginModel = new LoginModel();
and reference the Viewbag in your _layout's Partial
#Html.Partial("_LoginPartial", Viewbag.LoginModel)
Your "_LoginPartial" expects "LoginModel" model, but since you're not giving it any, Razor engine sets its model to the current view model ("db.Telemarketings.ToList()").
All you have to do is somehow set its model, probably like so:
#Html.Partial("_LoginPartial", new LoginModel())
Simplest way was to remove model declaration from Login Div :).
You can use this code
#Html.Partial("Partial page", new ModelFroLogin())
I'm new to Spring MVC and trying to get a Post/Redirect/Get pattern working. We're trying to implement a survey where each page can display a variable number of questions. The way I'd like to implement this is a GET handler that prepares the next survey page and then hands that off to the view. In the same Controller, have a Post handler that processes the form's answers to the survey questions, submits that to the survey service, which returns the next page of questions, and then redirects that next surveyPage to the getNextPage GET handler.
Most of it is working, except the problem is that I don't know how to hand that 'next survey page' object from the POST handler to the getNextPage GET handler in the redirect. The redirect is working; it goes from the POST method to the GET method, but the surveyPage ModelAttribute is a new object in the GET method, and not the one that was set at the end of the POST method. As you can see, I've tried using ModelAttribute, but it doesn't work. I also tried using #SessionAttributes above the class, but then got a HttpSessionRequiredException.
We didn't know how to handle the dynamic form containing a variable # of questions with Spring MVC Forms, so we just did straight JSTL. It's funky but it works. That funkiness is what resulted in using the #RequestBody and SurveyPageBean coming back with the Post. Honestly, I don't know how the SurveyPageBean is populated. It looks like some Spring MVC magic, but it's working so I'm leaving it alone for now (another developer did this and then I picked it up, and we're both new to Spring MVC). Please don't get distracted by the unusual form handling, unless that is part of the problem with the empty surveyPage ModelAttribute not being redirected.
Here's the Controller snippet:
#Controller
#RequestMapping("/surveyPage")
public class SurveyPageController{
#RequestMapping(method=RequestMethod.GET)
public String getNextPage(#ModelAttribute("surveyPage") SurveyPage surveyPage, Model model) {
if(surveyPage.getPageId() == null) {
// call to surveyService (defined elsewhere) to start Survey and get first page
surveyPage = surveyService.startSurvey("New Survey");
}
model.addAttribute("surveyPage", surveyPage);
return "surveyPage";
}
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#RequestBody String body, SurveyPageBean submitQuestionBean, Model model, #ModelAttribute("surveyPage") SurveyPage surveyPage) {
// process form results, package them up and send to service, which
// returns the next page, if any
surveyPage = surveyService.submitPage(SurveyPageWithAnswers);
if (results.getPageId() == null) {
// The survey is done
surveyPage = surveyService.quitSurvey(surveyId);
return "redirect:done";
}
model.addAttribute("surveyPage ", surveyPage );
return "redirect:surveyPage";
}
Use Flash Attributes as shown in Warlock's Thoughts.
#RequestMapping(method = RequestMethod.POST)
public String handleFormSubmission(..., final RedirectAttributes redirectAttrs) {
...
redirectAttrs.addFlashAttribute("AttributeName", value);
return "redirect:to_some_url_handled_by_BController";
}
Your GET takes the surveyPage as a model attribute, which means it is reading it from the URL. In the POST, rather than adding the surveyPage to the model (which is lost because you are telling the client to redirect, which creates a new request and therefore a new model) you should add the surveyPage as a query parameter in your "redirect:surveyPage" You'll have to look at how the surveyPage is constructed from the query params in order to know what to put on the query string.
If, for instance, the surveyPage is constructed from a user, page number, and question count or something, I believe you could do something like "redirect:surveyPage?userId=1234&pageNumber=5678&questionCount=12 in order to pass that model attribute along.
I have a Spring MVC controller with an action that's called using AJAX.
#SessionAttributes({"userContext"})
public class Controller
{
...
#RequestMapping(value = "/my-url", method= { RequestMethods.POST })
public ModelAndView doSomething(#ModelAttribute("userContext") UserContext context,
SessionStatus sessionStatus)
{
BusinessObject obj = doSomeBusinessLogic(context.getUserName());
sessionStatus.setComplete();
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("someInt", obj.getId());
return mav;
}
}
When I run this action, I get the following exception:
net.sf.json.JSONException: There is a cycle in the hierarchy!
at t.sf.json.util.CycleDetectionStrategy$StrictCycleDetectionStrategy.handleRepeatedReferenceAsObject(CycleDetectionStrategy.java:97)
at net.sf.json.JSONObject._fromBean(JSONObject.java:833)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at org.springframework.web.servlet.view.json.writer.jsonlib.PropertyEditorRegistryValueProcessor.processObjectValue(PropertyEditorRegistryValueProcessor.java:127)
at net.sf.json.JSONObject._fromMap(JSONObject.java:1334)
Truncated. see log file for complete stacktrace
After doing some debugging I found out that Spring is placing the UserContext object onto the ModelAndView that I am returning. If I hard-code my user name and remove the context object from the method's parameters, the action runs successfully. Is there a way to configure Spring to omit the ModelAttribute-annotated parameters from the returned ModelAndView? As you can see, sessionStatus.setComplete() has no effect.
I've had similar problems in the past with #SessionAttributes. By declaring #SessionAttributes({"userContext"}) you're telling Spring that you want "userContext" to always be available in the model, and so Spring has no choice but to send your UserContext object out to the model, just in case you're going to be redirecting or doing something else which might end up at another Controller.
The "solution" (and I didn't like it much, but it worked) was to omit the #SessionAttributes annotation on the controller, add an HttpSession parameter to the necessary methods and "manually" manage what's in it.
I'm interested to see if there's a better way, because it seems #SessionAttributes has tremendous potential to tidy up controller-level code.
I registered a WebArgumentResolver to get to my session variable. This allowed me to keep this session variable out of the response while keeping my action unit testable.
Along with #ModelAttribute, pass #ModelMap as a method argument.
Based on business logic, error conditions -- if you do not need the attribute for certain scenarios, then remove it from the map.
public ModelAndView foo(#ModelAttribute("userContext") UserContext, #ModelMap map){
if(success){
return success.jsp
}
else{
map.remove("userContext");
return "error.jsp"
}
}
Not totally satisfied with having to pass the ModelMap as well, but I did not find any other easier way of doing it.
Cheers!!