Warning: This is my first web app.
I have 4 models, views and controllers. Lets call them A, B, C, D(ex. ModelA, ControllerA, ViewA). They are all basic views with list scaffolding.
/ControllerA/Index
User starts at ViewA and Selects an the first item, which redirects the user to ViewB
/ControllerB/Function?Aid=1
ViewB shows another list based on Selection from ViewA. Then the user Selects again is is redirected to ViewC
/ControllerC/Function?Aid=1&Bid=2
ViewC shows another list based on Selections from ViewA and ViewB. Then the user Selects again is is redirected to ViewD.
/ControllerD/Function?Aid=1&Bid=2&Cid=3
ViewD shows another list based on Selections from ViewA, ViewB, and ViewC, Then the user Selects again.
At this point I would like to POST Aid, Bid, Cid, and Did and save them in my database. Ideally the user would click the link, the data would be posted and then the site would redirect the user back to the homepage. Should I create another model and controller to Handle the post? I thought about trying to do the POST from controllerD but that doesn't seem like the proper way to do this.
The msdn tutorials only show posting directly from a view with a strongly typed model. I kinda stuck and I would prefer not to make this a complete mess.
Edit for Code
Controller
public ActionResult myFunction(int Aid = 0, int Bid, int Cid)
{
//query D stuff here
if (D == null)
{
return HttpNotFound();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(int Aid, int Bid, int Cid, int Did)
{
Quote myQuote = new Quote();
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
myQuote.A_ID_FK = Aid;
myQuote.B_ID_FK = Bid;
myQuote.C_ID_FK = Cid;
myQuote.D_ID_FK = Did;
if (ModelState.IsValid)
{
db.Quotes.Add(myQuote);
db.SaveChanges();
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
return RedirectToAction();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(Quote myQuote)
{
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
if (ModelState.IsValid)
{
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
db.Quotes.Add(myQuote);
db.SaveChanges();
return RedirectToAction();
}
return View(D.ToList());
}
It usually makes sense to put your post handler in the controller it's related to. This isn't always the case, as sometimes it would make more sense to make a new controller to handle all posts related to a certain task. You should also understand the distinction between a method IN a controller, and a controller. A controller is just a class that inherits from System.Web.Mvc.Controller and can have methods just like any other class. A perfectly reasonable controller could look like this:
public class DController : Controller
{
//GET /d
public ActionResult Index()
{
//MyModel is a class that would contain the logic to display
//the selections from A, B, and C
var model = new MyModel();
return View(model);
}
//POST /d/saveresults
//We only want this method to accept POST
[HttpPost]
public ActionResult SaveResults(MyEntity data)
{
var model = new MyModel();
model.SaveResultsToDatabase(data);
return Redirect("/");
}
}
The important thing in a controller is to keep logical processing to a minimum. There's nothing wrong with having an if statement here and there, but the majority of your logic should be handled by your model. A controller is there primarily to pass data between your views and models.
Related
"Please feel free to edit the title if it is misleading, since I am not sure if that's the correct way to ask"
I am new to ASP.MVC I am running into a seemly easy problem and having a hard time doing it.
What I am trying to do: I have two sets of scaffolds: View A, Controller A, Model B. and View B, Controller B, Model B.
Controller A looks like this:
public ActionResult LogIn(FormCollection formValues)
{
ModelA Model = new ModelA();
Model.EmailAddress = formValues["EmailAddress"];
}
I want to pass this formValues["EmailAddress"] from controller A to controller B.
In Controller B:
public ActionResult Initiate(FormCollection formValues, string phone, string method)
{
var ModelB = new ModelB();
var ModelA = new ModelA(); ---> This is null.
ModelB.Email = ModelA.EmailAddress --> This is null.
var userId = ModelB.dosomething(ModelB.Email, phone, method);
}
Is there away of doing that?
If the logic of ModelA and ModelB are going to be intertwined, I would recommend creating a new ViewModel, View, and Controller:
ModelAB
public class AB {
public ModelA ModelA { get; set; }
public ModelB ModelB { get; set; }
}
ModelABController
public ActionResult LogInAndInitiate(FormCollection formValues, string phone, string method)
{
var ModelB = new ModelB();
var ModelA = new ModelA(); // passed in, so its not null!
ModelA.EmailAddress = formValues["EmailAddress"];
ModelB.Email = ModelA.EmailAddress // passed in too!
var userId = ModelB.dosomething(ModelB.Email, phone, method);
}
Save the Model in TempData in Controller A like this
ModelA Model = new ModelA();
Model.EmailAddress = formValues["EmailAddress"];
TempData["ModelA"]=Model;
and Then You can Acces it in Controller B like this
ModelA modelA= TempData["ModelA"] as ModelA;
However TempData has very short life and can not be used in subsequent requests
to keep the TempData persistant for subsequent request you can use
TempData.Keep();
If the controller actions are completely unrelated (i.e., they do not call each other) you have two options:
1) Render the data to the client into a form and let the data be re-posted on the next request. This is of course only suitable for small amounts of data AND if you are sure it is neither sensitive nor a problem if the user tampers with the data.
2) Use sessions. This is what sessions are for, i.e., shopping cart contents, etc. You can opt to save this data in a database or have it in-memory in a session object only.
I wouldn't recommend using TempData for this, the idea behind TempData is to contain data which is useful for the very next request only. Think validation messages and that kind of data.
Is there any danger to use one of the 2 following methods? Is there any best practice?
I'm wondering if I will hit a wall when there will be many user online at the same time on the website.
Storing Model in Session for later reuse
public ActionResult Index()
{
var model = new Model();
Session["Model"] = model;
return View(model);
}
[HttpPost]
public ActionResult Index(FormCollection fc)
{
var model = (Model)Session["Model"];
//Update stuff
return View(model);
}
Recreating it on every requests
public ActionResult Index()
{
var model = new Model();
return View(model);
}
[HttpPost]
public ActionResult Index(FormCollection fc)
{
var model = new Model();
//Update stuff
return View(model);
}
Each model :
might contain a lot of calls to the database (it can take some time to
initialize them)
might contain list of information (a lot of data)
Edit
Feels like most avoid trying to answer the question and got stuck on unimportant details to the question.
I accepted the only answer which was able to stay on topic.
Atavari answer is completely off topic and didn't understood the question at all.
Both of those methods are wrong. Your post action should receive the model, not a FormCollection object.
[HttpPost]
public ActionResult Index(Model model)
{
//Update stuff
return View(model);
}
And, you usually redirect to a different page after the Add/Update is successful, and only return the same View if there's validation error. So, your post action should look more like this:
[HttpPost]
public ActionResult Index(Model model)
{
if(ModelState.IsValid)
{
//Update stuff
//return to a different page or whatever needs to be done
//after a successful update
}
// If model is not valid...
return View(model);
}
UPDATE:
If there are properties in your model that are not posted to your action (like a SelectList for a dropdown), you'll only need them if you want to return the same View (normally when there's validation error). In that case you don't have to recreate your model. You just repopulate those properties.
Let's say your model has a property called Items. This is how your actions should look like:
public ActionResult Index()
{
var model = new Model();
model.Items = GetItems();
return View(model);
}
[HttpPost]
public ActionResult Index(Model model)
{
if(ModelState.IsValid)
{
//Update stuff
//return to a different page or whatever needs to be done
//after a successful update
}
// If model is not valid...
model.Items = GetItems();
return View(model);
}
It depends on how many users you really have. Using session can be comfortable and easy but scalability may become an issue.
If you use it just to improve performance, I would suggest you to use cache instead. Take care that objects in cache can expire so you have always to check whether they are still there. Cache is better than session because it is self optimized. If memory gets low it's automatically cleaned, while session variables are never cleaned (unless of course when session expires).
I am developing an ASP.NET MVC application, populating dropdownlist to view working fine in get method. I am populating to view like this in my controller
[HttpGet]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, int i = 0)
{
employeeSuperClass.FamilyDetailsFields = new FamilyList();
employeeSuperClass.FamilyDetailsFields.familyMembersList.Insert(0, new EmployeeFamilyTable());
*employeeSuperClass.FamilyDetailsFields.employee_RelationTable = dt.GetRelations();*
*employeeSuperClass.FamilyDetailsFields.employee_BloodGroupTable = dt.GetBloodGroups();*
*employeeSuperClass.FamilyDetailsFields.employee_NationalityTable = dt.GetNationalities();*
return View("CreateEmployeeFamilyDetails", employeeSuperClass);
}
Please look into starred lines
In case if there is any errors in model am getting null reference
In post action method look like this
[HttpPost]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, string Command)
{
if (ModelState.IsValid)
{
return("some view");
}
else
{
return view(employeeSuperClass);
}
}
I know again we have to create instance to populate dropdownlist this is rubbish to do same again and again
Can anyone explain how to store dropdown list collection in view separately and post them also with model?
(note: employee_relationTable is IEnumerable collection and it is a relationtable type this table contains relation id and relationname fields and am using this table in this class like below
public IEnumerable<EmployeeRelationTable> employee_RelationTable { get; set; }
For rest also am using same approach
Can we post employee_RelationTable from view and how?
Please help and your help would be greatly appreciated
Assuming dt is a member field available to all methods, what you can do is DRY up the population of the DropDowns in the View Model in a separate method, e.g.
private void PopulateDropDownsOnViewModel(EmployeeSuperClass model)
{
model.FamilyDetailsFields = new FamilyList
{
employee_RelationTable = dt.GetRelations(),
employee_BloodGroupTable = dt.GetBloodGroups(),
employee_NationalityTable = dt.GetNationalities()
}
}
Which can be used in the Get:
[HttpGet]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, int i = 0)
{
PopulateDropDownsOnViewModel(employeeSuperClass);
return View("CreateEmployeeFamilyDetails", employeeSuperClass);
}
and in the Post (and any other controller actions which need the drop downs)
[HttpPost]
public ActionResult CreateEmployeeFamilyDetails(EmployeeSuperClass employeeSuperClass, string Command)
{
if (ModelState.IsValid)
{
return("some view");
}
else
{
PopulateDropDownsOnViewModel(employeeSuperClass);
return View(employeeSuperClass);
}
}
If the drop downs are static, you can also look at caching these to prevent wasted IO to the database.
But no, don't serialize the the data in the View somehow (remember WebForms ViewState?) or fetch data from the View directly - this violates the MVC paradigm - the controller is responsible for providing data for the View to render.
I'm having some issues with my DropDownLists, because when I post the information and my Model is not valid it comes back "empty" to the page triggering an error exactly like this question.
I've used the solution proposed there and it fixed my problem. Anyway, I wanted to avoid querying the database every time my ModelState is not valid and I came with this approach. I would like to know if it is valid or if there are better ways to do it now, considering that instead of MVC2 (which was the MVC version from the question) I'm now using MVC 5, maybe they added something new to tackle this.
What I've done was to use the TempData to persist the information when my model is not valid.
public class ViewModel
{
[DisplayName("Project")]
public int ProjectID { get; set; }
public List<SelectListItem> Projects { get; set; }
//Other fields
}
Now my Create() Action (that populates Projects)
[HttpGet]
public ActionResult Create()
{
ViewModel vmodel = new ViewModel();
vmodel.Projects = db.GetProjects(User.Identity.Name).Select(x => new SelectListItem { Text = x.Description, Value = x.Id }).ToList();
TempData["Projects"] = vmodel.Projects;
return View(vmodel);
}
And my post would be like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ViewModel vmodel)
{
//Clear TempData (in theory will clear my tempdata when read, so if this controller redirects to another action my tempdata will be clear)
List<SelectListItem> projects = (TempData["Projects"] as List<SelectListItem>);
if (ModelState.IsValid)
{
//...
}
//If it got here it's going back to the screen.
//Repopulate the TempData (allowing it to exist one more trip)
TempData["Projects"] = projects;
vmodel.Projects = projects
return View(atendimento);
}
Is this approach a good one? Is there a better way to achieve that without querying the database every single time?
Thanks a lot!
You don't need to use TempData at all as you have a property in your view model to hold the dropdown items.
public ActionResult Create()
{
ViewModel vmodel = new ViewModel();
vmodel.Projects = GetProjects();
return View(vmodel);
}
private List<SelectListItem> GetProjects()
{
return db.GetProjects(User.Identity.Name)
.Select(x => new SelectListItem { Text = x.Description,
Value = x.Id }).ToList();
}
And in the view
#Html.DropDownListFor(s=>s.ProjectID,Model.Projects)
And in your HttpPost action, If ModelState is not valid, Reload the Projects collection again (because http is stateless)
if(ModelState.IsValid)
{
// to do :Save and redirect
}
model.Projects = GetProjects();
return View(model);
You may cache the Projects so that you do not need to hit the database every time, if you are too much worried about performance.
Personally, I wouldn't worry about querying the database each time for this kind of operation.
What if projects are added/deleted? This could be the reason the save failed (selected project deleted) and the user would never realise it.
I usually write a method to populate all of my view model's SelectListItems and then use this in my Get and in my Post if the validation fails.
In an MVC4 project I need to "refresh" the page depending on some messages that can be present, otherwise I just redirect to a page, and if presenting again the page if them messages are present I would like to avoid just returning the View as it will cause then the double submission when the user tries to refresh it.
What I'm trying to do is this
[HttpGet]
public ActionResult SampleMethod()
{
viewModel = _builder.Build();
return View(viewModel);
}
[HttpPost]
public void SampleMethod(SampleViewModel viewModel)
{
if (ModelState.IsValid)
{
var response = serviceCall;
var errorMessages = response.ErrorMessages;
if (!errorMessages.Any())
{
//Redirect to proper view
}
else
vm = _builder.Build();
}
else vm = _builder.Build(); //There is some validation error I rebuild
CashbackOffersConfirmation(vm);
}
public ActionResult SampleMethodConfirmation(SampleViewModel viewModel)
{
return View("SampleMethod", viewModel);
}
It goes through the process
but the final page is .../SampleMethod instead of .../SampleMethodConfirmation and is blank,
Is this something to do with the routing (quite lost in this)? Is this a correct approach?
Thanks
In order to pass the object model from the view to the controller, you need to make a post request. Make sure you use a form that will generate the post request.
Also make the SampleMethodConfirmation method a post.
E.g.: add [HttpPost] on top of the method in the controller