[UPDATE][.NET MVC] Extra HtmlHelpers voor Enums

Door F.West98 op zaterdag 26 oktober 2013 01:50 - Reacties (8)
CategorieŽn: .NET, MVC, Programmeren, Views: 5.867

Ik zit veel met enums te werken op dit moment en het viel me op dat er erg weinig support is voor enums in de standaard HtmlHelpers. Daarom heb ik zelf twee nieuwe gemaakt, een dropdownlist en multiple radiobuttons.

DropdownList

Dit is eigenlijk een uitbreiding op een al bestaande HtmlHelper, maar met andere parameters.

Code voor de back-end:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace System.Web.Helpers {
    public static class EnumEditorHtmlHelper {
        /// <summary>
        /// Creates the DropDown List (HTML Select Element) from LINQ 
        /// Expression where the expression returns an Enum type.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <returns></returns>
        public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression)
            where TModel : class {
            TProperty value = htmlHelper.ViewData.Model == null
                ? default(TProperty)
                : expression.Compile()(htmlHelper.ViewData.Model);
            string selected = value == null ? String.Empty : value.ToString();
            return htmlHelper.DropDownListFor(expression, createSelectList(expression.ReturnType, selected));
        }

        /// <summary>
        /// Creates the select list.
        /// </summary>
        /// <param name="enumType">Type of the enum.</param>
        /// <param name="selectedItem">The selected item.</param>
        /// <returns></returns>
        private static IEnumerable<SelectListItem> createSelectList(Type enumType, string selectedItem) {
            return (from object item in Enum.GetValues(enumType)
                    let fi = enumType.GetField(item.ToString())
                    let attribute = fi.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault()
                    let title = attribute == null ? item.ToString() : ((DescriptionAttribute)attribute).Description
                    select new SelectListItem {
                        Value = item.ToString(),
                        Text = title,
                        Selected = selectedItem == item.ToString()
                    }).ToList();
        }
    }
}




Dat is alles over de back-end, nu het gebruik.
Het model die we in dit voorbeeld gebruiken:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
public class Model {
    public Availability availability {get; set;}
}

public enum Availability {
    [Description("Beschikbaar")]
    available = 0, // = 0 voor opslag in database mbv EF
    [Description("Nog niet bekend")]
    unknown = 1,
    [Description("Niet beschikbaar")]
    no = 2
}



En dan de view:

code:
1
@Html.DropDownListFor(model => model.availability)



En het resultaat:

HTML:
1
2
3
4
5
<select name="availability">
    <option value="available">Beschikbaar</option>
    <option value="unknown">Nog niet bekend</option>
    <option value="no">Niet beschikbaar</option>
</select>

De radiobuttons

De standaard manier om een enum te hebben en er een radiobuttonlist van te maken is allemaal aparte RadioButtonFor's gebruiken. Ik heb dit nu handig in ťťn functie gezet, mťt support voor het Description-attribuut voor mooiere teksten erbij. Er is een attribuut voor een seperator, dat is de tekst tussen elke radiobutton (met daarbij het label)

De back-end code:
De betreffende functies kan je gewoon samenvoegen met het andere stukje code, is zelfde class

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace System.Web.Helpers {
    public static class EnumEditorHtmlHelper {
        /// <summary>
        /// Multiple radios for Enum element
        /// </summary>
        /// <typeparam name="TModel">Type of the model</typeparam>
        /// <typeparam name="TProperty">Type of the property</typeparam>
        /// <param name="htmlHelper">The HTML helper</param>
        /// <param name="expression">Expression</param>
        /// <returns></returns>

        public static MvcHtmlString RadioButtonsFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression) where TModel : class {
                return RadioButtonsFor<TModel, TProperty>(htmlHelper, expression, null);
        }
        
        /// <summary>
        /// Multiple radios for Enum element
        /// </summary>
        /// <typeparam name="TModel">Type of the model</typeparam>
        /// <typeparam name="TProperty">Type of the property</typeparam>
        /// <param name="htmlHelper">The HTML helper</param>
        /// <param name="expression">Expression</param>
        /// <param name="seperator">Seperator between items</param>
        /// <returns></returns>

        public static MvcHtmlString RadioButtonsFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression, string seperator) where TModel : class {
            TProperty value = htmlHelper.ViewData.Model == null
                ? default(TProperty)
                : expression.Compile()(htmlHelper.ViewData.Model);
            string selected = value == null ? String.Empty : value.ToString();
            var radios = new List<MvcHtmlString>();
            var lister = Enum.GetValues(expression.ReturnType).GetEnumerator();
            for(var i = 0; lister.MoveNext() == true; i++) {
                var item = lister.Current;
                var fi = expression.ReturnType.GetField(item.ToString());
                var attribute = fi.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault();
                var title = attribute == null ? item.ToString() : ((DescriptionAttribute)attribute).Description;
                if (i != 0) {
                    radios.Add(MvcHtmlString.Create(seperator));
                }
                radios.Add(htmlHelper.RadioButtonFor(expression, item.ToString()));
                radios.Add(MvcHtmlString.Create(title));
            }
            return MvcHtmlString.Create(string.Concat(radios));
        }
    }
}



Het model is dezelfde als bij de dropdownlist.
Dit is de view:

code:
1
2
@Html.RadioButtonsFor(model => model.availability)
@Html.RadioButtonsFor(model => model.availability, "<br>")



En het resultaat:

HTML:
1
2
3
4
5
6
7
<input type="radio" name="availability" value="available" />Beschikbaar
<input type="radio" name="availability" value="unknown" />Nog niet bekend
<input type="radio" name="availability" value="no" />Niet beschikbaar

<input type="radio" name="availability" value="available" />Beschikbaar<br>
<input type="radio" name="availability" value="unknown" />Nog niet bekend<br>
<input type="radio" name="availability" value="no" />Niet beschikbaar<br>



Voor zover ik het heb getest werkt het prima, eventuele op- of aanmerkingen hieronder :)
Update!
De NuGet-Package is er inmiddels, hier te downloaden :)

Volgende: Tweakers Awards: Niets inhoudelijks 11-'13 Tweakers Awards: Niets inhoudelijks
Volgende: VrijMiBlog: Trends: LeapMotion & MYO, de nieuwe besturingsmethodes 05-'13 VrijMiBlog: Trends: LeapMotion & MYO, de nieuwe besturingsmethodes

Reacties


Door Tweakers user Nactive, zaterdag 26 oktober 2013 08:58

Ziet er zeer proper uit. Ik vindt het nog altijd zo jammer dat MVC enums niet out of the box goed ondersteund.

Door Tweakers user Precision, zaterdag 26 oktober 2013 10:52

Ziet er mooi uit, hoe zou je meertaligheid aanpakken?

Door Tweakers user F.West98, zaterdag 26 oktober 2013 11:07

Ik ben daar Łberhaupt nog niet mee bezig geweest, dus daar heb ik ook geen rekening mee gehouden. Misschien later nog eens.

Door Tweakers user Precision, zaterdag 26 oktober 2013 11:41

F.West98 schreef op zaterdag 26 oktober 2013 @ 11:07:
Ik ben daar Łberhaupt nog niet mee bezig geweest, dus daar heb ik ook geen rekening mee gehouden. Misschien later nog eens.
Voor meertaligheid vind je 1001 oplossingen op het web, maar er is niet 1 manier die sluitend is. Bij het gebruik van resource bundles moet je telkens een nieuwe deploy doen naar je productie omgeving als er een wijziging plaatsvindt in een resource file (typfout, woord vergeten, foute zinsbouw, foute leestekens, ...) wat resulteert in een IIS reset. Alle sessions worden beindigd voor die application pool, waardoor al je gebruikers zijn uitgelogd en dit kun je veelal niet maken.

[Reactie gewijzigd op zaterdag 26 oktober 2013 11:47]


Door Tweakers user jbdeiman, zaterdag 26 oktober 2013 13:31

Grappig, eenzelfde soort constructie hebben we zelf gebouwd, maar dan niet met het MVC framework maar een zelfgebouwde constructie. We bouwen software voor een zeer dynamische markt, waarbij we heel veel verschillende lijsten hebben die door gebruikers van het softwarepakket gebruikt kunnen worden.
We hebben hierbij een "dubbele" constructie gecreŽerd waarbij we enums creŽren (want = vele malen sneller dan altijd alle lijstjes ophalen uit een database), op basis van gemaakte settings door de applicatiebeheerder. Deze "enums" (wij noemen ze alleen anders, managed models) worden daarna weer gebruikt met eenzelfde soort contructie als je hier laat zien, in combinatie met de controls. Werkt als een trein en is daarmee ook nog eens enorm flexibel.

Door Tweakers user CodeCaster, zaterdag 26 oktober 2013 13:55

En wat moeten we met deze code doen? Copypasta'en? En wat als er dan een bugfix of nieuwe feature voor komt? Maak er eens een NuGet-package van. ;)

Daarnaast beginnen properties en enum-members in .NET doorgaans met een hoofdletter.

Door Tweakers user F.West98, zondag 27 oktober 2013 16:33

CodeCaster schreef op zaterdag 26 oktober 2013 @ 13:55:
En wat moeten we met deze code doen? Copypasta'en? En wat als er dan een bugfix of nieuwe feature voor komt? Maak er eens een NuGet-package van. ;)

Daarnaast beginnen properties en enum-members in .NET doorgaans met een hoofdletter.
Ik zal eens gaan kijken hoe ik dat ga doen, waarschijnlijk wel binnenkort. Zal er weer een post over maken dan :)

Wat bedoel je met de laatste opmerking?

Door Tweakers user Caelorum, maandag 28 oktober 2013 14:53

F.West98 schreef op zondag 27 oktober 2013 @ 16:33:
[...] Wat bedoel je met de laatste opmerking?
Hij doelde denk ik op het hooflettergebruik in jouw code

C#:
1
2
3
4
5
6
7
8
9
10
11
12
public class Model { 
    public Availability availability {get; set;} 
} 

public enum Availability { 
    [Description("Beschikbaar")] 
    available = 0, // = 0 voor opslag in database mbv EF 
    [Description("Nog niet bekend")] 
    unknown = 1, 
    [Description("Niet beschikbaar")] 
    no = 2 
}


Wordt doorgaans geschreven als (let op Pascal casing :))

C#:
1
2
3
4
5
6
7
8
9
10
11
12
public class Model { 
    public Availability Availability {get; set;} 
} 

public enum Availability { 
    [Description("Beschikbaar")] 
    Available = 0, // = 0 voor opslag in database mbv EF 
    [Description("Nog niet bekend")] 
    Unknown = 1, 
    [Description("Niet beschikbaar")] 
    No = 2 
}



Overigens worden doorgaans ook methodes met een hoofletter begonnen.
Zie http://msdn.microsoft.com...o/ms229043(v=vs.110).aspx
Niet dat je je er aan hoeft te houden hoor, maar deze stijl wordt door vrij veel mensen (zo niet bijna allemaal) gebruikt in .Net.

[Reactie gewijzigd op maandag 28 oktober 2013 15:00]


Reageren is niet meer mogelijk