MVC 3.0 Редактирование списка переменной длины и использование шаблона PRG - PullRequest
4 голосов
/ 07 июня 2011

Я создал представление со списком переменной длины, как описано здесь: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/.

Я пытаюсь использовать шаблон PRG с фильтрами действий, как описано в пункте 13 здесь: http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx.

У меня есть действие Edit:

    [HttpGet, ImportModelStateFromTempData]
    public ActionResult Edit(int id)
    {
    }

И действие post:

    [HttpPost, ExportModelStateToTempData]
    public ActionResult Edit(int id, FormCollection formCollection)
    {
        if (!TryUpdateModel<CategoryEntity>(category, formCollection))
        {
            return RedirectToAction("Edit", new { id = id });
        }

        // succes, no problem processing this...
        return RedirectToAction("Edit", new { id = id });
    }

Все отлично работает, включая проверки и сообщения об ошибках.

Единственная проблема, которую яесть то, что вновь добавленные элементы и удаленные элементы (удаленные / добавленные на стороне клиента) не сохраняются после перенаправления.Я пытаюсь найти способ обновить мою модель после перенаправления новыми элементами.Я изменил атрибут ImportModelStateFromTempData, чтобы использовать переопределение OnActionExecuting вместо переопределения OnActionExecuted, чтобы сделать ModelState доступным в действии, но я не вижу чистого способа обновить мою модель из переданного в ModelState.

Изменено ImportModelStateFromTempData:

public class ImportModelStateFromTempData : ModelStateTempDataTransfer
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        if (modelState != null)
        {
            filterContext.Controller.ViewData.ModelState.Merge(modelState);
        }
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        //ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        //if (modelState != null)
        //{
        //    //Only Import if we are viewing
        //    if (filterContext.Result is ViewResult)
        //    {
        //        filterContext.Controller.ViewData.ModelState.Merge(modelState);
        //    }
        //    else
        //    {
        //        //Otherwise remove it.
        //        filterContext.Controller.TempData.Remove(Key);
        //    }
        //}
        base.OnActionExecuted(filterContext);
    }
}

Любой вклад в это очень ценится, спасибо.

Хармен

ОБНОВЛЕНИЕ: Я подумал, что мог бы добавить еще немного своего (псевдо) кода, чтобы сделать егоболее ясно:

public class CategoryEntity
{
    public int Id;
    public string Name;
    public IEnumerable<CategoryLocEntity> Localized;
}

public class CategoryLocEntity
{
    public int CategoryId;
    public int LanguageId;
    public string LanguageName;
    public string Name;
}

Мое представление правки:

@model CategoryEntity

@{
    ViewBag.Title = Views.Category.Edit;
}

<h2>@Views.Category.Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript"><!--

    $(document).ready(function () {
        $('#addItem').click(function () {
            var languageId = $('#languageId').val();
            var index = $('#editor-rows').children().size() - 1;
            $.ajax({
                url: this.href + '?languageId=' + languageId + '&index=' + index,
                cache: false,
                error: function (xhr, status, error) {
                    alert(error);
                },
                success: function (html) {
                    $('#editor-rows').append(html);
                }
            });
            return false;
        });

        $("a.removeItem").live("click", function () {
            $(this).parents("div.editor-row:first").remove();
            return false;
        });
    });

--></script>

@using (Html.BeginForm()) 
{
    @Html.ValidationSummary(false)
    <fieldset>
        <legend>@Views.Shared.Category</legend>
        @Html.HiddenFor(model => model.Id)
        <div id="editor-rows">
            <div class="editor-row">
                <div class="editor-label">
                    @Html.LabelFor(model => model.Name, Views.Shared.NameEnglish)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.Name)
                    @Html.ValidationMessageFor(model => model.Name)
                </div>
            </div>

            @for (int i = 0; i < Model.Localized.Count; i++)
            {
                @Html.EditorFor(m => m.Localized[i], "_CategoryLoc", null, null)
            }
        </div>

        <div class="editor-label"></div>
        <div class="editor-field">
            @Html.DropDownList("languageId", (IEnumerable<SelectListItem>)ViewBag.LanguageSelectList)
            @Html.ActionLink(Views.Category.AddNewLanguage, "AddNewLanguage", null, new { id = "addItem" })
        </div>

        <p class="clear">
            <input type="submit" value="@Views.Shared.Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink(Views.Shared.BackToList, "Index")
</div>

Шаблон редактора для CategoryLocEntity:

@model CategoryLocEntity

<div class="editor-row">
    @Html.HiddenFor(model => model.Id)
    @Html.HiddenFor(model => model.LanguageId)
    <div class="editor-label">
        @Html.LabelFor(model => model.LanguageName, Model.LanguageName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Name)
        <a href="#" class="removeItem">@Views.Shared.Remove</a>
        @Html.ValidationMessageFor(model => model.Name)
    </div>
</div>

1 Ответ

1 голос
/ 07 июня 2011

Я нашел решение (возможно, не самое элегантное, но оно работает для меня). Я создал свой собственный ModelStateValueProvider для использования с UpdateModel. Код основан на DictionaryValueProvider. Смотри http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System.Web/System/Web/Mvc/DictionaryValueProvider%601.cs.htm.

public class ModelStateValueProvider : IValueProvider
{
    HashSet<string> prefixes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
    ModelStateDictionary modelStateDictionary;

    public ModelStateValueProvider(ModelStateDictionary modelStateDictionary)
    {
        if (modelStateDictionary == null)
            throw new ArgumentNullException("modelStateDictionary");

        this.modelStateDictionary = modelStateDictionary;

        FindPrefixes();
    }

    private void FindPrefixes()
    {
        if (modelStateDictionary.Count > 0)
            prefixes.Add(string.Empty);

        foreach (var modelState in modelStateDictionary)
            prefixes.UnionWith(GetPrefixes(modelState.Key));
    }

    public bool ContainsPrefix(string prefix)
    {
        if (prefix == null)
        {
            throw new ArgumentNullException("prefix");
        }

        return prefixes.Contains(prefix);
    }

    public ValueProviderResult GetValue(string key)
    {
        if (key == null)
            throw new ArgumentNullException("key");

        return modelStateDictionary.ContainsKey(key) ? modelStateDictionary[key].Value : null;
    }

    static IEnumerable<string> GetPrefixes(string key)
    {
        yield return key;
        for (int i = key.Length - 1; i >= 0; i--)
        {
            switch (key[i])
            {
                case '.':
                case '[':
                    yield return key.Substring(0, i);
                    break;
            }
        }
    }
}

public class ModelStateValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        return new ModelStateValueProvider(controllerContext.Controller.ViewData.ModelState);
    }
}

Я использую его в действии de Edit (Get), например:

[HttpGet, ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
  var category = new CategoryEntity(id);
  if (!ModelState.IsValid)
  {    
     TryUpdateModel<CategoryEntity>(category, 
         new ModelStateValueProviderFactory().GetValueProvider(ControllerContext));
  }
  return View(category);
}

Ждем ваших комментариев ...

...