View Called from Partial Not Submitting Form Data - asp.net-mvc

I hope I am able to put this question together well.
In a partial view I have a link to a create action:
public ActionResult CreateProject()
{
return View("EditProject", new Project());
}
Now this loads another view which allows editing of the blank model passed to it. But when form is submitted it is supposed to post to:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditProject(Project record)
{
if (ModelState.IsValid)
{
projectRepo.saveProject(record);
return View("Close");
}
else
{
return View("EditProject");
}
}
This method works for many of the tables and edit actions work just as well for the same view. But only for the create action (with the blank model) the form keeps calling to the create action, as I traced with the debugger.
One of my team mates has solved this problem so:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult EditProject(int id)
{
Project project = null;
if (id == 0)
{
project = new Project();
}
else
{
project = (from p in projectRepo.Projects
where p.ProjectID == id
select p).First();
}
return View(project);
}
And in the partial instead of having <%= Html.ActionLink("Create New", "CreateProject")%> there'd be <%= Html.ActionLink("Create New", "CreateProject", new { id = 0 })%>.
Now I was hoping to find out why the previous method would not go through, since it does for other tables in other views. Thanks.

By default your form will post to same URL it was rendered at. Since you called create action it will post back to create action, and not edit, 'cos views do not matter (-:
Explicitly use
<%= using( Html.BeginForm("Action","Controller) ){ %>

Related

Asp.net mvc. Routing with trailing slash

I want to have two routes, for example:
http://localhost:1227/Product
and
http://localhost:1227/Category/
trailing slash is the only difference.
In first case I want to show information about product in other case about category. "Product" and "Category" are names of some products or categories
Is it possible to implement?
My approach:
Routes:
routes.MapRoute("category route", "{name2}", new { controller = "test", action = "GoToView2" }, new { name2 = ".*/?" });
routes.MapRoute("product route", "{name}", new { controller = "test", action = "GoToView1"});
Controller:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
return View();
}
public ActionResult GoToView1(string name)
{
// call to db to get some information about product
return View((object) name);
}
public ActionResult GoToView2(string name2)
{
// call to db to get some information about category
return View((object)name2);
}
}
Index View:
......
<br/>
Show proudct
<br/>
Show category
......
But I see that only "GoToView2" is called for two link.
Any ideas how to fix it?
Thanks,
Why would one wants to do that? in your case you have to completely different controller, namely Product and Category and two different action, and even if we could implement the same URL to call different action based on slash, it would be a code smell(bad design), in your case you can simply call TestController/GoToView1 and TestController/GoToView2, if this is not the case, tell us exactly what problem you are trying to solve, maybe there would be a better approach.

Two partial views inside an MVC view

I have the following scenario, where I have one model (named Model A) in a view (View1).
This view initially loads a partial view (Partial View 1)
On button click of partial view, I am trying to pass the id generated to another partial view (Partial View 2).
But I am getting an error saying View 1 cannot be found, which loaded without any issues on first run.
If I remove the else statement, the page successfully reloads after submission.
Any tips on passing this model object successfully to the other view please.
I put id=1 and tested it and the same error occured.
I tried RenderAction, RenderPartial and all these failed
Page
#model MyModel
#{
if (ViewBag.Created ==0) {
#Html.Partial("CreateView1",Model);
}
else
{
{ Html.Action("Action2", "Area/Controller2", new { id = Model.Id }); }
}
}
Controller methods:
Controller 1:Entry point of view
[Route("{CreateView1}")]
public ActionResult Create() {
ViewBag.Created = 0;
return View(new MyModel());
}
[Route("{CreateView1}")]
[HttpPost]
public ActionResult Create(MyModel model) {
...........................
ViewBag.Created = 1;
}
Controller 2 which renders 2nd partial view:
public PartialViewResult Index(int createdId)
{
return PartialView(new List<Model2>());
}
Regarding View 1 cannot be found, is because the keyword return in your second Create action is missing. The button click submits the form to the Create method with [HttpPost] attribute and the end of the method, it needs a return View.
Reg Any tips on passing this model object successfully to the other view please, The return in the second Create method should be return View(model); and not 'return View(new MyModel);` as later on in the View you are going to use the Model.
Re I put id=1 and tested it and the same error occured., because runtime never reachs that point as the operation is being handed to '[HttpPost] Create' and it never get back to your Original Page.
There are other issues with your code as you are using different names in your code than what you mention in your description...
A simple solution is:
1- use the following return at the end of you [HttpPost]Create Action:
return RedirectToAction("Action2", "Area/Controller2", new { id = model.Id});
2- replace the following code in your initial page
if (ViewBag.Created ==0) {
#Html.Partial("CreateView1",Model);
}
else
{
{ Html.Action("Action2", "Area/Controller2", new { id = Model.Id }); }
}
with the following:
#Html.Partial("CreateView1",Model);
and remove anywhere you set ViewBag.Created = 0 or ViewBag.Created =1
I also assume the action action2 in controller Controller2 returns a valid Partial View.
Hope this help you get some idea to fix your code.
You may have omitted this for brevity, but you will want to return a viewresult at the end of your post action:
return View(new MyModel());
try this:
if (ViewBag.Created ==0) {
#Html.RenderPartial("CreateView1",Model);
}

Unwanted caching in MVC application

I have an issue with caching and have tried every solution I can find!
I have a simple create screen where one row is inserted in a table (although I also get the same issue when editing existing rows).
When the row is created, the user is returned to the previous screen, which still shows the old data. (same issue with edit)
Refreshing the page makes no difference. Difference browsers have the same problem. The data is successfully added in the database. Only by restating the application does it refresh the data on screen.
Things I have tried:
1:
DataContext.Refresh(RefreshMode.OverwriteCurrentValues)
2:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
3:
ModelState.Clear()
None of which made any difference. I've not had this problem before with edits or creates, so I must be missing something. Any help much appreciated!
The following is the relevant parts of the controller:
ISISDataContext db = new ISISDataContext(StudentISIS.Properties.Settings.Default.ISISConn.ToString());
public ActionResult Index()
{
var student = (ISIS2Models.Student)Session["CurrentUser"];
return View(student);
}
public ActionResult STGCreate(int id)
{
var enrolment = db.Enrolments.Single(e => e.EnrolmentID == id);
return View(enrolment);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult STGCreate([Bind(Exclude = "Id")] StudentGrade STGToCreate, FormCollection collection)
{
var STG = new StudentGrade();
STG.Grade = collection["StudentTG"];
STG.EnrolmentID = int.Parse(collection["Enrolment"]);
STG.DateChanged = DateTime.Now;
db.StudentGrades.InsertOnSubmit(STG);
db.SubmitChanges();
return RedirectToAction("Index");
}
Edit:
Here is the code from the index view which loops through enrolments to show the grade:
<%foreach (var en in Model.Enrolments) {%>
//Some table stuff
<td>
<%try
{ %>
<%= Html.ActionLink(en.StudentGrades.Grade,"STGEdit",new {controller = "Progress", id = en.StudentGrades.StudentGradeID})%>
<%}
catch (NullReferenceException) {%><%= Html.ActionLink("Set","STGCreate",new {controller = "Progress", id = en.EnrolmentID})%><% } %>
</td>
//Some more table stuff
<%}%
Where do the rows come from? is it your ISIS2Models.Student class? I can only assume it is because you have so little code in your Index method.
If it is, and you are storing that in the session, then you are not updating that value, so when you retrieve it from within Index it will still have the same old values.
What you need to do is get the updated model from the database each time you make a call to Index. Some method like this:
public ActionResult Index()
{
var currentUser = (ISIS2Models.Student)Session["CurrentUser"];
var student = GetStudentById(currentUser.ID);//this will get the up-to-date student record from the DB
return View(student);
}

After an ajax post, I have problems for displaying results

I have a view named Index.cshtml with a table of users (create/edit/update functionalities).
When buttons (create/edit/update) are clicked I have a jQuery dialog for it. Next I use an ajax post like this:
Edit >> call an action controller for editing and return a json >> update row table with javascript
Delete >> call an action controller for deleting and return a json >> delete row table with javascript
Create >> call an action controller for creating and redirect to Index.cshtml >> ???
For the create part, I have a problem: I would like "simply" to display my view but I am in an ajax post. I don't know how to proceed.
Here is my code (submitting data and then refreshing the view):
$.post($(this).attr('action'), $(this).serialize(), function (data, status) {
$('#my-modal').modal('hide');
if (data.operation == 'edit') {
// Edit
var row = $('#' + data.userid);
row.children(':eq(0)').text(data.company);
row.children(':eq(1)').text(data.username);
row.children(':eq(2)').text(data.email);
row.children(':eq(3)').text(data.firstname);
row.children(':eq(4)').text(data.lastname);
} else if (data.operation == 'delete') {
// Delete
var row = $('#' + data.userid);
row.remove();
} else {
// Create
alert(data);
$("#userList").html(data);
}
})
As you can see, I check the data.operation which tells me if I do an 'edit' or 'delete' of (last possibility) a 'create'. For the create the problem is that my entire view (< html> < body>...) is generated and added in my #userList div. That's duplicate things.
Maybe that's not the right thing to do?
Thanks.
UPDATED
Here is my Create action:
[HttpPost]
public ActionResult Create(UserCreateViewModel viewModel)
{
if (!ModelState.IsValid)
{
this.Response.StatusCode = 400;
return PartialView("Create", viewModel);
}
var userDTO = new UserDTO();
Mapper.Map(viewModel, userDTO);
_requestServiceClient.CreateNewUser(userDTO);
return RedirectToAction("Index");
}
Here is my Index action:
[Authorize]
public ActionResult Index(string q, int? page)
{
var users = _requestServiceClient.GetUsers();
...
...
if (Request.IsAjaxRequest())
return PartialView(userListPaged);
else
return View(usersListPaged);
}
After relexion, in case of 'create' maybe it is better in my view to simply reload the page like this:
...
} else if (data.operation == 'create') {
// Create >> refresh the page
location.reload();
}
Thanks anyway.
If your list is not too long I would suggest to send back the complete list in every ajax request, doesn't matter if update, create or delete.
So you can remove your data operation check (your :eq(0) stuff will fail if you change the order of the columns some day) and simply put the result from the ajax request in your #userlist div.
EDIT
But to solve your create problem:
Just replace in the Create action this line:
return RedirectToAction("Index");
with this line:
return PartialView("Index", _requestServiceClient.GetUsers());
and it should return only the new list.
EDIT2
Ok, your Index.cshtml seems to have the complete layout, not just the list as I thought. So you have to do this first:
Move just the HTML code required to build the list in a seperate View, name it _List.cshtml for example
At the place where the HTML code was in your Index.cshtml put #Html.Partial("_List") in there
At this point your index view should look the same as before but you have your list view seperatly
Change my example above to not return the Index but the _List partial:
return PartialView("_List", _requestServiceClient.GetUsers());
I hope that works for you.
Place this code into your partial view:
#{
Layout = null;
}
This code says that you don't need a master view for this page.

Should RenderAction be used with forms?

My setup:
Have a view for a route like: /Pages/Details/2
The page details view has <% Html.RenderAction("CreatePageComment", "Comments"); %> to render a comment form
Comment form posts to Comments/CreatePageComment
/Comments/CreatePageComment returns RedirectToAction when a comment is created successfully
This all works nicely
My question:
If there is a validation error, how should I return to /Pages/Detail/1 and show the error in the comment form?
If I use RedirectToAction, it seems validation is tricky; should I even be using the Post-Redirect-Get pattern for validation errors, instead of just returning?
If I return View() it kicks me back to showing the CreateComment.aspx view (with validation, but just a form on a white page), not the /Pages/Details/2 route that called the RenderAction.
If the PRG pattern should be used, then I think I just need to learn how to do validation while using PRG. If not — and to me this seems better handled by returning View() — then I don't know how to get the user returned to the initial view, showing the form errors, while using RenderAction.
This feels like the game where you tap your head and rub your belly at the same time. I wasn't good at that one either. I'm new at MVC, so that's likely the problem here.
I believe the answer is to use TempData, for example:
In my view (/Steps/Details) I have:
<!-- List comments -->
<% Html.RenderAction("List", "Comments", new { id = Model.Step.Id }); %>
<!-- Create new comment -->
<% Html.RenderAction("Create", "Comments", new { id = Model.Step.Id }); %>
In my comments controller I have my POST method:
// POST: /Comments/Create
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id, Timestamp, ByUserId, ForUserId")]Comment commentToCreate)
{
if (ModelState.IsValid)
{
//Insert functionality here
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
//If validation error
else
{
//Store modelstate from tempdata
TempData.Add("ModelState", ModelState);
//Redirect to action (this is hardcoded for now)
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
}
Also in the comments controller is my GET method:
//
// GET: /Comments/Create
public ActionResult Create(int id)
{
if (TempData.ContainsKey("ModelState"))
{
ModelStateDictionary externalModelState = (ModelStateDictionary)TempData["ModelState"];
foreach (KeyValuePair<string, ModelState> valuePair in externalModelState)
{
ModelState.Add(valuePair.Key, valuePair.Value);
}
}
return View(new Comment { StepId = id });
}
This works great for me, but I'd appreciate feedback on whether this is a good practice, etc.
Also, I noticed that MvcContrib has a ModelStateToTempData decoration that appears to do this, but in a cleaner way. I'm going to try that next.

Resources