ASP.NET MVC ObjectDisposedException when trying to pass id - c#

What I am trying to do is create a function that will pass through the id of the user and then will load up a page that displays all the applications that a user has made to each driving instructor.
I have already made a function that will display all applications from the database regardless of who created the application. I have also made a function that allows users to create applications. I have also created a field in the application table named User_UserId so when an application is created and commited to a database, it will add the specific userId that created the application.
My question is, how would I make it so that when you clicked on show all applications on a specific users account, it would only pull up applications that have that User_UserId attached. Could I add UserId to the class that stores the input form and set the UserId in there to the UserId that is creating the application?
My attempts so far all end up with this error in the User Controller on line - IList<Application> applications = user.Applications.ToList();:
An exception of type 'System.ObjectDisposedException' occurred in EntityFramework.dll but was not handled in user code
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I have a function that allows me to view applications by Driving instructors and that works fine, it uses a similar method where it asks for the specific driving instructor in the create form. Then I have a function that lists the application by driving instructor.
Here is the code. Some main points to make are;
Primary key UserId in user class is set as a string
In the class that stores the input data, there is no UserId field as that is passed in the method.
The field in Applications table that stores the user Id is named User_UserId
Please inform me if there is any more pieces of code you require.
User Controller that opens the view for GetApplications:
public ActionResult GetApplications(string id)
{
User user = userService.GetUser(id);
IList<Application> applications = user.Applications.ToList();
return View(applications);
}
IDAO/DAO Files:
IList<Application> GetApplications(string id, XContext context);
public IList<Application> GetApplications(string id, XContext context)
{
User user = context.Users.Find(id);
return user.Applications.ToList();
}
IService/Service Files:
IList<Application> GetApplications(string id);
public IList<Application> GetApplications(string id)
{
using (var context = new XContext())
{
IList<Application> Applications;
Applications = userDAO.GetApplications(id, context);
return Applications;
}
}
HTML view for GetUser that leads to GetApplications, Model uses User class:
#Html.ActionLink("Show Applications", "GetApplications", "User") ||
Context class:
public class XContext : DbContext
{
public XContext() : base("XContext")
{
Database.SetInitializer(new XInitializer());
}
public DbSet<User> Users { get; set; }
public DbSet<Application> Applications { get; set; }
public DbSet<Instructor> Instructors { get; set; }
}
UserService GetUser function:
public User GetUser(string id)
{
using (var context = new XContext())
{
return userDAO.GetUser(id, context);
}
}
UserDAO GetUser function:
public User GetUser(string id, XContext context)
{
return context.Users.Find(id);
}
User Class:
using System.ComponentModel.DataAnnotations;
public class User
{
[Key]
public string UserId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public virtual ICollection<Application> Applications { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
Application Class:
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string Level { get; set; }
public string Manual { get; set; }
public string InstructorName { get; set; }
}
I have set breakpoints on the controller function that breaks and here are the results:
User user = userService.GetUser(id);
id = "1"
user = null
IList<Application> applications = user.Applications.ToList();
id = "1"
applications = null
When I continue and get the error thrown, I am given
id = "1"
applications = null
user.Application = null

Your Application class is incorrect. It doesn't have UserId
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string UserId { get; set; }
public string Level { get; set; }
public string Manual { get; set; }
public string InstructorName { get; set; }
}
And I think you have the same bug in your Instructor class
You have the bugs in your DAO.
Replace
public User GetUser(string id, XContext context)
{
return context.Users.Find(id);
}
with
public User GetUser(string id, XContext context)
{
return context.Users.Include(i=> i.Applications).FirstOrDefault(i=>i.UserId==id);
}
And replace
public IList<Application> GetApplications(string id, XContext context)
{
User user = context.Users.Find(id);
return user.Applications.ToList();
}
with
public IList<Application> GetApplications(string id, XContext context)
{
return context.Applications.Where(i=>i.UserId==id).ToList();
}
User service code:
public List<Application> GetApplications(string id)
{
using (var context = new XContext())
{
return userDAO.GetApplications(id, context);
}
}
Your controller code:
public ActionResult GetApplications(string id)
{
var applications = userService.GetApplications(id);
return View(applications);
}

Related

Add an object that has a many to many relationship with the current user

I have a project and user Model that inherits the default identity class.
These two share a many to many relationship.
public class Project
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<AppUser> users { get; set; }
}
public class AppUser : IdentityUser
{
public string DisplayName { get; set; }
public ICollection<Project> projects { get; set; }
}
In my projects controller I display all the projects that belong to the current user;
(I check for the user with the user id)
[Authorize]
public IActionResult Index()
{
var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
IEnumerable<Project> objProjectList = _unitOfWork.Project.Where(userId);
return View(objProjectList);
}
and on the post method I'm trying to add the current user to the newly created project object to establish the relation between these two i.e. (this user is a member of this project)
I've used dependency injection to inject usermanager that uses my custom user class (Appuser, which inherits the default scaffolded identity)
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public async Task<IActionResult> CreateAsync(Project obj)
{
if (ModelState.IsValid)
{
AppUser user = await _userManager.GetUserAsync(User);
obj.users.Add(user);
_unitOfWork.Project.Add(obj);
_unitOfWork.Project.Save();
return RedirectToAction("Index");
}
else
{
return View(obj);
}
}
However I'm getting the following error;
System.NullReferenceException: 'Object reference not set to an instance of an object.'
What exactly am I doing wrong? Should I be adding the user some other way?
I'd appreciate any input, thanks.
In your model design:
public ICollection<AppUser> users { get; set; }
ICollection is an interface, you can't use it to add something direcetly, Or you will get NullReferenceException. You can create an instance, and then use it to add what you want.
AppUser user = await _userManager.GetUserAsync(User);
//create an instance.
List<AppUser> users = new List<AppUser>();
users.Add(user);
obj.users = users;
_unitOfWork.Project.Add(obj);
_unitOfWork.Project.Save();
return RedirectToAction("Index");

Blazor + SQLTableDependency + SignalR: Notify specific groups from OnChange event

I have a Blazor application that uses SQLTableDependency to detect database changes, then notify all clients about the change via SignalR. This works but I need a way to be able to detect changes and only notify specific SignalR groups. Because SQLTableDependency doesn't care about who inserted, altered, or deleted a record in the database, I am not sure how to know which group to send the update too. Please see below for more details about my app and what i'm trying to accomplish.
For each customer we setup a new organization. An organization has its own list of assets, and can have multiple users.
Organization.cs
public class Organization
{
public int OrganizationId { get; set; }
public string OrganizationName { get; set; }
public List<Asset> Assets { get; set; }
public List<ApplicationUser> Users { get; set; }
public bool IsDisabled { get; set; }
}
Asset.cs
public class Asset
{
public int AssetId { get; set; }
public string SerialNumber { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public DateTime DateAdded { get; set; }
}
ApplicationUser.cs
public class ApplicationUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public List<Connection> Connections { get; set; }
public string Timezone { get; set; }
}
Connection.cs - I am storing each SignalR Connection in the database.
public class Connection
{
public string ConnectionId { get; set; }
public string UserName { get; set; }
public bool Connected { get; set; }
public string Group { get; set; }
public DateTime ConnectionTime { get; set; }
}
AssetService.cs
public class AssetService : IAssetService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public AssetService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<Asset> AddAssetAsync(Asset asset, string currentUserName)
{
try
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<DataContext>();
if (asset.Device != null)
{
db.Entry(asset.Device).State = EntityState.Modified;
}
asset.DateAdded = DateTime.UtcNow;
await db.Assets.AddAsync(asset);
await db.SaveChangesAsync();
return asset;
}
}
catch (System.Exception ex)
{
throw ex;
}
}
}
AssetHub.cs - SignalR Hub
public class ChatHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IServiceScopeFactory _serviceScopeFactory;
public ChatHub(UserManager<ApplicationUser> userManager, IServiceScopeFactory serviceScopeFactory)
{
_userManager = userManager;
_serviceScopeFactory = serviceScopeFactory;
}
public async Task SendAssetToGroup(string userName, string location, Asset asset)
{
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await _userManager.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == userName);
if (user != null)
{
var group = $"{user.AccountId}-{location}";
await Clients.Group(group).SendAsync("AssetUpdate", user.Email, asset);
}
}
}
public override async Task OnConnectedAsync()
{
var httpContext = Context.GetHttpContext();
var location = httpContext.Request.Query["location"];
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await db.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == httpContext.User.Identity.Name);
if (user != null)
{
var group = $"{user.OrganizationId}-{location}";
var connection = new Connection { Connected = true, ConnectionId = Context.ConnectionId, Group = group, UserName = user.UserName };
await Groups.AddToGroupAsync(connection.ConnectionId, group);
user.Connections.Add(connection);
db.Users.Update(user);
}
}
await db.SaveChangesAsync();
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
if (!string.IsNullOrWhiteSpace(Context.ConnectionId))
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
var connection = await db.Connections.Where(x => x.ConnectionId ==
Context.ConnectionId).FirstOrDefaultAsync();
if (connection != null)
{
await Groups.RemoveFromGroupAsync(connection.ConnectionId, connection.Group);
db.Connections.Remove(connection);
await db.SaveChangesAsync();
}
}
}
await base.OnDisconnectedAsync(exception);
}
}
AssetTableChangeService.cs - Here is where I need help. When SQLTableDependency detects a change to the Assets table, I need to be able to call the SendAssetToGroup Method in the AssetHub. Since users are apart of organizations, I don't want to push the update out to all organizations, I only want to send the update only to users that are apart of the specific organization group.
public class AssetTableChangeService : IAssetTableChangeService
{
private const string TableName = "Assets";
private SqlTableDependency<Asset> _notifier;
private IConfiguration _configuration;
public event AssetChangeDelegate OnAssetChanged;
public StockTableChangeService(IConfiguration configuration)
{
_configuration = configuration;
// SqlTableDependency will trigger an event
// for any record change on monitored table
_notifier = new SqlTableDependency<Asset>(
_configuration.GetConnectionString("DefaultConnection"),
TableName);
_notifier.OnChanged += AssetChanged;
_notifier.Start();
}
private void AssetChanged(object sender, RecordChangedEventArgs<Asset> e)
{
OnAssetChanged.Invoke(this, new AssetChangeEventArgs(e.Entity, e.EntityOldValues));
}
public void Dispose()
{
_notifier.Stop();
_notifier.Dispose();
}
So the flow should look like this.
The user logs in - establishing a connection through SignalR
The connection information is stored in the database.
The connection is added to a SignalR group based on what page the user is connecting from and the OrganizationId.
User creates a new Asset from the UI.
The AddAsset method is called in the Asset Service.
The Asset gets inserted into the database.
The SQLTableDependency detects the change and then calls the AssetChanged handler method.
The AssetChanged handler method calls the OnAssetChanged event.
The AssetHub needs to subscribe to the OnAssetChanged event.
When the OnAssetChanged event is fired, a handler method in the AssetHub needs to call the SendAssetToGroup Method.
When user navigates from the Assets page to another, the SignalR connection is removed from the database, and the connection is removed from the group.
I have everything working up until steps 9 and 10. Is there anyway to make this possible since SQLTableDependency doesn't care about who made the change, so I have no way of looking up the connection group in which the update needs to be pushed too. Any ideas?
When the UI is working with a class for example called : Student
The UI component joins a group called "Student" or "BlahNamespace.Student".
If its a list just the name, if its an entity join the both the name and another group with the ID as a string concatenated
"BlahNamespace.Student:201" In your case you could append the the organization's name as well for finer grain if the db knows this from the entity.
The server can notify the groups as needed depending on the operation.
I inject the hub into the API controller to achieve this.
Personally I would not use the signalr service to transfer the data, keep it light weight, just "signal" the change. The client can then decide how to deal with that. That way the data can be only accessed one way via the API with all the configured security.

How to track identity changes in EF Core?

I have a service in my application which creates a User, and saves it to the database. Here's a method of my service:
public async Task<UserDTO> CreateUserAsync(User newUser)
{
var result = (UserDTO)await _userRepository.CreateUserAsync(newUser);
if (result != null)
{
await _userRepository.SaveChangesAsync();
}
return result;
}
And a method from a UserRepository:
public async Task<User> CreateUserAsync(User newUser) => (await _dbContext.AddAsync(newUser)).Entity;
Here's a User class:
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The problem is when the user is being added via service, UserId is not known yet. It has default value 0, then ef core saves it to a database, finding a proper UserId. But value returned by my methods has no UserId updated - it is still 0, and i would like to return updated value. How to achieve that in a proper way?
newUser WILL have an Id read from the database.
Your code is casting from User to UserDTO, which is unlikely to work.

Retrieving parent/child records (object containing list) from Azure Mobile Services using EF

I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.

Proper design of common repository and context

I have a UserEntity. This user can have a collection of ImageEntity as gallery.
Thus, a UserRepository and UserContext are made for UserEntity and ImageRepository and ImageContext are made for ImageEntity.
When adding an Image to a User's gallery, the code-behind in the UserRepository is adding the image using the ImageRepository to the database (with the user's GUID) and then simply adding it to its collection.
The models:
public class ImageEntity : IEntityBase
{
public DateTime DateCreated { get; set; } = DateTime.Now;
public Guid Id { get; set; } = Guid.Empty;
public bool isActive { get; set; } = true;
public string FullPath { get; set; } = "";
public Guid RelatedTo { get; set; } = Guid.Empty;
}
public class UserEntity: IEntityBase, IFileRelatableEntity
{
...
public ICollection<ImageEntity> RelatedFiles { get; } = new List<ImageEntity>();
}
The repo of User:
public void AddRelatedFiles<T>(Guid p_guid, IEnumerable<IFormFile> p_files) where T : class, IEntityBase, IFileRelatableEntity
{
UserEntity user = GetSpecific<UserEntity>(p_guid);
string relatedWorkAppId = GetSpecific<UserEntity>(user.AssignedToWork).ApplicativeId;
foreach (var file in p_files)
{
// Add using the ImageRepo
ImageEntity relatedFile = m_fileRepository.AddRelatedFile(p_guid, "Users\\" + relatedWorkAppId, file);
// Add to the entity
user.RelatedFiles.Add(relatedFile);
}
}
Problem:
This resulted another creation of SQL Table named ImageEntity only for the images related to UserEntity.
This is redundant
Not scalable, since adding another entity (like JobEntity) will result creation of another table.
How do I enforce?:
relatedTo in the ImageEntity to be FK?
Not creating a special table, but .Includeing using the main table of ImageEntity?
Maybe you have other suggestions?

Categories

Resources