I've just switched to using msbuild to precompile my website and now I'm getting this strange error:
I have a call to Membership.GetUser() which throws:
[NullReferenceException: Object reference not set to an instance of an object.]
System.Web.Security.Membership.GetCurrentUserName() +36
System.Web.Security.Membership.GetUser() +7
...
Reflector shows the implementation of Membership.GetCurrentUserName is:
private static string GetCurrentUserName()
{
if (HostingEnvironment.IsHosted)
{
HttpContext current = HttpContext.Current;
if (current != null)
{
return current.User.Identity.Name;
}
}
IPrincipal currentPrincipal = Thread.CurrentPrincipal;
if ((currentPrincipal != null) && (currentPrincipal.Identity != null))
{
return currentPrincipal.Identity.Name;
}
return string.Empty;
}
At first glance the most likely explanation is that:
HttpContext.Current is not null, and
HttpContext.Current.User is null or has a null Identity property.
All other paths seem to have a test for null.
So I suggest you trace the type and contents of HttpContext.User.
HttpContext.Current.User is an IPrincipal, and most concrete implementations of IPrincipal that I know of don't allow a null identity, so I'd bet on HttpContext.User being null.
Related
I bind the model to a session in ASP MVC Framework like this:
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Cart cart = null;
if(controllerContext.HttpContext.Session != null)
{
cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
}
if(cart == null)
{
cart = new Cart();
if (controllerContext.HttpContext.Session != null)
{
controllerContext.HttpContext.Session[sessionKey] = cart;
}
}
return cart;
}
Now I want to do the same thing in ASM MVC Core, and this was my attempt:
public Task BindModelAsync(ModelBindingContext bindingContext)
{
Cart cart = null;
if (bindingContext.HttpContext.Session != null)
{
cart = (Cart)JsonConvert.DeserializeObject(bindingContext.HttpContext.Session.GetString(sessionKey));
}
if (cart == null)
{
cart = new Cart();
if (bindingContext.HttpContext.Session != null)
{
bindingContext.HttpContext.Session.SetString(sessionKey, JsonConvert.SerializeObject(cart));
}
}
return Task.CompletedTask;
}
I also have the class for model binder provider.
But I get a run-time error on this line, saying that the object is null:
cart = (Cart)JsonConvert.DeserializeObject(bindingContext.HttpContext.Session.GetString(sessionKey));
The string returned from 'GetString(sessionKey)' is null. The full message is:
System.ArgumentNullException: 'Value cannot be null. Parameter name: value''.
The question doesn't mention what exception is thrown, but this code is guaranteed to fail the first time an attempt is made to read from the session.
The second snippet tries to deserialize a string without checking whether it's null or not :
cart=(Cart)JsonConvert.DeserializeObject(bindingContext.HttpContext.Session.GetString(sessionKey));
Or, in a more readable way:
var json=bindingContext.HttpContext.Session.GetString(sessionKey);
cart = (Cart)JsonConvert.DeserializeObject(json);
JsonConvert.DeserializeObject() will throw if its argument is null.
The json string must be checked before calling DeserializeObject. With some cleanup, the code could look like this:
var session=bindingContext.HttpContext.Session;
if(session == null)
{
return null;
}
var json = sessio.GetString(sessionKey);
if (!String.IsNullOrWhitespace(json))
{
var cart=JsonConvert.DeserializeObject<Cart>(json);
return cart;
}
else
{
var emptyCart=new Cart();
var value=JsonConvert.SerializeObject(emptyCart);
session.SetString(sessionKey, value);
return emptyCart;
}
The null safe operator can be used to handle missing context values, eg during testing :
var session=bindingContext?.HttpContext?.Session;
This will return null if any of the objects is null.
I found this resource for creating an AuthorizationHandler and followed along to create one. My handler checks for given_name, a string value, and if given_name has a value of "Bob", authorization success.
The problem, I can't access the given_name claim and its value. I can see all the expected claims when I inspect context.User but context.User.HasClaim(c => c.Type == ClaimTypes.GivenName) always returns false.
How do I check for the presence of a claim and get its value?
Update - As a workaround, I can access all the claims by calling .ToList() on context.User.Claims and then using .Any() on the list. It works but I haven't seen this approach in any examples.
For Asp.NET Core 6, you can pass in the AuthorizationHandlerContext.
Here is a sample AuthorizationHandler that leverages that context for a ClaimsPrincipal.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyIdentityApp.Security
{
public class CanReadAffiliatedOwnerInfoHandler : AuthorizationHandler<ManageAffiliatedOwnerInfoRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ManageAffiliatedOwnerInfoRequirement requirement)
{
var authFilterContext = (HttpContext?)context.Resource;
if (authFilterContext == null)
{
return Task.CompletedTask;
}
else
{
//access user's claims here, via context.User
var loggedInUserAffiliatedOwners = context.User.Claims.FirstOrDefault(c => c.Value == OwnerIdAttemptingToAccess);
if (loggedInUserAffiliatedOwners != null)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
}
I validate the input using ModelState.IsValid:
[HttpGet]
[Route("subjects")]
[ValidateAttribute]
public IHttpActionResult GetSubjects(bool? isActive = null)
{
//get subjects
}
If I pass in the uri ~/subjects/?isActive=abcdef, I get the error message:
The value 'abcdef' is not valid for Nullable`1.
If the input parameter is not nullable
public IHttpActionResult GetSubjects(bool isActive){
//get subjects
}
I get the error message:
The value 'abcdef' is not valid for Boolean.
I want to override the message if nullable type so I can maintain the message ("The value 'abcdef' is not valid for Boolean."). How can I do this since in the ModelState error I don't get the data type. I am implementing the validation as a custom ActionFilterAttribute (ValidationAttribute).
You can change callback that formats type conversion error messages. For example, let's define it right into Global.asax.cs:
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
ModelBinderConfig.TypeConversionErrorMessageProvider = this.NullableAwareTypeConversionErrorMessageProvider;
// rest of your initialization code
}
private string NullableAwareTypeConversionErrorMessageProvider(HttpActionContext actionContext, ModelMetadata modelMetadata, object incomingValue)
{
var target = modelMetadata.PropertyName;
if (target == null)
{
var type = Nullable.GetUnderlyingType(modelMetadata.ModelType) ?? modelMetadata.ModelType;
target = type.Name;
}
return string.Format("The value '{0}' is not valid for {1}", incomingValue, target);
}
}
For not nullable types Nullable.GetUnderlyingType will return null, in this case we will use original type.
Unfortunately you cannot access default string resources and if you need to localize error message you must do it on your own.
Another way is to implement your own IModelBinder, but this is not a good idea for your particular problem.
Lorond's answer highlights how flexible asp.net web api is in terms of letting a programmer customize many parts of the API. When I looked at this question, my thought process was to handle it in an action filter rather than overriding something in the configuration.
public class ValidateTypeAttribute : ActionFilterAttribute
{
public ValidateTypeAttribute() { }
public override void OnActionExecuting(HttpActionContext actionContext)
{
string somebool = actionContext.Request.GetQueryNameValuePairs().Where(x => x.Key.ToString() == "somebool").Select(x => x.Value).FirstOrDefault();
bool outBool;
//do something if somebool is empty string
if (!bool.TryParse(somebool, out outBool))
{
HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
response.ReasonPhrase = "The value " + somebool + " is not valid for Boolean.";
actionContext.Response = response;
}
else
{
base.OnActionExecuting(actionContext);
}
}
Then decorate the action method in the controller with the action filter attribute
I have a custom claim that determines language user will be using.
I have extension to ClaimsPrincipal, that checks for that claim and generates appropriate culture:
public static CultureInfo Culture(this ClaimsPrincipal principal)
{
Claim claim = principal.FindFirst(CustomClaimTypes.Language);
if (claim == null)
return CultureInfo.CreateSpecificCulture("en");
else
return CultureInfo.CreateSpecificCulture(claim.Value);
}
I've also implemented custom UserNameSecurityTokenHandler and custom ClaimsAuthorizationManager and both work fine. Actually entire solution works just fine, except for the fact that I'm unable to find appropriate point in WCF pipeline where I can set Thread.CurrentCulture based on the extension above.
I've tried ICallContextInitializer, IDispatchMessageInspector, but none of the above can actually see my ClaimsPrincipal (it gets overriden with empty principal). However, in my service operations, ClaimsPrincipal.Current points properly to identity I've created in UserNameSecurityTokenHandler.
Where should I set this Claim-dependant culture?
For WCF data service i use ProcessingRequest event:
public class ConfigurationManagementDataService : SecurityDataService<ConfigurationContext>
{
public ConfigurationManagementDataService()
{
Database.SetInitializer<ConfigurationContext>(null);
ProcessingPipeline.ProcessingRequest += OnProcessingRequest;
}
public void OnProcessingRequest(object sender, DataServiceProcessingPipelineEventArgs dataServiceProcessingPipelineEventArgs)
{
//assign IPrincipal to a Thread.Principal
//we must set both, but Thread.CurrentPrincipal is a main
if (context != null)
{
context.User = principal;
}
Thread.CurrentPrincipal = principal;
I'm doing a message inspector in WCF:
public class LogMessageInspector :
IDispatchMessageInspector, IClientMessageInspector
which implements the method:
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
I can get the name of the invoked service with:
instanceContext.GetServiceInstance().GetType().Name
But how do I get the name of the invoked operation?
It's not pretty, but this is what I did to get the operation name:
var action = OperationContext.Current.IncomingMessageHeaders.Action;
var operationName = action.Substring(action.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1);
var operationName = OperationContext.Current.IncomingMessageProperties["HttpOperationName"] as string;
This approach is similar to others presented here, but uses Path.GetFileName:
Path.GetFileName(OperationContext.Current.IncomingMessageHeaders.Action);
The return value of this method and the format of the path string work quite harmoniously in this scenario:
The characters after the last directory character in path. If the last
character of path is a directory or volume separator character, this
method returns String.Empty. If path is null, this method returns
null.
OperationContext.Current.IncomingMessageHeaders.Action.Split('/').ToList().Last();
Little late to the party but I had to dig a little deeper than existing answers on this question because they seem to involve getting the action name and not the operation name. (Frequently they are the same so getting the action name does, in fact, get the operation name.)
Microsoft's Application Insights SDK Labs' WCF library makes this concerted effort:
private string DiscoverOperationName(OperationContext operationContext)
{
var runtime = operationContext.EndpointDispatcher.DispatchRuntime;
string action = operationContext.IncomingMessageHeaders.Action;
if (!string.IsNullOrEmpty(action))
{
foreach (var op in runtime.Operations)
{
if (op.Action == action)
{
return op.Name;
}
}
}
else
{
// WebHttpDispatchOperationSelector will stick the
// selected operation name into a message property
return this.GetWebHttpOperationName(operationContext);
}
var catchAll = runtime.UnhandledDispatchOperation;
if (catchAll != null)
{
return catchAll.Name;
}
return "*";
}
private string GetWebHttpOperationName(OperationContext operationContext)
{
var name = WebHttpDispatchOperationSelector.HttpOperationNamePropertyName;
if (this.HasIncomingMessageProperty(name))
{
return this.GetIncomingMessageProperty(name) as string;
}
return "<unknown>";
}