Làm thế nào tôi có thể kiểm soát nhiều hơn trong ASP.NET?


124

Tôi đang cố gắng xây dựng một "ứng dụng vi mô" rất đơn giản mà tôi nghi ngờ sẽ được một số Stack Overflow'rs quan tâm nếu tôi hoàn thành nó. Tôi đang lưu trữ nó trên trang C # của tôi trong trang web Depth, đó là vanilla ASP.NET 3.5 (tức là không phải MVC).

Dòng chảy rất đơn giản:

  • Nếu người dùng vào ứng dụng bằng một URL không chỉ định tất cả các tham số (hoặc nếu bất kỳ tham số nào không hợp lệ) tôi muốn chỉ hiển thị các điều khiển nhập của người dùng. (Chỉ có hai.)
  • Nếu người dùng nhập các ứng dụng với một URL mà không có tất cả các thông số cần thiết, tôi muốn hiển thị các kết quả các điều khiển đầu vào (để họ có thể thay đổi các thông số)

Dưới đây là các yêu cầu tự áp đặt của tôi (hỗn hợp thiết kế và thực hiện):

  • Tôi muốn bài đăng sử dụng GET thay vì POST, chủ yếu để người dùng có thể đánh dấu trang dễ dàng.
  • Tôi không muốn URL cuối cùng trông thật ngớ ngẩn sau khi gửi, với các bit và phần không liên quan trên đó. Chỉ cần URL chính và các thông số thực sự xin vui lòng.
  • Lý tưởng nhất là tôi muốn tránh yêu cầu JavaScript. Không có lý do chính đáng cho nó trong ứng dụng này.
  • Tôi muốn có thể truy cập các điều khiển trong thời gian kết xuất và đặt giá trị, v.v. Đặc biệt, tôi muốn có thể đặt các giá trị mặc định của các điều khiển thành các giá trị tham số được truyền vào, nếu ASP.NET không thể tự động làm điều này đối với tôi (trong các hạn chế khác).
  • Tôi rất vui khi tự mình thực hiện tất cả các xác thực tham số và tôi không cần nhiều đến các sự kiện phía máy chủ. Thật đơn giản để đặt mọi thứ khi tải trang thay vì gắn các sự kiện vào các nút, v.v.

Hầu hết điều này đều ổn, nhưng tôi chưa tìm thấy cách nào để loại bỏ hoàn toàn khung nhìn và giữ phần còn lại của chức năng hữu ích. Sử dụng bài đăng từ bài đăng trên blog này, tôi đã quản lý để tránh nhận được bất kỳ giá trị thực tế nào cho chế độ xem - nhưng nó vẫn kết thúc như một tham số trên URL, trông thực sự xấu xí.

Nếu tôi biến nó thành một dạng HTML đơn giản thay vì một dạng ASP.NET (nghĩa là lấy ra runat="server") thì tôi không nhận được bất kỳ khung nhìn ma thuật nào - nhưng sau đó tôi không thể truy cập các điều khiển theo chương trình.

Tôi có thể làm tất cả những điều này bằng cách bỏ qua hầu hết ASP.NET và xây dựng một tài liệu XML với LINQ to XML và triển khai IHttpHandler. Điều đó cảm thấy một chút mức độ thấp mặc dù.

Tôi nhận ra rằng các vấn đề của tôi có thể được giải quyết bằng cách nới lỏng các ràng buộc của tôi (ví dụ: sử dụng POST và không quan tâm đến tham số dư thừa) hoặc bằng cách sử dụng ASP.NET MVC, nhưng các yêu cầu của tôi có thực sự không hợp lý không?

Có lẽ ASP.NET chỉ không quy mô xuống để sắp xếp của ứng dụng? Mặc dù có một sự thay thế rất có thể: Tôi chỉ là ngu ngốc, và có một cách làm hoàn toàn đơn giản mà tôi chưa tìm thấy.

Có ai nghĩ gì không? (Đưa ra ý kiến ​​về việc làm thế nào sức mạnh bị sụp đổ, v.v ... Điều đó tốt - tôi hy vọng tôi chưa bao giờ tự nhận mình là chuyên gia ASP.NET, vì sự thật hoàn toàn ngược lại ...)


16
"Nhận xét về việc làm thế nào những người hùng mạnh bị sụp đổ" - tất cả chúng ta đều không biết gì, chỉ là những thứ khác nhau. Tôi mới chỉ bắt đầu tham gia ở đây, nhưng tôi ngưỡng mộ câu hỏi hơn tất cả các điểm. Rõ ràng là bạn vẫn đang suy nghĩ và học hỏi. Kudos cho bạn.
duffymo

15
Tôi không nghĩ mình từng chú ý đến ai đó đã từ bỏ việc học :)
Jon Skeet

1
Đúng trong trường hợp chung. Rất đúng trong khoa học máy tính.
Mehrdad Afshari

3
Và cuốn sách tiếp theo của bạn sẽ là "ASP.NET in Depth"? :-P
chakrit

20
Vâng, nó sẽ ra mắt vào năm 2025;)
Jon Skeet

Câu trả lời:


76

Giải pháp này sẽ cung cấp cho bạn toàn bộ quyền truy cập theo chương trình vào các điều khiển bao gồm tất cả các thuộc tính trên các điều khiển. Ngoài ra, chỉ các giá trị hộp văn bản sẽ xuất hiện trong URL khi gửi để URL yêu cầu GET của bạn sẽ "có ý nghĩa" hơn

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="JonSkeetForm.aspx.cs" Inherits="JonSkeetForm" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Jon Skeet's Form Page</title>
</head>
<body>
    <form action="JonSkeetForm.aspx" method="get">
    <div>
        <input type="text" ID="text1" runat="server" />
        <input type="text" ID="text2" runat="server" />
        <button type="submit">Submit</button>
        <asp:Repeater ID="Repeater1" runat="server">
            <ItemTemplate>
                <div>Some text</div>
            </ItemTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

Sau đó, trong mã phía sau của bạn, bạn có thể làm mọi thứ bạn cần trên PageLoad

public partial class JonSkeetForm : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        text1.Value = Request.QueryString[text1.ClientID];
        text2.Value = Request.QueryString[text2.ClientID];
    }
}

Nếu bạn không muốn một hình thức có runat="server", thì bạn nên sử dụng các điều khiển HTML. Làm việc với mục đích của bạn dễ dàng hơn. Chỉ cần sử dụng các thẻ HTML thông thường và đặt runat="server"và cung cấp cho họ ID. Sau đó, bạn có thể truy cập chúng theo chương trình mã mà không cần a ViewState.

Nhược điểm duy nhất là bạn sẽ không có quyền truy cập vào nhiều điều khiển máy chủ ASP.NET "hữu ích" như GridViews. Tôi đã đưa vào một Repeaterví dụ của mình vì tôi giả sử rằng bạn muốn có các trường trên cùng trang với kết quả và (theo hiểu biết của tôi) a Repeaterlà điều khiển DataBound duy nhất sẽ chạy mà không có runat="server"thuộc tính trong thẻ Biểu mẫu.


1
Tôi đã có rất ít lĩnh vực thực hiện thủ công rất dễ dàng :) Điều quan trọng là tôi không biết mình có thể sử dụng runat = server với các điều khiển HTML thông thường. Tôi chưa thực hiện kết quả nào, nhưng đó là một chút dễ dàng. Gần đó rồi!
Jon Skeet

Thật vậy, <form runat = "server"> sẽ thêm trường ẩn __VIEWSTATE (và một số khác) ngay cả khi bạn đặt EnableViewState = "Sai" ở cấp độ trang. Đây là cách để đi nếu bạn muốn mất ViewState trên trang. Đối với tính thân thiện của Url, urlrewriting có thể là một tùy chọn.
Sergiu Damian

1
Không cần phải viết lại. Câu trả lời này hoạt động tốt (mặc dù điều đó có nghĩa là có một điều khiển với ID của "người dùng" - vì một số lý do tôi không thể thay đổi tên của điều khiển hộp văn bản tách biệt với ID của nó).
Jon Skeet

1
Chỉ cần xác nhận, điều này thực sự làm việc rất tốt. Cảm ơn rất nhiều!
Jon Skeet

14
Có vẻ như bạn nên viết nó bằng asp cổ điển!
ScottE

12

Bạn chắc chắn (IMHO) đi đúng hướng bằng cách không sử dụng runat = "server" trong thẻ FORM của bạn. Điều này chỉ có nghĩa là bạn sẽ cần trích xuất trực tiếp các giá trị từ Request.QueryString, như trong ví dụ này:

Trong trang .aspx:

<%@ Page Language="C#" AutoEventWireup="true" 
     CodeFile="FormPage.aspx.cs" Inherits="FormPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>ASP.NET with GET requests and no viewstate</title>
</head>
<body>
    <asp:Panel ID="ResultsPanel" runat="server">
      <h1>Results:</h1>
      <asp:Literal ID="ResultLiteral" runat="server" />
      <hr />
    </asp:Panel>
    <h1>Parameters</h1>
    <form action="FormPage.aspx" method="get">
    <label for="parameter1TextBox">
      Parameter 1:</label>
    <input type="text" name="param1" id="param1TextBox" value='<asp:Literal id="Param1ValueLiteral" runat="server" />'/>
    <label for="parameter1TextBox">
      Parameter 2:</label>
    <input type="text" name="param2" id="param2TextBox"  value='<asp:Literal id="Param2ValueLiteral" runat="server" />'/>
    <input type="submit" name="verb" value="Submit" />
    </form>
</body>
</html>

và trong mã phía sau:

using System;

public partial class FormPage : System.Web.UI.Page {

        private string param1;
        private string param2;

        protected void Page_Load(object sender, EventArgs e) {

            param1 = Request.QueryString["param1"];
            param2 = Request.QueryString["param2"];

            string result = GetResult(param1, param2);
            ResultsPanel.Visible = (!String.IsNullOrEmpty(result));

            Param1ValueLiteral.Text = Server.HtmlEncode(param1);
            Param2ValueLiteral.Text = Server.HtmlEncode(param2);
            ResultLiteral.Text = Server.HtmlEncode(result);
        }

        // Do something with parameters and return some result.
        private string GetResult(string param1, string param2) {
            if (String.IsNullOrEmpty(param1) && String.IsNullOrEmpty(param2)) return(String.Empty);
            return (String.Format("You supplied {0} and {1}", param1, param2));
        }
    }

Mẹo ở đây là chúng tôi đang sử dụng ASP.NET Literals bên trong các thuộc tính value = "" của các kiểu nhập văn bản, vì vậy bản thân các hộp văn bản không phải chạy runat = "server". Sau đó, các kết quả được bọc bên trong Bảng điều khiển ASP: và thuộc tính Hiển thị được đặt khi tải trang tùy thuộc vào việc bạn có muốn hiển thị bất kỳ kết quả nào hay không.


Nó hoạt động khá tốt, nhưng các URL sẽ không thân thiện như, ví dụ như StackOverflow.
Mehrdad Afshari

1
Các URL sẽ khá thân thiện, tôi nghĩ ... Đây có vẻ là một giải pháp thực sự tốt.
Jon Skeet

Argh, tôi đã đọc các tweet của bạn trước đó, đã nghiên cứu nó và bây giờ tôi đã bỏ lỡ câu hỏi của bạn khi chuẩn bị những đứa trẻ tinh tế của tôi cho bồn tắm ... :-)
splattne

2

Được rồi Jon, vấn đề quan điểm đầu tiên:

Tôi đã không kiểm tra xem có bất kỳ loại thay đổi mã nội bộ nào kể từ 2.0 hay không nhưng đây là cách tôi xử lý để thoát khỏi chế độ xem cách đây vài năm. Trên thực tế, trường ẩn đó được mã hóa cứng bên trong HtmlForm, do đó bạn nên lấy ra cái mới của mình và tự mình thực hiện các cuộc gọi để thực hiện các cuộc gọi. Lưu ý rằng bạn cũng có thể bỏ __eventtarget và __eventtarget ra nếu bạn dính vào các điều khiển đầu vào cũ đơn giản (mà tôi đoán bạn muốn vì nó cũng không yêu cầu JS trên máy khách):

protected override void RenderChildren(System.Web.UI.HtmlTextWriter writer)
{
    System.Web.UI.Page page = this.Page;
    if (page != null)
    {
        onFormRender.Invoke(page, null);
        writer.Write("<div><input type=\"hidden\" name=\"__eventtarget\" id=\"__eventtarget\" value=\"\" /><input type=\"hidden\" name=\"__eventargument\" id=\"__eventargument\" value=\"\" /></div>");
    }

    ICollection controls = (this.Controls as ICollection);
    renderChildrenInternal.Invoke(this, new object[] {writer, controls});

    if (page != null)
        onFormPostRender.Invoke(page, null);
}

Vì vậy, bạn nhận được 3 phương thức tĩnh đó và gọi chúng bỏ qua phần viewstate đó;)

static MethodInfo onFormRender;
static MethodInfo renderChildrenInternal;
static MethodInfo onFormPostRender;

và đây là hàm tạo kiểu của biểu mẫu của bạn:

static Form()
{
    Type aspNetPageType = typeof(System.Web.UI.Page);

    onFormRender = aspNetPageType.GetMethod("OnFormRender", BindingFlags.Instance | BindingFlags.NonPublic);
    renderChildrenInternal = typeof(System.Web.UI.Control).GetMethod("RenderChildrenInternal", BindingFlags.Instance | BindingFlags.NonPublic);
    onFormPostRender = aspNetPageType.GetMethod("OnFormPostRender", BindingFlags.Instance | BindingFlags.NonPublic);
}

Nếu tôi hiểu đúng câu hỏi của bạn, bạn cũng không muốn sử dụng POST làm hành động của các biểu mẫu của mình, vì vậy đây là cách bạn làm điều đó:

protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)
{
    writer.WriteAttribute("method", "get");
    base.Attributes.Remove("method");

    // the rest of it...
}

Tôi đoán điều này là khá nhiều đó. Cho tôi biết làm thế nào nó đi.

EDIT: Tôi quên các phương thức trang viewstate:

Vì vậy, Biểu mẫu tùy chỉnh của bạn: HtmlForm có được bản tóm tắt hoàn toàn mới (hoặc không) Trang: System.Web.UI.Page: P

protected override sealed object SaveViewState()
{
    return null;
}

protected override sealed void SavePageStateToPersistenceMedium(object state)
{
}

protected override sealed void LoadViewState(object savedState)
{
}

protected override sealed object LoadPageStateFromPersistenceMedium()
{
    return null;
}

Trong trường hợp này, tôi niêm phong các phương thức vì bạn không thể niêm phong Trang (ngay cả khi nó không trừu tượng Scott Guthrie sẽ bọc nó thành một phương thức khác: P) nhưng bạn có thể niêm phong Biểu mẫu của mình.


Cảm ơn vì điều này - mặc dù nghe có vẻ như khá nhiều công việc. Giải pháp của Dan hoạt động tốt với tôi, nhưng thật tốt khi có nhiều lựa chọn hơn.
Jon Skeet

1

Bạn có nghĩ về việc không loại bỏ POST mà thay vào đó chuyển hướng đến một url GET phù hợp khi biểu mẫu được POST. Đó là, chấp nhận cả GET và POST, nhưng trên POST xây dựng một yêu cầu GET và chuyển hướng đến nó. Điều này có thể được xử lý trên trang hoặc thông qua một HTTPModule nếu bạn muốn làm cho nó độc lập với trang. Tôi nghĩ rằng điều này sẽ làm cho mọi thứ dễ dàng hơn nhiều.

EDIT: Tôi giả sử rằng bạn đã đặt EnableViewState = "false" trên trang.


Ý kiến ​​hay. Chà, ý tưởng khủng khiếp về việc bị buộc phải làm điều đó, nhưng tốt về mặt nó có thể hoạt động :) Sẽ thử ...
Jon Skeet

Và vâng, tôi đã thử EnableViewState = false ở mọi nơi. Nó không hoàn toàn vô hiệu hóa nó, chỉ cần cắt nó xuống.
Jon Skeet

Jon: Nếu bạn không sử dụng các điều khiển máy chủ bị nguyền rủa (không có runat = "server") và bạn hoàn toàn không có <form runat = "server">, ViewState sẽ không gặp rắc rối. Đó là lý do tại sao tôi nói không sử dụng điều khiển máy chủ. Bạn luôn có thể sử dụng bộ sưu tập Request.Form.
Mehrdad Afshari

Nhưng không có runat = server trên các điều khiển, sẽ rất khó để truyền lại giá trị cho các điều khiển khi kết xuất. May mắn thay, các điều khiển HTML với runat = server hoạt động tốt.
Jon Skeet

1

Tôi sẽ tạo một mô-đun HTTP xử lý định tuyến (tương tự MVC nhưng không tinh vi, chỉ là một vài ifcâu lệnh) và đưa nó đến aspxhoặc ashxcác trang. aspxđược ưa thích vì việc sửa đổi mẫu trang dễ dàng hơn. Tôi sẽ không sử dụng WebControlstrong aspxtuy nhiên. Chỉ cần Response.Write.

Nhân tiện, để đơn giản hóa mọi thứ, bạn có thể thực hiện xác thực tham số trong mô-đun (vì nó có thể chia sẻ mã với định tuyến) và lưu nó vào HttpContext.Itemsvà sau đó hiển thị chúng trong trang. Điều này sẽ hoạt động khá giống như MVC mà không cần tất cả chuông và còi. Đây là những gì tôi đã làm rất nhiều trước ngày ASP.NET MVC.


1

Tôi thực sự rất vui khi hoàn toàn từ bỏ lớp trang và chỉ xử lý mọi yêu cầu với trường hợp chuyển đổi lớn dựa trên url. Evey "trang" trở thành một mẫu html và đối tượng ac #. Lớp mẫu sử dụng biểu thức chính quy với đại biểu khớp so sánh với bộ sưu tập khóa.

những lợi ích:

  1. Nó thực sự rất nhanh, ngay cả sau khi biên dịch lại, hầu như không có độ trễ (lớp trang phải lớn)
  2. Kiểm soát thực sự chi tiết (rất tốt cho SEO và tạo DOM để chơi tốt với JS)
  3. bài thuyết trình tách biệt với logic
  4. jQuery có toàn quyền kiểm soát html

bummers:

  1. công cụ đơn giản mất nhiều thời gian hơn trong đó một hộp văn bản yêu cầu mã ở một vài nơi, nhưng nó thực sự mở rộng rất tốt
  2. Tôi luôn luôn muốn làm điều đó với chế độ xem trang cho đến khi tôi thấy một chế độ xem (urgh) sau đó tôi quay lại với thực tế.

Jon, chúng ta đang làm gì trên SO vào sáng thứ bảy :)?


1
Đó là buổi tối thứ bảy ở đây. Điều đó có ổn không? (Tôi rất thích xem biểu đồ phân tán thời gian / ngày đăng bài của tôi, btw ...)
Jon Skeet

1

Tôi nghĩ rằng asp: điều khiển Repeater đã lỗi thời.

Công cụ mẫu ASP.NET là tốt nhưng bạn có thể dễ dàng thực hiện việc lặp lại với một vòng lặp for ...

<form action="JonSkeetForm.aspx" method="get">
<div>
    <input type="text" ID="text1" runat="server" />
    <input type="text" ID="text2" runat="server" />
    <button type="submit">Submit</button>
    <% foreach( var item in dataSource ) { %>
        <div>Some text</div>   
    <% } %>
</div>
</form>

ASP.NET Forms khá ổn, có sự hỗ trợ tốt từ Visual Studio nhưng điều này runat = "server", điều đó thật sai lầm. ViewState để.

Tôi đề nghị bạn hãy xem điều gì làm cho ASP.NET MVC trở nên tuyệt vời, người mà nó di chuyển ra khỏi cách tiếp cận ASP.NET Forms mà không vứt bỏ tất cả.

Bạn thậm chí có thể viết nội dung nhà cung cấp bản dựng của riêng mình để biên dịch các chế độ xem tùy chỉnh như NHaml. Tôi nghĩ bạn nên xem ở đây để kiểm soát nhiều hơn và chỉ đơn giản dựa vào thời gian chạy ASP.NET để gói HTTP và như một môi trường lưu trữ CLR. Nếu bạn chạy chế độ tích hợp thì bạn cũng có thể thao tác yêu cầu / phản hồi HTTP.

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.