A bit new to Umbraco, so this might be a bit of a scattered question.
I'm using 5.1.
I have a document type called Auction with a selected Template called Auction Details
My end goal is to call this controller method on my AuctionSurfaceController
[ChildActionOnly]
public PartialViewResult Detail(string id)
{
Guid auctionId;
if (Guid.TryParse(id, out auctionId))
{
var auction = auctionService.Client.GetAuction(auctionId);
return PartialView(auction);
}
return null;
}
As of this moment when I go to /Auction - it hits this method and passes in "Auction" into the method, when I go to /Auction/{GUID} i just get a 404
Could I please get some general guidance - or requests for clarifications on how to accomplish this. I would very much appreciate it
Cheers!
It sounds like the routing is working properly.
Assuming that you're executing from the context of being on an 'auction detail' page, it would then make sense that /{GUID} would serve as the id parameter. (rather than www.mysite.com/auctions/auction/id)
Sometimes this question comes up because there's more than one form on a page, and it's difficult to figure out how umbraco will know which controller Umbraco will post to. This is where the bind attribute comes into play.
However, if you want to use custom routing, because Umbraco 5 is built on MVC, you can always create your own areas and controllers.
Related
I'm fairly new to MVC and I hope someone can spare some time to point me in the right direction. I was given a school assignment to create a simple wiki page where a user can search for and create articles. So I have a main index page where I have a zone for the menu, another one for the table of contents, a welcome message, and a search box for an article.
One important directive I have is to implement my own routing (without modifying the RouteConfig.cs file). Each of the following requests must call the right action method in my controller:
/ or /home or home/index: displays the welcome page;
home/index/<title of article>: displays the article;
home/index/<inexisting title>: invitation to create the article;
home/modify/title: modify the article;
home/add/title: add an article;
home/delete/title: delete the article.
For now, I have a basic HomeController with an ActionResult Index() method that returns the index view, but I am a bit stumped as to how to properly get started and the next methods I should be creating to respond to these requests, most notably, how I go about passing on the specified parameters to these controller methods.
I would appreciate any advice to kick start things.
-Jeff
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
I am growing increasingly frustrated with Umbraco. I've been working on an Umbraco project for a little while now and I have been unable to figure out how to solve my problem (one of many).
Scenario - I have a content managed page and on this page users can search for things (these things come from an entirely different database to Umbraco). This is fine, I've created a child action which loads the form for search and created the controller method which returns the results. However, each result I need to link to another content managed page (the same content managed page for each result) that takes an ID so I can inject some dynamic data into the content managed page.
Great, I've got a method for handling this request in my surface controller and I can create an Html.ActionLink for each result that when clicked hits my controller action with the id parameter.
Problem - When each link is clicked the url that gets hit is
http://localhost:5645/umbraco/surface/{controllername}/{actionname}/?id={id}
However this request is outside the Umbraco context, so when I try to return any page with the following controller code, I get the following error:
[ActionName("ShowDetials")]
public ActionResult GetProperty(int propertyId)
{
return View("~/Views/TestView.cshtml");
}
Cannot render a macro when UmbracoContext.PageId is null.
So my initial thoughts were to have a separate controller which inherits from Umbraco.Web.Mvc.RenderMvcController and try to handle the request in there, but then this raises other issues like being unable to use #Html.ActionLink to link to a RenderMvcController, I also don't want to have two controllers to handle content from the same section.
The other issues I have is passing custom models to the view.
If anyone can help me, I would be eternally grateful.
For any developer having this same issue the only way I have been able to solve it is by having two controllers. A surface controller to handle the initial rendering of the search form and page and to display the results of the search, then a separate rendermvccontroller to handle clicking on an individual result and displaying a 'details' page.
However, this in my opinion isn't great and I would still greatly appreciate it if anyone has another solution.
I'm writing a new asp.net mvc application and I've been toying with the idea of allowing my user to post short, concise urls to content that he has posted. Those short url's will be handy in cramped spaces like Twitter and comment areas. I like this idea as I'm not a huge fan of url shorteners because they're so vague and you're never really sure what you're going to get. Instead of using a url shortener I want to give my client the ability to post:
http://domain.com/p/234
which does a 301 redirect to:
http://domain.com/2009/08/10/this-is-the-content-title
Now, this is a pretty simple process with a couple of extra routes and a custom ActionResult. The custom ActionResult I implemented is an extension method on a RedirectToRouteResult... It's fairly straightforward but about 20 lines of code nonetheless. I played around with doing the same functionality, only this time with an ActionFilter. My action filter looks like:
public class PermanentRedirectAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.StatusCode = 301;
}
}
and my action method looks like (I removed a bunch of code to simplify):
[PermanentRedirect]
public ActionResult ShortUrl(int id)
{
return RedirectToAction("Post", id);
}
My question is this: Did I miss something or is it this simple? I've found some other posts where people are looking to do something similar and they always create a custom ActionResult. Besides using less overall code, given that this behavior may need to be used elsewhere on other action methods, I don't see why it shouldn't be an ActionFilter. With that being said I'm fairly new to the Request and Response objects so I'm not sure if I'm missing something.
The code you have shown will work just fine. However, I recommend using a custom action result instead of using an action filter.
One reason for this is that it will provide more information for your unit tests since there are fewer things to verify. That is, with a custom action result you can verify that the right type of result was used and that it has the right properties set on it. With your current design you'd have to separately verify both the action result data and that you correctly applied the attribute.
Another reason is that the code will be cleaner: There will be fewer things for other developers (or you in 2 weeks) to look at or understand. Looking at a simple return PermanentRedirectToAction("Post", id) is much easier than looking at an attribute and the return data.
If I'm not mistaken - the conventions of ASP.NET MVC seem to want me to do the following for a controller view.
Thats to day I create 'Products' directory into which I place my 'Index' view. I then create a 'ProductsController' and create an 'Index' method on it which returns a View. Returning just View() with no arguments will go and fetch the 'Index.aspx' page because its the same name as the method.
public class ProductsController : Controller
{
public ActionResult Index()
{
return View(); // looks for Index.aspx in Products directory
}
}
Now thats just fine. BUT I'll end up with a billion Index.aspx pages, and I'm one of these people that never closes any files so I'll end up going crazy.
Alternatively I can create Products/Products.aspx and change my controller to the following :
public class ProductsController : Controller
{
public ActionResult Index() // my default routing goes to Index (from sample project)
{
return View("Products");
}
}
I understand how that works, and that its completely fine within the MVC design pattern to do this. Its not a hack or anything like that.
My problem (after listening to this PDC video) is that convention over customizabiltity is favored in MVC (or whatever the correct phrase is).
So I'm wondering if I'm missing a third way, or if people are just fine with 50 Index tabs in Visual Studio?
Having the default action for each controller have the same name just simplifies the routing (check global.asax). Also, it all things (Products, Books, Contacts, ...) use actions/views with the same name for the same function, then the code becomes much easier to navigate and understand. This use of convention is especially important when working as part of a team as it will encourage consistent code across developers.
While looking at another question, I ran across SimplyRestfulRouting in the MVCContrib project on codeplex. This might give you some ideas.
I think you should name the method after the action and name the view (if it makes sense and it's not shared between actions, the same as the action). You should probably change your routing mechanism as Index isn't really a descriptive name. The action name should represent what it does (just like any method) and shouldn't be hardcoded to Index or something like that. Routing should be edited instead.
It's just a pattern. Use the pattern, but make it work for you. Personally, I like to have my Views named by their function and I don't have many Index.aspx pages, because I don't have many indices.