Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature/netcore] "/$count" not work #1189

Closed
ErliSoares opened this issue Jan 3, 2018 · 16 comments
Closed

[feature/netcore] "/$count" not work #1189

ErliSoares opened this issue Jan 3, 2018 · 16 comments

Comments

@ErliSoares
Copy link

ErliSoares commented Jan 3, 2018

I did not understand the reason, but when I try to return the count of the records does not come and not the error.

image

It seems duplicate: #1160 #1153

I tried several ways, I did not understand why it does not work, putting in and taking out the "? $ Count = true" return is always the same.

Package

Microsoft.AspNetCore.All 2.0.3
Microsoft.AspNetCore.OData 7.0.0-beta1

@ErliSoares
Copy link
Author

Source code:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseAuthentication();

    var builder = new ODataConventionModelBuilder(app.ApplicationServices);
    builder.EntitySet<Conta>(nameof(Conta));

    app.UseMvc(routebuilder =>
    {
        routebuilder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
        routebuilder.MapODataServiceRoute("odata", "api", builder.GetEdmModel());
        routebuilder.EnableDependencyInjection();
    });
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddOData();
    services.AddMvc(options => 
    {
        ////https://github.com/OData/WebApi/issues/597
        ////https://q-a-assistant.info/computer-internet-technology/exception-connecting-excel-to-net-core-v1-1-odata-v4-add-at-least-one-media-type/3883239
        //// loop on each OData formatter to find the one without a supported media type
        foreach (var outputFormatter in options.OutputFormatters.OfType<Microsoft.AspNet.OData.Formatter.ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
        {
            // to comply with the media type specifications, I'm using the prs prefix, for personal usage
            outputFormatter.SupportedMediaTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
        }
        foreach (var inputFormatter in options.InputFormatters.OfType<Microsoft.AspNet.OData.Formatter.ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
        {
            // to comply with the media type specifications, I'm using the prs prefix, for personal usage
            inputFormatter.SupportedMediaTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
        }
    }).AddJsonOptions(opt =>
    {
        opt.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
        opt.SerializerSettings.Converters.Clear();
        opt.SerializerSettings.Converters.Add(new DtoJsonConverter());
        opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        opt.SerializerSettings.ContractResolver = new DtoContractResolver();

    });
            
    services.AddDbContext<ErpContext>(options => 
    {
        options.UseMySql(Session.Connection.GetStringConexao());
    });   
}

Controller:

    [Produces("application/json")]
    [Route("api/[controller]")]
    public class ContaController : ODataController
    {
        private ErpContext _erpContext;

        /// <summary>
        /// Inicializa uma nova instância da classe <see cref="ContaController"/>.
        /// </summary>
        /// <param name="erpContext"></param>
        public ContaController(ErpContext erpContext)
        {
            _erpContext = erpContext;
        }

        /// <summary>
        /// Retorna todas as contas cadastradas baseado na requisição OData
        /// </summary>
        /// <returns>Lista de todos os registros</returns>
        [HttpGet]
        [ProducesResponseType(typeof(void), 401)] //Unauthorized
        [ProducesResponseType(typeof(void), 500)] //Internal Server Error
        [ProducesResponseType(typeof(IQueryable<Conta>), 200)] //OK
        [EnableQuery]
        public IActionResult Get() //Microsoft.AspNet.OData.Query.ODataQueryOptions<Conta> queryOptions
        {
            return Ok(_erpContext.Conta.AsQueryable());
        }

        /// <summary>
        /// Retorna uma conta especifica
        /// </summary>
        /// <para name="codigo">Código da conta</para>
        /// <returns>Registro do código informado</returns>
        [HttpGet("{codigo}")]
        [ProducesResponseType(typeof(Conta), 200)] //OK
        [ProducesResponseType(typeof(void), 401)] //Unauthorized
        [ProducesResponseType(typeof(void), 404)] //Not Found
        [ProducesResponseType(typeof(void), 500)] //Internal Server Error
        [EnableQuery]
        public IActionResult Get([FromODataUri]int codigo)
        {
            var item = new ContaDal().Where(dto => dto.Codigo == codigo).GetFirst();
            if (item == null)
            {
                return NotFound();
            }
            return Ok(item);
        }

        /// <summary>
        /// Insere uma nova conta
        /// </summary>
        /// <para name="value">Conta para ser inserida</para>
        /// <returns>Conta criada preenchida com o código de chave primária</returns>
        [HttpPost]
        [ProducesResponseType(typeof(void), 400)] //Bad Request
        [ProducesResponseType(typeof(void), 401)] //Unauthorized
        [ProducesResponseType(typeof(void), 500)] //Internal Server Error
        [ProducesResponseType(typeof(Dto.Core.ValidationResult), 422)] //Unprocessable Entity
        [ProducesResponseType(typeof(ContaInsertUpdate), 201)] //Created
        public IActionResult Post([FromBody]ContaInsertUpdate value)
        {
            if (value == null)
            {
                return BadRequest();
            }
            var dto = value.ToDto();
            var resultValidation = Bll.Helpers.Validation.Validade(dto);
            if (resultValidation != null && resultValidation.HasError)
            {
                return UnprocessableEntity(resultValidation);
            }
            if (new ContaDal().Insert(dto) == false)
            {
                return StatusCode(500);
            }
            //TODO Retornar o objeto inserido (Com código)
            return Created(GetAbsoluteUriWithOutQuery(Request) + "/" + value.Codigo, value);
        }

        /// <summary>
        /// Atualiza conta existente
        /// </summary>
        /// <para name="value">Conta para ser atualizada</para>
        [HttpPut("{codigo}")]
        [ProducesResponseType(typeof(void), 200)] //OK
        [ProducesResponseType(typeof(void), 400)] //Bad Request
        [ProducesResponseType(typeof(void), 401)] //Unauthorized
        [ProducesResponseType(typeof(void), 404)] //Not Found
        [ProducesResponseType(typeof(Dto.Core.ValidationResult), 422)] //Unprocessable Entity
        [ProducesResponseType(typeof(void), 500)] //Internal Server Error
        public IActionResult Put(int codigo, [FromBody]ContaInsertUpdate value)
        {
            if (value == null || value.Codigo != codigo)
            {
                return BadRequest();
            }
            var dto = value.ToDto();
            var resultValidation = Bll.Helpers.Validation.Validade(dto);
            if (resultValidation != null && resultValidation.HasError)
            {
                return UnprocessableEntity(resultValidation);
            }
            using (var dal = new ContaDal())
            {
                var item = dal.Where(dtoQ => dtoQ.Codigo == codigo).GetFirst();
                if (item == null)
                {
                    return NotFound();
                }
                dal.Update(value.ToDto());
            }
            return Ok();
        }

        /// <summary>
        /// Apaga uma conta existente
        /// </summary>
        /// <para name="codigo">Código da conta para ser apagada</para>
        [HttpDelete("{codigo}")]
        [ProducesResponseType(typeof(void), 200)] //OK
        [ProducesResponseType(typeof(void), 401)] //Unauthorized
        [ProducesResponseType(typeof(void), 404)] //Not Found
        [ProducesResponseType(typeof(Dto.Core.ValidationResult), 422)] //Unprocessable Entity
        [ProducesResponseType(typeof(void), 500)] //Internal Server Error
        public IActionResult Delete(int codigo)
        {

            using (var dal = new ContaDal())
            {
                var dto = dal.Where(dtoQ => dtoQ.Codigo == codigo).GetFirst();
                if (dto == null)
                {
                    return NotFound();
                }
                dto.Deleted = true;
                var resultValidation = Bll.Helpers.Validation.Validade(dto);
                if (resultValidation != null && resultValidation.HasError)
                {
                    return UnprocessableEntity(resultValidation);
                }
                dal.Delete(dto);
            }

            return Ok();
        }

        public class ContaInsertUpdate
        {
            public ContaInsertUpdate()
            {
                EmpresafuncionarioCodigo = 0;
                CdLimite = 0.00000;
                CdUtilizado = 0.00000;
                CdSaldo = 0.00000;
                PermitirQuitacao = true;
            }
            public int Codigo { get; set; }

            public int? LojaCodigo { get; set; }

            public int? SetorCodigo { get; set; }
            public string Codigocontabel { get; set; }

            public int? FuncionarioCodigo { get; set; }

            public int? ContacontabelCodigo { get; set; }

            public int? PlanodespesaCodigo { get; set; }

            public int? SubplanodespesaCodigo { get; set; }

            public int? PlanoreceitaCodigo { get; set; }

            public int? SubplanoreceitaCodigo { get; set; }

            public int? EmpresafuncionarioCodigo { get; set; }

            public double? CdLimite { get; set; }

            public double? CdUtilizado { get; set; }

            public double? CdSaldo { get; set; }

            public string NBanco { get; set; }

            public int? Tipo { get; set; }

            public int? ContaCP { get; set; }

            public string NumeroConta { get; set; }

            public string Agencia { get; set; }

            public string Descricao { get; set; }

            public string Autor { get; set; }

            public double? Saldo { get; set; }

            public double? SaldoDisponivel { get; set; }

            public bool? Imprimir { get; set; }

            public int? Numeracaocheque { get; set; }

            public int? Relatoriocheque { get; set; }

            public int? Relatoriorelacaocheque { get; set; }

            public bool? PermitirQuitacao { get; set; }

            public Dto.Entities.Conta ToDto()
            {
                return new Dto.Entities.Conta
                {
                    Agencia = Agencia,
                    EmpresafuncionarioCodigo = EmpresafuncionarioCodigo,
                    Autor = Autor,
                    CdLimite = CdLimite,
                    CdSaldo = CdSaldo,
                    CdUtilizado = CdUtilizado,
                    Codigo = Codigo,
                    Codigocontabel = Codigocontabel,
                    ContacontabelCodigo = ContacontabelCodigo,
                    ContaCP = ContaCP,
                    Descricao = Descricao,
                    FuncionarioCodigo = FuncionarioCodigo,
                    Imprimir = Imprimir,
                    LojaCodigo = LojaCodigo,
                    NBanco = NBanco,
                    Numeracaocheque = Numeracaocheque,
                    NumeroConta = NumeroConta,
                    PermitirQuitacao = PermitirQuitacao,
                    PlanodespesaCodigo = PlanodespesaCodigo,
                    PlanoreceitaCodigo = PlanoreceitaCodigo,
                    Relatoriocheque = Relatoriocheque,
                    Relatoriorelacaocheque = Relatoriorelacaocheque,
                    Saldo = Saldo,
                    SaldoDisponivel = SaldoDisponivel,
                    SetorCodigo = SetorCodigo,
                    SubplanodespesaCodigo = SubplanodespesaCodigo,
                    SubplanoreceitaCodigo = SubplanoreceitaCodigo,
                    Tipo = Tipo
                };
            }
        }

        protected static Uri GetAbsoluteUriWithOutQuery(HttpRequest request)
        {
            UriBuilder uriBuilder = new UriBuilder();
            uriBuilder.Scheme = request.Scheme;
            uriBuilder.Path = request.Path.ToString();
            uriBuilder.Host = request.Host.Host;
            if (request.Host.Port.HasValue)
            {
                uriBuilder.Port = request.Host.Port.Value;
            }
            return uriBuilder.Uri;
        }

        /// <summary>
        /// 422 UNPROCESSABLE ENTITY
        /// </summary>
        /// <param name="value">Description error</param>
        /// <remarks>
        /// The server understands the content type of the request entity (hence a 415 Unsupported Media Type status code is inappropriate), and the syntax of the request entity is correct (thus a 400 Bad Request status code is inappropriate) but was unable to process the contained instructions.
        /// </remarks>
        protected ObjectResult UnprocessableEntity(object value)
        {
            return StatusCode(422, value);
        }
    }

@ErliSoares
Copy link
Author

ErliSoares commented Jan 6, 2018

I was debugging to see if I could find any solution, and I realized I was not calling ODataPathRouteConstraint.Match (...), so I decided to remove the controller attribute [Route ("api / [controller]")] and started calling, but even that's not how it worked.

@ErliSoares
Copy link
Author

When debugging happened this error in this line:

"Microsoft.OData.UriParser.ODataUnrecognizedPathException: Resource not found for the segment 'conta'.\r\n at Microsoft.OData.UriParser.ODataPathParser.CreateDynamicPathSegment(ODataPathSegment previous, String identifier, String parenthesisExpression)\r\n at Microsoft.OData.UriParser.ODataPathParser.CreateFirstSegment(String segmentText)\r\n at Microsoft.OData.UriParser.ODataPathParser.ParsePath(ICollection1 segments)\r\n at Microsoft.OData.UriParser.ODataPathFactory.BindPath(ICollection1 segments, ODataUriParserConfiguration configuration)\r\n at Microsoft.OData.UriParser.ODataUriParser.Initialize()\r\n at Microsoft.OData.UriParser.ODataUriParser.ParsePath()\r\n at Microsoft.AspNet.OData.Routing.DefaultODataPathHandler.Parse(String serviceRoot, String odataPath, IServiceProvider requestContainer, Boolean template)"

URL: http://localhost:8080/api/conta?$count=true

Does anyone have any suggestions for what it might be?

@ErliSoares
Copy link
Author

After hours is a cause of the problem, the integrity name can not be the same as the driver name and service URL.

It does not work:

builder.EntitySet <Conta> ("Conta");
public class ContaController : Core.ODataController

It works:

builder.EntitySet <Conta> ("ContaTest");
public class ContaTestController : Core.ODataController

Is this a limitation of the library or was it to work?

@vickityvik
Copy link

vickityvik commented Jan 10, 2018

+1 @ErliSoares. I am also able to get the plain response using [Route()] but it does not contain any of the "@OData" properties, similar to your GET Portman response. However, I was unable to get it to work even by changing the names.

Can confirm issue using:

  • out-of-the-box WebAPI project in VS
  • targeting .NET Core 2.0
  • Microsoft.AspNetCore.OData 7.0.0-beta1

@ErliSoares
Copy link
Author

Show the controller and the configuration, maybe I'll find something to help you

@vickityvik
Copy link

Startup:

namespace WebApiPoc
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOData();
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            IEdmModel model = GetEdmModel(app.ApplicationServices);
            app.UseMvc(routeBuilder =>
            {
                routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
                routeBuilder.MapODataServiceRoute("odata", "api", model);
                routeBuilder.EnableDependencyInjection();
            });
        }

        private static IEdmModel GetEdmModel(IServiceProvider serviceProvider)
        {
            var builder = new ODataConventionModelBuilder(serviceProvider);
            builder.EntitySet<Models.Organization>("OrganizationTest");

            return builder.GetEdmModel();
        }
    }
}

Controller:

namespace WebApiPoc.Controllers
{
    public class OrganizationTestController : ODataController
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

Thanks, @ErliSoares!

@ErliSoares
Copy link
Author

I was unable to identify for sure, but test the URL: http://host:port/api/OrganizationTest
Do not forget the capital and tiny that must be the same.

@ErliSoares
Copy link
Author

If it does not work, I'll give you some changes to see if it works.

@vickityvik
Copy link

@ErliSoares - that did it! Thanks! If I remember correctly, there's a way to make routes case insensitive. I'll look, and if I find anything I'll report back.

@ErliSoares
Copy link
Author

Thanks, if I find a way I'll report back too.

@robward-ms
Copy link
Contributor

@ErliSoares - Form the comments on Jan 6/7, the Resource not found for the segment 'conta' is expected if you query http://.../contra instead of http://.../Contra, i.e. it is case sensitive. I've tried querying with $count=true for both cases where the entity name does not match the clr type name and when they do, both work as expected but it is case sensitive.

I do notice than in your initial bug, you are using "conta" in the Url and "nameof(Contra)" in the model. nameof("Contra") will produce "Contra" so it's unclear why you received a response when you should have received a 404.

Are you still having issues with $count?

@robward-ms
Copy link
Contributor

#1231 filed for documenting how to configure case insensitivity.

@igor-moskvitin
Copy link

@ErliSoares did you try to use http://host:port/api/OrganizationTest(1)? Has a very similar configuration, but routed to public IEnumerable<string> Get() instead of public string Get(int id).

@AlanWong-MS
Copy link
Contributor

@ErliSoares we're currently reviewing open issues in preparation for our upcoming WebAPI 7.x release. Is the behavior that you're seeing still applicable? Please review the posts from Rob and Igor above. Thank you.

@mikepizzo
Copy link
Member

PR #1409 makes case-insensitive (and unqualified operation names) the default for WebAPI OData 7.x. This is documented in PR #1426, including how to disable this default if you want the old behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants