Всем, кто программирует в среде ASP.NET MVC, хорошо известно, насколько широко используются метаданные в .NET вообще и в MVC в частности. В MVC, атрибуты применяются как при генерации разметки, так и при валидации данных, полученных с клиента.
При использовании классической модели программирования сайтов это прекрасно работает. Но что, если Вы работаете с использование ajax и формируете html разметку динамически на клиенте? Вы хотите иметь метаданные модели (далее МДМ) на клиенте? Я — да!
Прямой путь — это сформировать json, включив туда и данные, и МДМ. Мы всегда можем написать в контроллере что-то вроде:
public ActionResult GetData()
{
return Json(new {
data = new { данные },
meta = new { мета }
});
}
Это просто, но не удобно при работе с моделью в браузере, да и выглядит как-то неприглядно.
Мне пришло в голову, что передавать МДМ следует так же, как это делает сервер или браузер, а именно в заголовках http. Давайте попробуем сделать следующее:
- Сформируем объект мета.
- Cериализуем его в строку json.
- Сделаем енкодинг или конвертацию строки в base64 (это необходимо, так как заголовок http передается в ASCII).
- Cоздадим заголовок http и именем “meta-data”.
Далее я написал простой код, который реализует эту идею.
Для начала определим модель и поставим атрибуты на свойства.
public class Data
{
[ReadOnly(true)]
[DisplayName("Номер")]
public int Id { get; set; }
[DisplayName("Название")]
public string Name { get; set; }
}
Напишем простой метод действия, который будет возвращать клиенту данные и МДМ.
public ActionResult GetData()
{
var data = new Data { Id = 1, Name = "Test" };
var meta = ModelMetadataProviders.Current.GetMetadataForType(() => data, typeof(Data));
var metaForJS = meta.Properties.ToDictionary(
p => p.PropertyName,
p => new { displayName = p.GetDisplayName(), readOnly = p.IsReadOnly });
var jsonMeta = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(metaForJS);
// Uri.EscapeDataString - использовано для простоты кода. В реальном приложении используйте base64.
Response.Headers.Add("data-meta", Uri.EscapeDataString(jsonMeta));
return Json(data, JsonRequestBehavior.AllowGet);
}
Наберем в адресной строке браузера localhost:67578/Hab/GetData и посмотрим на заголовок http.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
data-meta:5B%7B%22displayName%22%3A%22%D0%9D%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD
%D0%B8%D0%B5%22%2C%22readOnly%22%3Afalse%7D%2C%7B%22displayName%22%3A%22%D0%9D
%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%22%2C%22readOnly%22%3Atrue%7D%5D
X-AspNet-Version: 4.0.30319
...
Убедимся, что все работает как надо, и перейдем к клиентской части кода.
function createField(name, value, meta) {
// Создаем поле с учетом метаданных
return $('<div/>').append(
$("<label/>").attr('for', name).text(meta[name].displayName),
$("<input type='text'/>").attr("name", name).attr("readonly", meta[name].readOnly).val(value)
);
}
$(function () {
$.getJSON("/Hab/GetData")
.done(function (data, s, xhr) {
// получаем, декодируем и создаем js объект meta
var meta = $.parseJSON(decodeURIComponent(xhr.getResponseHeader("data-meta")));
// создаем и выводим поля на экран
for (var p in data)
$('body').append(createField(p, data[p], meta));
})
.error(function(d,s){alert(s);});
});
В результате мы получаем два поля ввода с учетом метаданных модели на сервере.
Особенно элегантно данный подход выглядит при использовании на клиенте backbone. Мы можем получить МДМ, переопределив метод parse:
var model= Backbone.Model.extend({
....
parse: function(data, xhr){
this.meta = $.parseJSON(decodeURIComponent(httpRequest.getResponseHeader("data-meta")));
return data;
},
......
});
получив МДМ в model.meta в виде объекта js, можем использовать МДМ во view для рендеринга модели
var view = Backbone.View.extend({
render: functoin(){
var m = this.model.toJSON();
this.$el.html(this.template(_.extend(m, { meta: this.model})));
}
});
используя для tempalte что то вроде:
<script >
...
<label><%-meta.name.displayName%></label>
<input type='text' value="<%name%>" <%-meta.readOnly ?"readonly":"" %> />"
...
</script>
В заключение добавлю, что описанный подход можно легко применить для валидации модели на клиенте.
Автор: Che603000