Debugging Swashbuckle Error - Failed to load API Definition - swagger

Is there any way to get a stack trace or inner exceptions on Swashbuckle/Swagger errors? At some point, it stopped working. I'm not sure if it was when I upgraded from .Net Core 2.0 to 2.1, but I'm pretty sure it was still working after that. When I navigate to myapidomain/swagger/index.html I get this error:
Which is not very helpful. It was working 2 or so weeks ago... I didn't change any Swagger configuration. It's the same as it's always been:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "My.API",
Description = "Test"
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHsts();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "PropWorx API V1");
});
...
}
I'm using Swashbuckle.AspNetCore 3.0.0 on an ASP.Net Core 2.1 Web API.

Agree that the UI debugging isn't really useful, however the full exception trace can be scraped by opening up your browser's debugging tools (e.g. F12 on Chrome), refreshing your /swagger endpoint, and then examining the swagger.json payload - this is an XHR request which will fail with a 500 status code.
(I would suggest on a big project that you bookmark the link, so that in future you can just go straight to the json file, e.g. https://MyHost/swagger/v1/swagger.json)
e.g. in the below contrived error, I've duplicated the same route between two methods:
[HttpGet]
[Route("parseenum")]
public ActionResult CheckEnum([FromQuery] MyEnum someEnum)
...
[HttpGet]
[Route("parseenum")]
public ActionResult CheckEnum2([FromQuery] MyEnum someEnum)
...
Which produces the error:
Which you should now be able to track down and fix.

If your api have same two or more [HttpGet] its not working swagger.
You should be specify [HttpGet] , [HttpGet ("{id}")]
[HttpGet]
`[HttpGet ("{id}")]`

I found that the SwaggerFunctions need to by Anonymous for the Swagger/UI to complete.
public static class SwaggerFunctions
{
[SwaggerIgnore]
[FunctionName("Swagger")]
public static Task<HttpResponseMessage> Swagger(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger/json")]
HttpRequestMessage req,
[SwashBuckleClient] ISwashBuckleClient swashBuckleClient)
{
return Task.FromResult(swashBuckleClient.CreateSwaggerDocumentResponse(req));
}
[SwaggerIgnore]
[FunctionName("SwaggerUi")]
public static Task<HttpResponseMessage> SwaggerUi(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swagger/ui")]
HttpRequestMessage req,
[SwashBuckleClient] ISwashBuckleClient swashBuckleClient)
{
return Task.FromResult(swashBuckleClient.CreateSwaggerUIResponse(req, "swagger/json"));
}
}

i have this problem today and try so much to resolve it , you should must remove all [Route] before [http] tag`s in C# controller code like this code
for example:
[Route("~/api/getAll")]
[HttpGet]
public ActionResult<List<asteriksModel>>GetAll()
{
return _context.asterList.ToList();
}
and your route code mustbe like this
[HttpGet]
public ActionResult<List<asteriksModel>>GetAll()
{
return _context.asterList.ToList();
}
in my project it works fine

Related

ASP.NET Core 2.1 MVC - Not being able to set the cookie received from the web api response

My ASP.NET Core 2.1 based MVC (client) application consumes webapi.
Client application url:
http://testdomain.com:8458/
Web Api url:
http://testdomain.com:8404/
On web api action method setting up cookie as below,
public async Task<IActionResult> GetOrdersAsync(string parameter)
{
HttpContext.Response.Cookies.Append(
"cookieKey",
"cookieValue",
new Microsoft.AspNetCore.Http.CookieOptions { IsEssential = true }
);
return Ok();
}
On client MVC application which calls the webapi, in the response object I can see the presence of 'set-cookie' header
[Microsoft.AspNetCore.Cors.EnableCors]
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ShowOrdersAsync(string parameter)
{
var response = await serviceClient.GetOrdersFromBackendAsync(parameter);
return Ok(response);
}
But cookie in the response header, not getting stored in the browser cookie store
What Im missing? how to set the cookie received from webapi app in the client app browser?
On Startup.cs included below pipeline code,
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
// Add framework services.
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=LandingPage}/{id?}");
});
}
According to your codes and description, I found you try to use MVC controller method to call web api and the api return the set-cookie header.
During this action, the MVC controller is the client and the web api is the server, the server return the set-cookie header to the MVC client. This part is right.
But the MVC controller will not auto return this set cookie to the browser client, since the MVC is the server and you don't have any codes to add the set-cookie header.
If you want to set cookie to client, I suggest you could add codes into the MVC controller not web api controller, since MVC controller could add the cookie to the client browser not the web api.
More details, you could refer to below codes:
[Microsoft.AspNetCore.Cors.EnableCors]
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ShowOrdersAsync(string parameter)
{
var response = await serviceClient.GetOrdersFromBackendAsync(parameter);
HttpContext.Response.Cookies.Append(
"cookieKey",
"cookieValue",
new Microsoft.AspNetCore.Http.CookieOptions { IsEssential = true }
);
return Ok(response);
}
According to your description, you could firstly read the cookie and then add it into the MVC controller response.
More details, you could refer to below codes:
public IActionResult Index()
{
HttpClient httpClient = new HttpClient();
var re = httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://localhost:44387/WeatherForecast")).Result;
var Cookies = ExtractCookiesFromResponse(re);
foreach (var item in Cookies)
{
HttpContext.Response.Cookies.Append(
item.Key,
item.Value,
new Microsoft.AspNetCore.Http.CookieOptions { IsEssential = true });
}
return View();
}
public static IDictionary<string, string> ExtractCookiesFromResponse(HttpResponseMessage response)
{
IDictionary<string, string> result = new Dictionary<string, string>();
IEnumerable<string> values;
if (response.Headers.TryGetValues("Set-Cookie", out values))
{
Microsoft.Net.Http.Headers.SetCookieHeaderValue.ParseList(values.ToList()).ToList().ForEach(cookie =>
{
result.Add(cookie.Name.Value, cookie.Value.Value);
});
}
return result;
}
Result:

Swagger not loading - Failed to load API definition: error undefined message

As I tried to change one public method to private. The issue will be gone.
But when I defined the public method on both method as the below controller code, it will bring up the error.
Anyone help/advice if possible would be really appreciate
WEBLABController.cs
[Route("api/SearchLABResults")]
[HttpPost]
public async Task<IActionResult> SearchLABResults([FromBody] SP_GET_LABResult_Overview.input data)
{
IActionResult response = Unauthorized();
......
return response;
}
AuthenticationController.cs
[Route("api/GenerateToken")]
[HttpPost]
public async Task<IActionResult> GenerateToken([FromBody] AuthenticationModel.input data)
{
IActionResult response = Unauthorized();
......
return response;
}
If you scroll down the swagger view, You see that every model you are using is shown there. Swagger needs that those names be unique.
SP_GET_LABResult_Overview.input and AuthenticationModel.input are both generate input schema and that cause InvalidOperationException.
Try to change one of the input classes to something else or change their schema on the fly like this.
services.AddSwaggerGen(options =>
{
options.CustomSchemaIds(type => type.ToString());
});

Access session variable in razor view .net core 2

I'm trying to access session storage in a razor view for a .net core 2.0 project. Is there any equivalent for #Session["key"] in a .net 2.0 view? I have not found a working example of how to do this - I am getting this error using the methods I have found:
An object reference is required for the non-static field, method, or propery HttpContext.Session
View:
#using Microsoft.AspNetCore.Http
[HTML button that needs to be hidden/shown based on trigger]
#section scripts {
<script>
var filteredResults = '#HttpContext.Session.GetString("isFiltered")';
</script>
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(30);
});
services.AddMvc();
// Added - uses IOptions<T> for your settings.
// Added - replacement for the configuration manager
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//exception handler stuff
//rewrite http to https
//authentication
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
You can do dependency injection in views, in ASP.NET Core 2.0 :)
You should inject IHttpContextAccessor implementation to your view and use it to get the HttpContext and Session object from that.
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
<script>
var isFiltered = '#HttpContextAccessor.HttpContext.Session.GetString("isFiltered")';
alert(isFiltered);
</script>
This should work assuming you have the relevant code in the Startup.cs class to enable session.
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(s => s.IdleTimeout = TimeSpan.FromMinutes(30));
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
To set session in a controller, you do the same thing. Inject the IHttpContextAccessor to your controller and use that
public class HomeController : Controller
{
private readonly ISession session;
public HomeController(IHttpContextAccessor httpContextAccessor)
{
this.session = httpContextAccessor.HttpContext.Session;
}
public IActionResult Index()
{
this.session.SetString("isFiltered","YES");
return Content("This action method set session variable value");
}
}
Use Session appropriately. If you are trying to pass some data specific to the current page, (ex : Whether the grid data is filtered or not , which is very specific to the current request), you should not be using session for that. Consider using a view model and have a property in that which you can use to pass this data. You can always pass these values to partial views as additional data through the view data dictionary as needed.
Remember, Http is stateless. When adding stateful behavior to that, make sure you are doing it for the right reason.
put this at the top of the razor page
#using Microsoft.AspNetCore.Http;
then you can easily access session variables like that
<h1>#Context.Session.GetString("MyAwesomeSessionValue")</h1>
if you get null values , make sure you include that in your Startup.cs
& make sure that options.CheckConsentNeeded = context is set to false
For more information about CheckConsentNeeded check this GDPR
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
//options.CheckConsentNeeded = context => true;
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set session timeout value
options.IdleTimeout = TimeSpan.FromSeconds(30);
options.Cookie.HttpOnly = true;
});
}
Also make sure you are adding app.UseSession(); to your app pipeline in Configure function
for more info about Sessions in Asp.net Core check this link Sessions in Asp.net Core
tested on .net core 2.1
As others have mentioned, I think the real solution here is not to do this at all. I thought about it, and while I have a good reason for using the session, since the razor tags are only useful for the initial page load anyway it makes more sense to just populate the view model in the controller with the stored session values.
You can then pass the view model with the current session values to your view, and access your model instead. Then you don't have to inject anything into your view.
below code worked for me in .net 6
in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromDays(1);
});
services.AddMvc().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSession();
}
in controller
[HttpPost]
public async Task<IActionResult> GetData([FromBody] IncomignRequest request)
{
if (request?.UserId != null)
{
HttpContext.Session.SetString("CurrentUser", request.UserId);
return Json(true);
}
else
return Json(false);
}
in HTML
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
<script>
var user = #Json.Serialize(#HttpContextAccessor.HttpContext.Session.GetString("CurrentUser"))
</script>

Does Swashbuckle.AspNetCore support documentation when route has parameter?

I follow the instructions by https://github.com/domaindrivendev/Swashbuckle.AspNetCore
create a new asp.net core web api project
add nugget package Swashbuckle.AspNetCore
Add code for swagger
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "API (version 1.0)",
Description = "A RESTFUL API"
});
options.IncludeXmlComments(xmlDocPath);
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
Add code for controller
//[Route("{abc}/v1/Values")]
[Route("v1/Values")]
public class ValuesController : Controller
{
// GET api/values
/// <summary>
/// aaaaaaaaaaaaaaaaaaaaaa
/// </summary>
/// <returns></returns>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// POST api/values
/// <summary>
/// bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
/// </summary>
/// <param name="value"></param>
[HttpPost]
public void Post([FromBody]string value)
{
}
}
Run it, the api service works fine, and the swagger works fine with documentation.
If I change the Route from [Route("v1/Values")] to [Route("{abc}/v1/Values")], the api service works fine, but the swagger shows 500 error.
If I change Route from [Route("v1/Values")] to [Route("{abc}/v1/Values")] and remove the documentation, comment following code options.IncludeXmlComments(xmlDocPath) the api service works fine, and the swagger works fine without documentation.
Based on above, seems Swashbuckle.AspNetCore doesn’t support documentation when route has parameter, is it true? If no, does anyone know how to address it?

OData V3 action in ASP.NET web api doesn't get trigger

I am using OData V3 endpoints using asp.net with webapi 2.2. I have successfully implemented CRUD operation with it. Now, I would like to add some custom actions along with CRUD operations. I have followed the article ( http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v3/odata-actions ) to create the action with OData V3 with web api.
When I type
URI:
http://localhost:55351/odata/Courses(1101)/AlterCredits
it throws following error:
<m:error><m:code/><m:message xml:lang="en-US">No HTTP resource was found that matches the request URI 'http://localhost:55351/odata/Courses(1101)/AlterCredits'.</m:message><m:innererror><m:message>No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.</m:message><m:type/><m:stacktrace/></m:innererror></m:error>
I have also tried adding a custom route convetion for non-bindable actions. (https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v3/ODataActionsSample/ODataActionsSample/App_Start/WebApiConfig.cs ) Not sure if I have to use this.
Here is my code:
WebApiConfig.cs :---
namespace ODataV3Service
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
IList<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault(); //Do I need this?
//conventions.Insert(0, new NonBindableActionRoutingConvention("NonBindableActions"));
// Web API routes
config.Routes.MapODataRoute("ODataRoute","odata", GetModel(), new DefaultODataPathHandler(), conventions);
}
private static IEdmModel GetModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.ContainerName = "CollegeContainer";
modelBuilder.EntitySet<Course>("Courses");
modelBuilder.EntitySet<Department>("Departments");
//URI: ~/odata/Course/AlterCredits
ActionConfiguration atlerCredits = modelBuilder.Entity<Course>().Collection.Action("AlterCredits");
atlerCredits.Parameter<int>("Credit");
atlerCredits.Returns<int>();
return modelBuilder.GetEdmModel();
}
}
}
CoursesController.cs:----
[HttpPost]
//[ODataRoute("AlterCredits(key={key},credit={credit})")]
public async Task<IHttpActionResult> AlterCredits([FromODataUri] int key, ODataActionParameters parameters)
{
if (!ModelState.IsValid)
return BadRequest();
Course course = await db.Courses.FindAsync(key);
if (course == null)
{
return NotFound();
}
int credits = course.Credits + 3;
return Ok(credits);
}
Global.asax:----
namespace ODataV3Service
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
I have done research online and found this link. Web API and OData- Pass Multiple Parameters But this one is for OData V4. I am using OData V3 and Action.
Thanks,
First, your action AlterCredits is defined as:
ActionConfiguration atlerCredits = modelBuilder.Entity<Course>().Collection.Action("AlterCredits");
It means AlterCredits bind to the collection of Course.
Second, your method AlterCredits in your controller is defined as:
public async Task<IHttpActionResult> AlterCredits([FromODataUri] int key, ODataActionParameters parameters)
{
...
}
It means AlterCredits listen to the call on the entity of Course.
Therefore, you got the No HTTP resource was found error message.
Based on your sample code, I create a sample method for your reference:
[HttpPost]
public async Task<IHttpActionResult> AlterCredits(ODataActionParameters parameters)
{
if (!ModelState.IsValid)
return BadRequest();
object value;
if (parameters.TryGetValue("Credit", out value))
{
int credits = (int)value;
credits = credits + 3;
return Ok(credits);
}
return NotFound();
}
Then, if you send a request:
POST ~/odata/Courses/AlterCredits
Content-Type: application/json;odata=verbose
Content: {"Credit":9}
You can get a response like this:
{
"d":{
"AlterCredits":12
}
}
For your questions:
IList conventions = ODataRoutingConventions.CreateDefault(); //Do I need this?
Answer: No, you needn't. Just using the default as:
config.Routes.MapODataServiceRoute("ODataRoute", "odata", GetModel());
//[ODataRoute("AlterCredits(key={key},credit={credit})")]
Answer: No, you needn't the ODataRouteAttribute for bind action.
Thanks.

Resources