MVC Helpers?

Oct 22, 2010 at 8:31 PM

I was wondering if the project comes with any MVC helpers to assist in the same way as the controls do for classic ASP.NET solutions?

If not, is this on the roadmap for a future release?

Otherwise looks awesome. Like the fact that sprite optimization is included!

Oct 23, 2010 at 5:09 PM
Edited Oct 23, 2010 at 5:10 PM

Yes, this is the next item on my list of features to implement. I am planning on developing the required MVC Helpers/RouteHandlers this week, and will likely have an early release up by next weekend.

Oct 23, 2010 at 6:11 PM

Wow, that sounds awesome. Let me know when you have something that I could try and I'll be happy to give you some early feedback on the API. If I don't hear anything I'll just grab the release when it shows up :)

Thanks!

Oct 27, 2010 at 4:03 AM

A quick FYI, I have a proof of concept already put together with regard to the HtmlHelpers; nothing committed as of yet though. I have a small re-design around how I will handle the concept of partial resource definitions with MVC (doesn't fully mesh), but I still expect to have a release of the core Helpers up on Saturday as planned. 


API will look something very close to:

    <%= Html.CompositeCssResource("~/Styles/File1.css", "~/Styles/File2.css", etc) %>   

    public static MvcHtmlString CompositeCssResource(this HtmlHelper htmlHelper, params String[] resources)
    public static MvcHtmlString CompositeCssResource(this HtmlHelper htmlHelper, String referenceName, IEnumerable<String> resources)

    <%= Html.CompositeScriptResource("~/Scripts/File1.js", "~/Scripts/File2.js", etc) %>

    public static MvcHtmlString CompositeScriptResource(this HtmlHelper htmlHelper, params String[] resources)
    public static MvcHtmlString CompositeScriptResource(this HtmlHelper htmlHelper, String referenceName, IEnumerable<String> resources)

    <%= Html.CompositeImageResource("MySprite1", new [] { new Resource("MyImage1", "~/Images/File1.png"), new Resource("MyImage2", "~/Styles/File2.css"), etc }) %>   

    public static MvcHtmlString CompositeImageResource(this HtmlHelper htmlHelper, String referenceName, IEnumerable<Resource> resources)

    <%= Html.CompositeImageResource(ResourceType.Jpeg, "MySprite2", new [] { new Resource("MyImage1", "~/Images/File1.png"), new Resource("MyImage2", "~/Styles/File2.css"), etc }) %>      

    public static MvcHtmlString CompositeResource(this HtmlHelper htmlHelper, ResourceType resourceType, String referenceName, IEnumerable<Resource> resources)

    <%= Html.CssSprite("MySprite1", "MyImage1", "Some Alt Text") %>

    public static MvcHtmlString CssSprite(this HtmlHelper, String spriteName, String imageName)
    public static MvcHtmlString CssSprite(this HtmlHelper, String spriteName, String imageName, String alt)
    public static MvcHtmlString CssSprite(this HtmlHelper, String spriteName, String imageName, String alt, String id)

Oct 30, 2010 at 11:30 PM

Source Code committed; will finish performance/stress testing tomorrow and then post a new release.

Nov 1, 2010 at 9:13 PM

Downloaded - will give it a spin and see how it works.

I do see two potential issues with the API outlined above:

  1. Resources are typically included from multiple pages (such as a layout/master page and a content page). I do this by having a content region inside the head section of the master page, which allows content pages to inject additional css/script files. It seems impossible to combine both of these sets into a single resource.
  2. Style sheets sometimes have image references that need to be dynamic, so I have a ResourceController that allows me to generate/retrieve these. This might give me trouble if you're mapping the virtual paths into absolute ones (i.e. expecting physical files to exist).

Thanks for the quick addition, and do share your thoughts on the above if you have some :)

Nov 1, 2010 at 9:32 PM
Edited Nov 1, 2010 at 9:43 PM

Thank you for the early feedback.

With regard to the first point, you are very correct. With the WebControls, the merging of resources across the Master/View pages was easy as everything was written out at render time regardless. I can easily support this for the MVC Helpers as well. You would require the use of the <xpedite:CompositeResourcePlaceholder> control in the Master page head as the resources will need to be written to the page head after everything has been fully rendered. I had thought about adding this from the get go, but a colleague and I were discussing the usefulness, and I had opted to wait for someone to ask for it... apparently that didn't take long :D

As for the second point, I would be curious to know more about how you are handling dynamic CSS resources? If you have your own route, then everything might be okay as if there is no physical file associated with the request, then a web-request will be made to retrieve the resource from the server. If I am miss-understanding the requirement, please flesh out in a little more detail and I will see what I can do. 

* Edit *

I will probably add the support for the same "extendable" composite resource definitions as seen with the webcontrols tonight. Will likely use different method names than the existing API.... something like <% Html.ExtendCompositeCssResource("referenceName", new [] { /* resource */ }) %> as it makes no sense to waste time generating the html fragment that won't be written out anywhere.

Nov 1, 2010 at 9:54 PM

lol, sorry :o) .. using a web control should be able to do the trick, but wouldn't be very MVC-ish (I could live with it, but would prefer something that didn't stick out as much).

Maybe it'd be possible with the use of two helpers instead of one. The first would register resources and the second would emit the optimized result. That might work if the helpers are executed in the appropriate order, although I'm not familiar enough with the MVC internals to know where you might put the required state for such a solution.

In the simple case I just have an action method on a controller to fetch a particular resource type given a string parameter (which would be the path to the physical resource that needs to be processed). A request to "/resources/css/themes/red.css" would match action method Css with "themes/red.css" as parameter. Occasionally I'm also returning resources that depend on values from the view model, which are then passed along to specialized action methods, but I should probably accept that these are unsuitable for optimization ;)

Nov 1, 2010 at 11:34 PM
Edited Nov 1, 2010 at 11:48 PM

I agree, and that is partly why I deferred implementing the overloaded MVC Helpers to register partial resources. Also, thinking about it further, I am not sure my original plan will work, as the "Placeholder" has the be the last thing to fire. I am fairly certain that the resources themselves won't be registered during the correct stage of the page lifecycle. The WebControls work because they are registered during the PreRender phase and combined during the actual Render phase. Not sure I can replicate with MVC as easily; I will have to investigate (thinking something along similar to BeginForm/EndForm).

In the mean time, most views already have a palceholder for the head tag? What are your thoughts on doing something like defining a static class of "shared" definitions like so.

  public static class SharedResourceSet
  {
    public static readonly IEnumerable<String> CommonCss = new[] 
    {
      "/Styles/SharedStyle1.css",
      "/Styles/SharedStyle2.css",
      "/Styles/SharedStyle3.css"
    };
  }

 and then inside the view's head ContentPlaceHolder doing something like

<asp:Content ID="Content2" ContentPlaceHolderID="head" runat="server">
  <%= Html.CompositeCssResource(SharedResourceSet.CommonCss.Concat(new[] 
      {
        "/Styles/PageStyle5.css",
        "/Styles/PageStyle6.css",
        "/Styles/PageStyle7.css"
      }) %>
</asp:Content>

 Requires that each page repeat this resource definition, but depending on how you feel, may be better than the Placeholder WebControl? Regardless, I will do some investigation and see if I can't come up with something more pure.

Nov 2, 2010 at 12:42 AM
Edited Nov 2, 2010 at 4:03 PM

Had an idea that may work, curious on your take though before I commit the solution.

I am thinking you could wrap a ContentPlaceholder in the master page with something similar to the following:

    <% using (Html.CreateCompositeResourcePartContext())
       {
         Html.CompositeCssResourcePart("MasterPageReference", new[] { "/Styles/Style1.css", "/Styles/Style2.css" });
         %>
        <asp:ContentPlaceHolder ID="head" runat="server">
        </asp:ContentPlaceHolder>
    <% } %>
And then on any given view with a reference to the "head" ContentPlaceHolder
<asp:Content ID="HeadContentPlaceholder" ContentPlaceHolderID="head" runat="server">
  <% Html.CompositeCssResourcePart(
     "MasterPageReference", 
     new[] 
     {
       "/Styles/PageStyle5.css",
       "/Styles/PageStyle6.css",
       "/Styles/PageStyle7.css"
     }) %>
</asp:Content>
By using a "context" wrapper around the head ContentPlaceholder, I can validate that everything is running in the correct order (i.e., can verify context exists when CompositeCssResourcePart is invoked) 
and removes the need for the suggested shared static class.
Thoughts?
Nov 2, 2010 at 4:11 PM
Edited Nov 2, 2010 at 4:13 PM

Sought the feedback of a colleague of mine, and we are both thinking that the first option is actually a little cleaner (in terms of keeping the Xpedite API clean).

Might suggest starting with the first approach of defining an enumerable of shared resources, and maybe even defining a custom HtmlHelper extension such as:

public static class CustomXpediteExtensions
{   
  private static readonly IEnumerable<String> SharedCss = new[]
    {
      "/Styles/SharedStyle1.css",
      "/Styles/SharedStyle2.css",
      "/Styles/SharedStyle3.css"
    };

  public static MvcHtmlString CustomCompositeCssResource(this HtmlHelper htmlHelper, IEnumerable<String> resources)   
  {      
    return htmlHelper.CompositeCssResource(SharedCss.Concat(resources));  
  }
}

And then inside the view's head ContentPlaceHolder referenceing the custom extension method:

<asp:Content ID="Content2" ContentPlaceHolderID="head" runat="server">
  <%= Html.CustomCompositeCssResource(new[]
      {
        "/Styles/PageStyle5.css",
        "/Styles/PageStyle6.css",
        "/Styles/PageStyle7.css"
      }) %>
</asp:Content>

Just a suggestion. Please let me know what you think.

Nov 8, 2010 at 6:47 PM

Sorry, got crazy busy around here so had to put this on hold for a bit. Hope to be able to get back to it by the end of the week.