Multidimensional Lists - less Umbraco, more C# to be honest.
Hiya!
So, I'm a bit of a C# noob and I'm having some issues getting a multidimensional list to do quite what I want, hoping that some kind soul can point me in the right direction.
I am able to iterate through each image in my media library and add values from those that meet a filename condition to a List inside a List, but I'd like to make the last element of the inner List into another List.
In more natural language what I'd like to achieve is a List of copyright holder names (string), with an external url (string), and a List of all the urls for their images on my site (that meet the filename condition).
I'll be using all this for a dynamically updating page that has a grid of all the copyright holder's name/external url, with the local urls populating a thumbnail gallery. I suppose I might even want it to be a Dictionary so I can pass two local urls for srcset purposes (but I guess the syntax is the same?).
I'll attempt to pseudocode out what I'd like my list structure to be:
List<string[]> XSXS = new List,<string, string, string, List[]>
I know the above is nonsense code; I just can't for the life of me figure out how/if I can declare that final List.
I am completely open to the idea that this is totally the wrong way to go about it and would gladly take suggestions otherwise - it wouldn't be the first time!
Here's my code as it stands, which currently is overwriting the data that I'd like to put in a List to a single element:
//set globals
int i;
int j;
bool first = true;
//get all images from media library
var allMedia = Umbraco.MediaAtRoot().DescendantsOrSelfOfType("image");
//declare list of lists
List<string[]> XSXS = new List<string[]>();
//iterate through each image
foreach (var image in allMedia)
{
//declare locals
var imageName = image.Name;
String copyright = "";
String copyrightUrl = "";
//extract metadata
var physicalPath = Server.MapPath(image.Url);
var directories = ImageMetadataReader.ReadMetadata(physicalPath);
var IptcDir = directories.OfType<MetadataExtractor.Formats.Iptc.IptcDirectory>().FirstOrDefault();
if (IptcDir != null)
{
var descriptor = new MetadataExtractor.Formats.Iptc.IptcDescriptor(IptcDir);
copyright = descriptor.GetCopyrightNoticeDescription();
copyrightUrl = descriptor.GetSourceDescription();
}
//iterate through XS images and check metadata against XSXS List to qualify if to be added as a unique entry or append an existing entry
bool check = false;
if (imageName.EndsWith("XS"))
{
if (first)
{
XSXS.Add(new[] { copyright, copyrightUrl, image.Url });
first = false;
}
else
{
//iterate through XSXS List to check if this metadata exists
for (i = 0; i < XSXS.Count(); i++)
{
//no match - continue
if (!XSXS[i][0].Contains(copyright))
{
continue;
}
//is match - append
else
{
//append existing entry
XSXS[i][2] = image.Url; //this should be adding to a new list rather than overwriting
check = true;
break;
}
}
//create unique entry as none found in XSXS list
if (!check)
{
XSXS.Add(new[] { copyright, copyrightUrl, image.Url });
}
}
}
}
I'm going to be doing some guessing here of what your object XSXS is supposed to contain as I'm not 100% sure I understand your requirements ... I might be a bit wrong but hopefully you can pick through and amend.
You need an object in that list - a List can only contain a list of a items.
On the left hand side of your declaration for XSXS you have a list of string arrays - here each string array in the list is the item(s) in the list. On the right hand side you're trying to create a list with lots of "things" - we need this thing to be a single thing and we need both the left and right side to match! This "thing" can be a bespoke object/class where you can put all your types of strings and the like.
So you need to change
List<string[]> XSXS = new List,<string, string, string, List[]>
to:
List<XSXSItem> XSXS = new List<XSXSItem>();
or easier - we can shortcut this to
var XSXS = new List<XSXSItem>():
and rely on the right side to do the declaration and typesetting effectively.
Then it looks like this XSXS item can contain none-to-many image media items (this is the bit I'm slightly unsure of and you might need to redesign this object). So we have a list of copyright items and within each one we have a list of images that have a matching copyright?!
So how do we go about defining XSXSItem so you can add the attributes like copyrightUrl that you seem to need and also add an image to a variable sized list. Easy, just create a new class to store this in which also contains a list of another class.
Now I'm guessing that you're doing all this in a Razor Umbraco View and haven't quite got into creating Models, Views, Controllers and compiling code and the like .... that's fine! Let's do something for now that is not recommended for the long haul but gets you started and put the classes inside the view.
You can do this by just pasting the following at the bottom of your razor view. I know you're going to clean this up eventually and put this into it's own Model file and keep the c# purists happy but this will work for now:
@functions {
public class XSXSItem
{
public XSXSItem()
{
// this is in the constructor of the class to "init" a new list so you can just start adding image items to the list in your code straight away
XSXSImageItems = new List<XSXSImageItem>();
}
public string Copyright { get; set; }
public string CopyrightUrl { get; set; }
public List<XSXSImageItem> XSXSImageItems { get; set; }
}
public class XSXSImageItem
{
public string ImageUrl { get; set; }
public int ImageId { get; set; }
public string ImageName { get; set; }
}
}
Right so we've now got a class where we can store your XSXS Items and their associated media items. I can't test your entire code as I don't have the libraries you have but I think you need something like:
// get all images from media library
var allMedia = Umbraco.MediaAtRoot().DescendantsOrSelfOfType("image");
// declare list to hold xsxs media items and their children
var XSXS = new List<XSXSItem>();
//iterate through each image
foreach (var image in allMedia)
{
//declare locals
var imageName = image.Name;
string copyright = "";
string copyrightUrl = "";
//extract metadata
var physicalPath = Server.MapPath(image.Url);
var directories = ImageMetadataReader.ReadMetadata(physicalPath);
var IptcDir = directories.OfType<MetadataExtractor.Formats.Iptc.IptcDirectory>().FirstOrDefault();
if (IptcDir != null)
{
var descriptor = new MetadataExtractor.Formats.Iptc.IptcDescriptor(IptcDir);
copyright = descriptor.GetCopyrightNoticeDescription();
copyrightUrl = descriptor.GetSourceDescription();
}
//iterate through XS images and check metadata against XSXS List to qualify if to be added as a unique entry or append an existing entry
bool check = false;
if (imageName.EndsWith("XS"))
{
// check if it's in the list already by using Linq to search the copyright for a match. The .FirstOrDefault is basically give me the item or Null
var existingItem = XSXS.Where(x => x.Copyright == copyright).FirstOrDefault();
if (existingItem == null)
{
var newItem = new MyXSXSItem
{
Copyright = copyright,
CopyrightUrl = copyrightUrl
};
// add the image
newItem.XSXSImageItems.Add(new XSXSImageItem
{
ImageId = image.Id,
ImageUrl = image.Url,
ImageName = image.Name
});
// add this new item to our master XSXS list
XSXS.Add(newItem);
}
else
{
// base item is there - just add an image to the lsit
existingItem.XSXSImageItems.Add(new XSXSImageItem
{
ImageId = image.Id,
ImageUrl = image.Url,
ImageName = image.Name
});
}
}
}
Steve,
not only did you did understand my requirements, you absolutely nailed it! Thank you so much!
As a hands-on type of person that was actually just the kind of simplified intro to MVC that I've been looking for, and has given me lots to think on in terms of how I can refactor a large proportion of my website and cut out a load of passing data attributes over to JS to have JS populate the HTML.
I can already see how I could add a second collection of urls for srcset purposes, or loading a full size image in a modal. So that's super useful.
Anyway, here's the end result.
As a pointer. This is likely to be an expensive (in terms of processing required) operation. You should put this into a partial view and then cache that partial view.
It's easier than it sounds. Basically just create a partial view with the same logic then call it from your page.
Multidimensional Lists - less Umbraco, more C# to be honest.
Hiya!
So, I'm a bit of a C# noob and I'm having some issues getting a multidimensional list to do quite what I want, hoping that some kind soul can point me in the right direction.
I am able to iterate through each image in my media library and add values from those that meet a filename condition to a List inside a List, but I'd like to make the last element of the inner List into another List. In more natural language what I'd like to achieve is a List of copyright holder names (string), with an external url (string), and a List of all the urls for their images on my site (that meet the filename condition). I'll be using all this for a dynamically updating page that has a grid of all the copyright holder's name/external url, with the local urls populating a thumbnail gallery. I suppose I might even want it to be a Dictionary so I can pass two local urls for srcset purposes (but I guess the syntax is the same?).
I'll attempt to pseudocode out what I'd like my list structure to be:
I know the above is nonsense code; I just can't for the life of me figure out how/if I can declare that final List.
I am completely open to the idea that this is totally the wrong way to go about it and would gladly take suggestions otherwise - it wouldn't be the first time!
Here's my code as it stands, which currently is overwriting the data that I'd like to put in a List to a single element:
Thanks in advance!
Hi,
I'm going to be doing some guessing here of what your object XSXS is supposed to contain as I'm not 100% sure I understand your requirements ... I might be a bit wrong but hopefully you can pick through and amend.
You need an object in that list - a List can only contain a list of a items.
On the left hand side of your declaration for XSXS you have a list of string arrays - here each string array in the list is the item(s) in the list. On the right hand side you're trying to create a list with lots of "things" - we need this thing to be a single thing and we need both the left and right side to match! This "thing" can be a bespoke object/class where you can put all your types of strings and the like.
So you need to change
to:
or easier - we can shortcut this to
and rely on the right side to do the declaration and typesetting effectively.
Then it looks like this XSXS item can contain none-to-many image media items (this is the bit I'm slightly unsure of and you might need to redesign this object). So we have a list of copyright items and within each one we have a list of images that have a matching copyright?!
So how do we go about defining XSXSItem so you can add the attributes like copyrightUrl that you seem to need and also add an image to a variable sized list. Easy, just create a new class to store this in which also contains a list of another class.
Now I'm guessing that you're doing all this in a Razor Umbraco View and haven't quite got into creating Models, Views, Controllers and compiling code and the like .... that's fine! Let's do something for now that is not recommended for the long haul but gets you started and put the classes inside the view.
You can do this by just pasting the following at the bottom of your razor view. I know you're going to clean this up eventually and put this into it's own Model file and keep the c# purists happy but this will work for now:
Right so we've now got a class where we can store your XSXS Items and their associated media items. I can't test your entire code as I don't have the libraries you have but I think you need something like:
Steve, not only did you did understand my requirements, you absolutely nailed it! Thank you so much!
As a hands-on type of person that was actually just the kind of simplified intro to MVC that I've been looking for, and has given me lots to think on in terms of how I can refactor a large proportion of my website and cut out a load of passing data attributes over to JS to have JS populate the HTML.
I can already see how I could add a second collection of urls for srcset purposes, or loading a full size image in a modal. So that's super useful. Anyway, here's the end result.
Happy to help.
As a pointer. This is likely to be an expensive (in terms of processing required) operation. You should put this into a partial view and then cache that partial view.
It's easier than it sounds. Basically just create a partial view with the same logic then call it from your page.
https://our.umbraco.com/documentation/reference/templating/mvc/partial-views
HTH
Steve
is working on a reply...