Restrict access to data in OData $expand operation - c#

I have two entities from my database exposed in an ASP.NET WebApi 2 OData: service Employee and Activity. For simplicity, let's assume they look like this:
public class Employee {
public int Id { get; set; }
public string Type { get; set; }
}
public class Activity {
public int Id { get; set; }
public int EmployeeId { get; set; }
public virtual Employee OpenedBy { get; set; }
}
Please note that the OpenedBy property corresponds to a navigation property, e.g. I can run the following OData query:
GET http://localhost/odata/Activities?$expand=OpenedBy
I would like to block certain Employee types from being shown in OData. Let's assume I can't do this on the data source, so I have to do it in code.
What I've done so far is to block these types in the EmployeesController (inherits from EntitySetController):
[Queryable]
public override IQueryable<Employee> Get() {
return dbContext.Employees.Where(e => e.Type != "Restricted").AsQueryable();
}
[Queryable]
protected override Employee GetEntityByKey([FromODataUri] int key) {
var employee = dbContext.Employees.Find(key);
if (employee == null || employee.Type == "Restricted") {
throw new ODataException("Forbidden");
}
return employee;
}
This works fine. However, I noticed that if I run the query:
GET http://localhost/odata/Activities?$expand=OpenedBy
I do not hit the code in the Employees controller and consequently the restricted employee records are visible. What is a good way to prevent this from happening?

In this case: http://localhost/odata/Activities?$expand=OpenedBy
I think you should change the code in ActivitiesController, $expand will hit Get Activity method there.
If you do not want to expand the OpenedBy all the time, you can add an attribute:
[NotExpandable]
Hope this can help :)

Since you say "I noticed that if I run the query" I get the impression that this is a side-effect that you are happy to restrict in all circumstances. If this is the case this article by Mike Wasson could be of use to you.
In that article he suggests two methods to restrict odata access to a property:
An attribute on your model
Programmatically removing it from your EDM
I didn't try the first and I'm not sure which namespace or libraries you would need to do it but, in the case of the question it would look like this:
public class Activity {
public int Id { get; set; }
public int EmployeeId { get; set; }
[IgnoreDataMember]
public virtual Employee OpenedBy { get; set; }
}
I have used the second method and this would look something like this for the example given in the question:
var activities = modelBuilder.EntitySet<Activity>("Activities");
activities.EntityType.Ignore(a => a.OpenedBy);
I have restricted some navigational Collections this way and it works very well.

Related

Web Api, Update DB, connection reset 200ok

I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies
private readonly BackendContext _context;
public CompaniesController(BackendContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
return _context.Companies;
}
I get
I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?
Additional code:
Context
public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
{
public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Vendor> Vendors { get; set; }
public DbSet<VendorToProduct> VendorToProducts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceItem> InvoiceItems { get; set; }
}
Model
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
UPDATE
I've added some values to the table and I got the response of the first company:
[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}
BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key.
UPDATE
Error:
Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:
I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices
While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.
Project your result into an explicitly defined shape and return only what you want to return:
var result = _context.Companies
.Select(c => new
{
c.ID,
c.Name,
c.Description,
c.CustomerID
})
.ToList();
This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.
There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.
As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:
public class CompanyViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CustomerID { get; set; }
}
and returning that:
return _context.Companies
.Select(c => new CompanyViewModel
{
ID = c.ID,
Name = c.Name,
Description = c.Description,
CustomID = c.CustomerID
})
.ToList();
But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.
Another option when that's the case could be to universally set your JSON serialization to ignore circular references:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore );
But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.

The instance of entity type 'XY' cannot be tracked because another instance with the same key value for {'XId', 'YId'} is already being tracked

Sorry to raise this one again - I see it plenty of times on here but I am still puzzled about it in my case, especially because I seem to be able to 'overcome' it, but I'm not sure I like how, and I would love to understand why.
I have a many to many relationship between Staff and Departments, with the StaffDepartment table being set up with a composite key like so:
modelBuilder.Entity<StaffDepartment>()
.HasKey(sd => new { sd.StaffId, sd.DepartmentId });
I suspect that must be something to do with the exception I sometimes get:
InvalidOperationException: The instance of entity type 'StaffDepartment' cannot be tracked because another instance with the same key value for {'StaffId', 'DepartmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I say sometimes because, strangely, if I change the department in my staff details page and update, it goes through fine, but when I leave it the same (maybe just changing the staff name for example) I receive this exception.
I tried implementing the various suggestions on this
similar but apparently different SO discussion
and all sorts of ways of tinkering with my repository update method, and strangely enough this is the only way I have got it working:
public async Task UpdateStaff(StaffDto staff)
{
var staffToUpdate = await _db.Staff
.Include(d => d.Departments)
.ThenInclude(d => d.Department)
.SingleOrDefaultAsync(s => s.Id == staff.Id);
// Without either these two line I get the exception
_db.StaffDepartment.RemoveRange(staffToUpdate.Departments);
await _db.SaveChangesAsync();
_mapper.Map(staff, staffToUpdate);
await _db.SaveChangesAsync();
}
So it seems strange that I should have to call SaveChangesAsync() twice in same method. What is actually happening there? And how would I achieve the suggestion in the error When attaching existing entities, ensure that only one entity instance with a given key value is attached without doing so.
Entity classes look like so:
public class Staff
{
public int Id { get; set; }
...
public ICollection<StaffDepartment> Departments { get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
...
public ICollection<StaffDepartment> Staff { get; set; }
}
public class StaffDepartment
{
public int StaffId { get; set; }
public Staff Staff { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
}
And the staffDto that comes back from the domain etc. is simply:
public class StaffDto
{
public int Id { get; set; }
...
public List<DepartmentDto> Departments { get; set; }
}
The repositories are all registered as transient i.e.
services.AddTransient<ICPDRepository, CPDRepository>();

Creating a Blog Comments and Reply section using ASP.NET MVC 4 (nested collections)

I'm building a Blog Comment and Reply section and I have these three classes mapped to my DB. The first class holds a collection of related comments to an article, the second class holds a collection of related remarks to the comments:
public class Article
{
public int ArticleID { get; set; }
public byte[] Image { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DatePublished { get; set; }
public string Author { get; set; }
public CategoryTyp Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public int UserID { get; set; }
public string Description { get; set; }
public DateTime CommentDate { get; set; }
public virtual ICollection<Remark> Remarks { get; set; }
}
public class Remark
{
public int RemarkID { get; set; }
public int CommentID { get; set; }
public int ArticleID { get; set; }
public string RemarkDetail { get; set; }
public DateTime RemarkTime { get; set; }
}
And inside my Controller:
public ActionResult GetArticle(int id)
{
var article = db.Articles.Include("Comments").Where(a => a.ArticleID == id).SingleOrDefault();
return View(article);
}
I understand the basis of eager loading but my questions are:
How do you implement it when you're pulling data from multiple related tables?
What is the best practice of populating it to the View? Once I create a View Model how do I stuff the related collections?
1) With multiple related tables you can have two scenarios:
a) Multiple top level relations: you simply add multiple Include statements (I would suggest using lambda expressions instead of strings for this, to avoid typos).
db.Articles
.Include(a=>a.Comments)
.Include(a=>a.SomethingElse)
.FirstOrDefault(a=>ArticleID==id); // Side note: I would suggest this instead of your Where plus SingleOrDefault
For these scenarios I always use a helper method like this one.
b) Multiple nested related entities:
db.Articles
.Include(a=>a.Comments.Select(c=>c.Remarks)
.FirstOrDefault(a=>ArticleID==id);
2) It's a bit up to you how you pass the data to the views. One best practice I can tell you is that you shouldn't let views lazy load any dependant entities or collections. So your use of Include is correct, but I would even suggest to remove the virtual (deactivate lazy loading) to avoid missing an Include by accident.
Regarding the ViewModels you mention, you are actually not using view models, but your data models. This is OK in most cases, unless you need to format the data somehow or add extra information. Then you would need to create a View Model and map it from the data coming from EF.
Another scenario would be if you used WebAPI or an Ajax Action. In that case, I would suggest to use a DTO (equivalent to a ViewModel) to be able to better control the data returned and its serialization.
One last comment about ViewModels is that if you have heavy entities but you only need a few properties, a good choice is to use Projections, to instruct EF to only load the required properties, instead of the full object.
db.Articles
.Include(a=>a.Comments)
.Select(a=>new ArticleDto { Id = a.ArticleID, Title = a.Title })
.ToListAsync();
This will translate to a "SELECT ArticleID, Title FROM Articles", avoiding returning the article bodies and other stuff that you might not need.
You can chain the relationships with Include. For example:
var article = db.Articles.Include("Comments.Remarks").Where(a => a.ArticleID == id).SingleOrDefault();
I'm not sure what you mean by your second question, though. By issuing this query you already have all the comments and all the remarks for those comments. Therefore, you can access them off of the article instance out of the box:
foreach (var comment in article.Comments)
{
...
foreach (var remark in comment.Remarks)
{
...
}
}
How you handle that with your view model is entirely up to you. You could map the comments/remarks to view models of their own, set them directly on the view model, etc. That's all down to what the needs of your application are, and no one but you can speak to that.

NHibernate: (Fluent) Mapping / Querying based upon a Getter that accesses an already mapped collection property

I don't know how to phrase this properly. I'm working with a pre-existing domain, where certain entities can contain children that are versioned. The children are responsible for their own version number, but ultimately, this version number only makes sense in context of the attached parent entity.
public class Blog
{
public virtual IList<VersionedItem> VersionedItems { get; set; }
public virtual CurrentVersionedItem {
get {
return VersionedItems.OrderByDescending(x => x.Version).FirstOrDefault();
}
}
}
public class VersionedItem
{
public virtual Blog { get;set; }
public virtual int Version { get; set; }
public virtual string Content { get; set; }
public virtual int SomeNumber { get; set; }
}
And what I'd like to achieve:
var blogs = Session.Query<Blog>(x=> x.CurrentVersionedItem.SomeNumber == 5)
While the IQueryable provider of NHibernate is forgiving, I will not eat everything. Is there a way to define a (fluent) mapping that resolves the "CurrentVersionedItem" property properly?
I'm also aware of the fact CurrentVersionedItem could potentially return null in this scenario (if it worked in the first place).
Why won't you do like this:
var item = session.Query<VersionedItem>().FirstOrDefault(q => q.SomeNumber == 5);
Blog blog;
if (item != null)
blog = item.Blog;

Use EF to select object and its properties

I am working in EF 4.3.1 and everything is going pretty well on the Insert side. But now I am trying to pull the objects I inserted back out to show in a View in my MVC3 project and having problems.
In my DAL, I have the following method to get a Church object from the database:
public virtual TEntity GetById(object id)
{
return _db.Find(id);
}
The object itself looks like this:
public class Church
{
//Basic Properties
public int Id { get; set; }
public string Name { get; set; }
public string Website { get; set; }
... (some properties)
//Church Contacts and Services
public List<Contact> Contacts { get; set; }
public List<Service> Services { get; set; }
....
}
Notice the last two Properties: Contacts and Services. Each is a 1:Many table within my database. It appears that Find() does not return any of this, though. It just sets the two properties to null.
The actual entries in the database look just how I would expect them to. Each Church has a couple contacts and services associated with them. Yet, it never maps back to the model...
Any thoughts?
EDIT Yeah, looks like it wasn't loading those properties until I explicitly told it to. I modified my query by adding this to my ChurchRepository (inherits from GenericRepository)
public override Church GetById(object id)
{
return _db.Include("Contacts").Include("Services").FirstOrDefault(c => c.Id == (int)id);
}
You should mark navigation properties as virtual or you could also include them without lazy loading that is best practice.
Try this
db.Churces.Include(entity=>entity.Services).
Include(entity=>entity.Contacts).SingleOrDefault(entity=>entity.Id == id)
You may have to import System.Data.Entity namespace for Include extension method.

Categories

Resources