SaaS CMS has officially launched! Learn more now.

Blocks without controller in area

Vote:
 

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?

#80747
Jan 30, 2014 23:44
Vote:
 

Does anybody have a suggestion?

#80790
Feb 02, 2014 18:32
Vote:
 

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?

#80791
Feb 02, 2014 21:03
Vote:
 

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.

#80798
Feb 03, 2014 8:02
Vote:
 

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 :)

#80822
Feb 03, 2014 12:35
Vote:
 

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

#80908
Feb 04, 2014 17:39
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.