Include inner item when return JSon - c#

I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.

You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.

Related

Entity Framework Get Document by UserId

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; }
}
I also have a project controller where I want to display ALL projects that contain the current user. (projects can have multiple users)
I also want to be able to create projects.
[Authorize]
public IActionResult Index(string id)
{
IEnumerable<Project> objProjectList = _unitOfWork.Project.GetAll();
return View(objProjectList);
}
I started off by passing the user id through an anchor tag like so.
<a class="nav-link text-dark"
asp-area="" asp-controller="Project" asp-action="Index"
asp-route-id="#UserManager.GetUserId(User)">Projects</a>
How do I use the id to get the projects that only contain the user corresponding to the id in my project controller?
How can I use the same id to create a project with the user attached on a post route in the same controller?
Should I avoid passing sensitive data like user id through anchor tags and get the user id some other way?
I'd appreciate any input, thanks.
You can try something like this . But is good idea to use ViewModels ti protect your database. Also, all your logic should be in Service classes not in the controllers. The way You pass the Id is totally fine.
public interface IProjectService
{
IEnumerable<Project> GetAllProjectsByUserId(object userId);
}
public class ProjectService : IProjectService
{
public IEnumerable<Project> GetAllProjectsByUserId(string userId)
{
return _unitOfWork.Project.Where(x => x.users.Any(x =>
x.Id = userId)).ToList();
}
}
Give the Service to the dependency container in the StartUp class
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProjectService , ProjectService >();
}
Then you call the Service in the Controller
private readonly IProjectService projectService;
public ControllerName(IProjectService projectService)
{
this.projectService = projectService;
}
[Authorize]
public IActionResult Index(string id)
{
var viewModel = projectService.GetAllProjectsByUserId(id);
return View(objProjectList);
}
Тhere are more things to do, such as repositories, dtos eg, but this will be a good for the beginning

EF WebAPI Self Referencing Loop

I am getting a self Referencing issue with EF and I'm trying to over come it but still allow the Service to be able to perform a GET passing in {[FromODataUri] int key} a key and return an IQuerable Obj to get the Expanded tables if necessary. Below is a slimmed down version of the tables. Any suggestions on how to handle the situation.
public class People
{
public int PeopleId {get;set;}
public string PeopleName {get;set;}
public int? ProductId{get;set;}
public virtual Product Product{get;set;}
}
The ProductId is a PK in Product but its not required. As per the convention it doesn't have to be Decorated with the PK DataAnnotation overide.
public class Product
{
public Product()
{
PeopleCollection = HashSet<People>();
}
public int ProductId {get;set;}
public string ProductName {get;set;}
public virtual ICollection<People> Peoples{get;set;}
}
In this case I recommend using DTO's or using anonymous objects, for example:
public IHttpActionResult Get() {
var response = db.YourTable.Select(x=>new{
x.PeopleId,
x.PeopleName,
x.ProductId,
Product= new {
x.ProductId,
x.ProductName
}
}).toList();
return Ok(response);
}
Thats how I would do it with anonymous objects, If you want to use DTO's you just need to map them, hope this is what you are looking for.
For just a specific id:
public IHttpActionResult Get(int id) {
var response = db.YourDb.Select(x=>new{
x.PeopleId,
x.PeopleName,
x.ProductId,
Product= new {
x.ProductId,
x.ProductName
}
})Where(x=>x.PeopleId == id).toList();
return Ok(response);
}
Note, this method is using query string parameters
I figured this out after some time. The self referencing issue will come up if you are Inheriting from APIController but if you switch to inherit from ODataController everything works.
So
public class MyController : ApiController
{
..... Bunch of code here
}
To
public class MyController : ODataController
{
..... Bunch of code here
}

How to map users identity and Auditable properties in view model

This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.

How can I send a query to an IQuerable method on an ASP.NET controller?

I am using Entity Framework 6 and trying to get some data from my controller. The data is from a table of words which has a key of WordId (the actual words).
My controller has this method:
[Route("Get")]
public IQueryable<Word> Get()
{
return db.Words;
}
Here’s my Word object:
public class Word
    {
        public string WordId { get; set; } // WordId (Primary key) (length: 20)
        public int CategoryId { get; set; }
    }
Is there a way that I can use this method to just get those words that have a first character between A and E ?
Never return Entity or IQueryables from the controller, instead create a model and return it as mentioned below:
string matchStr = "abcde";
[Route("Get")]
public List<WordModel> Get()
{
return db.Words.Where(p=>matchStr.ToUpper().Contains(p.UniqueID.ToUpper().FirstOrDefault())).Select(p=>new WordModel(){
WordId = p.WordId,
CategoryId = p.CategoryId
}).ToList();
}
Create a model class WordModel, as
public class WordModel
{
public string WordId { get; set; }
public int CategoryId { get; set; }
}
I agree with Sophia. It is a good idea to expose view models (designed based on the view) rather than the actual entities. Lets assume WordVm is that in your case. The return type form the controller action will be a List. Add a reporsitory or a manager class that will be called from your action method that returns your result. I am creating a manager that has access to the db context class with the Word entity.
[Route("Get")]
public List<WordVm> Get()
{
var manager = new WordsManager()
return manager.GetWords();
}
public class WordsManager
{
public List<WordVm> GetWords()
{
return repo.Words.Where(a => {
var t = a.Trim().ToUpper().Substring(0, 1)[0];
return t >= 'A' && t <= 'E';
}).ToList();
}
}
Thank you,
Soma.

Data from Collection is missing from database (ASP.NET MVC)

For simplicity and testing, i'm doing this from a new project and then apply it to my actual project once i understand the problem.
I made a model, named Person, which contains a List property, named ServiceNeeded. Users in the front end may encode as much string of services as they wish, so the input field for ServiceNeeded is dynamically created. In the POST method, those string input binds as expected. I save the Person object into the database, and works as expected. When I try to retrieve the people objects from the database, all but ServicesNeeded are present.
Here are my codes
Model (Person.cs):
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public List<String> ServiceNeeded { get; set; }
public Person()
{
this.ServiceNeeded = new List<String>();
}
}
Controller(Index and [POST]Create methods):
public ActionResult Index()
{
var x = db.People.ToList();
return View(db.People.ToList());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
As I said the input fields for ServicesNeeded are dynamically create but properly bind to the model.
Here are some screenshots during runtime:
After user do some input:
The I added a variable before Index returns the View to check in runtime:
As seen, the same Person is present and everything but ServicesNeeded. I am fairly new with ASP.NET MVC and web development in general. I do handle other collections in my project, although are collection of objects. Only this particular case I do not understand. Where could my error be?
You need to tell the dbcontext to load the references if lazy loading is not enabled.
First change ServiceNeeded to a virtual property
public virtual List<String> ServiceNeeded { get; set; }
then load the property when you pull down the person by using Include
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
Article on loading related entities https://msdn.microsoft.com/en-us/data/jj574232.aspx
Look in the database; are the records there? I think the issue is that the ServicesNeeded collection is not persisted to the database; try adding:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
foreach (var service in person.ServicesNeeded)
db.ServicesNeeded.Add(service);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
I'm don't believe the relationships attached to the Person object auto-persist on SaveChanges(), and are probably not making it to the database.
I think the best idea that suits Entity Frame work is to create another Model for your 'ServiceNeeded', add it to your 'DbContext', save any services in the corresponding DbSet and retrieve them using .Include in LINQ. After this lengthy introduction look at the following codes:
In your Models:
public class MyService{
public Guid Id {get;set;}
public string ServiceName {get;set;}
public Guid PersonId {get;set;}
}
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual IList<MyService> ServiceNeeded { get; set; }
}
In your ApplicationDbContext:
public System.Data.Entity.DbSet<MyService> MyServices { get; set; }
public System.Data.Entity.DbSet<Person> People { get; set; }
In your POST ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person, List<String> ServiceNeeded)
{
if (ModelState.IsValid)
{
db.People.Add(person);
db.SaveChanges();
db.Entry(person).GetDatabaseValues();
foreach (string service in ServiceNeeded){
db.MyServices.Add( new MyService {ServiceName = service,
PersonId = person.Id})
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
Please note that I removed person.Id = Guid.NewGuid(); so that the Id
be generated by Database Identity and then I re-get the person.Id so that I create the MyService record.
and finally as #JamieD77 suggested, in your GET ActionResult:
public ActionResult Index()
{
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
}

Categories