I would like to add a new part to orchard administration area,
I need an iframe in this part, that shows another page in another website.
*page, and other custom content type is not what I need.
Is it possible to create something like that?
Thanks,
NadavSt
Perfectly possible.
You will need a custom module. With a controller called AdminController.
Add a method called Index and a new view in the views folder called Index.cshtml and do whatever you want in there.
public ActionResult Index() {
//do stuff...
return View();
}
Then to create a link to it in the menu, add a file called AdminMenu.cs in the root of your module with something like the following.
public class AdminMenu : INavigationProvider
{
public Localizer T { get; set; }
public string MenuName { get { return "admin"; } }
public void GetNavigation(NavigationBuilder builder)
{
builder
.Add(T("My Admin Area"), "4", item => item.Action("Index", "Admin", new { area = "Orchard.MyModule" }).Permission(StandardPermissions.SiteOwner));
}
}
So that creates a item in the menu called "My Admin Area" at position 4 that links to a method called Index in the Admin controller. You can add permissions so the item only appears for those users with that permission. Make sure you protect your action in the controller as well.
I would recommend checking random Core modules to see how they do stuff, it is the best way to see how things work and get examples. Most modules implement the functionality you are after.
Good luck :)
Related
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")
I'm working on a new MVC/Razor website, and we've gotten to the point where we need to put in a real menu structure. Currently, our menus are hardcoded into the global shared layout, we need to populate the menus from code, so that we can determine, after login, which menus will be displayed for a user.
We'll want the menus to appear on pretty much every page, except for login and a few other special cases. So we'll continue to one them to be rendered in the shared layout.
It looks simple enough to create an #Html.DisplayFor template that would render a menu item. The question is, how to get data to it?
The shared layout is used with quite a number of views, handling a number of different models, being loaded from a number of controllers.
Adding a List member to each model, and then populating it in each controller, seems tedious, bothersome, and error prone.
Or we could skip adding the collection to each model, and instead have each controller stick it in the ViewBag. That doesn't seem all that great, either.
The only possibility I've been able to dream up, to keep from having to repeat this for every controller, is to define a common base class, derived from Controller, that all of the controllers that use the shared layout could derive from, in turn, that would stuff the MenuItem collection into the ViewBag.
But I'm wondering if I'm missing something. Is there some preferred way of dealing with this situation?
Shared/base view models is a way to go as you mentioned, but in my opinion its not very "single responsibility". Having to inherit from a base model and add menu items on each page is tedious as you mentioned.
If I was you I would use:
#Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
This would call an action method, and then you can bind a specific menu model and also return a specific partial view that would be rendered in the view that called it
#Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
Create a TopMenu.cshtml
You can even go as far as passing in values into the controller action to modify the output model/view.
You can call #Html.Action from your layout/shared view.
EDIT
Added [ChildActionOnly] as highlighted in comment as this prevents access to the action unless it was called from a parent action and should be used here
If I understand your question correctly...
I prefer to have a MasterViewModel that every ViewModel for every page inherits from. The MasterViewModel holds things common to every page, like the user's name, navigation rules, etc. The shared layout then uses #model MasterViewModel, which is populated by an ActionFilterAttribute.
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
I then have a MasterViewModelAttribute:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
Then, every controller derives from a base controller, which implements the attribute:
[MasterViewModel]
public class BaseController : Controller
{
}
And for each controller:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
And finally, the layout:
#model MasterViewModel
// omitted
<div class="nav">
#foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
The thing I like about the pattern is that you still get strong type-checking by using ViewModels (as opposed to the ViewBag), and you don't have to deal with all the extra nav stuff when you want to unit test an action, for example.
This also has an advantage over using partial views and #Html.Action() in that you don't incur a second request to the server for every page load.
I m new to Umbraco and doing some test / learning work.
I have created a new controller named Home2, please note I don't have a doc type for Home2. I want to create non Umbraco view/ pages that can work with Umbraco. On running I m getting below error:
Below is my code for Controller and view, please guide what I should do to create custom controllers and views that can work with Umbraco ? Do I need to create document type even for non Umbraco types ?
Controller:
namespace Web.Controllers
{
public class Home2Controller : Umbraco.Web.Mvc.RenderMvcController
{
//
// GET: /Home/
public override ActionResult Index(RenderModel model)
{
//Do some stuff here, then return the base method
return base.Index(model);
}
}
}
View:
#{
ViewBag.Title = "Index";
}
Index
Hello welcoem to our page....
Thanks
Many people are confused about routing in umbraco, and this is a common question here on SO. Check out my latest answer: Can I change URL of content?
First you need to register your route when the app is starting. Create a new class which inherit from Umbraco.Core.ApplicationEventHandler. Then overwrite ApplicationStarted to add your rules. Like this:
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//Custom route
RouteTable.Routes.MapRoute(
"SomeName",
"Something/{action}/{id}",
new
{
controller = "MyController",
action = "Index",
id = UrlParameter.Optional
});
}
Then create a controller which inherit from RenderMvcController (not a surface controller. Surface and Render controllers are used for 2 different things): http://our.umbraco.org/documentation/reference/Templating/Mvc/surface-controllers)
public class MyController : Umbraco.Web.Mvc.RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
//Do your db stuff here...
return PartialView("~/Views/Partials/MyView.cshtml", model);
}
}
If you want to create a custom page thats not handled within Umbraco you need to add it to the umbracoReservedUrls list which is found in your web.config eg
<add key="umbracoReservedUrls" value="~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/Home2/Index" />
Umbraco takes care of all the routing so your page must exist within the content section of the back-office even if it's not umbraco related. So you first create a document type and then a template for it. Once you've done that, you add the page in the content section.
You can then view that page (go to properties for the page in content, then click link to document). At this point you can add a custom controller if you wish and it should derive from RenderMvcController or SurfaceController (if you do form posts)
Imagine you have a secured site and a View that can be generated in multiple ways, depending on the user role. Say, an Admin sees all, Manager sees some columns and some action buttons, User sees other columns and other action buttons.
How would you implement this? As far as I see it, there are three main options:
Controller with [Authorize] attribute and the action returning 1 View per user role, being those views tailored to the role;
Controller with [Authorize] attribute and the action returning 1 View for all roles, with logic to hide/show columns, fields, buttons;
Controller with [Authorize] attribute and the action returning 1 View, which based on the roles renders different Partial Views.
I prefer the third approach, but do you see a better way to implement this?
Thank you in advance
Depending on the complexity of your view, either the first or the third option would seem ok to me. As for the second option; Its generally recommended to avoid logic in views so I'd stay away from that one.
If you go for the third option you should consider using EditorTemplates and DisplayTemplates. This will allow you to make your (main) view agnostic to what partial view to render.
Make your viewmodel (or part of it) inherit from a single base class. Create display and/or editor templates for each view model type and in your view just say Html.DisplayFor( ... ) or Html.EditorFor( ... ). MVC will automatically pick the right template, without the need for logic in your view.
What I have been doing for menus and other navigational items is that I have a ViewModel class for it. Here is a simplified version.
ViewModel
public class Action
{
public string DisplayName { get; set; } // localized
public string Url { get; set;
}
public class MenuViewModel
{
public List<Action> Actions { get; set; }
public MenuViewModel()
{
this.Actions = new List<Action>();
}
}
I fill that depending on the user role. Admin gets more links etc.
That ViewModel is part of "main" view model
public class AlbumEditorViewModel
{
public MenuViewModel Menu { get; set; }
}
Then I'll pass that view model for the partial view that is responsible for the menu.
View (Razor)
#model AlbumEditorViewModel
.. razor stuff here ..
#Html.Partial("Menu", Model.Navigation)
.. razor stuff here ..
Partial View
#model MenuViewModel
<ul>
#foreach (var action in Model.Actions)
{
<li>
#GridHelper.GetAction(action)
</li>
}
</ul>
I hope that this will give you ideas
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.