locked
How Do I Block Data from Rendering in an API Controller? RRS feed

  • Question

  • User-939035612 posted

    I have an api controller that gets data from a service that returns more data than I want rendered in the json output, but I also need to access the same data from Razor Pages. How do I block that output at the controller level?

    Normally I would just omit the data from the get task that the service performs, so that it would render as null in json output, but if I do that I can't access it from server side code in a Razor Page. The api controller is used because json is needed for an infinite scroll plugin. Otherwise I would not even need a controller.

    Is there perhaps a quick line of code that can be added to a Controller that tells it to omit a specific field from the json output?

    Here is the task redacted for efficiency from the Service file:

    public async Task<Posts[]> GetPosts()
            { 
                var posts = await _context.Posts.Where(post => post.Active == true)
                                .Select(p => new Posts { 
                                Postid = p.Postid,
                                Title = p.Title,
                                Description = p.Description,
                                Userid = p.Userid 
                                })
                            .ToArrayAsync();
                return posts;
            }

    A second task then paginates the results of the first one

    public async Task<List<Posts>> GetPaginatedResult(int currentPage, int pageSize)
            {
                var data = await GetPosts();
                return data.OrderByDescending(d => d.Postid).Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
            }

    The controller is as follows:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using PostAlmostAnything.Data;
    using PostAlmostAnything.Models;
    using PostAlmostAnything.SiteServices;
    using PostAlmostAnything.AppCode;
    
    namespace PostAlmostAnything.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class PaginatedPostsController : ControllerBase
        {
            private readonly ApplicationDbContext _context;
    
            public PaginatedPostsController(PostsService postService)
            {
                PostService = postService;
            }
            public PostsService PostService { get; }
     [HttpGet("{id:int}/{sid:int}")]
            public async Task<List<Posts>> GetPaginatedPostsAsync(int id, int sid)
            {
                int CurrentPage = id;
                int PageSize = sid;
                return await PostService.GetPaginatedResult(CurrentPage, PageSize);
            }
    
            
        }
    }

    Obviously there are reasons not to render the Userid field in the json output, but I also need to access the Userid in razor pages that need to get the UserName based on that id. I am using the default Identity tables in my database and those tables by default have no foreign key relationships with one another, so when they are scaffolded automatically they do not create navigation classes. Identity also stores the UserId as a string instead of a unique identifier like the old AspnetMembership provider did, so I'm not really sure how to go about creating a foreign key relationship between string values. I seem to recall trying to do that once and running into some kind of error message about SQL Server not supporting such relationships with strings. As a result I have another task called GetUserNambeById that I call from razor pages to populate UserName fields. This requires GetPosts to return the UserId so that it can be passed to GetUserNameById.

    Thursday, March 19, 2020 4:07 AM

All replies

  • User1120430333 posted

    Maybe, you could look into using a DTO. 

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/data/using-web-api-with-entity-framework/part-5

    https://www.codeproject.com/articles/1050468/data-transfer-object-design-pattern-in-csharp

    The Payroll EF entity doesn't have AuthorFirstName and AuthorLastName properties,  but I needed them at the viewmodel creation  for the Payroll Index.cshtml  Razor page. So I shaped a DTO to get the properties there. On that same token. you shape a DTO to exclude properties. Myself, EF entities never travel, because that's the job of the DTO. All the DTO(s) are kept in a classlib project called Entities,  and all projects that need to know about the DTO(s) have reference to the Entities project.

    https://en.wikipedia.org/wiki/Data_transfer_object

    namespace Entities
    {
        public class DtoPayroll
        {
            public int PayRollId { get; set; }
            public int AuthorId { get; set; }
            public string AuthorFirstName { get; set; }
            public string AuthorLastName { get; set; }
            public int? Salary { get; set; }
        }
    }
    
    using DAL.Models;
    using System;
    using System.Threading.Tasks;
    using Entities;
    using System.Collections.Generic;
    using Microsoft.EntityFrameworkCore;
    using System.Data.SqlClient;
    using System.Linq;
    
    namespace DAL
    {
        public class DaoPayroll :IDaoPayroll
        {
            private PublishingCompanyContext pc;
            private IDaoAuthor _daoauthor;
    
            public DaoPayroll(PublishingCompanyContext dbcontext, IDaoAuthor daoAuthor)
            {
                pc = dbcontext;
                _daoauthor = daoAuthor;
            }
    
            public async Task<List<DtoPayroll>> GetAll()
            {
                var dtos = new List<DtoPayroll>();
    
                var payrolls = await pc.Payroll.ToListAsync();
    
                foreach (var payroll in payrolls)
                {
                    var dtoauthor = await _daoauthor.Find(payroll.AuthorId); 
    
                    var dto = new DtoPayroll
                    {
                        PayRollId = payroll.PayrollId,
                        AuthorId = payroll.AuthorId,
                        AuthorFirstName = dtoauthor.FirstName,
                        AuthorLastName = dtoauthor.LastName,
                        Salary = payroll.Salary
                    };
    
                    dtos.Add(dto);
                }
    
                return dtos;
            }
    
            public async Task<DtoPayroll> Find(int id)
            {
                var dto = new DtoPayroll();
    
                var payroll = await pc.Payroll.FindAsync(id);
    
                if (payroll != null)
                { 
                    var dtoauthor = await _daoauthor.Find(payroll.AuthorId);
    
                    if (dtoauthor != null)
                    {
                        dto.PayRollId = payroll.PayrollId;
                        dto.AuthorId = payroll.AuthorId;
                        dto.AuthorFirstName = dtoauthor.FirstName;
                        dto.AuthorLastName = dtoauthor.LastName;
                        dto.Salary = payroll.Salary;
                    }
                    else
                    {
                        throw new Exception($"Author with ID = {id} was not found.");
                    }
                }
                else
                {
                    throw new Exception($"Payroll with ID = {id} was not found.");
                }
    
                return dto;
    
            }
    
            public async Task<DtoPayroll> FindPayRollByAuthorId(int id)
            {
                var dto = new DtoPayroll();
                
                var payroll = await pc.Payroll.Where(a =>a.AuthorId == id).SingleOrDefaultAsync();
    
                if (payroll != null)
                {
                    dto.PayRollId = payroll.PayrollId;
                }
    
                return dto;
            }
    
            public async Task Add(DtoPayroll dto)
            {
                var payroll = new Payroll
                {
                    AuthorId = dto.AuthorId,
                    Salary = dto.Salary
                };
    
                pc.Payroll.Add(payroll);
                await pc.SaveChangesAsync();
    
            }
    
            public async Task Update(DtoPayroll dto)
            {
                var payroll = new Payroll
                {
                    PayrollId = dto.PayRollId,
                    AuthorId = dto.AuthorId,
                    Salary = dto.Salary
                };
    
                pc.Entry(payroll).State = EntityState.Modified;
                await pc.SaveChangesAsync();
    
            }
    
            public async Task Delete(int id)
            {
                var payroll =  pc.Payroll.Find(id);
    
                if (payroll != null)
                {
                    pc.Payroll.Remove(payroll);
                    await pc.SaveChangesAsync();
                }
            }
    
        }
    }
    
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using DAL;
    using Entities;
    using Microsoft.AspNetCore.Mvc;
    
    namespace WebApi3.x.Controllers
    {
        [Route("[controller]")]
        [ApiController]
        public class PayRollController : ControllerBase
        {
            private IDaoPayroll dao;
            public PayRollController(IDaoPayroll daoPayroll)
            {
                dao = daoPayroll;
            }
    
            [HttpGet]
            [Route("GetAll")]
            public async Task<List<DtoPayroll>> GetAll()
            {
                return await dao.GetAll();
            }
    
            [HttpGet]
            [Route("Find")]
            public async Task<DtoPayroll> Find(int id)
            {
                return await dao.Find(id);
            }
    
    
            [HttpGet]
            [Route("FindPayRollByAuthorId")]
            public async Task<DtoPayroll> FindPayRollByAuthorId(int id)
            {
                return await dao.FindPayRollByAuthorId(id);
            }
    
            [HttpPost]
            [Route("Add")]
            public async Task Add(DtoPayroll dto)
            {
                await dao.Add(dto);
            }
    
            [HttpPost]
            [Route("Update")]
            public async  Task Update(DtoPayroll dto)
            {
                 await dao.Update(dto);
            }
    
            [HttpPost]
            [Route("Delete")]
            public async Task Delete(DtoId dto)
            {
                await dao.Delete(dto.Id);
            }
        }
    }

    Thursday, March 19, 2020 7:13 AM
  • User-854763662 posted

    Hi CopBlaster ,

    Not clear about your expected result . Could you share more details that can reproduce the result you get now and the expected result ?

    Best Regards,

    Sherry

    Friday, March 20, 2020 6:26 AM
  • User-939035612 posted

    I just want a simple way to tell a controller to omit a specific field from the result without having to omit the field itself from my GetPosts task because I need that field sometimes, but I don't want the value of that field rendered in json output by a controller.

    The controller is only used to render json output so that I can access it with javascript. The problem is that unwanted data that is necessary when accessing GetPosts from a Razor Page is being rendered in the json output. 

    I would love to see a solution where I could just add the name of the field I don't want rendered by the controller in the controller somehow. Ideally with some type of syntax that can be added using just a couple likes of code that specifies which field it is and the fact that it should not be rendered in the json output.

    Friday, March 20, 2020 10:08 PM
  • User-854763662 posted

    Hi CopBlaster ,

    You may try custom ActionFilterAttribute to set value of the specific field as null or 0  , refer to the below simple demo , you can modify depend on your code:

    public class OmitFieldAttribute:ActionFilterAttribute
    {
            public override void OnActionExecuted(ActionExecutedContext context)
            {
                var result = context.Result as ObjectResult;
                var value = result.Value;
                if(value is List<Links> linklist)
                {
                    foreach (var link in linklist)
                    {
                        link.CategoryId = 0;
                    }
                }
    
            }
    }

    Controller:

    [OmitField]
    public async Task<List<Links>> LinkCategoryList()
    {
           var data = _context.Link
                    .Select(l => new Links
                    {
                        Id=l.Id,
                        Title=l.Title,
                        Url =l.Url,
                        Description=l.Description,
                        CategoryId=l.CategoryId
                    })
                    .ToList();
            return data;
    }

    Result:

    Best Regards,

    Sherry

    Thursday, March 26, 2020 9:24 AM
  • User-939035612 posted

    Thanks, that looks exactly like what I am looking for I will give it a go.

    Thursday, March 26, 2020 9:38 PM
  • User-939035612 posted

    I'm having trouble figuring out how exactly your controller interacts with the class. I tried using an array to set the post userid values in my controller but it just returns the same stuff. What does your OnActionExecuted return? Where is your context coming from? Could you maybe post all the source code?

    public async Task<List<Posts>> GetPaginatedPostsAsync(int id, int sid)
            {
                int CurrentPage = id;
                int PageSize = sid;
                var data = await PostService.GetPaginatedResult(CurrentPage, PageSize);
                foreach(var r in data)
                {
                    r.Userid = "0";
                }
                return data;
            }

    Saturday, March 28, 2020 5:36 AM