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
Related
I need to pass objects between ActionMethods and Views in an ASP.net MVC 5 app.
I'm using it for a multi page signup - and for a multi page payment.
Is this bad practice? I haven't seen a good way to pass objects between different controllers.
Code:
public ActionResult Join1()
{
//
return View("Join2", MyObject);
}
[HttpPost]
public ActionResult Join2(MyObject MyObj)
{
//manipulate object
//return
}
It seems to be an effective way to do it - though I haven't seen many people do it this way. I haven't seen objects being passed between action methods much at all.
Is there a flaw in this approach, a better way of passing models between Views - or should each ActionMethod stick to passing simple data with, say, TempData instead of objects?
Why haven't I seen any sample projects doing things like this?
I've seen return RedirectToAction("act"); plenty - but that is Get and passing an object in a URI is limiting - and I don't want users to be able to manipulate or see the data being passed.
thx.
Unless I have misunderstood the description, your code is not doing what you think it's doing. That first return View("Join2", MyObject); statement is not executing the second ActionMethod, it is only passing your data into the View that happens to have the same name as the second method. Therefore the code implied by //manipulate object will not run before that View is rendered and sent back to the user.
Assuming the View file Join2.cshtml exists, and it contains the default #using (Html.BeginForm()), then users submitting the form will cause the Join2 Action to be executed and the same view rendered with the manipulated data - unless, of course, you add another return View() statement that names a different View.
The reason you haven't seen this done much is that the MVC convention is to have a View named the same as the ActionMethod, this makes the code slightly simpler and also much easier for other ASP.NET developers to understand because it is what they are expecting to see.
If you want the form rendered by each View to then execute a different ActionMethod when it is posted back, the place to do that is in the View code, where Html.BeginForm() has several overloads that allow you to do just that, e.g. in Join.cshtml you could write:
#using (Html.BeginForm("Join2", "JoinController"))
{
// form fields and stuff
}
// Produces the following form element
// <form action="/JoinController/Join2" action="post">
To address the final part of your question, "I don't want users to be able to manipulate or see the data being passed", sorry to say it but your proposed code doesn't prevent that: users can see the data in the web form, before it is ever posted back to the Join2 method; they can manipulate the data by sending an HTTP POST containing any data they want back to the Join2 method.
If you absolutely, positively need to actually execute Join2() from within Join(), before anything is passed back to the user, then you can call it just like any other C# method:
var myResult = Join2(MyObject);
Then you have an ActionResult object that you can manipulate or return straight to the browser. But why you would want to do this, is beyond me.
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.
I wanted to put a random image on every viewpage of my mvc project. So i created a method that returns a partialView and call that method in the shared Layout page.
This works fine when I try to login with a correct username and password. The used is loged in and every page contains a random image. But when I give the invalid combination of username and password. The shared layout page does not find the controller I want to call with my #Html.Action and actualy the login view should be returned with an error message 'invalid combination of username and password' and ofcourse, with the random image.
InnerException:
{"A public action method 'RandomSponsor' was not found on controller 'Project.WebUI.Controllers.HomeController'."}
My Html.Action in shared layout.
#Html.Action("RandomSponsor", "Home")
Method in homecontroller.
[HttpGet]
[ChildActionOnly]
public ActionResult RandomSponsor()
{
var model = service.getRandomSponsor();
return PartialView("RandomSponsor", model);
}
The getRandomSponsor method works fine, this one always returns one random string value that is returned to the RandomSponsor.cshtml view.
RandomSponsor.schtml (only contains the image string)
<img src="~/Content/Images/Advert/#(Model)" alt="a" />
I searched the web for this problem but didn't found a solution, does anyone know the answer to this one?
Might it be something with HttpGet of HttpPost?
Regards.
If the executing request is a POST, then it will try to find a method RandomSponsor accepting HttpPost. If this makes sense, you could remove HttpGet and that should do the trick.
This can also happen if you have many layers of calls that start with a POST (I had an action returning a view returning a partial view calling RenderAction), then the call to RenderAction will still look for a POST method
Very similar to this problem that I had here - How to solve "public action method 'methodActionName' was not found on controller 'controllerNameController'"
And if you want to continue to accept the HTTP GET verb and fix the problem of cascading post request into a get request add this to your method
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
Keep in mind that [HttpGet] is the same as [AcceptVerbs(HttpVerbs.Get)]
This will happen if the request is a POST but the controller method is annotated [HttpGet]. For example, you might issue a POST that returns a view containing partial views called with #Html.Action, using controller methods annotated with [HttpGet]. If the original request is a POST, all of the controller methods subsequently called will need to support POST.
To fix it you can use the AcceptVerbs attribute to specify that your controller method accepts both POST and GET:
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get)]
Received this error all of the sudden on several different PartialViews (not all of them) when customizing an install of MVCForum. We had not made any changes to the methods or views concerning the errors so it was really frustrating as to why they were broken.
After trying the other solutions on this post and others, went back through the changes made and what ended up stopping the errors was that we had changed the bindings in IIS to another domain that had the 'enforce lower case url' URL Rewrite rule enabled.
When we disabled the enforce lowercase rule, the errors stopped and the site worked as it was supposed to. It's not a URL Rewrite issue (I don't think) because we are able to enforce www using it with no errors. It's a lowercase rewrite issue. Didn't matter if we had the lowercase rule before or after the www rule.
This solution probably doesn't apply to many cases of this error, but it worked for us. Hopefully someone else can benefit from such a simple fix.
I just solved this issue strangely enough on my local PC, by making sure my entire request path was lower case. So give that a try.
I know this is a pretty old thread - but as it's top Google result I thought I'd add a potentially missing link for MVC.Net 5.2.6.
Scenario
I was attempting to call a child action via #Html.Action("ActionName", new { Id = 123})
and received an error much like the above, but none of the other solutions worked. I could hit the controller action externally (i.e. HttpGet), but the child action kept throwing the exception and was driving me nuts!
The solution I found
After two-ing and fro-ing for some time, I started playing with my routing attributes. I had the controller set up as:
[Route("{action}")]
[RoutePrefix("Prefix")]
[RouteArea("AreaName")]
As there was only one public action i wanted, "Index", I removed the {action} and placed an explicit route attribute on the public action and put my ChildActionOnly attribute back on the child.
After I did that, I hit the run and hey presto - the action was hit.
Might be worth a try if you're getting this error while using attribute routing. Note I did attempt to route the child action and this didn't work.
In my case, the same issue was happening randomly with the implicit :
using (Html.BeginForm())
Changing above to :
using (Html.BeginForm("Action","Controller", FormMethod.Post))
fixed this issue.
Did you give it a shot with Html.RenderAction? It is typically faster then Html.Action as it interact directly into the response stream as opposed to building a string.
You can view the following topics for more info:
What is the difference (if any) between Html.Partial(view, model) and Html.RenderPartial(view,model) in MVC2?
Html.Partial vs Html.RenderPartial & Html.Action vs Html.RenderAction
Another thing to note is that for Html.Action or Html.RenderAction, your view doesn't need to be in Shared folder, that is only required if you use Html.Partial or Html.RenderPartial
What is the point in using a viewmodel when all the relevant data is already available through the parameter in the signature of the controller? I've seen a lot of examples similar to this:
public ActionResult Index(BasicPage currentPage)
{
var model = new BasicViewModel { Heading = currentPage.Heading, Body = currentPage.MainBody, MyBlock = currentPage.MyBlock };
return View(model);
}
Why not just send in the "currentpage" in this example directly to the View? Is there something that is considered bad practise by doing like that?
The above was a general question for asp.net-mvc. I'll add a question about Episerver as well here as well and if I'm lucky someone can answer that as well.
When looking through the Alloy site done with MVC I also saw similar behavior like above where a viewmodel is created in all controllers, why not just send in the Page that is sent into the controller directly to the View? It seems like an unnecessary step to create the viewmodel? Most likely I'm missing some important point here =)
The idea behind viewmodels is that they are optimised for presentation. If you use domain objects you end up having to put logic into the view itself to control the way things are displayed. Putting logic into the view is poor design so the viewmodel gives you some flex so that you have this logic managed in a more appropriate place.
In addition, you can make your viewmodel simpler, only including the fields that are required for the specific view.
Your code example is not a good idea I agree. But having a view model is a good pattern. You might not need it right away but having a view model available and in place is a good option to have for upcoming additions. I would go with Joel's concept from his MVC templates: http://world.episerver.com/Download/Items/EPiServer-CMS/EPiServer-7---CMS/EPiServer-7-MVC-Templates/
what are your top lessons learned when starting asp.net mvc that you would highlight to someone starting out so they can avoid these mistakes?
Use Html.Encode() everywhere you print data, unless you have a very good reason to not do so, so you don't have to worry about XSS
Don't hardcode routes into your views or javascripts - they're going to change at some point, use Url.Action() instead
Don't be afraid of using partial views
MVC is no silver bullet, first evaluate if it's indeed the best tool of choice for solving your problem.
Don't forget the "Unit Tests" part of the pattern.
Try to always use a ViewModel to pass data between the Controller and the View.
You may think you don't need one, you can just pass your model around, but suddenly you need a list box with several options for editing a model, or displaying a message (not validation message) and you start adding items to the ViewData, with magic strings as keys, making the app harder to maintain.
There are also some security issues that you solve with a ViewModel.
For instance:
class user:
int id
string name
string email
string username
string password
Your view let's the user change his name and email and posts to the action
public ActionResult Edit(User user)
{
--persist data
}
Someone could tamper your form and post a new password and username and you will need to be very careful with the DefaultBinder behavior.
Now, if you use a ViewModel like:
class userEditViewModel:
int id
string name
string email
The problem is gone.
Whenever it is possible make your view typed
Avoid logic in your views
stay away from the HttpContext
Get Steve Sandersons Pro ASP.NET MVC Framework
Debug into the Sourcecode
If you make a Controller method with a different parameter name from id for a single parameter method, you have to make a new route. Just bite the bullet and use id (it doesn't care about the type) and explain it in the comments.
Makes sure you name your parameters with RedirectToAction :
return RedirectToAction("DonateToCharity", new { id = 1000 });
You lose your ViewData when you RedirectToAction.
Put javascript in seperate files, not into the view page
name of the controller :)
unit test Pattern
Don't use the Forms collection, use model binding.
Try not to use ViewData, create a ViewModel.
If you have a loop or an if in your View, write an HTML helper.
Kindness,
Dan
Don't let your controller become a fat one and do too much work. I've seen 1000+ line controllers in the past and it just becomes an absolute nightmare to understand what's going.
Utilise unit testing for your controllers to ensure that dependencies are kept under control and that your code is testable.
Don't get drawn into letting jQuery and fancy clientscript define the behaviour of your application, try and use it as sparingly as you can and let it enhance your application instead.
Use partial views and HTML helpers whenever possible to ensure that your Views do not become unwieldy and a maintenance nightmare.
Use a ViewModel whenever possible.
Use a dependency injection framework to handle your dependencies (MvcContrib has several controller factories, though it's simple enough to roll your own).
Use a different controller for every section of your site (e.g., Home, Account)
Learn how to use ViewData and TempData
Learn what's the use of RenderPartial