Showing posts with label razor. Show all posts
Showing posts with label razor. Show all posts

Wednesday, December 4, 2013

Inline markup delegates and razor helper delegate wars

So I had some markup that was being duplicated all over the place in MVC.

This is mostly boilerplate bootstrap paneling, with masonry on top.

How would you provide a generic razor to meet bootstrap/masonry without creating a bunch of classes or partials to account for special case(s)?  How in razor would you write a method that can take inline markup (for example any @<text> call)?

 One way is @helper Take note that HelperResult's documentation says specifically it is not intended to be used directly in your code. So I headed in down the path of inline razor helper delegates:

Wednesday, February 6, 2013

AutoScaffold Index.cshtml tabular view site-wide

I was tired of generating the index.cshtml list over and over again everytime the model or viewmodel changes. So how could I get that to be runtime generated? Enter Haacked's article on Mvc Tabular Display Template Updated to work with Razor and with IEnumerable First the DisplayTemplate
@using System
@using MyMvc.Models
@model IEnumerable<dynamic>
@{
    //http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
    var items = Model.ToArray();
    var metadata = ModelMetadata.FromLambdaExpression(m => m.ToArray()[0], ViewData);
    var properties = metadata.Properties.Where(pm => TableViewHelper.ShouldShow(pm, ViewData)).OrderBy(pm => pm.Order);
}

<table>
    <thead>
        <tr>
            @foreach (var property in properties)
            {
                <th>@property.GetDisplayName()</th>
            }
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            var itemMetadata = ModelMetadata.FromLambdaExpression(m => item, ViewData);
            <tr>
               
                @foreach (var property in properties)
                {
                    var propertyMetdata = itemMetadata.Properties.Single(m => m.PropertyName == property.PropertyName);
                    <td>
                        @Html.DisplayFor(m => propertyMetdata.Model)
                    </td>
                }
            </tr>
        }
    </tbody>
    
</table>

<span>Showing @Model.Count() items</span>
Then the helper
@using System
@using MyMvc.Models
@model IEnumerable<dynamic>
@{
    //http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
    var items = Model.ToArray();
    var metadata = ModelMetadata.FromLambdaExpression(m => m.ToArray()[0], ViewData);
    var properties = metadata.Properties.Where(pm => TableViewHelper.ShouldShow(pm, ViewData)).OrderBy(pm => pm.Order);
}

<table>
    <thead>
        <tr>
            @foreach (var property in properties)
            {
                <th>@property.GetDisplayName()</th>
            }
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            var itemMetadata = ModelMetadata.FromLambdaExpression(m => item, ViewData);
            <tr>
               
                @foreach (var property in properties)
                {
                    var propertyMetdata = itemMetadata.Properties.Single(m => m.PropertyName == property.PropertyName);
                    <td>
                        @Html.DisplayFor(m => propertyMetdata.Model)
                    </td>
                }
            </tr>
        }
    </tbody>
    
</table>

<span>Showing @Model.Count() items</span>
Then add a Views/Shared/Index.cshtml
@model System.Collections.Generic.IEnumerable<dynamic>
@{
    ViewBag.Title = "Index";
}

@Html.DisplayForModel("Table")

Change your Mvc AddView template

I didn't like that the AddView template ignored the DisplayAttribute(Order=0). So I adjusted it following http://blogs.msdn.com/b/webdev/archive/2009/01/29/t4-templates-a-quick-start-guide-for-asp-net-mvc-developers.aspx I added the following to my CodeTemplates/AddView/CSHTML/List.tt file
IEnumerable<PropertyInfo> SortProperties(IEnumerable<PropertyInfo> props)
    {
      var pl = props.ToList();
      var length = pl.Count;
      var q = from p in pl
            let da = p.GetCustomAttribute<DisplayAttribute>()
            where da != null
            let o = da.GetOrder()
            where o.HasValue
            orderby o
            select new { p, o.Value };

      var ordered = q.ToList();
      var remainder = pl.Except(ordered.Select(o => o.p).ToArray()).ToArray();
      var destination = new PropertyInfo[length];

      foreach (var o in ordered)
      {
        destination[o.Value] = o.p;
      }

      if (ordered.Count == length)
        return ordered.Select(o => o.p).ToArray();

      var remainderCount = 0;
      for (int i = 0; i < pl.Count; i++)
      {
        if (destination[i] != null)
          continue;
        destination[i] = remainder[remainderCount];
        remainderCount++;

      }
      return destination;


    }
then updated GetEligibleProperties to call it in the property loop. Here's the unit test:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using Microsoft.VisualStudio.TestTools.UnitTesting;


namespace MyMvc.Tests.CodeTemplates.AddView.CSHTML
{
  [TestClass]
  public class ListTests
  {
    class SortTester
    {
      public Guid FileGuid { get; set; }

      [Display(Order = 0)]
      public string FileName { get; set; }

      public int CustomerId { get; set; }

      [Display(Order = 1)]
      public int? ExpectedTrxCount { get; set; }
    }
    IEnumerable<PropertyInfo> SortProperties(IEnumerable<PropertyInfo> props)
    {
      var pl = props.ToList();
      var length = pl.Count;
      var q = from p in pl
            let da = p.GetCustomAttribute<DisplayAttribute>()
            where da != null
            let o = da.GetOrder()
            where o.HasValue
            orderby o
            select new { p, o.Value };

      var ordered = q.ToList();
      var remainder = pl.Except(ordered.Select(o => o.p).ToArray()).ToArray();
      var destination = new PropertyInfo[length];

      foreach (var o in ordered)
      {
        Console.WriteLine("ordering:" + o.p.Name);
        destination[o.Value] = o.p;
      }

      if (ordered.Count == length)
        return ordered.Select(o => o.p).ToArray();

      var remainderCount = 0;
      for (int i = 0; i < pl.Count; i++)
      {
        if (destination[i] != null)
          continue;
        destination[i] = remainder[remainderCount];
        remainderCount++;

      }
      foreach (var d in destination) Console.WriteLine(d.Name);
      return destination;


    }
    [TestMethod]
    public void Sort_DisplayOne_IsSecond()
    {
      var type = typeof(SortTester);
      
      var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
      var expected = props.First(p => p.Name == LinqOp.PropertyOf(()=>new SortTester().FileName).Name);
      var sorted = SortProperties(props);
      var actual = sorted.First();
      Assert.AreEqual(expected, actual);

    }
    [TestMethod]
    public void Sort_DisplayZero_IsFirst()
    {
      var type = typeof(SortTester);

      var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
      var expected = props.First(p => p.Name == LinqOp.PropertyOf(() => new SortTester().ExpectedTrxCount).Name);
      var sorted = SortProperties(props);
      var actual = sorted.Skip(1).First();
      Assert.AreEqual(expected, actual);

    }
  }
}

Here's the source of LinqOp

Wednesday, May 23, 2012

Foray into custom model binders

I have a complex viewmodel
public class ScenarioCreationDTO
  {
    [Required]
    [StringLength(500)]
    public string Comments { get; set; }
    [Required]
    public IEnumerable<int> PriceLists { get; set; }
    public IEnumerable<PriceListCurrencyDTO> Currencies { get; set; }
    public IEnumerable<PriceListCountryDTO> Countries { get; set; }
  }
Currencies has a compound key. How do I get binding to work for either checkBoxList or RadioButtons? Sometimes the user is allowed to select multiple, other times not. Perhaps the problem was in the view?
<tbody>
                                    @foreach (SelectListItem item in ViewBag.CurrencySelect)
                                    { 
                                        <tr>
                                            <td>
                                                @if (ViewBag.MultipleCurrencies)
                                                {
                                                    @Html.RadioButton("currencies", item.Value, item.Selected)
                                                }
                                                @Html.Label(item.Text)
                                            </td>
                                        </tr>
                                    }
                                </tbody>
I did not find a RadioButtonList helper, nor a RadioButtonFor<T> that took multiple values. Thanks to Msdn magazine and Buildstarted.com I now have a custom model binder.
protected override void OnApplicationStarted()
    {
      base.OnApplicationStarted();
      ModelBinders.Binders.Add(typeof(PriceListCurrencyDTO),new PriceListCurrencyBinder());

- global.asax.cs
public class PriceListCurrencyBinder:IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
      var selected = value.AttemptedValue;
      var result = Shared.SyntaxSugar.Helpers.Deserialize<PriceListCurrencyDTO>(selected);
      return result;
    }
  }

var currencySelect=currencies.Select(c => new SelectListItem()
      {
        Text = c.CurrencyCode,
        Value = Shared.SyntaxSugar.Helpers.Serialize(c),
        Selected = priceListCurrenciesSelected != null && priceListCurrenciesSelected.Any(plc=>c.PriceListID==plc.PriceListID && c.CurrencyCode==plc.CurrencyCode)
      }).ToArray();
      ViewBag.CurrencySelect = currencySelect;
[HttpPost]
    public virtual ActionResult Create(byte regionID, Int64 dealID, Model.ViewModel.Scenarios.ScenarioCreationDTO data){
//...
}
and finally the view
<tbody>
                                    @foreach (SelectListItem item in ViewBag.CurrencySelect)
                                    { var i=-1; i++;
                                        <tr>
                                            <td>
                                                @if (ViewBag.MultipleCurrencies)
                                                {
                                                    @Html.RadioButton("currencies["+i+"]", item.Value, item.Selected)
                                                }
                                                @Html.Label(item.Text)
                                            </td>
                                        </tr>
                                    }
                                </tbody>

Friday, August 20, 2010

Visual Studio find and replace with regular expressions totally sucks.

The syntax for regular expressions in Visual studio's find and replace option are incredibly convoluted.

I wanted to replace all instances of width in a style sheet with something that was variable based.

so width:75px; would become width:@(width=75)px;

This syntax is using the new Razor view engine and the result is we set a local variable and render it to the page in a nice terse expression. Combined with:

left:@(leftStart+width)px;

we now have the beginning of a nice sliced row of images. Where all the things on a particular row would incrementally build, and then on the next row, I can reset leftStart.

This was the syntax for finding all lines of an html document and capturing the width:
width\:{[0-9]+}

And the syntax for the replacement?
width\:\@(width=\1)

I understand that c# syntax uses a ton of the same conflicting symbols, but... could you guys make something like rexexpal so that we can iteratively solve for the expressions we need?