I'm not sure that EPiServer will play nice with areas. Can you paste route config, view engine and "does not work" effect or error?
My CustomRoute:
public class CustomRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var others = RouteTable.Routes.Where(r => r != this);
RouteData result = null;
foreach (var rd in others)
{
result = rd.GetRouteData(httpContext);
if (result != null)
break;
}
if (result == null) return null;
if (!result.DataTokens.ContainsKey("area"))
{
result.DataTokens.Add("area", SiteDefinition.Current.Name);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
My ViewEngine:
public class EPiServerViewEngine : RazorViewEngine
{
private static readonly string[] AreaViewFormats =
{
"~/Areas/{2}/Views/Pages/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Blocks/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{1}.cshtml"
};
private static readonly string[] ViewFormats =
{
"~/Views/Pages/{1}/{0}.cshtml",
"~/Views/Blocks/{1}/{0}.cshtml",
};
public EPiServerViewEngine()
{
this.AreaPartialViewLocationFormats = this.AreaPartialViewLocationFormats.Union(AreaViewFormats).ToArray();
this.AreaViewLocationFormats = this.AreaViewLocationFormats.Union(AreaViewFormats).ToArray();
this.ViewLocationFormats = this.ViewLocationFormats.Union(ViewFormats).ToArray();
this.PartialViewLocationFormats = this.PartialViewLocationFormats.Union(ViewFormats).ToArray();
}
}
I add these in my global.asax:
ViewEngines.Engines.Add(new EPiServerViewEngine());
routes.Add(new CustomRoute());
There is no error, the view is just not rendering because it can not find it. When I move it to /Views/Shared (no area) the block is shown.
I think that EPiServer "is not aware" of Mvc areas therefore you should trick it a bit. I'm not familiar with your areas utilization scenario but as far as I know if you add path to your area views folder inside your custom view engine, EPiServer's template renderer scanner should pick it up and regsiter default convension mappings between block types and discovered views.
public class SiteViewEngine : RazorViewEngine
{
private static readonly string[] AdditionalPartialViewFormats = new[]
{
TemplateCoordinator.BlockFolder + "{0}.cshtml",
TemplateCoordinator.PagePartialsFolder + "{0}.cshtml",
"~/Areas/Alloy/Views/Blocks/{0}.cshtml"
};
public SiteViewEngine()
{
PartialViewLocationFormats = PartialViewLocationFormats.Union(AdditionalPartialViewFormats).ToArray();
}
}
If you need a more dynamic way to regsiter view folders in all application's areas you can enumerate those via route table:
var areaNames = RouteTable.Routes.OfType<Route>()
.Where(d => d.DataTokens != null && d.DataTokens.ContainsKey("area"))
.Select(r => r.DataTokens["area"]);
Another way, which I feel as floating against the stream and most probably is over-engineering your can play around with template resovler. Either you can register templates through template registration plugin:
[ServiceConfiguration(typeof(IViewTemplateModelRegistrator))]
public class TemplateCoordinator : IViewTemplateModelRegistrator
{
public void Register(TemplateModelCollection viewTemplateModelRegistrator)
{
// ...
viewTemplateModelRegistrator.Add(typeof(AreaBlock), new TemplateModel
{
Name = "AreaBlock",
Inherited = true,
AvailableWithoutTag = true,
Path = "~/Areas/Alloy/Views/Blocks/AreaBlock.cshtml"
});
}
}
This is not really flexible and needs knowledge of particular blocks with views somewhere in areas. Another way is to resolve it dynamically during template resolving event:
...
context.Locate.TemplateResolver().TemplateResolving += OnTemplateResolving;
private void OnTemplateResolving(object sender, TemplateResolverEventArgs args)
{
if (args.ItemToRender.GetType().IsSubclassOf(typeof(BlockLocatedInArea)))
{
var type = typeof(PartialViewRenderTemplate<>);
args.SelectedTemplate = new TemplateModel
{
Name = "AreaBlock",
Inherited = true,
AvailableWithoutTag = true,
Path = "~/Areas/Alloy/Views/Blocks/AreaBlock.cshtml",
TemplateType = type.MakeGenericType(new[] { args.ItemToRender.GetType() }),
TemplateTypeCategory = TemplateTypeCategories.MvcPartialView
};
}
}
For this you would need template render "placeholder" class:
public class PartialViewRenderTemplate<T> : IRenderTemplate<T>
{
}
This approach needs some knowledge about block that is going to be rendered and make decision about template to use. In this case I made block as subclass of another "block with view in area class".
Again, I got feeling that this is a hacky workaround. Please somebody give any other ideas :)
I fixed it with the TemplateCoordinator. Thank you. It would be great to overrule this route somewhere instead of this hacky solution, but everythings better than nothing :D
Hi,
For some basic views in my webapplication I do not want to create a controller. I have managed to create a model, a view in ~/Views/Shared/ and the view is presented to me in the page.
However, I am working with Areas and i created a custom RouteBase to force this area. I also added a custom viewengine in which I added the Area(Partial)ViewFormat "~/Areas/{2}/Views/Shared/{1}.cshtml".
This does not work. Can someone tell me how i can change the location of my shared controller-less blocks?