ASP.Net Core API routing without attributes [duplicate] - c#

What am I missing that I'm greeted with a 404 for this controller? I really don't want to use attribute-based routing. I also don't want action to be part of any URIs.
I'm using Visual Studio 2017 and .Net Core 1.1.
TestController.cs
using System;
using Microsoft.AspNetCore.Mvc;
namespace Foo.Controllers
{
public class TestController : Controller
{
public long Get() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
}
Note that this works with a [Route("api/Test")] attribute. But I don't want to use attribute-based routing. And as soon as I take that attribute off, I get 404s.
Startup.cs
namespace Foo
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
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, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller}/{id?}"
);
});
}
}
}
Note there's also some stuff in here for Autofac/DI, but I took that out to remove the distraction.
Debug output from a request
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3308908Z","tags":{"ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.operation.id":"0HL37O0HBESDL","ai.application.ver":"1.0.0.0"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request starting HTTP/1.1 GET http://localhost:50129/api/test","severityLevel":"Information","properties":{"DeveloperMode":"true","Host":"localhost:50129","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Hosting.Internal.WebHost","Path":"/api/test","Protocol":"HTTP/1.1","Method":"GET","Scheme":"http"}}}}
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:50129/api/test
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3633954Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request successfully matched the route with name 'default' and template 'api/{controller}/{id?}'.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Routing.RouteBase","RouteName":"default","{OriginalFormat}":"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.","RouteTemplate":"api/{controller}/{id?}"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3663952Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"No actions matched the current request","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler","{OriginalFormat}":"No actions matched the current request"}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3693962Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request did not match any routes.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Builder.RouterMiddleware","{OriginalFormat}":"Request did not match any routes."}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3753962Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Connection id \"0HL37O0H95P8K\" completed keep alive response.","severityLevel":"Verbose","properties":{"DeveloperMode":"true","ConnectionId":"0HL37O0H95P8K","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Server.Kestrel","{OriginalFormat}":"Connection id \"{ConnectionId}\" completed keep alive response."}}}}
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.Message","time":"2017-03-10T14:18:01.3878990Z","tags":{"ai.cloud.roleInstance":"Desktop","ai.internal.sdkVersion":"aspnet5c:2.0.0","ai.location.ip":"::1","ai.operation.id":"0HL37O0HBESDM","ai.application.ver":"1.0.0.0","ai.internal.nodeName":"Desktop","ai.operation.name":"GET /api/test"},"data":{"baseType":"MessageData","baseData":{"ver":2,"message":"Request finished in 54.7982ms 404","severityLevel":"Information","properties":{"DeveloperMode":"true","ElapsedMilliseconds":"54.7982","AspNetCoreEnvironment":"Development","CategoryName":"Microsoft.AspNetCore.Hosting.Internal.WebHost","StatusCode":"404"}}}}
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 54.7982ms 404

This is not works, as mapping to action method is not defined. AFAIK, you may achieve the WebApi REST like routing ONLY using the attribute routing and you may define it on controller level:
[Route("api/[controller]")]
public class TestController : Controller
{
[HttpGet]
public long Get() => DateTimeOffset.Now.ToUnixTimeSeconds();
}
Update: have found this github issue Web API not working with convention based routin and:
for ASP.NET Core MVC we decided to adopt MVC 5.x's conventional routing approach and not Web API 2.x's approach. With the conventional routing approach, the route must specify both a controller and an action.
You may change route template to
template: "api/{controller}/{action}/{id?}"
But in this case, your URL will be /api/test/get.
Update 2 (based on the guide): you can include the NuGet package for Microsoft.AspNetCore.Mvc.WebApiCompatShim and still use ApiController. The code is on GitHub if you are curious as to what it does. Then you can define the WebApi routing:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
}

Related

Access JsonOptions from a middleware in ASP.NET Web Api

In a ASP.NET Web API project I have a custom json converter. It works perfectly fine in a scenario with a typical API controller methods. But somehow I can not access the configured JsonOptions from a middleware which works outside the standard Web API pipeline.
A simple middleware to reproduce the problem:
public sealed class TestMiddleware
{
public async Task Invoke(HttpContext httpContext, IOptions<JsonOptions> jsonOptions)
{
// Some logic to check the middleware must be applied
// ...
// Return a response
await httpContext.Response.WriteAsJsonAsync(data, jsonOptions.Value.SerializerOptions);
}
}
And the configuration fragment:
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers()
.AddJsonOptions(configure =>
{
configure.JsonSerializerOptions.Converters.Add(new MyCustomConverter());
configure.JsonSerializerOptions.IgnoreNullValues = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseMiddleware<TestMiddlewre>();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
When a http request hits the middleware IOptions<JsonOptions> jsonOptions has a SerializerOptions object which doesn't contain converters that I have setup in the configuration.
Is there a way to access the actual JsonOptions with the proper configured SerializerOptions?
The problem was quite tricky. There are two namespaces containing a JsonOptions class:
Microsoft.AspNetCore.Mvc
Microsoft.AspNetCore.Http.Json
Using the first one resolved my problem. Internally these two classes are very similar and I didn't pay attention for the corresponding namespace during the middleware implementation.

Getting Access-Control-Allow-Origin error even tho i added app.useCors (Angular, c# .net core API)

Still getting Access-Control-Allow-Origin error even tho i added useCors in my startup class. I am also using angular 5. I used to get this error when I ran my project but i fixed it by adding cors in my startup, but now i am getting it when i try to call post api method from my angular project.
my startup.cs code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
You can use proxy in your client and change client's origin to whatever you want.
The advantages of this solution are :
You don't need to define CORS in you'r back-end ( increase security )
You don't need to be worry about your client as well as you just run your project via npm start, as soon as you want to release your application you can run ng build command and proxy will be ignored from your setting.
For using proxy
edit "start" of your package.json to look below
"start": "ng serve --proxy-config proxy.conf.json",
create a new file called proxy.conf.json in the root of the project and inside of that define your proxies like below
{
"/api/*": {
"target": "http://localhost:56492",
"secure": "false"
}
}
Important thing is that you use npm start instead of ng serve
Now in you'r service.ts , post/get requests url instead of localhost:56492/... just type /api/Film.
Read more from here : Proxy Setup angular 2 cli

Asp.Net Core 2 Using In memory database for Prototyping getting Cannot resolve scoped service

I am using Asp.Net Core 2 with Entity Framework Core 2 using code first development. I am trying to setup an in memory database that gets called in the startup.cs so I don't have to worry if someone change the models, then having to update the database since I am still in prototyping. The article I was trying to follow is https://stormpath.com/blog/tutorial-entity-framework-core-in-memory-database-asp-net-core
I can get the article to work in Core 1 and then if I upgrade the package to core 2 after that is still works, but the project I am currently working on is already in core 2 and I keep getting at error when i try's to call
var context = app.ApplicationServices.GetService<ApiContext>();. The error I get is
System.InvalidOperationException: 'Cannot resolve scoped service 'TestWebsite.DA.Repositories.ApiContext' from root provider.'
The Message in that exception is.
Cannot resolve scoped service 'TestWebsite.DA.Repositories.ApiContext' from root provider.
The code that i changed in the startup.cs is below.
public void ConfigureServices(IServiceCollection services)
{
AutoMapperConfiguration.Configure();
services.AddDbContext<ApiContext>(opt => opt.UseInMemoryDatabase("TestDatabase"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var context = app.ApplicationServices.GetService<ApiContext>();
AddTestData(context);
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I was wondering if the article I am following is the best way to do it in entity core 2 or if there is a better way and if there is a better way what may that be.
Inject IServiceProvider serviceProvider into your configure method so its signature looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
You should then be able to do:
var context = serviceProvider.GetService<ApiContext>();

.NET Core and Swagger API generation

I am creating a barebones .NET Core web api project (Started from blank template below)
https://andrewlock.net/removing-the-mvc-razor-dependencies-from-the-web-api-template-in-asp-net-core/
The code below was working fine, untill I added Swashbuckle.AspNetCore (Along with the configuration code below), now we get this error
InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider' has been registered.
Any ideas?
Please note: We are not using "builder.AppMvc()" as we are trying to slim this api down as much as possible.
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)
{
var builder = services.AddMvcCore();
builder.AddAuthorization();
builder.AddFormatterMappings();
builder.AddJsonFormatters();
builder.AddCors();
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
return Ok(new[] {"value1", "value2"});
}
}
Solution: Use AddMvc() instead of AddMvcCore() in Startup.cs and it will work.
or write:
services.AddMvcCore().AddApiExplorer();
These links can help:
No service for type 'Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory' has been registered
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/299
For ASP.NET Core 2.2 you should write:
services.AddMvcCore().AddApiExplorer();
These types of errors also appear if you try to add AspNetCore MVC controller with Swagger UI into the AspNetCore Web Application. Just separate them into two different projects: mvc controllers and web application.

Add a header to all responses in ASP.NET Core MVC

I would like to know how I can add Access-Control-Allow-Origin:* to my headers.
I've tried this unsuccessfully:
app.Use((context, next) =>
{
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
return next.Invoke();
});
Using app.use(...) and mutating context.Response.Headers from within Startup.Configure is correct, but it's important to do it at the right point in the chain. ASP.NET Core middleware components can "short-circuit" (see the ASP.NET Core Middleware docs), preventing further middleware from being called, and by experimenting with it I've inferred that UseMvc() does so. In an MVC application, then, this means you have to put your app.use(...) call before app.UseMvc().
In other words, starting from the template ASP.NET Core 2.0 application that Visual Studio generates for you, you want to modify Startup.Configure in Startup.cs to look something like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Add header:
app.Use((context, next) =>
{
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
return next.Invoke();
});
app.UseMvc();
}
I tried your code, and it worked beautifully... Placement is key: I'm pretty sure it needs to be early in the chain.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.Use((context, next) => {
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
return next.Invoke();
});
app.UseMvc();
app.UseWebSockets();
app.UseSignalR();
}
You can also try to use the in built CORS Middleware in the asp.net core framework rather than creating your own Middleware.
In Configure method of the Startup class add the following code.
// Add CORS for YourAnotherSite
app.UseCors(builder =>
builder.WithOrigins("http://YourAnotherSite.com"));
OR
Use Named Policies
In Configure method of the Startup class
options.AddPolicy("AllowMyOrigin",
builder => builder.WithOrigins("http://YourAnotherSite.com"));
and then in the ConfigureServices method of the startup class.
app.UseCors("AllowMyOrigin");
Alternatively, the Policy can be applied at each Controller or Action methods.
We've found the ApplicationBuilder methods inconsistent too - it's not clear when the handler is handing back to the chain (for instance UseStaticFiles()) and when it's not (UseMvc()).
You don't say what environment you're running under, but if you're intending on using IIS then don't give up on web.config yet! The url rewrite module works perfectly well, and allows you to set outbound rules on all request.
There's a good answer here: https://stackoverflow.com/a/26545975/548664

Categories