ASP.NET MVC xem từng phần: tiền tố tên đầu vào


120

Giả sử tôi có ViewModel như

public class AnotherViewModel
{
   public string Name { get; set; }
}
public class MyViewModel
{
   public string Name { get; set; }
   public AnotherViewModel Child { get; set; }
   public AnotherViewModel Child2 { get; set; }
}

Trong chế độ xem, tôi có thể kết xuất một phần với

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

Một phần tôi sẽ làm

<%= Html.TextBox("Name", Model.Name) %>
or
<%= Html.TextBoxFor(x => x.Name) %>

Tuy nhiên, vấn đề là cả hai sẽ hiển thị name = "Name" trong khi tôi cần có name = "Child.Name" để chất kết dính mô hình hoạt động bình thường. Hoặc, name = "Child2.Name" khi tôi hiển thị thuộc tính thứ hai bằng cách sử dụng cùng một chế độ xem một phần.

Làm cách nào để làm cho chế độ xem từng phần của tôi tự động nhận ra tiền tố bắt buộc? Tôi có thể chuyển nó như một tham số nhưng điều này quá bất tiện. Điều này thậm chí còn tệ hơn khi tôi muốn kết xuất nó một cách đệ quy chẳng hạn. Có cách nào để hiển thị các dạng xem từng phần với một tiền tố, hoặc thậm chí tốt hơn, với tính năng tự động nhận lại biểu thức lambda đang gọi để

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

sẽ tự động thêm đúng "Con". tiền tố cho chuỗi tên / id đã tạo?

Tôi có thể chấp nhận bất kỳ giải pháp nào, bao gồm các công cụ và thư viện chế độ xem bên thứ 3 - Tôi thực sự sử dụng Spark View Engine (tôi "giải quyết" vấn đề bằng cách sử dụng macro của nó) và MvcContrib, nhưng không tìm thấy giải pháp ở đó. XForms, InputBuilder, MVC v2 - bất kỳ công cụ / thông tin chi tiết nào cung cấp chức năng này sẽ rất tuyệt vời.

Hiện tại tôi đang nghĩ về việc tự viết mã cái này nhưng có vẻ như lãng phí thời gian, tôi không thể tin rằng thứ tầm thường này lại không được triển khai.

Rất nhiều giải pháp thủ công có thể tồn tại và tất cả chúng đều được hoan nghênh. Ví dụ: tôi có thể buộc các thành phần của mình dựa trên Tiền tố IPartialViewModel <T> {public string; T Mô hình; }. Nhưng tôi thích một số giải pháp hiện có / đã được phê duyệt.

CẬP NHẬT: có một câu hỏi tương tự không có câu trả lời ở đây .

Câu trả lời:


110

Bạn có thể mở rộng lớp trợ giúp Html bằng cách này:

using System.Web.Mvc.Html


 public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
        string name = ExpressionHelper.GetExpressionText(expression);
        object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
        var viewData = new ViewDataDictionary(helper.ViewData)
        {
            TemplateInfo = new System.Web.Mvc.TemplateInfo
            {
                HtmlFieldPrefix = name
            }
        };

        return helper.Partial(partialViewName, model, viewData);

    }

và chỉ cần sử dụng nó trong quan điểm của bạn như thế này:

<%= Html.PartialFor(model => model.Child, "_AnotherViewModelControl") %>

và bạn sẽ thấy mọi thứ đều ổn!


17
Điều này sẽ không chính xác đối với kết xuất từng phần lồng nhau. Bạn cần phải thêm tiền tố mới để tiền tố cũ từ helper.ViewData.TemplateInfo.HtmlFieldPrefixtheo hình thức{oldprefix}.{newprefix}
Ivan Zlatev

@Mahmoud Mã của bạn hoạt động tốt, nhưng tôi nhận thấy rằng ViewData / ViewBag trống khi đến lúc thực thi mã trong Phần. Tôi thấy rằng trình trợ giúp, kiểu HtmlHelper <TModel> có một thuộc tính ViewData mới, ẩn mô hình cơ sở. Với điều đó, tôi thay thế new ViewDataDictionary(helper.ViewData)bằng new ViewDataDictionary(((HtmlHelper)helper).ViewData). Bạn có thấy bất kỳ vấn đề với điều đó?
Pat Newell

@IvanZlatev Cảm ơn. Tôi sẽ sửa bài sau khi kiểm tra nó.
Mahmoud Moravej

2
@IvanZlatev là đúng. Trước khi bạn đặt tên, bạn nên làmstring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc

2
Một bản sửa lỗi dễ dàng cho các mẫu lồng nhau là sử dụng HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (tên)
Aman Mahajan

95

cho đến nay, tôi đã tìm kiếm cùng một thứ mà tôi đã tìm thấy bài đăng gần đây này:

http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, new ViewDataDictionary
{
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Child1" }
})
%>

3
Cảm ơn vì liên kết, đây là lựa chọn tốt nhất được liệt kê ở đây
BZ

32
Siêu muộn để bên này, nhưng nếu bạn làm phương pháp này, bạn nên sử dụng ViewDataDictionaryconstructor mà mất hiện tại ViewData, hoặc bạn sẽ mất lỗi mô hình nhà nước, dữ liệu xác nhận vv
bhamlin

9
dựa trên bình luận của bhamlin. bạn cũng có thể chuyển tiền tố lồng nhau như (thử đây là vb.net ví dụ): Html.RenderPartial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) With {.TemplateInfo = New TemplateInfo () With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
hubson bropa

1
Câu trả lời tuyệt vời, +1. Có lẽ nó sẽ có giá trị hiệu chỉnh nó để có @bhamlin bình luận vào tài khoản
ken2k

1
Lưu ý rằng nếu bạn cần trả lại phần này từ bộ điều khiển, bạn cũng cần đặt tiền tố này ở đó. stackoverflow.com/questions/6617768/…
The Muffin Man,

12

Câu trả lời của tôi, dựa trên câu trả lời của Mahmoud Moravej bao gồm cả bình luận của Ivan Zlatev.

    public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
            string name = ExpressionHelper.GetExpressionText(expression);
            object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
            StringBuilder htmlFieldPrefix = new StringBuilder();
            if (helper.ViewData.TemplateInfo.HtmlFieldPrefix != "")
            {
                htmlFieldPrefix.Append(helper.ViewData.TemplateInfo.HtmlFieldPrefix);
                htmlFieldPrefix.Append(name == "" ? "" : "." + name);
            }
            else
                htmlFieldPrefix.Append(name);

            var viewData = new ViewDataDictionary(helper.ViewData)
            {
                TemplateInfo = new System.Web.Mvc.TemplateInfo
                {
                    HtmlFieldPrefix = htmlFieldPrefix.ToString()
                }
            };

        return helper.Partial(partialViewName, model, viewData);
    }

Chỉnh sửa: Câu trả lời của Mohamoud không chính xác đối với kết xuất từng phần lồng nhau. Bạn cần nối tiền tố mới vào tiền tố cũ, chỉ khi cần thiết. Điều này không rõ ràng trong các câu trả lời mới nhất (:


6
Sẽ rất hữu ích cho những người khác nếu bạn giải thích rõ hơn về cách cải thiện ở trên dựa trên câu trả lời hiện có.
Leigh

2
Sau một lỗi sao chép và dán, lỗi này hoạt động hoàn hảo cho ASP.NET MVC 5. +1.
Greg Burghardt

9

Sử dụng MVC2 bạn có thể đạt được điều này.

Đây là chế độ xem được đánh máy mạnh:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcLearner.Models.Person>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Create</h2>

    <% using (Html.BeginForm()) { %>
        <%= Html.LabelFor(person => person.Name) %><br />
        <%= Html.EditorFor(person => person.Name) %><br />
        <%= Html.LabelFor(person => person.Age) %><br />
        <%= Html.EditorFor(person => person.Age) %><br />
        <% foreach (String FavoriteFoods in Model.FavoriteFoods) { %>
            <%= Html.LabelFor(food => FavoriteFoods) %><br />
            <%= Html.EditorFor(food => FavoriteFoods)%><br />
        <% } %>
        <%= Html.EditorFor(person => person.Birthday, "TwoPart") %>
        <input type="submit" value="Submit" />
    <% } %>

</asp:Content>

Đây là dạng xem được nhập mạnh cho lớp con (phải được lưu trữ trong một thư mục con của thư mục dạng xem được gọi là EditorTemplates):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcLearner.Models.TwoPart>" %>

<%= Html.LabelFor(birthday => birthday.Day) %><br />
<%= Html.EditorFor(birthday => birthday.Day) %><br />

<%= Html.LabelFor(birthday => birthday.Month) %><br />
<%= Html.EditorFor(birthday => birthday.Month) %><br />

Đây là bộ điều khiển:

public class PersonController : Controller
{
    //
    // GET: /Person/
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Index()
    {
        return View();
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        Person person = new Person();
        person.FavoriteFoods.Add("Sushi");
        return View(person);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Person person)
    {
        return View(person);
    }
}

Đây là các lớp tùy chỉnh:

public class Person
{
    public String Name { get; set; }
    public Int32 Age { get; set; }
    public List<String> FavoriteFoods { get; set; }
    public TwoPart Birthday { get; set; }

    public Person()
    {
        this.FavoriteFoods = new List<String>();
        this.Birthday = new TwoPart();
    }
}

public class TwoPart
{
    public Int32 Day { get; set; }
    public Int32 Month { get; set; }
}

Và nguồn đầu ra:

<form action="/Person/Create" method="post"><label for="Name">Name</label><br /> 
    <input class="text-box single-line" id="Name" name="Name" type="text" value="" /><br /> 
    <label for="Age">Age</label><br /> 
    <input class="text-box single-line" id="Age" name="Age" type="text" value="0" /><br /> 
    <label for="FavoriteFoods">FavoriteFoods</label><br /> 
    <input class="text-box single-line" id="FavoriteFoods" name="FavoriteFoods" type="text" value="Sushi" /><br /> 
    <label for="Birthday_Day">Day</label><br /> 
    <input class="text-box single-line" id="Birthday_Day" name="Birthday.Day" type="text" value="0" /><br /> 

    <label for="Birthday_Month">Month</label><br /> 
    <input class="text-box single-line" id="Birthday_Month" name="Birthday.Month" type="text" value="0" /><br /> 
    <input type="submit" value="Submit" /> 
</form>

Bây giờ điều này đã hoàn thành. Đặt điểm ngắt trong hành động Tạo bài điều khiển để xác minh. Tuy nhiên, không sử dụng điều này với danh sách vì nó sẽ không hoạt động. Xem câu hỏi của tôi về cách sử dụng EditorTemplates với IEnumerable để biết thêm về điều đó.


Vâng, nó trông giống như nó. Vấn đề là danh sách không hoạt động (trong khi các mô hình xem lồng nhau thường nằm trong danh sách) và đó là v2 ... mà tôi chưa sẵn sàng sử dụng trong sản xuất. Nhưng vẫn tốt khi biết rằng nó sẽ là thứ mà tôi cần ... khi nó đến (nên +1).
queen3 28/09/09

Tôi cũng đã xem qua điều này một ngày, matthidinger.com/archive/2009/08/15/… , bạn có thể muốn xem liệu bạn có thể tìm ra cách mở rộng nó cho các trình chỉnh sửa (mặc dù vẫn ở trong MVC2). Tôi đã dành một vài phút cho nó, nhưng vẫn tiếp tục gặp sự cố vì tôi chưa đủ với các biểu thức. Có lẽ bạn có thể làm tốt hơn tôi.
Nick Larsen

1
Một liên kết hữu ích, cảm ơn. Không phải tôi nghĩ rằng có thể làm cho EditorFor hoạt động ở đây, bởi vì nó sẽ không tạo ra [0] chỉ mục mà tôi cho là (tôi cá là nó chưa hỗ trợ nó chút nào). Một giải pháp sẽ là kết xuất đầu ra EditorFor () thành chuỗi và tinh chỉnh đầu ra theo cách thủ công (thêm các tiền tố bắt buộc). Tuy nhiên, một vụ hack bẩn thỉu. Hừm! Tôi có thể thực hiện phương thức mở rộng cho Html.Helper (). UsePrefix () sẽ chỉ thay thế name = "x" bằng name = "prefix.x" ... trong MVC v1. Vẫn còn một chút công việc nhưng không nhiều. Và Html.WithPrefix ("tiền tố"). RenderPartial () hoạt động theo cặp.
queen3 28/09/09

+1 để đề xuất Html.EditorForphương pháp hiển thị biểu mẫu con. Đây chính xác là những gì các mẫu biên tập viên được cho là được sử dụng. Đây nên là câu trả lời.
Greg Burghardt

9

Đây là một câu hỏi cũ, nhưng đối với bất kỳ ai đến đây tìm kiếm giải pháp, hãy cân nhắc sử dụng EditorFor, như được đề xuất trong nhận xét tại https://stackoverflow.com/a/29809907/456456 . Để chuyển từ dạng xem một phần sang mẫu trình chỉnh sửa, hãy làm theo các bước sau.

  1. Xác minh rằng chế độ xem một phần của bạn bị ràng buộc với ComplexType .

  2. Di chuyển chế độ xem một phần của bạn sang một thư mục con Trình soạn thảo Các mẫu của thư mục chế độ xem hiện tại hoặc đến thư mục Được chia sẻ . Bây giờ, nó là một mẫu biên tập viên.

  3. Thay đổi @Html.Partial("_PartialViewName", Model.ComplexType)thành @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Mẫu trình soạn thảo là tùy chọn nếu đó là mẫu duy nhất cho loại phức tạp.

Các phần tử đầu vào Html sẽ tự động được đặt tên ComplexType.Fieldname.


8

PartailFor cho asp.net Core 2 trong trường hợp ai đó cần.

    public static ModelExplorer GetModelExplorer<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression)
    {
        if (expression == null)
            throw new ArgumentNullException(nameof(expression));
        return ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
    }

    public static IHtmlContent PartialFor<TModel, TResult>(this IHtmlHelper<TModel> helper, Expression<Func<TModel, TResult>> expression, string partialViewName, string prefix = "")
    {
        var modelExplorer = helper.GetModelExplorer(expression);
        var viewData = new ViewDataDictionary(helper.ViewData);
        viewData.TemplateInfo.HtmlFieldPrefix += prefix;
        return helper.Partial(partialViewName, modelExplorer.Model, viewData);
    }

3

Tôi cũng gặp phải vấn đề này và sau nhiều lần đau đớn, tôi thấy việc thiết kế lại các giao diện của mình dễ dàng hơn để tôi không cần phải đăng lại các đối tượng mô hình lồng nhau. Điều này buộc tôi phải thay đổi quy trình làm việc giao diện của mình: chắc chắn bây giờ tôi yêu cầu người dùng thực hiện theo hai bước những gì tôi mơ ước làm trên một bước, nhưng khả năng sử dụng và khả năng bảo trì mã của phương pháp tiếp cận mới có giá trị lớn hơn đối với tôi bây giờ.

Hy vọng điều này sẽ giúp một số.


1

Bạn có thể thêm một trình trợ giúp cho RenderPartial lấy tiền tố và đưa nó vào ViewData.

    public static void RenderPartial(this HtmlHelper helper,string partialViewName, object model, string prefix)
    {
        helper.ViewData["__prefix"] = prefix;
        helper.RenderPartial(partialViewName, model);
    }

Sau đó, một trình trợ giúp khác nối giá trị ViewData

    public static void GetName(this HtmlHelper helper, string name)
    {
        return string.Concat(helper.ViewData["__prefix"], name);
    }

và như vậy trong quan điểm ...

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, "Child.") %>

trong một phần ...

<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>

Vâng, đó là những gì tôi đã mô tả trong nhận xét cho stackoverflow.com/questions/1488890/… . Một giải pháp tốt hơn nữa sẽ là RenderPartial cũng sử dụng lambda, rất dễ thực hiện. Tuy nhiên, cảm ơn vì mã, tôi đoán đó là cách tiếp cận không đau và lâu dài nhất.
queen3 29/09/09

1
tại sao không sử dụng tiền tố helper.ViewData.TemplateInfo.HtmlFieldPrefix =? Tôi đang sử dụng nó trong trình trợ giúp của mình và có vẻ như nó đang hoạt động tốt.
ajbeaven

0

Giống như bạn, tôi thêm thuộc tính Tiền tố (một chuỗi) vào ViewModels của tôi mà tôi thêm vào trước tên đầu vào liên kết mô hình của tôi. (YAGNI ngăn cản những điều bên dưới)

Một giải pháp thanh lịch hơn có thể là mô hình dạng xem cơ sở có thuộc tính này và một số HtmlHelpers để kiểm tra xem mô hình dạng xem có xuất phát từ cơ sở này không và nếu có thì nối tiền tố vào tên đầu vào.

Hy vọng rằng sẽ giúp,

Dan


1
Hừ, quá tệ. Tôi có thể tưởng tượng ra nhiều giải pháp thanh lịch, với việc kiểm tra thuộc tính HtmlHelpers tùy chỉnh, thuộc tính, kết xuất tùy chỉnh, v.v. Nhưng ví dụ: nếu tôi sử dụng MvcContrib FluentHtml, tôi có viết lại tất cả chúng để hỗ trợ các bản hack của mình không? Thật kỳ lạ rằng cuộc đàm phán không ai về nó, như thể tất cả mọi người chỉ sử dụng ViewModels single-level phẳng ...
queen3

Thật vậy, sau khi sử dụng framework trong bất kỳ khoảng thời gian nào và cố gắng đạt được mã chế độ xem sạch đẹp, tôi nghĩ ViewModel theo tầng là không thể tránh khỏi. Ví dụ: Mô hình Xem Giỏ hàng trong Mô hình Xem Đơn đặt hàng.
Daniel Elliott

0

Còn trước khi bạn gọi RenderPartial thì sao

<% ViewData["Prefix"] = "Child."; %>
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

Sau đó, một phần của bạn, bạn có

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>

1
Điều này về cơ bản giống với việc chuyển nó theo cách thủ công (loại an toàn để lấy tất cả các mô hình chế độ xem từ IViewModel với "IViewModel SetPrefix (string)") và rất xấu, trừ khi tôi viết lại tất cả các trình trợ giúp Html và RenderPartial để chúng tự động quản lý điều này. Vấn đề không phải là làm thế nào để làm điều đó, tôi đã giải quyết điều này rồi; vấn đề là, nó có thể được thực hiện tự động.
queen3 28/09/09

vì không có nơi nào chung mà bạn có thể đặt mã sẽ ảnh hưởng đến tất cả các trình trợ giúp html, bạn sẽ không thể thực hiện việc này một cách tự động. xem câu trả lời khác ...
Anthony Johnston
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.