Sitecore context not loaded in custom controller - asp.net-mvc-3

I followed this tutorial, and created this code:
using Glass.Sitecore.Mapper;
using Sitecore.Mvc.Controllers;
using Sitecore.SecurityModel;
using SitecoreCMSMVCBase.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace SitecoreCMSMVCBase.Controllers
{
public class CommentController : SitecoreController
{
ISitecoreContext _context;
ISitecoreService _master;
public CommentController()
: this(
new SitecoreContext(),
new SitecoreService("master"))
{
}
/// <summary>
/// This constructor can be used with dependency injection or unit testing
/// </summary>
public CommentController(ISitecoreContext context, ISitecoreService master)
{
_context = context;
_master = master;
}
[HttpGet]
public override ActionResult Index()
{
var model = _context.GetCurrentItem<CommentPage>();
return View(model);
}
[HttpPost]
public ActionResult Index(Comment comment)
{
var webModel = _context.GetCurrentItem<CommentPage>();
if (ModelState.IsValid)
{
var masterModel = _master.GetItem<CommentPage>(webModel.Id);
if (masterModel.CommentFolder == null)
{
CommentFolder folder = new CommentFolder();
folder.Name = "Comments";
using (new SecurityDisabler())
{
_context.Create(masterModel, folder);
}
masterModel.CommentFolder = folder;
}
using (new SecurityDisabler())
{
comment.Name = DateTime.Now.ToString("yyyyMMddhhmmss");
//create the comment in the master database
_master.Create(masterModel.CommentFolder, comment);
webModel.CommentAdded = true;
}
}
return View(webModel);
}
}
}
Models are identical with tutorial, so I will not paste them.
My route configuration looks like this:
routes.MapRoute(
"CommentController", // Route name
"Comment/{action}/{id}", // URL with parameters
new { controller = "Comment", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
When I navigate to /comment I see this exception:
Glass.Sitecore.Mapper.MapperException: Context has not been loaded
I tried with commenting my route specification (as there was nothing about routes in tutorial), and then error is different (throwing by Sitecore CMS itself):
The requested document was not found
Do you know how to load Sitecore context into custom Controller, and make this simple example work? I was looking everywhere but couldn't find any good answer...

I think this is more a Glass setup issue, rather than an MVC routing problem.
To setup Glass, you need to initialise the context in your application start method in your Global.asax file.
var loader = new Glass.Sitecore.Mapper.Configuration.Attributes.AttributeConfigurationLoader(
"Glass.Sitecore.Mapper.Tutorial.Models, Glass.Sitecore.Mapper.Tutorial");
Glass.Sitecore.Mapper.Context context = new Context(loader);
For other Glass-setup related stuff I recommend following the first tutorial on the glass.lu website.
http://www.glass.lu/tutorials/glass-sitecore-mapper-tutorials/tutorial-1-setup/

This method doesn't need Glass at all!
First step is to set your route in Global.asax file.
routes.MapRoute(
"DemoController", // Route name
"Demo/{action}/{param}", // URL with parameters
new { controller = "Demo", action = "Index", param = "", scItemPath = "/sitecore/content/DemoHomePage" } // Parameter defaults
);
Notice that controller is not taken as parameter, but is fixed, to prevent handling it by Sitecore. More info here and here. Notice that there is one additional parameter - scItemPath. It contains path to item which by default will be included in page context.
Having this route our traffic from /demo is handled by DemoController and Index action. Inside this action all you need is to add is this line:
Sitecore.Data.Items.Item item = Sitecore.Mvc.Presentation.PageContext.Current.Item;
item variable will contain your Sitecore item pointed by scItemPath.
And that's all - it should work well now - hope it helps!

Related

Custom MVC routing based on URL stored in database

I'm trying to add some custom routing logic based on url's stored in a database for mvc. (CMS Like), I think its fairly basic, but I feel like i'm not really getting anywhere.
Basically a user may type url's such as:
www.somesite.com/categorya/categoryb/categoryf/someitem
www.somesite.com/about/someinfo
In the database these items are stored, along with the type they are, i.e. a normal page, or a product page.
Depending on this I then want to actually hit a different 'action' method, i.e. I would like the above to hit the methods:
PageController/Product
PageController/Normal
These actions then load the content for this page and display the same view (product view, or a normal view).
Using the normal way of routing won't work, since I could potentially have things like;
cata/producta
cata/catb/catc/catd/cate/catf/producta
Now i've been looking here : ASP.NET MVC custom routing for search
And trying to use this as a basis, but how do I actually 'change' my action method I want to hit within the InvokeActionMethod call?
Using MVC 3.0 btw.
Thanks for any help/suggestions
Final Solution:
Global.asax
routes.MapRoute(
"Default",
"{*path}",
new { controller = "Page", action = "NotFound", path= "Home" }
).RouteHandler = new ApplicationRouteHandler();
Route Handlers
public class ApplicationRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new ApplicationHandler(requestContext);
}
}
public class ApplicationHandler : MvcHandler, IRequiresSessionState
{
public ApplicationHandler(RequestContext requestContext)
: base(requestContext)
{
}
protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
{
var url = RequestContext.RouteData.Values["path"].ToString();
var page = SomePageService.GetPageByUrl(url);
if (page == null)
{
RequestContext.RouteData.Values["Action"] = "NotFound";
}
else
{
RequestContext.RouteData.Values["Action"] = page.Action;
RequestContext.RouteData.Values["page"] = page;
}
return base.BeginProcessRequest(httpContext, callback, state);
}
}
Maybe not an exact solution for your situation, but I've recently had to handle something similar so this might point you in the right direction.
What I did was setup a simple route in Global.asax with a catch-all parameter which calls a custom RouteHandler class.
// Custom MVC route
routes.MapRoute(
"Custom",
"{lang}/{*path}",
new { controller = "Default", action = "Index" },
new { lang = #"fr|en" }
).RouteHandler = new ApplicationRouteHandler();
ApplicationRouteHandler.cs :
public class ApplicationRouteHandler : IRouteHandler
{
/// <summary>
/// Provides the object that processes the request.
/// </summary>
/// <param name="requestContext">An object that encapsulates information about the request.</param>
/// <returns>
/// An object that processes the request.
/// </returns>
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string path = requestContext.RouteData.Values["path"] as string;
// attempt to retrieve controller and action for current path
Page page = GetPageData(path);
// Method that returns a 404 error
if (page == null)
return SetupErrorHandler(requestContext, "ApplicationRouteHandler");
// Assign route values to current requestContext
requestContext.RouteData.Values["controller"] = page.Controller;
requestContext.RouteData.Values["action"] = page.Action;
return new MvcHandler(requestContext);
}
}
Obviously the way you retrieve the action and controller names from your database will probably be much different than mine, but this should give you an idea.

Routing and multiple views in Asp.Net MVC

I have a controller action method that needs to be able to serve multiple views. These views are generated by XSLT.
Now, the views have images in them (hundreds each), and each view needs to have its own folder with images to refer to. How should this work?
If the images in the source XML has an href that is a simple relative path ("images/image.svg"), how can I get this path to resolve in the view in the application?
If I could put the images folder in the same folder as the view, and use a relative path there, it would be easy, but that doesn't work, because I'm serving multiple views from the action. Here is the routing:
routes.MapRoute(
"Parameter",
"{controller}/{action}/{lang}/{prod}",
new { controller = "Manuals", action = "Product", lang = "en-US", prod = "sample" }
);
So if I try using a relative path for the img src attribute, it resolves to something like "/Manuals/Product/en-US/images/image.svg"
And in fact, if I put it relative to the view, the image is located in "/Views/Manuals/en-US/images/image.svg"
So is there no way to have relative image paths like this in Asp.Net MVC? Or am I misunderstanding MVC routing completely?
This is what I have done before:
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
MapRoute(routes, "", "Home", "Index");
/* other routes */
MapRoute(routes, "{*url}", "Documentation", "Render");
}
}
Now any routes that are not matched are passed to the DocumentationController. My documentation controller looks as follows:
public class DocumentationController : Controller
{
public ActionResult Render(string url)
{
var md = new MarkdownSharp.Markdown();
// The path is relative to the root of the application, but it can be anything
// stored on a different drive.
string path = Path.Combine(Request.MapPath("~/"), GetAppRelativePath().Replace('/', '\\')) + ".md";
if (System.IO.File.Exists(path))
{
string html = md.Transform(System.IO.File.ReadAllText(path));
return View("Render", (object)html);
}
// return the not found view if the file doesn't exist
return View("NotFound");
}
private string GetAppRelativePath()
{
return HttpContext.Request.AppRelativeCurrentExecutionFilePath.Replace("~/", "");
}
}
All this does is to find markdown files and render them accordingly. To update this for your case, you may want to do the following:
routes.MapRoute(
"Parameter1",
"{controller}/{action}/{lang}/{*url}",
new { controller = "Manuals", action = "Download", lang = "en-US", prod = "sample" }
);
Make sure it is after the {controller}/{action}/{lang}/{prod} route. This should cause a URL such as /Manuals/Product/en-US/images/image.svg or even images/image.svg (if the browser is in /Manuals/Product/en-US/sample to invoke the the Download action. You can then adapt the code I wrote to map that URI to the physical location. A problem you may run into is that "images" are considered to be product and that /Manuals/Product/en-US/images would think its a product.
The Images action can be can look as follows.
public ActionResult Download(string url)
{
/* figure out physical path */
var filename = /* get filename form url */
var fileStream = [...];
Response.Headers.Remove("Content-Disposition");
Response.Headers.Add("Content-Disposition", "inline; filename=" + filename);
string contentType = "image/jpg";
return File(fileStream, contentType, filename);
}
You can get more information of the FileResult at MSDN.

Import does not resolve needed type in asp.net mvc 3 with mef

I used an example on extensible asp.net mvc 3 to build my plug-able application, but I encountered a problem. In a plug-in I declared and implmemented an interface.
But, in plug-in controller when I want to use this class, the application throws an error and it seems that EntityConfig was not initialized. How can this be fixed?
[Export(typeof(IController)), ExportMetadata("controllerName", "Concept")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ConceptController : Controller
{
[Import(typeof(IEntityConfig))]
private IEntityConfig EntityConfig;
public ActionResult Index()
{
var obs = EntityConfig.EntityName;
return View("~/Bin/Views/Concept/Index.cshtml",obs );
}
}
public interface IEntityConfig
{
string EntityName { get;}
}
[Export(typeof(IEntityConfig))]
public class TestEntity : IEntityConfig
{
public string EntityName
{
get{return "Test";}
}
}
Edited :
In other side, when I is use this example, there is no problem in resolving EntityConfig, but in the view, when I want to load model as follows :
#using Concepts
#model Concepts.Models.TestModel
the application throws an error and tells me 'The type or namespace name 'Concepts' could not be found', although when I check container after it was initiated, I can see Concepts in in loaded assemblies.
Would you please help me ?
Thanks.
Edited :
I uploaded the samples :
First one
Second one
Edited (2011/22/09):
I tested the above code on other sample that #Matthew Abbott provided in his blog, and it worked, although this sample has been built against mvc 2.0.
Looking over your code, can you be sure that the part is actually being imported? Your constructor code for your composition container is such like:
var discoverableControllerFactory = new DiscoverableControllerFactory(
new CompositionContainer(
new DirectoryCatalog(extensionsPath))
);
You're only including your extensions path as a catalog. Can you guaruntee that you're also including your base application path, e.g.:
var discoverableControllerFactory = new DiscoverableControllerFactory(
new CompositionContainer(
new AggregateCatalog(
new DirectoryCatalog("bin"),
new DirectoryCatalog(extensionsPath)))
);
If the parts actually exist in your Unity container, you could add an export provider that grabs those parts from that container and allows them to be composed by MEF.
As for your second problem, you will need to subclass the System.Web.WebPages.Razor.RazorBuildProvider to ensure it includes assemblies in your extensions directory:
namespace ExtensibleMvcApplication
{
public class CustomRazorBuildProvider : RazorBuildProvider
{
public static IEnumerable<Assembly> _assemblies;
static CustomRazorBuildProvider()
{
string extensionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Extensions");
_assemblies = Directory.GetFiles(extensionsPath, "*.dll")
.Select(Assembly.Load);
}
public override void GenerateCode(System.Web.Compilation.AssemblyBuilder assemblyBuilder)
{
foreach (var assembly in _assemblies)
assemblyBuilder.AddAssemblyReference(assembly);
base.GenerateCode(assemblyBuilder);
}
}
}
Which you'd need to register in your config:
<buildProviders>
<remove extension=".cshtml" />
<add extension=".cshtml" type="ExtensibleMvcApplication.CustomRazorBuildProvider, ExtensibleMvcApplication"/>
</buildProviders>

RouteHandler vs ControllerFactory

new to asp.net mvc (using v3 + razor) and am wondering how to best solve a problem with creating dynamic routes based on a database. Essentially, the main site navigation will be entered into a database and I want to load them up as routes. i.e. - Load Category list from database, then append the routes to the routing engine if possible...
mysite.com/cars
mysite.com/televisions
mysite.com/computers
etc....
Each category after the slash comes from the db, but, there are regular entries like /about and /contactus that will not be in the database and have been statically entered in the global.asax... my question is:
For the dynamic database URLs should I use a custom RouteHandler or pehaps create a ControllerFactory that will match and handle the requests for the entries loaded from the database. Is it possible to have the DefaultControllerFactory handle the routing if my RouteHandler or CustomControllerFactory don't find the route in the list from the database? Thanks for any help, very first project with this so I'm not sure what the best route is ;) no pun intended...
Update:
Tried using a route constraint that pulls from the database but it conflicts with the default route now... here is my custom constraint and routes:
public class CategoryListConstraint : IRouteConstraint
{
public CategoryListConstraint()
{
var repo = new Repository<Topic>();
var cats = repo.All();
var values = new List<string>();
foreach (var c in cats)
{
values.Add(c.URI.Replace("/", "").Replace("?", ""));
}
this._values = values.ToArray<string>();
}
private string[] _values;
public bool Match(HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
and here are the routes:
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Categories",
"{category}/{*values}",
new { controller = "Category", action = "List" },
new CategoryListConstraint()
);
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
The home page www.mysite.com loads using the Default route. All the URLs that match the constraint list are loaded by the category route... but if I have the www.mysite.com/admin or www.mysite.com/aboutus these are getting picked up by the Categories route even though the values are not in the constraint list. Confused...
What about something like this?
Categories controller:
public ActionResult List(string category)
{
var products = _repo.Get(category); // however you are getting your data
return View(products);
}
Routes
routers.MapRoute(
"About",
"About",
new { controller = "Home", action = "About" });
//... other static routes
routes.MapRoute(
"CategoriesList",
"{id}",
new { controller = "Categories", action = "List" },
new { id = #"\w+" });
The incoming URL is tested against each Route rule to see if it matches - and if a Route rule matches then that rule (and its associated RouteHandler) is the one that is used to process the request (and all subsequent rules are ignored). This means that you want to typically structure your routing Rules in a "most specific to least specific" order
source
Found the exact solution I was looking for. Code is below. I managed to avoid using Controller Factories or implementing a custom IRouteHandler by using extending the RouteBase class which worked perfectly and allows me to pass control down to the default mvc route is something specific isn't hit. BTW - constraints ended up not working properly as the broke the controllers associated with the default route (although the default route was getting hit)
public class CustomRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{*URI}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// implement caching here
var list = GetConstraintList();
// set your values dynamically here
routeData.Values["controller"] = "Category";
// or
routeData.Values.Add("action", "List");
// return the route, or null to have it passed to the next routing engine in the list
var url = Util.StripSlashOnFrontAndBack(httpContext.Request.Path.ToLower()).Split('/')[0];
if (list.Contains(url))
return routeData;
return null; // have another route handle the routing
}
protected List<string> GetConstraintList()
{
using (var repo = new RavenRepository<Topic>())
{
var tops = repo.Query().Where(x => x.Hidden == false).ToList()
.Select(x=>x.Name.ToLower());
List<string> list = new List<string>();
list.AddRange(tops);
repo.Dispose();
return list ?? new List<string>();
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
Then my register routes method looks like so:
Routes.Clear();
// Set Defaults
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add(new App.Helpers.CustomRoutingEngine());
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Spark View Engine & Areas in ASP.NET MVC 2

Anyone got the areaDescriptorFilter working with the spark view engine in asp.net mvc 2?
I don't even have the option to add a filter on the service as shown in the following:
http://sparkviewengine.com/documentation/viewlocations#Extendingfilepatternswithdescriptorfilters
Thanks if you can help or at least try.
I'm using areas with Spark in a project of mine. All I had to do was add AreaRegistration classes for each area like:
public class AdminAreaRegistration : System.Web.Mvc.AreaRegistration
{
public override string AreaName
{
get { return "Admin"; }
}
public override void RegisterArea( AreaRegistrationContext context )
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
and then in the global.asax call:
AreaRegistration.RegisterAllAreas();
I have my area views located in a folder named "Admin" under the default "Views" folder, with appropriate controller folders under that:
\MvcProject
\Views
\Admin
\Home
\Index.spark
\Users
\Index.spark
from the page you linked:
the AreaDescriptorFilter is added by default
so you shouldn't need to worry about adding it yourself.

Resources