Thursday, April 18, 2013

Dynamically generated Javascript and CSS in ASP.NET MVC using Razor Views

I was always wondering why ASP.NET allows to generate dynamically HTML (including inline CSS and JS), but does not allow to do the same for standalone JS and CSS files. Today I spent some time to implement relatively simple and convenient solution of the problem on ASP.NET MVC platform.

Here is the list of features I want to get from solution:
  • Intelli-sense in editor of dynamic CSS and JS
  • Fully functional C# razor with server-side C# code in dynamic content
  • Ability to pass Model and use request parameters
  • I still want to use some static CSS, and JS besides dynamic
  • Adding another dynamic JS/CSS file should be as easy as adding static JS/CSS file
Here is what OUT OF SCOPE:
  • Bundling and minification of resources

Short story

To achieve these goals, I use existing functionality of CSHTML editor, which provides everything we need for inline CSS and JS. I create partial cshtml view with single Html element, either or and after rendering I trim the root tag and get dynamically rendered content file.
The steps you will need to do:
1) Implement CSHTML files to render dynamic resources
2) Implement Style and Script controllers, overriding handling unknown action, using "Magic" extensions
3) Modify web.config to allow controllers handle static file requests
4) Implement Magic extensions.
5) Enjoy

Here you can download source code.


Long story:

 

CREATING TARGET VIEW

Let's start with our final view, which declares our implementation goals. It is super simple home page view, that references our dynamic and static content, and demonstrate visually that using of dynamic resources works:
Index.cshtml:

@{
    ViewBag.Title = "Home page";
}
<link href="~/Styles/static.css" rel="stylesheet" />
<link href="~/Styles/dynamic.css?color=grey" rel="stylesheet" />
<script src="~/Scripts/static.js"></script>
<script src="~/Scripts/dynamicWithModel.js?message=Hallo%20world&otherParameter=My parameter value"></script>
<script src="~/Scripts/otherDynamic.js"></script>
 
<h2 class="dynamicCss">@ViewBag.Title</h2>
 
<script>
    StaticJsFunction();
    DynamicJsFunction();
    OtherDynamicJsFunction();
</script>

You can see that this view uses dynamic CSS and static CSS. It also uses one static JS file, and two dynamic JS files. Dynamic references has query string parameters which will impact the rendering of the resource. In DynamicWithModel.js I also wants to use model that I pass to the JS-view and which is respected during JS rendering.

I want my dynamic resources to be handler by controllers, while static resources should be stored just in Styles and Scripts folder of the web application.


Creating JS and CSS


For static resources I create folders in the root of the project. Regarding dynamic resources, since controllers should be named Styles and Scripts, I will create view folders accordingly with CSHTML files:

image

Content of those static files is very simple:
~/scripts/static.js:

function StaticJsFunction() {
    document.writeln("executing static JS

");
}


~/scripts/static.css:

body {
    background-color:InfoBackground;
}


Here are dynamic resources:
~/Views/Styles/dynamic.css.cshtml:


@{    var className = "dynamicCss";
    var bkColor = Request.QueryString["color"]??"red";
    }<style>.@className
{
    background-color:@bkColor;
}
</style>

As you see CSHTML is standard view with a single block STYLE node. Background color is retrieved from query string parameter, in case parameter is not specified default RED color is used.

~/Views/Scripts/DynamicWithModel.js.cshtml:


@model string<script>function DynamicJsFunction() {
    document.writeln("executing dynamic JS from: @Request.RawUrl...")
    @if (Model != null)
    {
        @: { document.writeln("Message passed to Model: @Model"); }
    }
    @if (Request.QueryString.HasKeys())
    {
        foreach (string key in Request.QueryString.Keys)
        {
            @:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
        }
    }
}
</script>



As you see this  JS is a function renders to current document model and query string parameters which were specified during rendering the JS content.

~/Views/Scripts/OtherDynamic.js.cshtml:

<script>    function OtherDynamicJsFunction() {
        document.writeln("executing Other dynamic JS from: @Request.RawUrl...
")
        @if (Request.QueryString.HasKeys())
        {
            foreach (string key in Request.QueryString.Keys)
            {
                @:{ document.writeln("Query string parameter. Key: @key , Value: @Request.QueryString[key]"); }
            }
        }
    }
</script>
This function does not use model, but also renders current query string parameters to Html document.



Creating Scripts and Styles Controllers


I’m creating two controllers which handles requests coming to ~/Styles/* and ~/Scripts/* paths.


    public class StylesController : Controller
    {
        public ActionResult Index()
        {
            return Content("Styles folder");
        }
 
        protected override void HandleUnknownAction(string actionName)
        {
            var res = this.CssFromView(actionName);
            res.ExecuteResult(ControllerContext);
        }
    }

Since I don't want to register action for every single CSS and JS file, I override HandleUnknownAction to handle all requests to controller that were not associated with declared action.

    public class ScriptsController : Controller
    {
        [ActionName("DynamicWithModel.js")]
        public ActionResult Dynamic(string message)
        {
            return this.JavaScriptFromView(model:message);
        }
 
        public ActionResult Index()
        {
            return Content("Scripts folder");
        }
 
        protected override void HandleUnknownAction(string actionName)
        {
            var res = this.JavaScriptFromView();
            res.ExecuteResult(ControllerContext);
        }
 
    }

For DynamicWithModel.js i want to pass model, retrieving it from input parameter. In this case since I cannot create method containing dot in a name, I have to use attribute ActionName (alternatively I can avoid using dots in resource names in Index.chtml).

In order to make these controllers handle requests with file extensions in URL you must modify your web.config:


<system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>




Implementing the "Magic" extensions:


As you might notice I used custom controller extensions JavaScriptFromView and CssFromView - these guys do all the magic. Here is it's implementation:


using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Web; namespace System.Web.Mvc {     /// 

    /// Mvc extensions for dynamic CSS and JS    /// 

    public static class MvcExtensions    {
        /// 









        /// CSS content result rendered by partial view specified        /// 
        /// "controller">current controller
        /// "cssViewName">view name, which contains partial view with one STYLE block only
        /// "model">optional model to pass to partial view for rendering
        /// 
        public static ActionResult CssFromView(this Controller controller, string cssViewName=nullobject model=null)
        {
            var cssContent = ParseViewToContent(controller,cssViewName, "style", model);
            if(cssContent==nullthrow new HttpException(404,"CSS not found");
            return new ContentResult() { Content = cssContent, ContentType = "text/css" };
        }

        /// 










        /// Javascript content result rendered by partial view specified        /// 
        /// "controller">current controller
        /// "javascriptViewName">view name, which contains partial view with one SCRIPT block only
        /// "model">optional model to pass to partial view for rendering
        /// 
        public static ActionResult JavaScriptFromView(this Controller controller, string javascriptViewName=nullobject model=null)
        {
            var jsContent = ParseViewToContent(controller,javascriptViewName, "script", model);
            if(jsContent==nullthrow new HttpException(404,"JS not found");
            return new JavaScriptResult() {Script = jsContent };
        }

        /// 










        /// Parse view and render it to a string, trimming specified HTML tag        /// 
        /// "controller">controller which renders the view
        /// "viewName">name of cshtml file with content. If null, then actionName used
        /// "tagName">Content rendered expected to be wrapped with this html tag, and it will be trimmed from result
        /// "model">model to pass for view to render
        /// 
        static string ParseViewToContent(Controller controller, string viewName, string tagName, object model = null)
        {
            using (var viewContentWriter = new StringWriter())
            {
                if (model != null)
                    controller.ViewData.Model = model;

                if (string.IsNullOrEmpty(viewName))
                    viewName = controller.RouteData.GetRequiredString("action");

                var viewResult = new ViewResult()
                {
                    ViewName = viewName,
                    ViewData = controller.ViewData,
                    TempData = controller.TempData,
                    ViewEngineCollection = controller.ViewEngineCollection
                };

                var viewEngineResult = controller.ViewEngineCollection.FindPartialView(controller.ControllerContext, viewName);
                if (viewEngineResult.View == null)
                    return null;

                try                {
                    var viewContext = new ViewContext(controller.ControllerContext, viewEngineResult.View, controller.ViewData, controller.TempData, viewContentWriter);
                    viewEngineResult.View.Render(viewContext, viewContentWriter);
                    var viewString = viewContentWriter.ToString().Trim('\r''\n'' ');
                    var regex = string.Format("<{0}[^>]*>(.*?)</{0}>", tagName);
                    var res = Regex.Match(viewString, regex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
                    if (res.Success && res.Groups.Count > 1)
                        return res.Groups[1].Value;
                    else throw new InvalidProgramException(string.Format("Dynamic content produced by viewResult '{0}' expected to be wrapped in '{1}' tag", viewName, tagName));
                }
                finally                {
                    if (viewEngineResult.View != null)
                        viewEngineResult.ViewEngine.ReleaseView(controller.ControllerContext, viewEngineResult.View);
                }
            }

        }

    }
}

 
Show time
image

As you can see you can modify query string parameters in runtime using IE Developer Tools:

image


and see results immediately:

image

in Network tab you can see actual responses and low level information:

image



image


Dynamic Javascript response:

image


Again, you can download source code from here.

Additional tricks:

Since you can add inline code actually inline you probably won't need it, but You also can use those controllers' actions as child actions to render inline resources if you need using Html.RenderAction/ Html.Action(...) extensions on your parent HTML view. If you do this make sure your Response.ContentType is not overwritten by Render action and if it was you need manually to restore it.


FileHandler VS Controller competition for handling file request


If you noticed there is a competition between Static file Handler and your Controller for handling requests to resource file (js/css). So it is important to understand how it works. So here it is:

By default StaticFileHandler has priority. This means if you create file ~/Views/Scripts/static.js.cshtml, and you already have file ~/Scripts/static.js - the last one (real static) will be used. But you can change this behavior using Routing. You need to do is to add in your RouteConfig.cs (located in App_Start for MVC4 template) the following line:

            routes.RouteExistingFiles = true;


It will deactivate Static File handler and redirect all file requests to Controllers.

Tuesday, August 28, 2012

XAML Explicit binding to Interface - Object 'null' cannot be used as an accessor parameter for a PropertyPath - Version 2

This is evolution of my previous post: http://blog.pmunin.com/2012/02/xaml-explicit-binding-to-interface.html
I've decided not to overwrite the old one, but create new post instead.

So eventually I decided to resolve issue using separate binding class, which is inherited from Binding, but contains all necessary improvements.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Data;
using System.Diagnostics;
using System.ComponentModel;
[assemblyXmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation""PMunin.com")]
namespace PMunin.com
{
    /// 
    /// Inherited from Binding, have some improvements:    ///  - does not crash VS2010 WPF designer if Path contains Interface.    ///  - ValidateOnDataErrors and ValidateOnExceptions are true by default.    /// 

    public class XamlExtBinding : Binding
    {
        public XamlExtBinding():base()
        {
            NotifyOnValidationError = true;
            ValidatesOnDataErrors = true;
            ValidatesOnExceptions = true;
        }
        public XamlExtBinding(string path):base(path)
        {
            NotifyOnValidationError = true;
            ValidatesOnDataErrors = true;
            ValidatesOnExceptions = true;
        }
        [TypeConverter(typeof(ExtPropertyPathConverter))]
        public new object Path
        {
            get
            {
                return base.Path;
            }
            set
            {
                base.Path = value as PropertyPath;
            }
        }
        /// 
        /// Required to fix bug "Prefix 'local' does not map to a namespace"        /// when Path with namespace used in DataTemplate like here (http://devexpress.com/Support/Center/p/Q352753.aspx)        /// 
        public class ExtPropertyPathConverter : TypeConverter        {
            static PropertyPathConverter pathConverter = new PropertyPathConverter();
            static bool IsInDesignMode(ITypeDescriptorContext context)
            {
                bool isInDesignMode = DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow);
                return isInDesignMode;
            }
            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
                bool res = true;
                try                {
                    res = pathConverter.CanConvertFrom(context, sourceType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            {
                var res = true;
                try                {
                    res = pathConverter.CanConvertTo(context, destinationType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
            {
                object res = null;
                try                {
                    res = pathConverter.ConvertFrom(context, culture, value);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
            public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                object res = null;
                try                {
                    res = pathConverter.ConvertTo(context, culture, value, destinationType);
                }
                catch                {
                    if (!IsInDesignMode(context)) throw;
                }
                return res;
            }
        }
    }
}


You should put this class in separate shared class library, that is referenced by you project with XAML files you need to edit. This will allow you to use in very simple way you get used to - anywhere you have {Binding ....}, or just replace it with {XamlExtBinding ...} or . No namespace declaration required.
This code was written long time ago, so I'm posting it now, but I'm not sure that included everything. Please feel free to comment - if you face any issues.

Monday, February 20, 2012

XAML binding to CompositeCollection

Some times there is a need to display in your ItemsControl (like TreeView or ListBox) some compound collection, which consists from several collections of elements.
Well, microsoft provides us CompositeCollection and CollectionContainer classes for this purpose. However it is a real pain to use data binding with this classes in XAML. Here is one of solution suggested, which looks a bit ugly if you ask me: http://wilberbeast.com/2011/05/31/compositecollection-binding-problem/

I'll show you alternative you can use to make it looks a bit better and neat.

For example I have a view model of Survey, which has properties:

public class SurveyViewModel{
       public InstructionsModel Instructions {get;}
       public ObservableCollection<QuestionModel> Children {get;}
       public ReviewModel Review {get;}
}

And I need to display in a listbox the following items:
[Instructions]
[Question1]

[Question2]
...
[QuestionN]
[Review]

I keep in mind that Children collection can be updated - in that case ListBox must  be automagically refreshed (new element might be added/removed by some action buttons).
I want to each item to be displayed in a different manner based on its type and associated DataTemplate.In order to do that, i need to assign in myListBox.ItemsSource to CompositeCollection with CollectionContainers inside. But if you will try it you will see how many gabage you will have to put in different parts of your XAML. Here is the alternative approach how I do it now:

<TreeView
    x:Name="tvSurvey">
    <TreeView.ItemsSource>
        <MultiBinding>
            <MultiBinding.Converter>
                <local:CompositeCollectionConverter />
            MultiBinding.Converter>
            <Binding Path="ViewModel.Instructions" />
            <Binding Path="ViewModel.Children" />
            <Binding Path="ViewModel.Review" />
        </MultiBinding>
    </TreeView.ItemsSource>
...
     TreeView.Resources with datatemplates for:
     InstructionsModel, QuestionModel, ReviewModel
...
</TreeView>

As you already may see there is no magic here: just use MultiBinding, which supports current DataContext (unlike CollectionContainer) plus CompositeCollectionConverter (IMultiValueConverter) for converting multibinding to composite collection.

Here is the code of CompositeCollectionConverter:


    public class CompositeCollectionConverter:IMultiValueConverter
    {
 
        public object Convert(object[] values
            , Type targetType
            , object parameter
            , System.Globalization.CultureInfo culture)
        {
            var res = new CompositeCollection();
            foreach (var item in values)
                if (item is IEnumerable) 
                    res.Add(new CollectionContainer() 
                            { 
                                Collection = item as IEnumerable 
                            });
                else res.Add(item);
            return res;
        }
 
        public object[] ConvertBack(object value
            , Type[] targetTypes
            , object parameter
            , System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }



Enjoy :-)

XAML Explicit binding to Interface - Object 'null' cannot be used as an accessor parameter for a PropertyPath


Object 'null' cannot be used as an accessor parameter for a PropertyPath. An accessor parameter must be DependencyProperty, PropertyInfo, or PropertyDescriptor.

Note: Implementation of this post is obsolete. Please see new version here: http://blog.pmunin.com/2012/08/xaml-explicit-binding-to-interface.html


Everyone who works with MVVM pattern in WPF had met this problem with data binding to interface property. When you use bindin with explicit PropertyPath (like Path=(local:IMyInterface.MyProperty)) your designer is no longer working (neither Visual Studio XAML, nor Expression Blend).

The interesting/weird thing is that it screw up only if you use this binding in visual tree of your user control or windows, but not in DataTemplate/HierarchicalDataTemplate. Explicit binding inside DataTemplate works fine for me.


Finally I've found some workaround solution that allows you to use binding to interfaces' properties without breaking you Visual Studio and Expression Blend designers in main visual tree. But you will have to add something to your XAML. Basically instead of writing  Path=(local:IMyInterface.MyProperty), you will have to write  XamlExt.Path=(local:IMyInterface.MyProperty).

This is how you can achieve it:
1) Create Attached Property to Binding class.
2) Register Xml namespace mapping.
3) Enjoy full MVVM pattern in your Xaml applications with working designer

Here is code of XAML Markup Extension in separate class library:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Data;
using System.Diagnostics;
using System.ComponentModel;
 
namespace MyWPFApplication.Extensions
{
    /// 
    /// Binding extensions required to bind to interface property 
    /// without breaking designer
    /// 
    public static partial class XamlExt
    {
 
 
        public static object GetPath(System.Windows.Data.Binding obj)
        {
            return obj.Path;
        }
        public static void SetPath(System.Windows.Data.Binding obj, object value)
        {
 
            try
            {
                obj.Path = (PropertyPath)value;
            }
            catch (Exception e)
            {
                PresentationTraceSources
                   .DataBindingSource
                   .TraceData(TraceEventType.Error, 0, e);
                throw;
            }
        }
 
        public static readonly DependencyProperty PathProperty =
            DependencyProperty.RegisterAttached("Path"
                        typeof(PropertyPath)
typeof(XamlExt)
new PropertyMetadata());
 
    }
 
}


In order to use it conveniently in you XAML, you just need to add attribute in Assembly.cs:

[assemblyXmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"MyWPFApplication.Extensions")]



Now add modification to your old code.
Old code:

<Button
   x:Name="btnAdd"
   Content="Add"
   Command="{Binding Path=(local:IViewModel.AddCommand)}" />


New code:


<Button
   x:Name="btnAdd"
   Content="Add"
   Command="{Binding XamlExt.Path=(local:IViewModel.AddCommand)}" />




Note: Implementation of this post is obsolete. Please see new version here: http://blog.pmunin.com/2012/08/xaml-explicit-binding-to-interface.html


Friday, January 27, 2012

XAML DataTemplates binding to interface

Some times it is much convenient to use interface as a View Model for a View. In WinForms there was no problem with it (using BindingSource for instance), but in WPF DataBinding engine is limited.
Let's say you have any TreeView (the same works for ListBox, any ItemsControl or ContentControl, ContentPresenter - whatever has *TemplateSelector property). And you want nodes of this TreeView to display different content using different DataTemplates. And you want these DataTemplates to by mapped be bounded to Interface, instead of class. Another words View Model of each node datatemplate is defined by interface.

So here is the solution to achieve it.

I've created an MarkupExtension, that allows to assign very easily TemplateSelector, which will select appropriate datatemplate based on compatibility of interface.
The XAML will look like this:

        <ListBox
            ItemsSource="{Binding Source={StaticResource nodes}}"
            ItemTemplateSelector="{local:InterfaceTemplateSelector nodeDataTemplate}"
            >
            <ListBox.Resources>

                <HierarchicalDataTemplate
                    x:Key="nodeDataTemplate"
                    DataType="{x:Type local:INode}"
                    ItemsSource="{Binding Path=(local:INode.Children)}"
                    >...
                HierarchicalDataTemplate>
            ListBox.Resources>
        ListBox>

If you want to have several templates for different nodes than you will need the following XAML modifications:
For ListBox you will have to change property ItemTemplateSelector="{local:InterfaceTemplateSelector 'rootNodeDataTemplate,nodeDataTemplate'}"
and create second data template:
                <HierarchicalDataTemplate
                   x:Key="rootNodeDataTemplate"
                   DataType="{x:Type local:IRootNode}"
                   ItemsSource="{Binding Path=(local:IRootNode.Children)}">....
InterfaceTemplateSelector has two properties:
1) ResourceKeys (also first constructor parameter)
2) ResourceKeysSeparator (also second optional constructor parameter) - default value ","
InterfaceTemplateSelector initialization parameter is CSV-string representing keys of resources which are data templates targeted to Interface.

This selector will take all available resource DataTemplates and check DataType each of it (which should have interface assigned) against type of content Item that is about to render.
Keep in mind that sequence of keys in InterfaceTemplateSelector defines priority of associating DataTemplate for content Item. Let's say you have Item that implement both interfaces (INode and IRootNode). But in our case it will get rootNodeDataTemplate assigned, because its key mentioned first.

Here is the implementation the markup extension, to make it work accordingly:

    public class InterfaceTemplateSelectorExtension : System.Windows.Markup.MarkupExtension
    {
        public InterfaceTemplateSelectorExtension()
        {
            ResourceKeysSeparator = ",";
        }
 
        public string ResourceKeysSeparator { getset; }
 
        public InterfaceTemplateSelectorExtension(string resourceKeysCSV)
        {
            ResourceKeys = resourceKeysCSV;
        }
 
        public InterfaceTemplateSelectorExtension(string resourceKeys, string separator)
        {
            ResourceKeys = resourceKeys;
            ResourceKeysSeparator = separator;
        }
 
 
        /// 
        /// Comma separated resource keys specifying keys of DataTemplates that binds to interface
        /// 
        public string ResourceKeys { getset; }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return new InterfaceTemplateSelector(ResourceKeys.Split(new string[]{ResourceKeysSeparator}, StringSplitOptions.RemoveEmptyEntries));
        }
 
        public class InterfaceTemplateSelector:DataTemplateSelector
        {
            string[] resourceKeys;
            public InterfaceTemplateSelector(string[] resourceKeys)
            {
                this.resourceKeys = resourceKeys;
            }
 
            public override DataTemplate SelectTemplate(object item, DependencyObject container)
            {
                var c = (FrameworkElement)container;
                var dataTemplates = (from rk in resourceKeys
                                let resource = c.TryFindResource(rk)
                                where resource is DataTemplate
                                where (resource as DataTemplate).DataType is Type
                                select resource).Cast<DataTemplate>()
                                ;
 
                var itemType = item.GetType();
 
                var result = dataTemplates.FirstOrDefault(dt => 
                    (dt.DataType as Type).IsInstanceOfType(item)
                    );
 
                
                return result??base.SelectTemplate(item, container);
                
            }
        }
        
    }