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

3.0.0 preview 2 Razor Components: "InputFormatters" is empty when making Web API POST #7438

Closed
EricPalmer22 opened this issue Feb 10, 2019 · 7 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates

Comments

@EricPalmer22
Copy link

With a new "Razor Components" project using .NET Core 3.0.0 preview 2, I am trying to make an Http call to a controller on the server to save game data to a SQLite database. But, any POST call that I to do to a controller method with a [FromBody] parameter attribute, I get the following error: `System.InvalidOperationException: 'Microsoft.AspNetCore.Mvc.MvcOptions.InputFormatters' must not be empty. At least one 'Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter' is required to bind from the body.

I see that a JsonInputFormatter should be included by default when I register the MVC services, but for some reason there are no InputFormatters in the context.

To test this, I created a brand new Razor Components project under .NET Core 3.0.0 templates.
I registered MVC and HttpClient services on my Server.Startup class:

public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            
            if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
            {
                // Setup HttpClient for server side in a client side compatible fashion
                services.AddScoped<HttpClient>(s =>
                {
                    // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
                    var uriHelper = s.GetRequiredService<IUriHelper>();
                    return new HttpClient
                    {
                        BaseAddress = new Uri(uriHelper.GetBaseUri())
                    };
                });
            }
            services.AddRazorComponents<App.Startup>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            };
            app.UseMvc();
            app.UseStaticFiles();
            app.UseRazorComponents<App.Startup>();
        }
    }

I created a CounterController class on my Server with a simple test method:

public class CounterController : Controller
    {
        [HttpPost]
        [Route("/api/Counter/Test")]
        public void Test([FromBody]string value)
        {
            var test = value;
        }
    }

And updated the Counter.cshtml file to inject the Httpclient and make a call to our controller:

@page "/counter"
@using System.Net.Http;
@inject HttpClient Client

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>

@functions {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
        var stringReq = new StringBody() { value = "test" };
        Client.PostJsonAsync("/api/Counter/Test", stringReq).Wait();
    }

    public class StringBody
    {
        public string value { get; set; }
    }
}

I can use [FromQuery] parameters and everything works fine, but I just cannot get sending JSON to work because of this error. I verified that the request is being made with the correct content type and the body is valid JSON. I'm also learning Blazor as I go here and don't have a solid background with MVC/Razor pages, so it's very possible I'm just doing something wrong. My goal is to have a web game that can persist game data to a database on the server. I figured I would have my EF dbcontext datalayer and my web api controller living on the server to accept client calls for saving/updating game state.

Full stacktrace:

System.InvalidOperationException: 'Microsoft.AspNetCore.Mvc.MvcOptions.InputFormatters' must not be empty. At least one 'Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter' is required to bind from the body.
   at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider.GetBinder(ModelBinderProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory.CreateBinderCoreUncached(DefaultModelBinderProviderContext providerContext, Object token)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory.CreateBinder(ModelBinderFactoryContext context)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.GetParameterBindingInfo(IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.CreateBinderDelegate(ParameterBinder parameterBinder, IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerCache.GetCachedResult(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
   at Microsoft.AspNetCore.Mvc.Routing.MvcEndpointDataSource.<>c__DisplayClass22_0.<CreateEndpoint>b__0(HttpContext context)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
@Eilon Eilon added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Feb 10, 2019
@pranavkm
Copy link
Contributor

We're in the midst of adding JSON formatters based on System.Text.Json. In the meanwhile you have to manually add back the JSON.NET based ones to your application.

  1. Add a PackageReference to Microsoft.AspNetCore.Mvc.NewtonsoftJson - https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/3.0.0-preview-19075-0444
  2. Change your ConfigureServices, services.AddMvc().AddNewtonsoftJson()

@EricPalmer22
Copy link
Author

There it is, thank you!

dougbu pushed a commit to dotnet-maestro-bot/AspNetCore that referenced this issue Feb 13, 2019
* Handle OPTIONS requests without a handler in Razor Pages

Fixes dotnet#7438
@aktxyz
Copy link

aktxyz commented Apr 10, 2019

the .AddNewtonsoftJson() approach is working ... but when trying to add a content-type to the json input formatter as shown below, the InputFormatters is empty during AddMvc config time, probably because it is not added until the .AddNewtonsoftJson()

any thought on how to work around this or if something else is wrong in the code below?

            services
                .AddMvc(options =>
                {
                    foreach (var formatter in options.InputFormatters)
                    {
                        if (formatter.GetType() == typeof(NewtonsoftJsonInputFormatter))
                            ((NewtonsoftJsonInputFormatter)formatter).SupportedMediaTypes.Add(
                                Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/plain"));
                    }
                })
                .AddNewtonsoftJson();

@endeffects
Copy link

@aktxyz

To fix the problem use:

            services.AddMvc().AddNewtonsoftJson().AddMvcOptions(e =>
            {
                e.InputFormatters.OfType<NewtonsoftJsonInputFormatter>().ToList().ForEach(formatter => formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/plain")));
            });

@aktxyz
Copy link

aktxyz commented Apr 16, 2019

aha ... very very nice

@marcuslindblom
Copy link

What is the equivalent way of doing the following in preview 7 if I want to be able to ReadAsAsync without the need of adding my own formatters every time? Is it possible to add default supported mediatypes?

MediaTypeFormatterCollection formatters = new MediaTypeFormatterCollection();
formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.xxx.v1+json"));                

TokenResponse token =  await tokenResponse.Content.ReadAsAsync<TokenResponse>(formatters);

@rynowak
Copy link
Member

rynowak commented Aug 12, 2019

Hi, it looks like you are posting on a closed issue/PR/commit!

We're very likely to lose track of your bug/feedback/question unless you:

  1. Open a new issue
  2. Explain very clearly what you need help with
  3. If you think you have found a bug, include detailed repro steps so that we can investigate the problem

Thanks!

@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates
Projects
None yet
Development

No branches or pull requests

7 participants