0

I'm working on part of my project which allows users to upload photos and add an entity to the database. This part of the schema is a one-to-many relationship where one user has many uploads. This was created using code first with the model shown below.

User Model:

   public class UserModel
    {
        public int Id { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [Required]
        public string DisplayName { get; set; }

        public virtual ICollection<PetPhotoModel> Uploads
        { get; set; }

        public virtual ICollection<RatingModel> Ratings { get; set; }
    }

In Controler this method is called just after the user uploads a photo:


        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> UploadPhoto([Bind("Id,PetName,Title,Description,ImageFile")] PetPhotoModel petPhotoModel)
        {
            if (ModelState.IsValid)
            {
                //Save image to wwwroot/image
                string wwwRootPath = _hostEnvironment.WebRootPath;
                string fileName = Path.GetFileNameWithoutExtension(petPhotoModel.ImageFile.FileName);
                string extension = Path.GetExtension(petPhotoModel.ImageFile.FileName);
                petPhotoModel.ImageName = fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;

                var folderPath = wwwRootPath + "\\UploadedPhotos\\";
                CreateFolderIfNotExist(folderPath);
                string path = Path.Combine(folderPath, fileName);

                using (var fileStream = new FileStream(path, FileMode.Create))
                {
                    await petPhotoModel.ImageFile.CopyToAsync(fileStream);
                }

                //GetLoggedInUser() gets the current user by id from the db context
                var user = GetLoggedInUser();

                user.Uploads.SetDefaultsAndAdd(petPhotoModel);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(petPhotoModel);
        }
    }

I have also written an generic extension method which instantiates a new List<T>if the ICollection property is null, converts the nulls in the object to something usable and adds it to the collection. In this case it would instantiate a List<PetPhotoModel> to the UserModel's Uploads property and add the PetPhotoModel object passed in from the contoler. I have debugged through this method and appears to work correctly with the end result of the list containing the model with correct data.

Extension Method:

   public static void SetDefaultsAndAdd<T>(this ICollection<T> collection, T model)
        {
            if (collection == null)
                collection = new List<T>();

            PropertyInfo[] properties = typeof(T).GetProperties();
            foreach (var info in properties)
            {
                // if a string and null, set to String.Empty
                if (info.PropertyType == typeof(string) &&
                   info.GetValue(model) == null)
                {
                    info.SetValue(model, String.Empty);
                }
                if (info.PropertyType == typeof(double) &&
                info.GetValue(model) == null)
                {
                    info.SetValue(model, 0);
                }
            }

            collection.Add(model);
        }
    }

GetLoggedInUser() in ControllerBase:

        protected UserModel GetLoggedInUser()
        {
            var userId = HttpContext.Session.GetInt32("Id");
            return _context.Users.FirstOrDefault(u => u.Id == userId);
        }

This property is null when get despite data existing with the matching user Id enter image description here enter image description here

This property is still null when changed by the extension method enter image description here

  • 2
    You are just filling collection in local variable, you have to return newly instanciated collection and assign it back to property. – Alexey Rumyantsev Feb 03 '21 at 15:30
  • Could you include the `GetLoggedInUser` method please. I think your problem is that you're not including the `Uploads` collection as part of your query. If you're using EF then adding something like this in your user query should help `.Include(u => u.Uploads)`. This tells EF to include the users uploads in the data retrieved from the database – Adam T Feb 03 '21 at 16:22
  • @AdamT I have added GetLoggedInUser() to the question I tried adding .Include(u => u.Uploads) but i got error 'UserModel' Does not contain definition for 'Include' – Robin Sanders Feb 03 '21 at 16:35

3 Answers3

1

The problem is that you can't change 'this' inside an extension method in case of reference types.

In case of value type you do something like this, (but this is really ... uncommon):

public static class IntEx
{
    public static void Change(this ref int value, int newValue)
    {
        value = newValue;
    }
}

Note the ref keyword there. Also be aware that this works only in C# 7.2 or in greater C# version.

int x = 7;
x.Change(6);
Console.WriteLine(x); //Will print 6

In case of reference type you can't change the reference itself. But you can change the referred object.
So, in your case your code tried to change the reference itself not the referenced value.

So, if you do the null check prior the extension method call, then it will be fine.

public static class CollectionEx
{
    public static void AddData<T>(this ICollection<T> coll, T data)
    {
        coll.Add(data);
    }
}
List<int> coll = null;

//...

if(coll == null)
    coll = new List<int>();
Console.WriteLine(coll.Count); //Will print out 0

coll.AddData(5);
Console.WriteLine(coll.Count); //Will print out 1
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
1

Have you tried...

return _context.Users.Include(e=>e.Uploads).FirstOrDefault(u => u.Id == userId)

Cheers.

0

@Peter Csala and @Uriel Gonçalves Both of these answers are correct since this question presents 2 problems. Thanks guys :)

I made the following changes

        protected UserModel GetLoggedInUser()
        {
            var userId = HttpContext.Session.GetInt32("Id");
            return _context.Users
                .Include(e => e.Uploads)
                .Include(e => e.Ratings)
                .FirstOrDefault(u => u.Id == userId);
        }

No longer using extention method and instead added this method in my ControllerBase called from the controller as SetDefaultsAndAdd(user.Uploads, petPhotoModel)

        protected ICollection<T> SetDefaultsAndAdd<T>(ICollection<T> collection, T model)
        {
            if (collection == null)
                collection = new List<T>();

            PropertyInfo[] properties = typeof(T).GetProperties();
            foreach (var info in properties)
            {
                // if a string and null, set to String.Empty
                if (info.PropertyType == typeof(string) &&
                   info.GetValue(model) == null)
                {
                    info.SetValue(model, String.Empty);
                }
                if (info.PropertyType == typeof(double) &&
                info.GetValue(model) == null)
                {
                    info.SetValue(model, 0);
                }
            }

            collection.Add(model);
            return collection;
        }