ASP.NET MVC Aides, la Fusion de deux objets htmlAttributes ensemble

j'ai une situation où je dois écrire un Helper HTML pour étendre un autre helper html. Normalement, l'aide devrait ressembler à ceci.

@Html.TextAreaFor(model => model.Content, new { @class = "some css", @data_bind = "some other stuff..." })

cela fonctionne bien, mais il doit être enveloppé dans un autre HTML qui est toujours le même. Je voulais l'encapsuler pour plus de commodité, comme ceci.

public static MvcHtmlString CondensedHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) {
            var stringBuilder = new System.Text.StringBuilder();
            var tag = new TagBuilder("div"); tag.AddCssClass("some_css");
            stringBuilder.Append(toolbar.ToString(TagRenderMode.SelfClosing));

            stringBuilder.Append(htmlHelper.TextAreaFor(expression, htmlAttributes));
            // more tags and such...

            return new MvcHtmlString(stringBuilder.ToString());
        }

La ligne stringBuilder.Append(htmlHelper.TextAreaFor... est ce que je veux changer. La classe CSS qui doit allez, il est toujours sera présent. Donc, je préfère l'inclure ici. Cependant, je voudrais être en mesure de spécifier des classes CSS supplémentaires dans le helper de haut niveau. Si. ..

@Html.CondensedHelperFor(model => model.Content, new { @class = "some_other_css" })

et les css statiques qui seront toujours là se sont infiltrés par L'intermédiaire de l'Assistant.

des idées?

30
demandé sur Piotr Szmyd 2011-05-18 03:19:07

4 réponses

tout d'abord, créer une méthode (la meilleure serait de créer une méthode d'extension) qui convertit un objet en IDictionary via type reflection:

public static IDictionary<string, object> ToDictionary(this object data) 
{
        if(data == null) return null; // Or throw an ArgumentNullException if you want

        BindingFlags publicAttributes = BindingFlags.Public | BindingFlags.Instance;
        Dictionary<string, object> dictionary = new Dictionary<string, object>();

        foreach (PropertyInfo property in 
                 data.GetType().GetProperties(publicAttributes)) { 
            if (property.CanRead) {
                dictionary.Add(property.Name, property.GetValue(data, null));
            }
        }
        return dictionary;
}

maintenant, utilisez C# 4.0 ExpandoObject, qui permet d'ajouter des propriétés à l'exécution. Vous finiriez avec quelque chose comme ceci:

public static MvcHtmlString CondensedHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) {
{
    var dictAttributes = htmlAttributes.ToDictionary();

    var result = new ExpandoObject();
    var d = result as IDictionary<string, object>; //work with the Expando as a Dictionary

    if(dictAttributes != null)
    {
        foreach (var pair in dictAttributes)
        {
            d[pair.Key] = pair.Value;
        }
    }

    // Add other properties to the dictionary d here
    // ...

    var stringBuilder = new System.Text.StringBuilder();
    var tag = new TagBuilder("div"); tag.AddCssClass("some_css");
    stringBuilder.Append(toolbar.ToString(TagRenderMode.SelfClosing));
    stringBuilder.Append(htmlHelper.TextAreaFor(expression, result));

    return new MvcHtmlString(stringBuilder.ToString());
}
22
répondu Piotr Szmyd 2011-11-09 17:02:22

vous pourriez être en mesure de le faire avec la méthode standard MVC helper.

HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)

htmlAttributes est un objet

50
répondu Noam 2012-09-21 14:17:50

@pszmyd: vous pouvez utiliser le ViewDataDictionary qui prend déjà un objet dans l'exemple du constructeur.

//
// Summary:
//     Initializes a new instance of the System.Web.Mvc.ViewDataDictionary class
//     by using the specified model.
//
// Parameters:
//   model:
//     The model.
public ViewDataDictionary(object model);

aussi ce que vous essayez de faire est assez simple - fusionner des valeurs clés. Dans le cas des attributs html, ce n'est pas simple. ex. l'élément peut contenir plusieurs classes ex. "Blue dr ltr", séparé par des espaces, alors que l'attribut style utilise le point-virgule comme délimiteur ex. 'width: 200px; font-size:0.8 em;'. C'est vraiment à vous de les analyser et vérifiez que les valeurs sont correctes (ne pas fusionner les classes css au lieu de les séparer avec des espaces, même avec le style).

d'après votre paramètre: object htmlAttributes vous venez de créer: var htmlAttr = new ViewDataDictionary<TModel>(htmlAttributes);

crée alors des méthodes d'extension personnalisées pour fusionner les attributs ex.

public static class ViewDataDictionaryExtensions
{
    public static ViewDataDictionary<TModel> MergeClasses<TModel>(this ViewDataDictionary<TModel> dict, string classes)
    {
        if (dict.ContainsKey("class"))
            dict["class"] += " " + classes;
        else
            dict.Add("class", classes);
        return dict;
    }
    public static ViewDataDictionary<TModel> MergeStyles<TModel>(this ViewDataDictionary<TModel> dict, string styles)
    {
        if (dict.ContainsKey("style"))
            dict["style"] += "; " + styles;
        else
            dict.Add("style", styles);
        return dict;
    }
}

ce n'est qu'une implémentation très simple qui ne tient pas compte des valeurs d'attribut dupliquées ou des séparateurs multiples. Mais j'espère que vous obtenez l'idée!

2
répondu Schalk 2011-05-27 14:01:13

quand je dois faire cela, je crée habituellement une vue partielle Puis J'utilise RenderPartial:

@Html.Partial(MVC.Shared.MyPartialView_cshtml, new ViewDataDictionary() {  
       { "Content", model.Content},
       { "Css", "some_other_css"},
});

je crée habituellement aussi une classe Model pour éviter d'utiliser magic string ( et ViewDataDictionary) comme dans l'exemple ci-dessus. À votre avis, vous pouvez

  • utilisez le contenu: @ViewBag.Sommaire
  • tester et utiliser le css par défaut si un CSS personnalisé n'est pas spécifié (@ViewBag.Css)

A petite remarque: il est également possible de changer le modèle par défaut utilisé lors de L'appel de TextAreaFor (ou autre méthode similaire). Pour cela, jetez un oeil à http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html

0
répondu Fabske 2011-05-26 16:52:46