Получение мета данных .NET на клиенте с использованием ajax

в 13:45, , рубрики: .net, ASP, asp.net mvc, axaj, backbone, javascript, метки: , , , ,

Всем кто программирует ASP.NET MVC, хорошо известно насколько широко используются мета данные в .NET вообще, и в MVC в частности. В MVC, атрибуты применяются, как при генерации разметки, так и при валидации данных полученных с клиента.
При использовании классической модели программирования сайтов это прекрасно работает. Но что если Вы работаете с использование ajax, и формируете html разметку динамически на клиенте? Вы хотите иметь мето данные модели (далее МДМ) на клиенте? Я да!

Прямой путь это сформировать json включив туда и данные и МДМ. Мы всегда можем написать в котроллере, что то вроде:

 public ActionResult GetData()
 {
   return Json(new { 
       data = new { данные }, 
       meta = new { мета }  
    });
 }

Это просто но, не удобно при работе с моделью в браузере, да и выглядит как то неприглядно.
Мне пришло в голову, это передавать МДМ следует так же, как это делает сервер или браузер, а именно в заголовках http. Давайте попробуем сделать следующее:

  1. Сформируем объект мета.
  2. Cериализуем его в строку json.
  3. Сделаем енкодинг или конвертацию строки в base64 (это необходимо, так как заголовок http передается в ASCII).
  4. 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js