Why is UmbracoContext.Current becoming null when calling static helper from inside UmbracoApiController?
Umbraco Version: 7.12.3
I have a few static methods that I use to convert IPublishedContent to something that can be returned as json, but for some reason, one of the methods loses the umbracoContext. I know they start out with it, as ToFeedData doesn't ever throw an error (it's called from beneath an UmbracoApiController). See the below code
public static Dictionary<string, object> ToFeedData(this IPublishedContent content) {
var data = new Dictionary<string, object> {
{ "id", content.Id },
{ "name", content.Name },
{ "url", content.Url },
{ "urlName", content.UrlName },
{ "createDate", content.CreateDate },
{ "updateDate", content.UpdateDate }
};
foreach (var prop in content.Properties) {
if (prop.HasValue && prop.PropertyTypeAlias != "seoSettings") {
data.Add(prop.PropertyTypeAlias, ConvertType(prop.Value));
}
}
return data;
}
public static object ConvertType(Object value) {
if (value == null) {
return null;
}
if (value is JObject) {
return null;
}
if (value is IEnumerable<IPublishedContent>) {
var item = (IEnumerable<IPublishedContent>)value;
return item.Where(x => x != null).Select(val => val.ToJson());
}
if (value is IPublishedContent) {
var node = (IPublishedContent)value;
return node.ToJson();
}
return value;
}
public static object ToJson(this IPublishedContent content) {
if (content is Image) {
if (UmbracoContext.Current == null) {
return null;
}
return content.ToImageModel();
}
else {
var data = new JObject();
data["id"] = content.Id;
data["name"] = content.Name;
--> data["url"] = content.Url;
data["urlName"] = content.UrlName;
data["createDate"] = content.CreateDate;
data["updateDate"] = content.UpdateDate;
foreach (var prop in content.Properties) {
if (prop.HasValue && prop.PropertyTypeAlias != "seoSettings") {
--> var result = ConvertType(prop.Value);
if (result != null) {
var alias = prop.PropertyTypeAlias;
var type = result.GetType();
if (type.IsPrimitive || type == typeof(string)) {
data[prop.PropertyTypeAlias] = (dynamic)result;
}
else if (result is IEnumerable<dynamic>) {
data[prop.PropertyTypeAlias] = JArray.FromObject(result);
}
else {
data[prop.PropertyTypeAlias] = JObject.FromObject(result);
}
}
}
}
return data;
}
}
The ToFeedData method is what is the entry point for this and it works fine. However, when it calls ToJson, the UmbracoContext isn't always passed down, which causes content.Url to throw an error about the missing context.
I added arrows at the two points where I encountered errors. The first arrow is where a null reference error sometimes happens complaining that UmbracoContext.Current is null. The second also complains about a null value, and I'm pretty sure the reason is the same. It's triggering the TextValueConverter, which calls ParseInternalLinks, which encounters the same issue generating urls as the first error.
This didn't used to be an issue. I believe it happened around the time I added hostnames to the root nodes (the site has multiple root nodes). I tried removing them, but unfortunately it didn't help.
Any ideas on how to resolve this? I know it's not because of nested content, as the node that throws the error is tied to an actual node.
I found the cause of the issue. The problem occurred when I removed all allowed templates from one of the doc types that was being run through this logic. After republishing the affected nodes, their template is still assigned to the old value. Since that template is no longer allowed for that doc type, it caused errors when trying to get the url and the id check I have in place failed to prevent this because the template wasn't set to 0.
As for why I did this, it made it so that the affected nodes throw a 404 when visited, as they aren't navigable pages. I've filed a bug report here about this issue. I reenabled the template on the doc type as a workaround in the meantime.
Actually turns out that wasn't the cause, though it is an issue. Made that change and it loaded successfully the first time I tried, but it failed on later attempts.
I do have the api collecting the data asynchronously, so it likely is running on another thread. I'm a little unsure on how the UmbracoContext should be passed in, as UmbracoContext.Current is readonly. However, I'm pretty sure that's not the problem, as the context exists immediately before the ToJson method is called.
I think the problem may have been the following line
return item.Where(x => x != null).Select(val => val.ToJson());
I converted that to
var result = new JArray();
foreach (var entry in item.Where(x => x != null)) {
result.Add(entry.ToJson());
}
return result;
and it is reliably working again. Not sure why the linq clause would impact the context though.
The difference between the two solutions is simple: the linq returns a statement that has not been executed, and the other one returns a prepared list.
The .Select(...) is only run when you actually invoke it, either by calling .ToList() / .ToArray() or similar, og when you start iterating it.
Why is UmbracoContext.Current becoming null when calling static helper from inside UmbracoApiController?
Umbraco Version: 7.12.3
I have a few static methods that I use to convert IPublishedContent to something that can be returned as json, but for some reason, one of the methods loses the umbracoContext. I know they start out with it, as ToFeedData doesn't ever throw an error (it's called from beneath an UmbracoApiController). See the below code
The ToFeedData method is what is the entry point for this and it works fine. However, when it calls ToJson, the UmbracoContext isn't always passed down, which causes content.Url to throw an error about the missing context.
I added arrows at the two points where I encountered errors. The first arrow is where a null reference error sometimes happens complaining that UmbracoContext.Current is null. The second also complains about a null value, and I'm pretty sure the reason is the same. It's triggering the TextValueConverter, which calls ParseInternalLinks, which encounters the same issue generating urls as the first error.
This didn't used to be an issue. I believe it happened around the time I added hostnames to the root nodes (the site has multiple root nodes). I tried removing them, but unfortunately it didn't help.
Any ideas on how to resolve this? I know it's not because of nested content, as the node that throws the error is tied to an actual node.
Just pass the context, instead of relying on it the way you are now.
You need to control your dependencies.
Are you by any chance running this is in a thread?
I found the cause of the issue. The problem occurred when I removed all allowed templates from one of the doc types that was being run through this logic. After republishing the affected nodes, their template is still assigned to the old value. Since that template is no longer allowed for that doc type, it caused errors when trying to get the url and the id check I have in place failed to prevent this because the template wasn't set to 0.
As for why I did this, it made it so that the affected nodes throw a 404 when visited, as they aren't navigable pages. I've filed a bug report here about this issue. I reenabled the template on the doc type as a workaround in the meantime.
Actually turns out that wasn't the cause, though it is an issue. Made that change and it loaded successfully the first time I tried, but it failed on later attempts.
I do have the api collecting the data asynchronously, so it likely is running on another thread. I'm a little unsure on how the UmbracoContext should be passed in, as UmbracoContext.Current is readonly. However, I'm pretty sure that's not the problem, as the context exists immediately before the ToJson method is called.
I think the problem may have been the following line
I converted that to
and it is reliably working again. Not sure why the linq clause would impact the context though.
The difference between the two solutions is simple: the linq returns a statement that has not been executed, and the other one returns a prepared list.
The .Select(...) is only run when you actually invoke it, either by calling .ToList() / .ToArray() or similar, og when you start iterating it.
is working on a reply...