We have a job application that uses a two step process. I need to tweak this slightly. The easiest way I can see doing this is removing the application creation stage from step one, and just passing it to step two which is a separate view.
Page 1 - Step 1
public ActionResult Apply(JobApplicant jobapp, string HiddenJobId)
{
jobapp.JobID = Convert.ToInt32(HiddenJobId);
//JobsHelper.CreateJobApplicant(jobapp);
return View("ApplyResume", jobapp);
}
The above code used to CreateJopApplication(). I commented this out and am now passing this to the next step/page.
However once on this new page I then have an [HttpPost] that takes that jobapp but at this point it is all null.
The jobapp(JobApplication) is about 140 columns, I'd hate to have to create a hidden text field for all of that. Is there an easier way to pass a model across two pages?
Step 2 - Page 2
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file, string applicantId, string coverLetter, JobApplicant jobapp)
{
// jobapp is null during this entire step, passed in null its as if the page before never had it.
JobsHelper.CreateJobApplicant(jobapp);
.......
You may be able to use TempData. It is like a session state but it's removed once it gets used. It is good for one round trip.
With a stateless framework like MVC, you can:
Persist inside the form using hidden fields
Persist in database
Persist in session state or temporary state
Related
I working on a web project where I first get data from the database and bind to the Html control. If there is a validation error I will send the same view back for rendering with the displayed validation errors. When the page comes up, there is an exception. I stepped through the code and found that the model was passed will null collection. Basically any property that was not binded to a textbox was changed to null. I was told not to use session or viewdata to keep temp storage. So I call a method SaveViewState where it save all the property value of the ViewModel property to a static variable like so
private static MyViewModel _viewModel;
private MyViewModel SaveViewModel(MyViewModel viewModel)
{
if (_viewModel == null)
{
_viewModel = new MyViewModel ();
}
if (!string.IsNullOrEmpty(viewModel.MyName))
_viewModel.MyName= viewModel.MyName;
if (!string.IsNullOrEmpty(viewModel.Number))
_viewModel.Number= viewModel.Number;
if (!string.IsNullOrEmpty(viewModel.Address))
_viewModel.Address= viewModel.Address;
if (!string.IsNullOrEmpty(viewModel.State))
_viewModel.State= viewModel.State;
}
It works but I think it is very inefficient and there must be a better way to implement ViewState in MVC with Session or ViewData or HiddenFields? By the way, I was told not to use those three.
Any help is appreciated. Thanks.
I am not sure if this solution is worse than using a session or hidden fields. In your action you should return the corresponding view with the same model that was posted. The ActionResult should be something like this:
public ActionResult SomePost(SomeModel model)
{
if (!ModelState.IsValid())
{
//error in validation
return View(model);
}
//post save redirect and stuff
return ... redirect?
}
The ModelState.IsValid() will test according to the DataAnnotations. Standard attributes like [Required], [MaxLength] etc. are available.
In this configuration, the use of a SaveViewModel function is not required. If your collection is null after post: re-query it, post it or fetch it from a ViewData like object.
There are good reasons not to use those three you mentioned, but if you know that reason you might want to consider it:
1) Use of session: will make scalability difficult because every request in a session must hit that specific server.
2) Hidden fields: Not really a problem IFF you realize the hidden field can be manipulated in a browser. So don't store ID's there
3) ViewData: basically breaks the MVC pattern; you can use it to store data but that's what a model is for. It totally legitimate to use ViewData from a filter. To provide some general functionality for example.
I'm working on a Web Application project using C# and MVC that will take in two URLs in a form and use them to create an instance of a class I have created called "ImageSwap." This model has some data (a username of the person performing the swap; two variables which hold the URLs of two images to be swapped; two variables which save the actual names of these files without all of the rest of the URL information; and two arrays which represent the file locations to check for these files). Right now, I have it so that the initial index view creates the instance of the class and passes it to the same view with the information put in through the form, submitted via POST, like so:
public ActionResult Index()
{
Ops.Operations.Models.ImageSwapModel newImageSwap = new Models.ImageSwapModel();
return View(newImageSwap);
}
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
//Perform data manipulation and set needed values
}
}
It then performs some functions on the data, such as parsing out the filename at the end of the URL, and a directory number (which is the first part of this filename, i.e. directory#_fileName.jpg). All of this works fine.
My problem is that I would like to pass this model to another view once it has data populated in all of its fields by this initial ActionResult so that I can have a verification view, which would allow the user to preview the two files side by side so that they can ensure they are swapping the appropriate images. They should then be able to hit another submit button which will initiate the actual moving/replacing of the images and be taken to a page confirming.
Is there a way to pass data from this controller to a different view? My confusion arises because I cannot create another version of an ActionResult of Index with the same input, but I do not want to have the actual swapping of the images occur without a preview and a prompt. Should I re-write my Index view so that it utilizes partial views in order to accomplish this? What is the easiest way to have data persist through multiple steps and views?
What is the easiest way to have data persist through multiple steps
and views?
Your question sounds like you're trying to achieve what you can easily do with sessions. The session object allows you to persist data between requests simply by adding it to the Session object on the HttpContext that exists within the base class that your controller extends, like so:
(Note the Serializable attribute. This allows your object to be serialized into the session object).
[Serializable]
public class ImageSwapModel {
// Your class's properties
}
Then in your controller you can do the following:
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
this.HttpContext.Session["ImageSwap"] = imageSwap;
}
}
When you want to retrieve the model you can grab it from the session like so:
var imageSwap = (ImageSwapModel)this.HttpContext.Session["ImageSwap"];
Taking it one step further:
Whilst the above will work fine, generally it's not a good practice to reference the HttpContext object directly in your code as it creates unnecessary coupling to the HttpContext object that can easily be avoided. Instead you should opt to inject an instance of the session object via Dependency Injection. Here is a similar answer that provides a basic idea as to how you can do this.
You can return different views with Models being passed to them in your one Index action like
if(some condition)
{
Return View("ViewVersion1", MyModelVersion1);
}
else
{
Return View("ViewVersion2", MyModelVersion2);
}
In my asp.NET MVC5 app I have a controller that supplies a view which is strongly typed vs a viewmodel. This viewmodel has a SelectList property (among others), and the controller supplies the data on creation from the database:
public ActionResult Simulation() {
var SimVM = new SimulationVM(
StrategyRepository.GetStrategies().Select(n => n.Name),
);
return View(SimVM);
}
The SelectList is then used as data source for a DropDown choice in a form. The HttpPost method does some datavalidation, i.e.,
[HttpPost]
public ActionResult Simulation(SimulationVM _simVM) {
if (ModelState.IsValid) {
// ...
}
else return View(_simVM);
}
So with the code above, the DropDown data is empty, since on posting, the SimulationVM object is created new. The usual trick of using Html.HiddenFor does not work on collections.
Of course, I could go back and fetch the data again from the database, but that seems to be bad, a database fetch for such a simple thing as a validation where I know the data hasn't changed.
What is the best (or for the sake of not being subjective: any) way to keep some data in the ViewModel (or repopulate it efficiently)?
If it is a requrement that you not go back to the database and you're 100% confident that the data will not change (i.e. this is a list of states as opposed to a list of orders or something) then you can add the collection to a session variable. Here's a link to a decent article:
https://code.msdn.microsoft.com/How-to-create-and-access-447ada98
That being said, I usually just go to the database and get the data again. If doing so for a second time is causing huge performance issues, it is most likely causing performance issues the first time and you should treat the problem rather than the symptom.
There is probably a very simple answer to this question but I'd like to know the best practice for handling this type of scenario. Running MVC4, I have a page that performs a redirects to a Controller action that performs a lookup and dynamically renders content on another page.
The page makes a call to the following controller for the lookup:
public ActionResult GetMembershipLevel(string userid)
{
var memberDetails = MemberDetails.GetMembershipDetails(userid);
TempData["MemberDetails"] = memberDetails;
var memberLevel = memberDetails.MemberLevel;
switch (memberLevel)
{
case 0:
return RedirectToAction("Basic");
case 1:
return RedirectToAction("Gold");
case 2:
return RedirectToAction("Platinum");
}
return null;
}
After the lookup, based on the Members details the data is stored in the TempData object and Redirected to an ActionResult within the same controller to provide the model data that get sent to 1 of potentially 3 views. I did this because each view will render a different set of the Model based on Member classifications:
public ActionResult Basic()
{
var details = (MembershipDetail)TempData["MemberDetails"];
ViewBag.Member = details.IndividualName;
return View(tuple);
}
In this code snippet, the Model is returned to a View called Basic and all this works fine. Except in the case that a manual refresh is made within the Browser. Then all Hell breaks out with the notorious "Object Not Set To an Instance Of an Object" at the line "ViewBag.Artist = details.IndividualName;"
I know the reason it's happening is because a refresh of the same page cannot and does not repeat the entire cycle that landed the user on the page. It was a Redirect with the user's selected Id initially and a refresh simply refreshes the existing page without the Id parameter causing the TempData to be null on subsequent refreshes.
I did some research and found a couple of suggestions.
1. Add modules runAllManagedModulesForAllRequests="true" to the WebConfig. (I ruled this out as a potential fix immediately as I don't believe it is relevant to my problem.
Decorate my Controller with the [HandleError] attribute. (This actually worked and alleviated the problem)
But my question, "Is this the correct and best method of handling the issue?"
I read concerning the attribute, "When you provide only the HandleError attribute to your class (or to your action method for that matter), then when an unhandled exception occurs MVC will look for a corresponding View named "Error" first in the Controller's View folder. If it can't find it there then it will proceed to look in the Shared View folder (which should have an Error.aspx file in it by default)"
So it leads me to believe that implementation of the [HandleError] attribute should require a corresponding View.
If this is correct; It seems what I've accomplished is resolution of the issue but not much more than a comparable try catch block that doesn't implement the catch.
Right or Wrong? Is this method acceptable under the circumstances?
I believe you should design your application in a different way.
Instead of having different actions that you redirect to (what stops a user from simply typing in a url with Gold instead of Basic?) you should instead have a single page that renders the correct view based on their membership status. This essentially solves your entire problem. Plus it makes it impossible for someone to type in a membership level they don't have.
Essentially, you would do this:
public ActionResult Membership()
{
var memberDetails = MemberDetails.GetMembershipDetails(userid);
var memberLevel = memberDetails.MemberLevel;
switch (memberLevel)
{
default:
case 0:
return View("Basic", memberDetails);
case 1:
return View("Gold", memberDetails);
case 2:
return View("Platinum", memberDetails);
}
}
I have a very large (77 actions) controller that I am using to make a site with wizard-like functionality. The site is like a "job application manager" with multiple components such as an admin component and an end-user component. The component I'm working with is the part where the user would actually fill out a job application. The way things are structured with the other components, it makes the most sense to put all of the job application stuff in the same controller. All of these actions perform similar things but on different models, like so:
public class ExampleController : Controller
{
public ActionResult Action1()
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to pull up correct model
return View(model)
}
[HttpPost]
public ActionResult Action1(FormCollection formValues)
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to update the model
return RedirectToAction("Action2");
}
public ActionResult Action2()
{
Guid appId = new Guid(Session["AppId"].ToString());
... // logic to pull up the correct model
return View(model)
}
... // on and on and on for 74 more actions
}
Is there any way to reduce some of the constant redundancy that's in every one of the actions? Here is what I am thinking:
Creating a member variable Guid to store the appId and then overriding OnActionExecuting to populate this variable. Is this a good idea?
Implementing some kind of paging to cut down on the number of actions. Any suggestions on how to do that?
I would say yes to your first point and "it depends" to your second. Don't change your design just because you have a lot of methods, if all 77 ActionResult methods make sense to have, then keep them around.
Using a member variable and overriding OnActionExecuting seems like a great way to refactor that appID Guid code into a single place, so you can quickly and easily modify it in the future.
Normally for wizard view, a single action and page is used with multiple divs which can be shown according to the steps.
For example, a registration wizard screen having 4 steps, can be be handled in a single page with divs for each steps. You can make use of JavaScript and css to make it a wizard flow.
Make use of ajax to update different models, if necessary in between the steps.
You may want to put your logic (Job Manager related) in a single repository/manager class. Different controllers associated with different views(e.g AdminController, EndUserController etc) can call methods from same repository/manager class.
Another option could be replacing this..
Guid appId = new Guid(Session["AppId"].ToString());
..with a call to something like the following:
private Guid GetAppId(){
return new Guid(Session["AppId"].ToString());
}
Now you could just use GetAppId() instead wherever you currently use appId. You could of course cache the GUID in the form of a class variable, as you suggest, but it might be a good idea to limit the access and use of that variable to a method like this (get it's value via the method). Might be a little more flexible in case you want to change something later.
As for dividing the page into several pages; sure, go ahead, if it makes sense and feels right. Over 70 actions in one class does sound like a lot. If it makes more sense to keep them there however, you could try to move as much logic as possible out from the methods themselves, and into helper-classes instead. I always try to keep the actions as small as possible, and put the logic in separate classes, each of which is tailored to do one specific thing.
My point is, if each action is no more than 2-4 lines, then 70+ actions is not necessarily a problem.