I have a contact page and this page shall either show a form or a success message or a failure message, so basically something like this:
#model MyApp.Models.ContactData
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
...Some static content...
If page was opened the first time
-> Render a form here
Else If form was posted and data successfully processed
-> Render a success message here
Else If form was posted but error occurred during processing
-> Render a failure message here
...Some static content...
</div>
I don't know what's the best way to achieve this with MVC 3. Do I create three completely separate views (which is something I'd like to avoid because of the static content which would be the same for all three views)? Or could I create three partial views and then decide based on an additional flag I could put into the model class which partial view to render? Or can I inject somehow the partial views dynamically from the controller into the view?
The controller I have so far looks like this:
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return ?; // What do I return now? It must somehow depend on result.
}
else
return View(contactData));
}
}
I had a similar page and behaviour with ASP.NET WebForms and the solution was there to put the three variable blocks of markup into asp:Panel controls and then switch on or off the Visible flag of those panels from code-behind. I guess I need quite another approach with ASP.NET MVC to reach the same goal.
What is the best way?
Thank you for suggestions in advance!
You can try this way:
[HttpPost]
public ActionResult Index(Contact contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
if (service.Process(contactData))
{
TempData["Success"] = "Your success message.";
return RedirectToAction("Index");
}
else
{
TempData["Error"] = "Your fail message.";
}
}
return View(contact);
}
Perhaps use the ViewBag to help achieve all this. Of course it's a dynamic, so you can add & check for any prop you want/need/expect.
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
ViewBag.ContactSuccess = true;
}
else
{
ViewBag.ModelStateErr= "some err";
}
return View(contactData));
}
Then in your View:
if (ViewBag.ContactSuccess !=null && ((bool)ViewBag.ContactSuccess))
{
//thanks for posting!
}
else
{
if (ViewBag.ModelStateErr !=null)
{
//show that we have an err
}
else
{
//we have no err nor a 'true' contact success yet
//write out the form
}
}
Looks like that you can issue an ajax call on the client side, and based on the Json result, you can render different content from the client side.
I'd suggest coding up three different Views
index.cshtml
contactSuccess.cshtml
contactFail.cshtml
Then in your Controller, you'll have similar code as before
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return View("contactSuccess.cshtml");
}
else
return View("contactFail.cshtml", contactData);
}
}
This way each view has an independent and you don't have a big inline IF block in the middle of your markup.
Alternatively (and this is how I'd do it) you can have the index.cshtml contain three partials...
_ContactForm.cshtml
_ContactSuccess.cshtml
_ContactFail.cshtml
and then you can load the partial views into the index view, and even swap them out dynamically using AJAX.
Related
I'm trying this code:-
If no query string supplied to the Index Method then render a Branch Locator View. When a Branch Id is selected in that View, post back to a Redirect To Route Result OR Action Result method and then redirect back to Index with a query string of the selected Branch Id.
I can run through the code successfully without and then with the query string.
I even run through the Index View and can see the Model working however, the Index View does not render, the Branch Selector View remains. Network developer tools shows the correct URL with query string correctly in place when doing the Redirect.
(NOTE: Both methods are on the same controller).
If I add the same query string directly in the Browser address bar it works fine!
I have this code:
[HttpGet]
public ActionResult Index()
{
var querystringbranchId = Request.QueryString["branchId"];
if(!string.IsNullOrEmpty(querystringId))
{
....do stuff like build a model using the branchId...
return View(Model);
}
return View("BranchSelector")
}
[HttpPost]
public RedirectToRouteResult BranchDetails(FormCollection formCollection)
{
var querystringBranchId = formCollection["BranchList"];
var branchId = int.Parse(querystringBranchId);
return RedirectToAction("Index", new { branchId });
}
Try using strongly typed model on the post, and specifying the param as an actual param - Using View models is going to be much better for you.
I have tested the below - It seemed to work as expected for me:
[HttpGet]
public ActionResult Index(int? branchId)
{
if (branchId.HasValue)
{
return View(branchId);
}
return View("BranchSelector");
}
[HttpPost]
public RedirectToRouteResult BranchDetails(MyModel myModel)
{
return RedirectToAction("Index", new { myModel.BranchId });
}
public class MyModel
{
public int BranchId { get; set; }
}
The View:
<div>
#using (Html.BeginForm("BranchDetails", "Home", FormMethod.Post))
{
#Html.TextBox("BranchId","123")
<input type="submit" value="Go"/>
}
</div>
#MichaelLake Thanks to your post I found the problem. I tried your code and sure enough it works as expected. I didn't mention I was using a Kendo Combobox control (!) loaded with the branches. I didn't mention that as the actual data I needed was available in the post method so, thought the issue was with the Controller methods. I had the Kendo control name as BranchList, I changed it to BranchId and it now works with the original code as expected! The Kendo name becomes the element Id and has to match to work.
Many Thanks!
This will work for you. Cheers :D
return RedirectToAction("Index", "ControllerName", new { branchId = branchId});
In MVC5 controller, I do have a ActionResult which will display different reports as per users choice. I am doing it as following with no errors.
MainController:
// POST: Report Init
[HttpPost]
public ActionResult ShowReport(ReportUserInput userInput)
{
return View(userInput);
}
ShowReport.cshtml [View File]:
#model App.ReportUserInput
<h2>ProjectBasedReport</h2>
#if(Model.rep_type == EnumOldReportTypes.ByGender)
{
Html.RenderAction("ByGender", Model);
}
else if (Model.rep_type == EnumOldReportTypes.ByAddress)
{
Html.RenderAction("ByAddress", Model);
}...
Here it works fine, I am only concerned with long if else, How can I call them without if condition something like:
HTML.%somefunction%
First make your actions names same as your enum's enumerators names. Then simply write following code instead of multiple if/else:
Html.RenderAction(Model.rep_type.ToString(), Model);
Or even if you can not match the enum and action names you could use a Dictionary to map your enum with proper action name:
var reportTypesActions=new Dictionary<EnumOldReportTypes, string>
{
{ EnumOldReportTypes.ByAddress, "ActionNameOfByAddress" },
{ EnumOldReportTypes.ByGender, "ActionNameOfByGender" }
};
Now you could write following code:
Html.RenderAction(reportTypesActions[Model.rep_type], Model);
I have recently taken over support of an ASP.NET MVC project, and am trying to work through some of the errors, one in particular has me stumped though.
We have a 'New' page to add new items, with the following code running when the page is posted:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
However, when it tries to load the Index view, I get the following error: "Object reference not set to an instance of an object." and it points to the following block of code at the top of a file called RecordsView.cshtml:
#for (var i = 0; i < Model.Records.Count; i++)
The record does add correctly though, it just doesn't load the listings page correctly, and since this is just a "nice to have" I thought I'd simplify things by changing it so that it either returns some text which generates an error as it's expecting a boolean returned.
Any ideas on how to fix this please? I'm stumped.
Thanks!
The flow of your code here doesn't look right:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
From your description above, it sounds as though you're POSTing from your New view, to this New action, which should then redirect, when successful, to your Index action. Currently, the above code is not doing that, and it also fails to redisplay the form if the model isn't valid. It should probably look more like this:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
// Redisplay the `New` view, passing the model
// back to it in order to repopulate the form
return View(i);
}
The important distinction between return View(...) and return RedirectToAction(...) is that the latter will run the action and return the associated view. The former will simply return the view. That has implications in that if the Index action builds a model, and passes it to the Index view, none of that will happen if you simply return the view.
You could of course do something like:
return View("Index", new YourModelType());
but that isn't going to work if, as discussed above, your Index action performs some other data construction for your model, such as building drop down lists, which new YourModelType() wouldn't do.
Also, when data that is sent to a POST action is valid, you should be redirecting to another action (as I've done above), rather than simply returning a view, in order to conform with the Post-Redirect-Get pattern, which prevents some types of duplicate form submissions.
You display Index view, and seems that it requires some Model - Model.Records. And you don't pass it in this HttpPost action.
If you have action for that Index page, then you can just do
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
return View(i);
}
It will just redirect a user to Index view, after creation of new RecordView item
Basically you may are trying to achieve PRG(Post/Redirect/Get) modal.
I guess, the problem is you are not sending the model for your GET request.
Post--> Save --> Redirect --> Load Data -->Assign to View in Index -->Access in view
//POST & REDIRECT
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}
//GET
public ActionResult Index()
{
var model=new MyViewModel();
model.Records=repository.GetRecords(i.DogIncident);
return View(model); //Assign to View in Index
}
Index.cshtml
#model MyViewModel
#for (var i = 0; i < Model.Records.Count; i++)
If Records is a list, make sure your ViewModel has a constructor:
public class RecordView
{
public List<Record> Records { get; set; }
public RecordView()
{
Records = new List<Record>();
}
}
You mentioned that the record adds correctly, so you must be passing a valid record model into your view from some other action than the one provided.
If #for (var i = 0; i < Model.Records.Count; i++) is the cause of this error, my guess is that the model exists, but the Records property has not been set. One immediate work around would be checking the existence of this property before accessing it. For example:
if (Model.Records != null) {
for (var i = 0; i < Model.Records.Count; i++) .....
}
i think you have collections are not instantiated, the error may be in models not in view models. this because when ever you have a collection you need to instantiate inside of constructor of that entity.
May be this is your answer...!Just look
[HttpPost]
public ActionResult New(RecordView)
{
if(ModelState.IsValid)
{
repositry.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}
I have an asp.net mvc4 application in which i have this snippet:
public bool Logout() {
try {
session["user"] = null;
return true;
}
catch {
return false;
}
}
when i put this code in a controller it's works but if i put it in a model class it didn't.
the problem is in the session["user"] = null; .
So how can i manage the session's variables in a model class?
This functionality should not be in a view model. The model should be used for passing data from controllers to views for displaying, and receiving submitted data from views.
See a question like What is a ViewModel in MVC to get a better explanation.
A logout function should be an action on a controller. Something like:
public ActionResult Logout()
{
Session["user"] = null;
// Redirect user to homepage
return Redirect("/");
}
In class access by the current context :
HttpContext.Current.Session["user"]....
I have an index action on a controller as follows...
public ActionResult Index(string errorMsg = "")
{
//do stuff
ViewBag.ErrorMsg=erorMsg;
return View();
}
I have another action that is an http post for Index.
When there is something wrong I want to reload the Index page and show the error...
I have my view already conditionally showing errorMsg. But I cannot figure out how to call Index and pass in the error string?
Typically, you'd just share the view between the two actions. I'm guessing you have actions that look something like this (the more info you provide about what index does, the better my example will be):
public ActionResult Index()
{
return View();
}
[HttpPost, ActionName("Index")]
public ActionResult IndexPost()
{
if (!ModelState.IsValid)
{
ViewBag.ErrorMsg = "Your error message"; // i don't know what your error condition is, so I'm just using a typical example, where the model, which you didn't specify in your question, is valid.
}
return View("Index");
}
And Index.cshtml
#if(!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
#ViewBag.ErrorMsg
}
#using(Html.BeginForm())
{
<!-- your form here. I'll just scaffold the editor since I don't know what your view model is -->
#Html.EditorForModel()
<button type="Submit">Submit</button>
}
If I understand you correctly you just need to hit the url with the errorMsg in the query string:
/*controllername*/index?errorMsg=*errormessage*
However, when there is something wrong you don't necessarily need to reload the page. Seems like you might be approaching this in the wrong way..?
You can use RedirectToAction to redirect to the page, with a querystring for errorMsg value.
[HttpPost]
public ActionResult Index(YourViewModel model)
{
try
{
//try to save and then redirect (PRG pattern)
}
catch(Exception ex)
{
//Make sure you log the error message for future analysis
return RedirectToAction("Index",new { errorMs="something"}
}
}
RedirectToAction issues a GET request. So your form values will be gone, because HTTP is stateless. If you want to keep the form values as it is in the form, return the posted viewmodel object again. I would get rid of ViewBag and add a new property called ErrorMsg to my ViewModel and set the value of that.
[HttpPost]
public ActionResult Index(YourViewModel model)
{
try
{
//try to save and then redirect (PRG pattern)
}
catch(Exception ex)
{
//Make sure you log the error message for future analysis
model.ErrorMsg="some error";
return View(model);
}
}
and in the view you can check this model property and show the message to user.