Question

I have a website that uses the ASP.NET Login Controls and Forms Authentication. I've placed the asp:LoginStatus control inside another Web User Control that manages the display of the Header portion of my site.

The issue I have is that the ReturnURL presented by the LoginStatus control references the path to the Header Control, not the page the user is currently on. This is probably due to the class hierarchy and that the Header Control (ascx) actually uses Server.Execute on an .aspx file to generate the HTML. This is a work around to avoid the issue of not being allowed to have more than one server-side form on a page.

So the actual class Hierarchy of the Page is as follows:

Default.aspx - Uses Page.Master
Page.Master includes <foo:Header> 
    (with a reference to "~/Controls/Components/Header.ascx")
Header.ascx simply includes an <asp:Literal> 
    on Page_Load performs a Server.Execute ("~/Controls/Pages/Header.aspx") 
    and writes the content out to the Literal
Header.aspx includes <asp:LoginStatus>

When the user clicks on the Login link they are correctly redirected to Login.aspx, however the ReturnURL displayed is (incorrectly - although I can understand why) "ReturnUrl=%2fControls%2fPages%2fHeader.aspx".

Once on the Login page I can quite happily handle the LoggedIn event to correctly redirect the user to the right place. What I would like to do is either: 1) Remove the ReturnURL from the query string altogether 2) Be able to control the ReturnURL when the LoginStatus Control is rendered.

I've done some Reflector-ing of the System.Web.UI.WebControls.LoginStatus and it appears that it is hard-coded to always use a ReturnURL, based on the following code:

private string NavigateUrl
{
    get
    {
        if (!base.DesignMode)
        {
            return FormsAuthentication.GetLoginPage(null, true);
        }
        return "url";
    }
}

It is always setting reuseReturnURL to true.

Possibly, my only choice is to roll my own LoginStatus control?

[EDIT: Originally for the sake of brevity, I omitted the following details]

Here is a really simple example of what I am trying to acheive:

Web Application Project has the following structure: WebSite - Controls - Components - Footer.ascx - Header.ascx - MasterPages - Site.Master - Default.aspx - Login.aspx

The page markups are below if you're interested.

I've created the Web User Control for separation of concerns, however on the Login page to use the asp:Login controls they need to be nested in a (server-side) form. The asp:LoginStatus control also needs to be nested in a (server-side) form. As you can't have more than one server-side form on a page, this breaks.

Also, the answer is not just to suppress the LoginStatus control on the Login page. Imagine if I just wanted to add a little Search Control on the main page, which would also rely on a (server-side) form. Hence the reason for using a Server.Execute and generating a page from an ASPX. This "tricks" .NET into allowing multiple server-side forms on a page. (Don't ask me how... I don't know!)

Maybe my entire architecture design is wrong, but how to others have multiple Web User Controls on a page that require server-side forms? Or don't they?

Site.Master markup:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebSite.MasterPages.Site" %>

<%@ Register TagPrefix="bs" TagName="Footer" Src="~/Controls/Components/Footer.ascx" %>
<%@ Register TagPrefix="bs" TagName="Header" Src="~/Controls/Components/Header.ascx" %>

<!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></title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body id="Body" runat="server">
    <div id="container">
        <!-- start header -->
        <bs:Header ID="Header" runat="server" />
        <!-- end header -->
        <div id="central">
            <div id="main">
                <asp:PlaceHolder ID="MainContentPlaceHolder" runat="server">
                    <!-- start main content -->
                    <div>
                        <asp:ContentPlaceHolder ID="MainContent" runat="server" />
                    </div>
                    <!-- end main content -->
                </asp:PlaceHolder>
            </div>
        </div>
        <!-- start footer -->
        <bs:Footer ID="Footer" runat="server" />
        <!-- end footer -->
    </div>

</body>
</html>

Default.aspx markup:

<%@ Page MasterPageFile="~/MasterPages/Site.Master" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebSite._Default" %>

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="MainContent">
    Main Body Content <br />
    <br />

</asp:Content>

Header.ascx markup:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Header.ascx.cs" Inherits="WebSite.Controls.Components.Header" %>
<div id="header">
    Header Content <br />
    <div id="loginstatus">
        <form id="Form1" runat="server">
        <asp:LoginView ID="displayloginname" runat="server">
            <AnonymousTemplate>
                <a href="../../Registration.aspx">Register</a>
            </AnonymousTemplate>
            <LoggedInTemplate>
                Welcome
                <asp:LoginName runat="server" ID="ctlLoginName" />
            </LoggedInTemplate>
        </asp:LoginView>
        <asp:LoginStatus ID="displayloginstatus" runat="server" LoginText="Login" LogoutPageUrl="~/Default.aspx"
            LogoutAction="Redirect" />
        </form>

        <br />
    </div>
</div>

Footer.ascx markup:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Footer.ascx.cs" Inherits="Website.Controls.Components.Footer" %>

        <div id="footer">
            Footer Content
            <ul class="links">
            <asp:PlaceHolder ID="ListItems" Runat="server">
                <li><a runat="server" id="HomeLink" href="~/">Home</a></li>
                <li><a runat="server" href="~/" ID="A1">About Us</a></li>
                <li><a id="A2" runat="server" href="~/">Contact Us</a></li>
                <li><a id="A3" runat="server" href="~/">Privacy Policy</a></li>
                <li><a id="A4" runat="server" href="~/">Accessibility Policy</a></li>
                <li><a id="A5" runat="server" href="~/">Legal Notices</a></li>
                <li><a id="A6" runat="server" href="~/">Sitemap</a></li>
                <li><a id="A7" runat="server" href="~/">RSS Feeds</a></li>
            </asp:PlaceHolder>
            </ul>

        </div>

Login.aspx markup:

<%@ Page MasterPageFile="~/MasterPages/Site.Master" Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="Website.Login" %>

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="MainContent">
    Main Body Content <br />
    <br />
    <form id="form1" runat="server">
    <div>

        <asp:Login ID="Login1" runat="server">
        </asp:Login>

    </div>
    </form>
</asp:Content>
Was it helpful?

Solution

I see two options:

  1. Put your form tag in your master page. Just below the <div id="container"> and wrapping all content in that div.
  2. Change your header to not require an ASP.NET form. Not sure if the LoginView or LoginStatus both require an ASP.NET form. And then you can leave the form tag for the aspx files that use the master page (like you have in Login.aspx). This is what I prefer is to only put form tags in aspx pages (not in master files) and make sure headers and footer do not need an ASP.NET form (they could use a regular form tag just not with runat=server).

OTHER TIPS

Why do you do a Server.Execute of the Header.aspx from your Header.ascx? Why don't you just put the Header.aspx code in the Header.ascx. That way the ReturnUrl will be the page you expect it to be.

Something does not sound quite right here:

on Page_Load performs a Server.Execute("~/Controls/Pages/Header.aspx")
and writes the content out to the Literal

I am not understanding why you are doing this? You mention that it is because you are trying to get around the problem of ASP.NET not allowing more than one HtmlForm on a page. But if you are just putting the html contents from header.aspx into a literal then you are not using an HtmlForm?? So can you just put the contents of Header.aspx in a normal form tag (without runat=server)? Or can the login status control be put outside of the form entirely? Do the contents of Header.aspx really need a form in the first place?

I would take a good look at why you are writing the contents of your header.aspx page out to a literal and try and fix the problem there instead of diving into the LoginStatus control and changing it to do something it is not intended to do.

One other possible suggestion: If you really do need to do to load Header into a literal, can you use Page.LoadUserControl instead? This may automatically resolve the urls for you? Not 100% sure though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top