MVC, PartialView has what looks like postback data? - asp.net-mvc

I'm new to MVC and I'm trying to make my first Ajax page, PartialView.
What I have is an action that looks like this
[HttpGet]
public PartialViewResult MainContent()
{
return PartialView(new ClockingModel());
}
[HttpPost]
public PartialViewResult MainContent(SingleClocking SingleClocking)
{
// update logic
return PartialView(myClockingModel);
}
What I'm finding is that the view renders, I enter some data and it posts back correctly. My partial view includes a select:
#Html.DropDownListFor(x => x.SingleClocking.InWhen, new SelectList(Model.DropDownValues, Model.DefaultValue))
The select shows the value that I changed on the GUI, not the new DefaultValue?
If this was plain Asp.Net I would assume this was because of postback data or viewstate but MVC doesn't use either of these?
I can see the view being returned via fiddler, it shows the select with the value as per the client, not the value I expected to see via the server.
I'm sure I'm just missing something simple here, any hints?
Also, could I look at the value of the View on the server? This won't work but something like: string viewText = PartialView(myClockingModel);
p.s. I don't think it's caching, I tried switching that off with an attribute

And even though I searched for a while before asking this I finally found the answer.
I feel a bit better knowing that Rick Strahl had the same confusion.
Posting as an answer for others to find
Long story short, this is by design, the blog is worth reading
http://weblog.west-wind.com/posts/2012/Apr/20/ASPNET-MVC-Postbacks-and-HtmlHelper-Controls-ignoring-Model-Changes

Related

How to access data from differnt partial view after POST

I have 3 partialviews with 3 viewmodels on page:
List of accounts
Modal popup (you can modify multiple accounts here)
Search panel
I want to refresh 1. after doing POST on 2. This is straightforward, but what if I want to keep results I got after using Search Panel?
I can do this in 2 ways but both seems bad (correct me if I am wrong).
First (the one I chose and works) is to store viewmodel used in 3. in TempData. I do Search (POST) and save passed viewmodel in TempData. Then whenever I do POST on different partialview I can refresh 1. using data(search parametrs) from TempData.
private const string SearchDataKey = "SearchData";
[HttpGet]
public PartialViewResult RefreshData()
{
if (TempData[SearchDataKey] != null)
return PartialView("AccountListView", PrepareAccountListViewModelForSearchData(TempData[SearchDataKey] as AccountSearchViewModel));
else
return PartialView("AccountListView", PrepareAccountListViewModel());
}
and saving ViewModel:
public PartialViewResult Search(AccountSearchViewModel searchParameters)
{
...
TempData[SearchDataKey] = searchParameters;
return PartialView("AccountListView", databaseAccountListViewModel);}
Second approach is to always POST "big" viewmodel with all 3 viewmodels. This way I will have data from Search's viewmodel but I will send many not needed information instead just Modal Popup's viewmodel which I need to call procedure.
I asked few MVC folks with better experience and they said they never had to store viewmodel in TempData but it still seems more reasonable than having 1 Big form and passing everything in every POST.
Do you know any better ways to handle this or which one is correct?
PS. Topic had "Best Practice" but was removed cause of warning message. I hope asking about opinion is still allowed on SO.
PS2. Most of my POSTs & GETs after initial load are through Ajax.
I do Search (POST)
This seems semantically incorrect to me. Searching is an action that shouldn't modify any state on the server. So using GET seems more appropriate. And when you use GET you have the benefit that all parameters are already present in the query string and thus preserved upon successive POST actions (like modifying an account in your case). So your RefreshData action could take the AccountSearchViewModel as parameter and the model binder will take care of the rest.

How to Use ViewData and ViewBag with Umbraco Surface Controllers

I have just spent 2 hours trying to work out why when I put a string in to View.Bag/ViewData inside my Surface controller, when I try and get the string back in the view I get null.
In the end I have solved the problem by putting the string in to a session variable insted.
Would like to know though why it wasn't working, and how to fix it.
Thanks in advance.
Update: Are you posting and redirecting? When you refresh the form does it prompt you about posting again? If not, it's because you have accidentally followed the best practice of 302ing from a form post (prevents a user refreshing and reposting form data). The examples I was following for login surface controllers all used return RedirectToCurrentUmbracoPage() which I blindly followed. But, as the name implies that really is doing a redirect and it is really two requests! (I stubbornly had to verify in Fiddler before I believed it). ViewData and ViewBag are only good for one request--so they are fundamentally broken in a POST 302. Session is good for multiple requests which is why it worked for you. TempData will work for you too, because as it turns out, TempData is a construct that is built on top of session and was specifically designed to carry state between two posts (removed on retrieve). I read somewhere that TempData would have been better named RedirectData and that helped it click for me.
So when you're dealing with Surface Controllers and POSTing you have three options that I know work:
Session (which you proved worked)
TempData (which is built on session, and from what I've read is both best practice and built specifically for this situation)
Use return CurrentUmbracoPage(); in your form post. I just verified in Fiddler that this is exactly one request (refreshing in the browser prompts a repost warning). I also verified that ViewData works this way. But, because the surface controller is rendered as a Child Action using #Html.Action(...) you have to use ParentActionViewContext to get at the right ViewData (my first answer which I'll leave for others that find this question).
Original answer is still useful when there is no redirect involved (GET or a POST that returns CurrentUmbracoPage())...
In many cases you're actually making a child action. Usually you're only one level deep but if you mix macros and partials you can actually get multiple levels deep. There is a ViewData for each level and you have to walk your way up the stack with ParentActionViewContext to get to the top ViewData that you populated in your controller.
See this comment from Shannon in answer to a question about surface controllers and viewdata (Shannon is a core contributor on the HQ team and has a lot of great content out there). Quoting here:
If you want to access the ViewData that you've set on the master ViewContext's on a ChildAction being rendered from the master's ViewContext then you need to use #ViewContext.ParentActionViewContext.ViewData["ErrorMessage"]
The ParentActionViewContext in this example is the ViewContext that is rendering the Umbraco template, not the ChildAction. That is because when you POST (whether inside of Umbraco or normal MVC), you are posting to a new Action and the rendering process starts from scratch, when you validate your model, update the ViewData, etc... this all happens on what will become the 'master' ViewContext when the view renders. This view then will render your ChildAction.
Twamley's answer above is excellent, in addition to this, I have found that using TempData.Add(key, value) works nicely.
An bare bones would look like:
SurfaceController
public class MyController : Umbraco.Web.Mvc.SurfaceController
{
public MyController()
{}
public ActionResult DoSomething()
{
// surface controller does something
// get a page by it's document/model type alias
var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
var node = umbracoHelper.TypedContentSingleAtXPath("//" + "Home")
TempData.Add("Message", "This value will be passed through");
return redirectToUmbracoPage(node);
}
}
View
#inherits UmbracoTemplatePage
#{
Layout = null;
}
#if (TempData.ContainsKey("Message"))
{
<p>#TempData["Message"]</p>
}
http://localhost/umbraco/Surface/My/DoSomething

ASP.NET MVC 3 POST ModelState and ViewModel

In my ASP.NET MVC 3 app I have a page where, after its form is submitted I need to change a value from the form. If I do it in the ViewModel it has no effect, I have to do it use ModelState["Field"].Value.
The thing is that I actually have a lot of work I have to do on the ViewModel when the pages loads AND after the POST. So I have a method which receives the ViewModel and do all that work. But, as I said, after the POST, modifying the ViewModel object is useless. Why is that? The method on the controller is returning View(viewModel);. Shouldn't the view use the ViewModel values?
I'd like to understand why is useless to modify the ViewModel and also know if there is some method in the ModelState which "refresh" the fields values throw a ViewModel object.
Here is my controllers code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(MyViewModel viewModel)
{
try
{
if (ModelState.IsValid)
//Do stuff and redirect
}
catch(Exception ex)
{
//Log and add error to the ModelState
}
someMethodWhichDoesALotOfThingsInTheViewModel(viewModel);
return View(viewModel);
}
I think the rest of the code is unnecessary, if anyone think it would help let me know!
Update
I don't want to lose any errors, not even those of the fields changed in the ViewModel. A good example would be a captcha, if the users makes any error in the form (only with the captcha field or only with any other fields and not with the captcha) I want to show him all his errors and also update the captcha. It makes no sense to render the form with the old captcha value inserted so I want to blank that field.
Update 2
I've opted to put #Value = Model.PropertyInViewModel in the HTML Attributes of each Html.TextBoxFor. But I'd prefer to avoid that nasty work so if someone comes out with any better solution please let me know.
The following will work:
ModelState.Remove("MyProperty");
viewModel.MyProperty = "new value";
You need to remove it from the model state because HTML helpers first look in there when binding their value.
All I could came out with is to add #Value = Model.PropertyInViewModel to each Html.TextBoxFor that is going to be modified in the ViewModel.
Another way, as #Darin Dimitrov said, would be to make my own helpers.
If you decide to remove modelstate values to prevent round-tripping take a look at this RemoveFor() extension method I made to remove ModelState errors without magic strings.

How to update Model when binding to a ViewModel?

I have an [HttpPost] action method signature like this:
[HttpPost]
public ActionResult Edit(ExistingPostViewModel model)
{
// Save the edited Post.
}
Now, in the past (when i didn't use ViewModels, e.g R&D), i had an implementation of an Edit method like this:
[HttpPost]
public ActionResult Edit(Post model)
{
var existingPost = repo.Find(model.Id);
TryUpdateModel(existingPost);
repo.Save(existingPost);
return RedirectToAction("Success", existingPost.Id);
}
Which worked great.
But i'm confused how to adapt the above to the ViewModel approach.
If i do this:
TryUpdateModel(existingPost)
With my ViewModel approach, not much happens. No errors, but nothing is being updated because MVC won't know how to update a Post from a ExistingPostViewModel (before it was Post -> Post).
Now, i'm using AutoMapper. So i thought i could map from the ViewModel to the Post, then save the post.
But then im basically overriding everything. Which i don't want to do and defeats the point of the cut down ViewModel.
Can anyone un-confuse me?
This seems like a really common scenario, and i am totally stumped as to how people solve this. I can only see 3 possible solutions:
Don't use a ViewModel in the HTTP POST. As i said i did this in the past for R&D and it works, but now i see how my View's have evolved (validation, simplicity), and i can't compromise that just for the sake of this problem.
Don't use TryUpdateModel. Possibly, but then how would i merge in the changes?
Use left-to-right. Ugh. But at the moment this seems to be the way im leaning.
Someone please give me solution #4! :)
BTW, i'm using ASP.NET MVC 3, Razor and Entity Framework.
I actually ran into this exact same issue in a project I'm currently working on. As much as I wasn't a fan of it, I ended up doing the left to right approach and manually mapping my viewmodel data back to my entity.
The only nice thing about this approach is it does give you more control. Since I started using more compound viewmodels, where you actually have fields from more than one entity in your viewmodel, it started making more sense to do things this way.
I'm also using AutoMapper, and you're absolutely right, it does get awkward when you're trying to do a simple update operation. Wish I had some super clever workaround for you, but the "old fashioned way" seems to get the job done best for the work I've been doing.
Not sure if this will help, but it is working for me. I have my underlying domain table as a Visitor Object. My viewmodel contains the Visitor Object plus a couple of IEnumerables for dropdowns.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id)
{
Visitor Visitor = new Visitor();
Visitor = db.Visitors.FirstOrDefault(v => v.VisitorID == id);
UpdateModel(Visitor, "Visitor");
db.SaveChanges();
return RedirectToAction("Index");
}
The UpdateModel works off my viewmodel because of the "Visitor" string telling it what values to compare.
For simple things where you don't have to run any controls prior to implementing the update what you are doing is okay (db.get(), and then update).
When things get complicated, you have to load the entity, and then select and apply user's changes from the view model, property by property. In those cases, you end up writing Update methods, which gets the new data as input, and then you load the existing entity, then compare states, and take the required actions based on the view model data. Actually in this case, probably you wont have an Update method but will have behaviors, like CancelPost, AddComment, EditPost (which also logs the edit reason), AddTagsToPost etc.

MVC Form Post to itself scenario

I have an MVC application. Say for example if we have a dropdown Cars makes which when selected posts to itself and gets Car Models. Now on post, the field Car makes loses it value. I am using Form.Get("combo_box_field_name") and if it has a value I am prepopulating the car make dropdown. As lot of controls on my form do this sort of post and i have to prepopulate the fields , I was wondering if this is the right way of doing stuff. I have done loads of asp classic works and also asp.net and looks like mvc is very similar in approach tp classic asp. If someone can guide in in the right way this can be handled it would be greatly appreciated.
I am not looking to use AJAX so pls dont tell me with regard to cascading dropdowns and I have a host of other controls in the form which will need updating on the form being posted to itself before I leave the page to save the data
I don't think I've ever used Form.get in my MVC application.
I post back to the controller which looks like;
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult MyAction(FormCollection collection)
{
MyClass myClass = new MyClass();
UpdateModel(myClass);
//do stuff with data
return View(myClass);
}
So basically you're letting MVC grab the data from the view for you.
You may need to reload your dropdown lists with this but you can get around that by using JSON to do partial postbacks.

Resources