How to refactor Umbraco for rendering controllers - asp.net-mvc

I have been running an Umbraco v7 site for about 3 years. Traditionally I have not rendered any pages using controllers, I have however set up some controllers that I POST forms to but that is about it. My existing View pages have logic baked into them and use external helper methods instead of controllers.
I am about to develop a new page and thought this would be my time to test a better design pattern. This page will render data from a controller as well as required form submission via a controller. What is the best route to proceed that will also go smoothly if I decide to do a refactor on my existing View pages?
I am more specifically looking for an answer around Render vs Surface controllers and which one would be better.It is my understanding that my routing would be unchanged if I went with a Render controller, but if I went with surface, I would have to have special routing?
But if I used a render controller, this does not support form submission?
Not sure what else I am missing?
Thanks Again,
Devin

You don't have to configure any special routing - everything is baked right in to Umbraco.
As a rule of thumb Surface Controllers are best used for reusable actions, custom controllers (Route Hijacking) are better for adding custom logic to whole pages (Document Types/Templates) in Umbraco.
Both approaches will enable you to accomplish exactly the same results - the only difference between them is abstraction.
Surface controllers are MVC Child Actions that inherit from Umbraco.Web.Mvc.SurfaceController - this adds helpful Umbraco specific properties and methods.
Surface Controllers are good for creating reusable things like forms or anywhere you need a partial to do anything complicated (i.e. backed by a controller). Have a look at the documentation here.
When you use a custom controller to change how pages are rendered that's called Route Hijacking
To do this you create your own controller than inherits from Umbraco.Web.Mvc.RenderMvcController like this:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff here, the return the base Index method
return base.Index(model);
}
}
This is a custom controller for the "Home" Document Type. You can of course return a custom model that inherits from RenderModel with your own properties and methods.
Fuller examples and documentation can be found here.
Post Requests
Both options allow you to handle POST requests by adding the [httppost] attribute like so:
Surface Controller:
public class YourSurfaceController: SurfaceController
{
public ActionResult YourAction()
{
// Do stuff
}
[HttpPost]
public ActionResult YourAction()
{
// Do stuff on POST
}
}
Controller for route Hijacking:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff here, the return the base Index method
return base.Index(model);
}
[HttpPost]
public ActionResult MobileHomePage(RenderModel model)
{
//Do some stuff on POST, the return the base Index method
return base.Index(model);
}
}

Related

How to post a custom model to a controller in Umbraco 7.5.8?

I have a Document type, Template, and a page in the CMS content tree which uses these for a Contact page. The document type has no CMS data properties, because it doesn't need any. I use Models Builder for other pages with no issue, but for this page I've created my own custom model within my MVC project.
I've read every tutorial I can find, and looked at every forum post and issue on the Umbraco forums and Stackoverflow, and for the life of me I can't figure out what I'm doing wrong. The model name and namespace do not conflict with the autogenerated Models builder one.
My understanding is for posting forms a SurfaceController is the way to go - a RenderController is intended more for presenting stuff. So my controller extends SurfaceController. uses Umbraco.BeginUmbracoForm(etc)
I've tried every combination of SurfaceController and RenderController with UmbracoTemplatePage, UmbracoViewPage and every way of changing my model to extend both RenderModel and IPublishedContent to test each. When trying RenderController I've overridden default Index method with RenderModel parameter to create an instance of my model with the renderModel parameter.
Usually the error I get is "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx". Sometimes combinations I've attempted allow the Get to succeed, then give this error on Post.
I've even tried to remove the page from the CMS and use a standard MVC controller and route - this allows me to display the page, and even using a standard Html.BeginForm on my view, I get an error when trying to post the form (despite a breakpoint in the code in controller being hit) which also states it "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx"
This CANNOT be this difficult. I'm ready to throw laptop out window at this stage.
What am I doing wrong???? Or without seeing my code at least can anyone tell me how this is supposed to be done? How do you post a custom model form to an Umbraco 7.5 controller, with no CMS published content properties required?
As it stands, my View looks like this:
#inherits UmbracoViewPage<Models.Contact>
...
using (Html.BeginUmbracoForm<ContactController>("Contact", FormMethod.Post
My Controller looks like this:
public class ContactController : SurfaceController
{
public ActionResult Contact()
{
return View("~/Views/Contact.cshtml");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(Contact contactForm)
{
...
}
And my model looks like this:
public class Contact : RenderModel
{
public Contact() : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent, UmbracoContext.Current.PublishedContentRequest.Culture)
{
}
public Contact(IPublishedContent content) : base(content, CultureInfo.CurrentUICulture)
{
}
[Display(Name = "First Name", Prompt = "First Name")]
public string FirstName { get; set; }
...
Update: If I use the model for my CMS page created automatically by models builder, the Get and Post work ok. However when I customise the model (i.e. I put a partial class of the same name in ~/App_Data/Models and regenerate models on Developer tab), the custom properties in my posted model are always null.
I can populate these manually from the request form variables, however this seems wrong and messy. What's going on here?
public class ContactPageController : SurfaceController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactPage contactForm)
{
try
{
contactForm.FirstName = Request.Form["FirstName"];
contactForm.LastName = Request.Form["LastName"];
contactForm.EmailAddress = Request.Form["EmailAddress"];
contactForm.Telephone = Request.Form["Telephone"];
contactForm.Message = Request.Form["Message"];
var captchaIsValid = ReCaptcha.Validate(ConfigurationManager.AppSettings["ReCaptcha:SecretKey"]);
if (ModelState.IsValid && captchaIsValid)
{
// Do what you need
TempData["EmailSent"] = true;
return RedirectToCurrentUmbracoPage();
}
if (!captchaIsValid)
{
ModelState.AddModelError("ReCaptchaError", "Captcha validation failed - Please try again.");
}
return RedirectToCurrentUmbracoPage();
}
catch (Exception ex)
{
LogHelper.Error(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, null, ex);
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
}
Further Info: Robert's approach first time, thanks for that. I had tried one approach using PartialViews and ChildActions but clearly I didn't do it correctly. Would I be right in saying the reason this approach is required at all (i.e. why you can't add the custom properties to the model the main view is bound to) is because I am using Models Builder?
So among the strange errors I received was one about 2 classes both wanting to represent the contact page, and another about it expecting one type of class in a dictionary (ContactPage) but receiving another (Contact) - even though I had made no reference to ContactPage in either view or controller. This suggests to me ModelsBuilder adds a mapping for document types to models on app startup behind the scenes? Which is maybe why you're better to take this approach of letting ModelsBuilder do its thing, and build your own model on top of that with a partial view in this way?
I've found the quality of documentation on this topic very poor indeed. Not sure if maybe the good stuff is behind closed doors, i.e. requires a paid Umbraco membership? For a supposedly open source system, that feels kinda shady to me.
Easy when you know how!!
For your situation, SurfaceController is the most likely candidate as you've surmised, however think of the actions in that Controller as applying to partial views, not the full view used by the page.
Don't try to use the ModelsBuilder ContactPage model, but rather create your own as you were originally (the original Contact model) - think of it as a Data Transfer Object perhaps if you do need to apply any of the properties back to the ContactPage model for any reason.
I've found I've had the greatest success with SurfaceController with the following conditions:
The Page Template does not inherit from the model intended for the Form; it inherits directly from the standard Umbraco PublishedContentModel or a ModelsBuilder generated model.
Implement a Partial View for the action defined in your SurfaceController - this view inherits from Umbraco.Web.Mvc.UmbracoViewPage<Contact> in your example.
The Partial View utilises BeginUmbracoForm<ContactController>, specifying POST action as one of the parameters (depending on which signature you're using)
You shouldn't need to populate any model properties using Request.Form like this.
For example, the code in one of my projects looks something like this:
SurfaceController
public class FormsController : SurfaceController
{
[ChildActionOnly]
public ActionResult ContactUs()
{
return PartialView(new ContactForm ());
}
[HttpPost]
public async Task<ActionResult> HandleContactUs(ContactForm model)
{
if (ModelState.IsValid)
{
if (!await model.SendMail(Umbraco)) // Do something with the model.
{
}
}
return RedirectToCurrentUmbracoPage(); // Send us back to the page our partial view is on
}
}
Partial View:
#inherits Umbraco.Web.Mvc.UmbracoViewPage<ContactForm>
#using Digitalsmith.ReCaptcha
#using (Html.BeginUmbracoForm<FormsController>("HandleContactUs"))
{
...
}
Contact Page Template:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage<ContactPage>
#{
Layout = "_Layout.cshtml";
}
#Html.Action("ContactUs", "Forms")

Different controllers for views on the same folder on asp.net MVC4

I've been researching about this. I found "MVCs areas" but I still couldn't do what I'm looking for.
MY VIEWS:
Views/
Student/Course
Student/Information
Student/Status
Student/GeneralSituation
etc, etc...
CONTROLLERS:
Controllers/Student
What I want to do is:
I don't want to have All the code from a lot of views in just one "Student" controller.
Any tip regarding how I can "split" my controllers in several files?
I'm just looking at the simplest approach, I don't want to make big modifications to my project.
I'm using MVC4.
Thanks in advance!..
PnP
Why not just make partial Controller classes and thus split one controller over a bunch of physical files?
Also, what do you mean by "the code from a lot of views"? Are you using a separate service layer and doing the business logic in there, because that's best practice. Controllers are meant to be very lightweight with code along the lines of this:
public ActionMethod DoSomething()
{
StudentViewModel vm = _studentService.GetSomeData();
return View(vm);
}
you can do a partial class StudentController:
your folder/files would look like this:
Controllers
StudentController
StudentController.Status.cs
StudentController.GeneralSituation.cs
StudentController.Course.cs
The code would be:
StudentController.Status.cs:
public partial class StudentController
{
[Actions relevant for Status of a student]
}
StudentController.GeneralSituation.cs:
public partial class StudentController
{
[Actions relevant for General Situation of a student]
}
Any reason why Area's don't work? From what you described, I don't really see why they wouldn't.
- Areas
- Students
- Controllers
HomeController Handles base /Students/ route
InformationController ~/Students/Information/{action}/{id}
StatusController ~/Students/Status/{action}/{id}
...
- Models
- Views
Home/
Information/
Status/
...
Shared/ Stick common views in here
If you're set on one monster controller (or partials), your controller should have very little actual 'View code' in it. Leave all that to view models - the controller just passes in the needed resources to build view data, keeping controllers thin.
Ie,
public class StudentController
{
...
// Actually I prefer to bind the id to a model and handle 404
// checking there, vs pushing that boiler plate code further down
// into the controller, but this is just a quick example.
public ActionResult Information(int id)
{
return View(new InformationPage(this.StudentService, id));
}
}
Then, InformationPage is one of your models that will handle building out all information applicable to that view.
public class InformationPage
{
public Student Student { get; set; }
public InformationPage(StudentService service, int studentId)
{
Student = service.FindStudent(studentId);
... Other view data ...
}
}

ASP.NET MVC, Reeling in Unroutable Page Nesting

The concept of routes is nothing new, and it works great for the concept of {area}/{controller}/{action}/{parameter}, but few sites are standalone UI interaction.
Websites often need parts of themselves that aren't really dedicated to taking data, but presenting it. For instance one of the sites I am working on has a large part of itself dedicated to user interaction (which the MVC system solves expertly. A Membership area, a place to manage information, a way to purchase items, etc.) - but it also needs a part that functions more like an old-fashioned website, where you're simply looking at pages like a folder structure.
one solution I have found is to try a custom view engine. This worked, but I quick found myself lost in a convoluted routing scheme. Another I guess I could go with is to just have an IgnoreRoute and put files in the ignored folder like normal html/aspx, but I'd really rather have the option of using Controllers so that there is a chance I can have data returned from a database, etc in the future.
So let me show you my current scenario...
Areas
Membership
Rules
Controllers
HomeController
FileView(string folder, string file)
Views
Home
General
Customize
Content
yyy.cshtml
xxx.cshtml
#Html.Partial("Content/yyy.cshtml")
xxx.cshtml
xxx.cshtml
etc. The Rules area is basically setup to function like a normal /folder/file/ structure. So here is my Controller for it..
public class HomeController : Controller
{
//
// GET: /Information/Home/
public ActionResult Index()
{
return View();
}
// **************************************
// URL: /Rules/{controller}/{folder}/{file}
// **************************************
public ViewResult FileView(string folder, string filename)
{
return View(String.Format("{0}/{1}", folder, filename));
}
}
Now, if I have a category, I simply have a lightweight controller that inherits from that Area's HomeController, like this...
public class GeneralController : Rules.Controllers.HomeController
{
// **************************************
// URL: /Rules/General/Customize/{id}
// **************************************
public ViewResult Customize(string id)
{
return FileView("Customize", id);
}
}
So then, for each folder in the 'sub' controller, I have a single Route that takes in the name of the file.
This works, but I feel it's excessively clunky. Can anyone suggest a better alternative? There are just too many pages, and too much nesting, to have a full ActionResult for each one. I also want to maintain clean urls.
Perhaps you can use a catch-all route for the Membership area, route it to a controller (MembershipController?) and have that controller just render the view that is catched by the route, like this:
public class MembershipController : Controller
{
public ActionResult Index(string pageTitle)
{
return View(pageTitle);
}
}
And the route:
routes.MapRoute(
"Membership",
"Membership/{*pageTitle}",
new {controller = "Membership", action = "Index", pageTitle = "NotFound"});
Of course, in the controller you should check whether the view exists or not, but this one should get you moving. Although I don't see why you want to have MVC in front of this when you just want to display (static?) content.

mvc and partials is this not breaking the mvc pattern?

I have a partial view that displays a search options like search by category, region, date etc. This exists on every page. The partial uses a viewmodel containing lists of regions, cats etc.
As this is being used on every page - I have to load these properties on the viewmodel in every action in my controllers to ensure the data is available to the partial view. Not that happy with that. (Have simply used inherited viewmodels)
I see the partial can call a renderaction method on the controller to get the data, but now I would have a view calling data from a controller - breaking the mvc pattern.
what are other people doing in this situation?
You can use custom ActionFilters to inject common functionality to your actions/controllers to avoid repeating the same code.
For example :
public class RequiresSearchOptions : ActionFilterAttribute {
public override void OnResultExecuting(ResultExecutingContext filterContext){
filterContext.Controller.ViewData["SearchOptions"] =
GetSearchOptions();
//Or manipulate the model :
//YourViewModel m =
// (YourViewModel)filterContext.Controller.ViewData.Model;
//m.SearchOptions = GetSearchOptions();
}
}
And then decorate your actions/controllers.
[RequiresSearchOptions]
public ActionResult Index() {
return View();
}
//or
[RequiresSearchOptions]
public class HomeController : Controller {
//Actions
}
For a while I have used partial requests to render reused widgets. In my opinion they are are a more MVC way of rendering widgets over RenderAction as they don't require the View to know what action is being called.
My partial requests render partial views so your existing code can be easily migrated. They can also be output cached in the same way as any asp.net mvc action.
Hope this helps.

ASP.NET StrongTyped Controller-Action View<TView,TModel>(TModel Data)

I'm using Asp.net MVC 1 and I would really like my controller actions to use StronglyTyped View(data) calls that enforce type checking at compile time and still let me use aspx pages under the default view engine. The ViewPages I call are strongly typed, but errors in the action's call to View(data) can't be caught at compile time because the built in Controller View(data) method isn't strongly typed and doesn't even check to see if the page exists at compile time.
I've implemented a partial solution (code below) using this post but (1) I can't get the generic View function to recognize the Type of strong view pages unless I create a code behind for the strongly typed view, and (2) Intellisense and refactoring don't work properly with this method which makes me doubt the reliability of the method I'm using.
Question:
Is there a better way to get type enforcement when calling Views from actions?
Alternative: Is there an alternative method where my action method can create an instance of a viewpage, set some properties directly and then render out its HTML to the action response?
Code:
Here's the base Class all my Controllers Inherit from to achieve what I have so far:
public class StrongController : Controller
{
protected ActionResult View<TView, TModel>(TModel model)
where TView : ViewPage<TModel>
where TModel : class
{
return View(typeof(TView).Name, model);
}
}
And here's an example Controller in use:
namespace ExampleMVCApp.Controllers
{
public class HomeController : StrongController
{
public ActionResult Index()
{
return View<ExampleMVCApp.Views.Home.Index, ExampleData>(new ExampleData());
}
}
}
ViewPage Code Behind Required for Type Recognition... Aspx header didn't work
namespace ExampleMVCApp.Views.Home
{
public class Issue : System.Web.Mvc.ViewPage<ExampleData>
{
}
}
I think you should give the T4MVC helpers a spin (one of the original announcements here). This would at least enable you to get rid of the code you already have, since these templates generate the code based on the Views you already have and you employ these "fake" method calls to address your views.
For having your calls to View to be strongly typed for the specific model declared by your view, I am not exactly sure if these helpers help you with that (though I suspect they do). However, if they don't you can still hack the T4MVC code to do so yourself or get in touch with the original author, David Ebbo, to suggest the feature for addition.

Resources