Translate knockout into razor to keep it's validation working

元气小坏坏 提交于 2019-12-12 03:55:33

问题


Here is the problem that I am facing and not sure how to even approach it:

I created models, controllers and views in ASP.NET MVC 4. At one point I had to create dynamic lists, so I opted out to KnockoutJS, what solves this problem extremely easy. So far so good. Then I realized that the validation I defined on my MVC models using I use Fluent Validation doesn't work anymore in the knockout view.

I searched through SO and found few viable solutions:

  • knockout validation
  • jquery validation
  • potentially express Knockout syntax in terms of razor

I tend to use the latter one, for several reasons. Mainly because it gives me opportunity not to introduce (learn, test, localize, spend time) another library.

I am quite familiar with MVC and love the way it supports localization giving full control on messages, labels etc. I also love Fluent Validation and don't want to replace it with others (more static, much harder to localize, much less flexible to my liking)

I found some examples on knockout to razor conversion, when data-bind has to become data_bind etc.

I cannot find a way to express the foreach loop with and in.

MVC view model

  public class ContactEmail
  {
    public string SelectedLabel { get; set; }
    public string Name { get; set; }
  }

 public class User
 {
   public IList<ContactEmail> Emails { get; set; }
 }


ViewBag.EmailLabels = new string[] { "label1", "label2", ... };

knockout model

  var viewModel = {
    EmailLabels: ko.observableArray(@Html.Json(ViewBag.EmailLabels as string[]) || []),
    Emails: ko.observableArray(@Html.Json(@Model.Emails) || []),
  } 

knockout view (that I wanted to transform)

    <table>
    <tbody data-bind="foreach: Emails">
      <tr>
        <td>
        @* How to make razor below work instead of knockout syntax below it? *@
        @*Html.DropDownListFor(m => ????, new { data_bind="options: $root.EmailLabels, value: SelectedLabel, optionsCaption: 'Choose...'" } )
          <select data-bind="options: $root.EmailLabels, value: SelectedLabel, optionsCaption: 'Choose...'"></select></td>
          <td>
            @* How to make razor below work as well instead of knockout syntax below ?!?!? *@
            @Html.TextBoxFor(m => ????, new { data_bind="value: Name, uniqueName: true" } )
              <input type="text" data-bind="value: Name, uniqueName: true" class="required email" />
          </td>
          <td>
              <a href="#" data-bind="click: function() { viewModel.removeEmail(this); }">Delete</a>
          </td>
      </tr>
    </tbody>
    </table>

I looked at MVC Controls toolkit what one guy mercilessly advertised will solve all my validation and localization and everything at all. I found it unusable, very proprietary and extremely hard to understand. Its like buying nuke to kill a bird.

So please those of you who had experience with marrying MVC with knockout, please step up and share your experience.

Any help will be greatly appreciated & thank you very much in advance.


回答1:


I think this is a bit of a hack, but it works.

The controller will return a collection of emails inside of the User.Emails property with the list that need to be rendered. What the Razor View produces is HTML of a table with just one row and the validation based off the first element of the Emails IEnumerable (this must be checked for null or may cause an exception).

When ko.applyBindings() occur on the client side, then the foreach on the tbody tag will produce all rows, and since the ko ViewModel as initialized with the whole collection as a mapped JsonString, then the whole list will render. The methods removeEmail and addEmail will work as well (I just tested the removeEmail option, it works =D)

@using Newtonsoft.Json
@model Stackoverflow5.Models.User

    <table>
        @{var tempdropdownlist = new List<SelectListItem>();}

        <tbody data-bind="foreach: Emails">
            <tr>
                <td>
                    @Html.DropDownListFor(m => m.Emails.First().SelectedLabel, tempdropdownlist,
                                      new { data_bind="options: $root.EmailLabels, value: SelectedLabel, optionsCaption: 'Choose...'" })

                <td>
                    @Html.TextBoxFor(m => m.Emails.First().Name, new { data_bind="value: Name, uniqueName: true" } )
                </td>
                <td>
                    <a href="#" data-bind="click: function () { viewModel.removeEmail(this); }">Delete</a>
                </td>
            </tr>
        </tbody>
    </table>


@section scripts
{
    <script src="~/Scripts/knockout-2.2.1.js"></script>
    <script src="~/Scripts/knockout.mapping-latest.js"></script>

    <script>
        //Model definition
        var viewModel,
            ModelDefinition = function (data) {
            //Object definition
            var self = this;

            //Mapping from ajax request
            ko.mapping.fromJS(data, {}, self);

            self.removeEmail = function(row) {
                self.Emails.remove(row);
            };

            self.addEmail = function() {
                //Method for adding new rows here
            };
        };

        $(function() {
            viewModel = new ModelDefinition(@Html.Raw(JsonConvert.SerializeObject(Model)));
            ko.applyBindings(viewModel);
        });
    </script>
}



回答2:


Edit: Update to include definitive Knockout Bindings

index.cshtml

@model Stackoverflow5.Models.User

<form>
    <table>
        <tbody>

            @{
                var tempdropdownlist = new List<SelectListItem>();
            }
            @for (var i = 0; i < @Model.Emails.Count; i++)
            {
                <tr>
                    <td>
                        @Html.DropDownListFor(m => m.Emails[i], tempdropdownlist,
                            new { data_bind = String.Format("options: $root.EmailLabels, value: Emails()[{0}].SelectedLabel, optionsCaption: 'Choose...'", i)})
                    </td>
                    <td>
                        @Html.TextBoxFor(m => m.Emails[i].Name, 
                            new { data_bind = String.Format("value: Emails()[{0}].Name(), uniqueName: true", i) })
                    </td>
                </tr>
            }

        </tbody>
    </table>

    <button type="submit">Test</button>
</form>

** Models (with validation working) **

public class ContactEmail
    {
        public string SelectedLabel { get; set; }

        [Required]
        [StringLength(20, MinimumLength = 2)]
        public string Name { get; set; }

    }

    public class User
    {
        public User()
        {
            Emails = new List<ContactEmail>();
            EmailLabels = new List<string> {"Important", "Spam", "Family"};
        }

        public List<ContactEmail> Emails { get; set; }
        public List<string> EmailLabels { get; set; }
    }


来源:https://stackoverflow.com/questions/15317871/translate-knockout-into-razor-to-keep-its-validation-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!