La página de inicio de sesión personalizada tiene un SPContext.Current que siempre es nulo
-
10-12-2019 - |
Pregunta
Encontré la solución
La solución fue agregar una colección de sitios raíz (ya que el código se ejecutaba en un subsitio, sin un sitio raíz).
Ver Esta solución
Actualización importante (basado en los comentarios recibidos hasta el momento)
No tengo Visual Studio instalado en SharePoint 2013 Server.Todo el código se compila en Visual Studio 2012 de forma remota utilizando referencias a las DLL de SharePoint 2013 requeridas y se firma digitalmente para que pueda implementarse en la caché de ensamblados global en SharePoint 2013 Server.
En el nivel más básico, esta afirmación falla en el punto de
Page_Load()
;SPContext.Current.Site.WebApplication.GetIisSettingsWithFallback( SPContext.Current.Site.Zone );
Porque
SPContext.Current
es siemprenull
.
Fondo
El cliente necesita que se pueda acceder al sitio de SharePoint mediante la autenticación basada en formularios para usuarios externos y la autenticación de Windows para usuarios internos (corporativos).He revisado y configurado ambos proveedores de autenticación y ahora obtengo el inicio de sesión predeterminado.
Aunque todo funciona, la autenticación de Windows aún muestra la pantalla de selección de proveedor cuando el cliente desea que la autenticación de Windows pase automáticamente y muestre una pantalla de inicio de sesión de autenticación basada en formularios si eso falla.
He estado luchando con esto durante días después de mirar varios blogs y artículos. Siento que no he avanzado más y eso es deprimente.Por el momento no puedo ejercitarme si me falta algo realmente fundamental.
Página de inicio de sesión personalizada
La página se basa en una fusión de;
%CommonProgramFiles%\Microsoft Shared\Web Server Extensions\15\Template\IdentityModel\Login\default.aspx
%CommonProgramFiles%\Microsoft Shared\Web Server Extensions\15\Template\IdentityModel\Forms\default.aspx
Este es solo un primer intento y me doy cuenta de que eventualmente no necesitaré todos estos controles, solo quería ver cómo se combinan las cosas.
<%@ Assembly Name="Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharepointIdentity" Namespace="Microsoft.SharePoint.IdentityModel" Assembly="Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#" Inherits="MyCustomSignInModule.SignInForm, MyCustomSignInModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2ae721af94bf9e9" MasterPageFile="~/_layouts/15/errorv15.master"%>
<%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
<SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" Id="ClaimsLogonPageTitleInTitleArea" />
</asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
<SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" Id="ClaimsFormsPageTitle" />
</asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" Id="ClaimsLogonPageMessage" />
<br />
<br />
<SharepointIdentity:LogonSelector ID="ClaimsLogonSelector" runat="server" />
<div id="SslWarning" style="color:red;display:none">
<SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" Id="ClaimsFormsPageMessage" />
</div>
<script language="javascript" >
if (document.location.protocol != 'https:') {
var SslWarning = document.getElementById('SslWarning');
SslWarning.style.display = '';
}
</script>
<asp:login id="signInControl" FailureText="<%$Resources:wss,login_pageFailureText%>" runat="server" width="100%">
<layouttemplate>
<asp:label id="FailureText" class="ms-error" runat="server"/>
<table width="100%">
<tr>
<td nowrap="nowrap"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pageUserName%>" EncodeMethod='HtmlEncode'/></td>
<td width="100%"><asp:textbox id="UserName" autocomplete="off" runat="server" class="ms-inputuserfield" width="99%" /></td>
</tr>
<tr>
<td nowrap="nowrap"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,login_pagePassword%>" EncodeMethod='HtmlEncode'/></td>
<td width="100%"><asp:textbox id="password" TextMode="Password" autocomplete="off" runat="server" class="ms-inputuserfield" width="99%"/></td>
</tr>
<tr>
<td colspan="2" align="right"><asp:button id="login" commandname="Login" text="<%$Resources:wss,login_pagetitle%>" runat="server" /></td>
</tr>
<tr>
<td colspan="2"><asp:checkbox id="RememberMe" text="<%$SPHtmlEncodedResources:wss,login_pageRememberMe%>" runat="server" /></td>
</tr>
</table>
</layouttemplate>
</asp:login>
<asp:label id="DebugText" class="ms-error" runat="server"/>
</asp:Content>
Ensamblaje del módulo MyCustomSignIn
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.IdentityModel.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.IdentityModel;
using Microsoft.SharePoint.IdentityModel.Pages;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Claims;
using Microsoft.SharePoint.ApplicationRuntime;
using Microsoft.SharePoint.Diagnostics;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using System.IdentityModel.Tokens;
using System.Web.Configuration;
namespace MyCustomSignInModule
{
public class SignInForm : FormsSignInPage
{
protected Label DebugText;
private static SPIisSettings _iisSettings;
internal static SPIisSettings iisSettings
{
get
{
if (_iisSettings == null)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
_iisSettings = SPContext.Current.Site.WebApplication.GetIisSettingsWithFallback(SPContext.Current.Site.Zone);
});
}
return _iisSettings;
}
}
protected void Page_Load(object sender, EventArgs e)
{
HttpRequest request = HttpContext.Current.Request;
bool isWindowsAuth = false;
string username = request["username"];
string password = request["password"];
// If no username is provided, it'll probably be Windows Authentication (NTLMv2 protocol)
if (String.IsNullOrEmpty(username))
{
isWindowsAuth = true;
}
try
{
//SPIisSettings iisSettings = SPContext.Current.Site.WebApplication.IisSettings[SPUrlZone.Default];
if (isWindowsAuth)
{
// Windows Authentication it is
if (iisSettings.UseWindowsClaimsAuthenticationProvider)
{
SPAuthenticationProvider provider = iisSettings.WindowsClaimsAuthenticationProvider;
RedirectToLoginPage(provider); // This should cause automatic sign in
}
}
else
{
// FBA authentication it is.
SPFormsAuthenticationProvider formsClaimsAuthenticationProvider = iisSettings.FormsClaimsAuthenticationProvider;
SecurityToken token = SPSecurityContext.SecurityTokenForFormsAuthentication(new Uri(SPContext.Current.Web.Url), formsClaimsAuthenticationProvider.MembershipProvider, formsClaimsAuthenticationProvider.RoleProvider, username, password, SPFormsAuthenticationOption.PersistentSignInRequest);
if (null != token)
{
EstablishSessionWithToken(token);
base.RedirectToSuccessUrl();
}
}
}
catch (Exception ex)
{
DebugText.Text = ex.ToString();
}
}
// Microsoft.SharePoint.IdentityModel.LogonSelector
private void RedirectToLoginPage(SPAuthenticationProvider provider)
{
string components = HttpContext.Current.Request.Url.GetComponents(UriComponents.Query, UriFormat.SafeUnescaped);
string url = provider.AuthenticationRedirectionUrl.ToString();
if (provider is SPWindowsAuthenticationProvider)
{
components = EnsureUrlSkipsFormsAuthModuleRedirection(components, true);
}
SPUtility.Redirect(url, SPRedirectFlags.Default, this.Context, components);
}
// Microsoft.SharePoint.Utilities.SPUtility
private string EnsureUrlSkipsFormsAuthModuleRedirection
(string url, bool urlIsQueryStringOnly)
{
if (!url.Contains("ReturnUrl="))
{
if (urlIsQueryStringOnly)
{
url = url + (string.IsNullOrEmpty(url) ? "" : "&");
}
else
{
url = url + ((url.IndexOf('?') == -1) ? "?" : "&");
}
url = url + "ReturnUrl=";
}
return url;
}
// Microsoft.SharePoint.IdentityModel.Pages.IdentityModelSignInPageBase
private void EstablishSessionWithToken(SecurityToken securityToken)
{
if (securityToken == null)
{
throw new ArgumentNullException("securityToken");
}
SPFederationAuthenticationModule fam = SPFederationAuthenticationModule.Current;
if (fam == null)
{
throw new InvalidOperationException();
}
fam.SetPrincipalAndWriteSessionToken(securityToken);
}
private SPAuthenticationProvider
GetAuthenticationProvider(string providerName)
{
SPIisSettings iisSettings =
SPContext.Current.Site.WebApplication.IisSettings[SPUrlZone.Default];
foreach (SPAuthenticationProvider currentProvider in
iisSettings.ClaimsAuthenticationProviders)
{
if (currentProvider.DisplayName.ToLower() ==
providerName.ToLower())
{
return currentProvider;
}
}
return null;
}
}
}
El proceso
Tengo una instancia de prueba de Sharepoint ejecutándose en la máquina virtual, pero no tengo instalado Visual Studio ya que todo el desarrollo se realiza de forma remota.El proceso actual que sigo;
- Construya la biblioteca de clases C# (esto será
MyCustomSignInModule.Dll
) - Firmar
MyCustomSignInModule.Dll
para que pueda colocarse en el VM GAC. - Registro
MyCustomSignInModule.Dll
en el GAC en la instancia de VM. - Ahorrar
SignInForm.aspx
en el%CommonProgramFiles%\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\
que es el equivalente a<SharePointSiteRoot>/_layouts/
directorio virtual en IIS. - En el sitio de Administración central de SharePoint, configure la página de inicio de sesión personalizada en mi página
~/_layouts/SignInForm.aspx
. - Restablecer IIS usando
iisreset
. - Pruebe el acceso al sitio de SharePoint (aquí es donde falla).
El problema
Si no está tan claro, el error es;
System.NullReferenceException: Object reference not set to an instance of an object. at
MyCustomSignInModule.SignInForm.b__0() at
Microsoft.SharePoint.SPSecurity.<>c__DisplayClass5.b__3() at
Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode) at
Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(WaitCallback secureCode, Object param) at
Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(CodeToRunElevated secureCode) at MyCustomSignInModule.SignInForm.get_iisSettings() at
MyCustomSignInModule.SignInForm.Page_Load(Object sender, EventArgs e)
SPContext.Current es nulo ¿por qué?
El NullReferenceException
está siendo causado por SPContext.Current
siendo nulo, pero no puedo entender por qué es nulo y ninguna cantidad de artículos que he visto hasta ahora me han ayudado a resolver esto.Espero que al publicar todo mi proceso aquí alguien pueda indicarme lo que me falta o lo que estoy haciendo mal.
Solución
Finalmente llegué al fondo de lo que estaba causando el problema. En primer lugar, me gustaría agradecer a ambos. @MdMazzotti y @Hugh Madera por su persistente ayuda.
Al final encontré la solución, que resultó deberse en parte a la forma en que se construyó el sitio.En mi caso particular, el sitio se creó como un subsitio en SharePoint; por alguna razón, esto significaba que no había una colección de sitios en la raíz del sitio web de SharePoint, lo cual fue la causa del problema. Null
SPContext
en mi código.
Después de agregar una colección de sitios raíz, mi código comenzó a funcionar como se esperaba.
Otros consejos
Solo estoy adivinando, pero aquí hay algunas cosas que creo que podrían ayudar.
Agregue este código JavaScript a la página de inicio de sesión personalizada (antes de su declaración if):
SP.SOD.executeFunc('SP.js', 'SP.ClientContext');
También puedes agregar este JavaScript a tu página para probar si funciona o no, usando la consola:
var clientContext = new SP.ClientContext.get_current();
console.log(clientContext);
Si devuelve algo, ¡entonces tenemos progreso!Si no, tendré que continuar mi búsqueda, ya que encuentro interesante tu pregunta.
EDITAR
En lugar de utilizar este código del lado del servidor:
if (_iisSettings == null)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
_iisSettings = SPContext.Current.Site.WebApplication.GetIisSettingsWithFallback(SPContext.Current.Site.Zone);
});
}
return _iisSettings;
Intente usar esto en su lugar:
if (_iisSettings == null)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
_iisSettings = Microsoft.SharePoint.SPContext.Current.Site.WebApplication.GetIisSettingsWithFallback(Microsoft.SharePoint.SPContext.Current.Site.Zone);
});
}
return _iisSettings;
Busqué y noté que en algunos de los ejemplos de código que encontré, _iisSettings era diferente al tuyo.¿Quizás es por eso que su SPContext.Current es nulo?
Por favor revisa esta respuesta:https://stackoverflow.com/questions/5081251/how-can-this-throw-a-nullreferenceexception
SPContext.Current no funciona con privilegios elevados, por lo que debe reescribir como en la respuesta anterior.
¿Puedes consultar este artículo de MSDN? http://msdn.microsoft.com/en-us/library/office/ms468609(v=office.14).aspx
Creo que debido a que usa un proyecto de biblioteca de clases de C# en Visual Studio, no está en un contexto web y no puede usar SPContext.Current como se explica en el artículo de MSDN anterior.