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

    Всем, кто программирует в среде 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(xhr.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>
    

    В заключение добавлю, что описанный подход можно легко применить для валидации модели на клиенте.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      0
      Это получается при каждом запросе будет создаваться отражение объекта среды CLR в JSON представление?
      Мне кажется, что, поскольку, аттрибуты классов и его членов практически не меняются, следовательно, их можно очень круто кешировать
        +1
        Можно и нужно. Это только демонстрация подхода.

      Only users with full accounts can post comments. Log in, please.