ASP.NET MVC Helpers, объединяя два объекта htmlAttributes вместе
У меня есть ситуация, когда мне нужно написать HTML-помощник для expand другого html-помощника. Обычно помощник будет выглядеть так.
@Html.TextAreaFor(model => model.Content, new { @class = "some css", @data_bind = "some other stuff..." })
Это отлично работает, но его нужно обернуть в какой-то другой HTML, который всегда один и тот же. Я хотел инкапсулировать его для удобства, например.
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());
}
Линия stringBuilder.Append(htmlHelper.TextAreaFor...
- это то, что я хочу изменить. Класс CSS, который должен пройти там, будет всегда. Поэтому я бы предпочел включить его здесь. Однако я хотел бы указать дополнительные классы CSS в помощнике верхнего уровня. Итак...
@Html.CondensedHelperFor(model => model.Content, new { @class = "some_other_css" })
И статический css, который всегда будет там, будет скрыт через Помощника.
Любые идеи?
Ответы
Ответ 1
Сначала создайте метод (лучше всего создать метод расширения), который преобразует объект в IDictionary через отражение типа:
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;
}
Теперь используйте С# 4.0 ExpandoObject, который позволяет добавлять свойства во время выполнения.
Вы получили бы что-то вроде этого:
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());
}
Ответ 2
Возможно, вы сможете сделать это со стандартным вспомогательным методом MVC.
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)
htmlAttributes
является объектом
Ответ 3
@pszmyd: вы можете использовать ViewDataDictionary, который уже принимает объект в конструкторе ex.
//
// 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);
То, что вы пытаетесь сделать, достаточно просто - слияние ключевых значений. В случае атрибутов html это не прямо. ех. элемент может содержать несколько классов ex. "blue dr ltr", разделенный пробелами, в то время как атрибут стиля использует в качестве разделителя ex-двоеточие. "Ширина: 200px; размер шрифта: 0.8em;". Вам действительно нужно разбираться и проверять правильность значений (не объединять классы css, а не разделять их пробелами, то же самое со стилем).
Я предлагаю по вашему параметру: object htmlAttributes
вы просто создаете: var htmlAttr = new ViewDataDictionary<TModel>(htmlAttributes);
затем создайте собственные методы расширения, чтобы объединить атрибуты 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;
}
}
Это только очень простая реализация, не учитывающая повторяющиеся значения атрибутов или несколько разделителей. Но надеюсь, что вы получите эту идею!
Ответ 4
Когда я должен это сделать, я обычно создаю частичный вид
Затем я использую RenderPartial:
@Html.Partial(MVC.Shared.MyPartialView_cshtml, new ViewDataDictionary() {
{ "Content", model.Content},
{ "Css", "some_other_css"},
});
Я обычно также создаю класс Model, чтобы избежать использования магической строки (и ViewDataDictionary), как в примере выше.
По вашему мнению, вы можете
- используйте Content: @ViewBag.Content
- проверьте и используйте по умолчанию css, если пользовательский не указан (@ViewBag.Css)
Небольшое примечание: также можно изменить шаблон по умолчанию, используемый при вызове TextAreaFor (или другого аналогичного метода). Для этого взгляните на http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html