Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hint/Fluent for razor section names?

Tags:

c#

asp.net-mvc

So I have a case where the layout has evolved to become more complicated. There's the usual things like @section styleIncludes{ ... }, then other sections that define all kinds of the things that each page can optionally(but almost always) specify like the structure of the current pages breadcrumb. The reason all these things are sections is because they are embedded in the structure of the layout.

I find myself making copies of previous pages, because there's 8 or so different sections, rather than try to remember the exact spelling of them, or piece meal copy/paste.

I am thinking it'd be better to create a fluent API for these so that I have some object that has 8 functions, each one returning the object itself, so you can do something like Sections.Style(some MVC text template or razor delgate?).Breadcrumb(etc.)

The main purpose is to be able to code these sections in a guided way and strongly type the names instead of relying on perfect typing or copy/paste.

However, extensions/helpers in razor return MvcHtmlString, and I imagine a @section is represented by something completely different.

Not asking you to write a complete solution for me, but just some ideas on how to precede.

What object should a helper return to represent a @section declaration? I.e. the analogy of a MvcHtmlString.

What would you suggest the parameter type for the fluent methods, like Style or Breadcrumb? I would like the razor passed to be similar in capability to writing razor in the curly braces of the section declaration. For example, ability to access local variables declared on the razor page, just as you can do with a regular section declaration. I don't want something like string concatenation like .SomeSection("<div...>Bunch of html stuffed in a string</div>")

In other words, if many of my cshtml pages begin something like

@{
  string title = "Edit Person"
  ViewBag.Title = title;
}
@section styles{
  .someOneOffPageSpecificStyle { width:59px }
}
@section javascript{
  //javascript includes which the layout will place at the bottom...
}
@section breadcrumb{
  <a ...>Parent Page</a> &gt; <a ...>Sub Page</a> &gt; @title
}

I'd rather have seom sort of fluent API like this, not really for the resulting style of code, but rather because it will be easier to write the code and not have problems with typos etc. since intellisense will assist:

@{
  string title = "Edit Person"
  ViewBag.Title = title;
}
@Sections
.Styles(@<text>
  .someOneOffPageSpecificStyle { width:59px }
</text>)
.Javascript(@<text>
  //javascript includes which the layout will place at the bottom...
</text>)
.Breadcrumb(@<text>
  <a ...>Parent Page</a> &gt; <a ...>Sub Page</a> &gt; @title
</text>)
like image 371
AaronLS Avatar asked May 10 '13 20:05

AaronLS


2 Answers

Most likely this isn't possible (using sections).

First we have to understand how MVC works under the hood. We write cshtml files, but these files will eventually be compiled into a .Net class that is instantiated and then methods are executed (at a bare minimum Execute()) which (most of the time) write to the response buffer for IIS to return (very similar if not exactly the same as how RenderAction works - Invokes a child action method and renders the result inline in the parent view). HtmlHelpers (or any custom helpers) are simply called from the instaniated class (as delegates), so they can only execute once the code in cshtml files are compiled.

The System.Web.Razor.Generator.SectionCodeGenerator requires a string definition to create Sections. So when you define a Section the string must exist in the cshtml file before the file is compiled, since HtmlHelper or/and Custom helpers won't be executed until the file is compiled there isn't a way to write a class or object that can update the cshtml file before it's compiled.

What you can do is write your own HtmlHelper or other Custom Helpers to do something similar to what Sections provide (without actually using any sections). For example I wrote this because I needed to write Javascript from partial views and/or templates (which you can't do with sections). If you are required to use sections, then this may not help.

The follow code is example only it is not how razor actually works (at all, after looking at some code). But to make this example make sense, I'll use razor-ish like code and naming conventions.

layout.cshtml

<html>
<body>
@RenderSection("MySectionName")

@RenderBody();
</body>
</html>

Index.cshtml

@{
  _layout = "layout";
}

@section MySection {
  <div>MySection</div>
}

<div>My Body</div>

Maybe be compiled into a class that resembles:

public class app_aspnet_layout : System.Web.Mvc.WebViewPage
{

  public Execute()
  {
    throw new NotImplementedException();
  }

  public void ExecutePageHierarchy(WebPageContext pageContext, 
                                   TextWriter writer)
  {
    writer.Write("<html>")
    writer.Write("<body>")

    var section = pageContext.SectionWriters["MySectionName"];
    section();

    pageContext.View.ExecutePageHierarchy(null, writer)

    writer.Write("</body>")
    writer.Write("</html>")
  }
}

public class app_aspnet_index : System.Web.Mvc.WebViewPage
{ 
  // generated from the _layout Definition
  private WebViewPage startPage = new app_aspnet_layout();

  public Execute()
  {
     WebPageContext pageContext = new WebPageContext();
     pageContext.View = this;

     pageContext.SectionWriters.Add("MySectionName", 
                                    this.Section_MySectionName);

     var writer = HttpContext.Current.Response.Stream.AsTextWriter();

     if (startPage != null)
     {
       startPage.ExecutePageHierarchy(pageContext, writer);
     }
     else
     {
       this.ExecutePageHierarchy(pageContext, writer);
     }
  }

  // html generated from non-section html
  public void ExecutePageHierarchy(WebPageContext pageContext, 
                                   TextWriter writer)
  {
    writer.Write("<div>My Body</div>");
  }

  public void Section_MySectionName(TextWriter writer)
  {
    writer.Write("<div>MySection</div>");
  }
}
like image 197
Erik Philips Avatar answered Oct 12 '22 10:10

Erik Philips


If you have resharper then I'd suggest a live code template (a powerful snippet).

You can create a template with a single parameter. The macro source of this parameter can be a comma delimited list of values. When you use the template/snippet it will present you with an intellisense box with your section names available for selection.

Assuming the names of the sections you use won't change that much then you shouldn't have to edit the template very often to include new section names.

like image 25
christophano Avatar answered Oct 12 '22 08:10

christophano