Is it possible to use Swagger with AspNetCore Odata? - c#

Yesterday I searched solution how to use swagger on Core Odata, I tried few libraries but with no success, it seams that currently it's not fully supported.

May be this info could be useful for somebody. Actually It's possible to use NSwag and create documentation for Odata Core from the box. There is workaround.
Just add swagger and Odata settings to Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.DefaultContractResolver();
});
services.AddOData();
//etc
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var builder = new ODataConventionModelBuilder(app.ApplicationServices));
builder.EntitySet<Test>(nameof(Test));
app.UseMvc(routebuilder =>
{
routebuilder.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
});
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly,
settings =>
{
settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
});
app.UseMvc();
//etc
}
Next mark Controller with route attribute as it would WebApi. Note: route should be different from odata.
Add [EnableQuery] to your IQueryable Action. Note2: you can't use [FromODataUri] for swagger docs Action with it should be marked as [SwaggerIgnore]
[Produces("application/json")]
[Route("api/test")]
public class TestController : Controller
{
[HttpGet]
[EnableQuery]
public IQueryable<Test> Get()
{
return _testService.Query();
}
//etc
}
Get swagger run!

Related

How can I change an endpoint that returns one object to be filterable on which properties to return?

I want to find the cleanest way to do this but haven't had much luck on google. I'm returning an Information object that has properties assemblies, configuration, and cassandra.
I want it to work so if I call /api/Information?filter=Assemblies,Configuration, it returns only the assemblies and configuration properties, leaving out cassandra.
I can do this currently but I'm doing it with if else statements. Is there a better approach to this, maybe with an interface?
I think what you are looking for that would work for .net very well is OData.
You can use odata to do that it is exactly what you need.
Startup.cs should be modified like this.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<yourDbContext>(opt => opt.UseInMemoryDatabase("yourList"));
services.AddControllers().AddOData(opt => opt.AddRouteComponents("api", GetEdmModel())
.Filter().Select());
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Send "~/$odata" to debug routing if enable the following middleware
// app.UseODataRouteDebug();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private static IEdmModel GetEdmModel()
{
// …
}
}
Controller should be like this:
public class InformationController : ControllerBase
{
[HttpGet]
[EnableQuery]
public IEnumerable<Information> Get()
{
//…your logic
}
}
Api
So if you do /api/Information?Select=Assemblies,Configuration you will only get assemblies and configuration.

.NET Core Endpoint + Global CORS

I've found this in official documentation -
We recommend against combining policies. Use the [EnableCors]
attribute or middleware, not both in the same app.
My scenario is quite simple - I want to enable CORS globally but disable it only for one specific controller endpoint (endpoint is used on frontend widget which can be embedded on any site so I can't have CORS on that endpoint).
I don't understand why they are recommending against combining both approaches - not only that they don't recommend but it just doesn't work.
This is the setup of CORS:
services.AddCors(opts =>
{
opts.AddPolicy(nameof(MyCorsPolicy), new MyCorsPolicy());
});
And this is registration in Configure method of startup
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env)
{
app.UseRouting();
app.UseCors(nameof(MyCorsPolicy));
app.UseHsts();
app.UseExceptionHandler(env);
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
And now in my XY controller method I have [DisableCors] attribute which just doesn't work.
Any help would be appreciated. Thank you.
After hundreds of tests and internal .NET Core debugging, only way I could implement this is by using global CORS:
services.AddCors(opts =>
{
opts.AddPolicy(nameof(MyCorsPolicy), new MyCorsPolicy());
});
Then I'd create another policy
public class AllowAnyCorsPolicy : CorsPolicy
{
public AllowAnyCorsPolicy()
{
Origins.Clear();
IsOriginAllowed = origin => true;
Headers.Clear();
Headers.Add("*");
Methods.Clear();
Methods.Add("*");
SupportsCredentials = true;
}
}
And apply that policy to specific controller method e.g.
[EnableCors(nameof(AllowAnyCorsPolicy))]
[HttpPost("/user/add")]
[AllowAnonymous]
public async Task<IActionResult> AddUser(UserRequestModel requestModel)
{
// ...
}
If I used [DisableCors] or even used default policy registration and then added pure [EnableCors] attribute to controller method, it just wouldn't work. Pretty weird way of their implementation because I think this can be simplified a lot, and I have no idea how this might behave in future, so we might even consider writing our own full CORS middleware.
Way 1. Because a default policy hasn't been configured, app.UseCors() alone doesn't enable CORS. Use RequireCors to enable all controllers.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireCors(MyCorsPolicy);//Enable Cors with endpoint routing
// /xy/getvalues2 and Razor Pages not allow cross-origin requests because no default policy was specified.
endpoints.MapGet("/xy/getvalues2",
context => context.Response.WriteAsync("xy/getvalues2")); //do XY Controller Action logic
endpoints.MapRazorPages();
});
}
Way 2. The [DisableCors] attribute does not disable CORS that has been enabled by endpoint routing. Uses [EnableCors("MyCorsPolicy")] to enable the "MyCorsPolicy" CORS policy for each controller. Disables CORS for the GetValues2 method.
[EnableCors("MyCorsPolicy")]
[Route("api/[controller]")]
[ApiController]
public class XYController : ControllerBase
{
// GET api/values
[HttpGet]
public IActionResult Get() =>
ControllerContext.MyDisplayRouteInfo();
// GET: api/values/GetValues2
[DisableCors]
[HttpGet("{action}")]
public IActionResult GetValues2() =>
ControllerContext.MyDisplayRouteInfo();
}

Can't see react page after serving data with .net core api

I want to show data in my react component but when I set API react doesn't work. It seems like middleware is setup wrong or I don't get how .net core and react go along. I know I basically run two different apps with .net core and react but somebody could tell me how to resolve my problem to just put this API data in react page? I made it with approach model first using Entity framework.
I tried to change middleware but in the end, API is always shown. When I change the routing of API, react works fine so everything is good with set up to react. But when they are two on the same route. I only see data from API. And i only want to get data from .net core and show it in react component.
UsersController.cs
[Route("[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly MafiaContext _context;
public UsersController(MafiaContext context)
{
_context = context;
}
// GET: Users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
app.js
<Layout>
<Route exact path="/" component={Home} />
<Route path="/users" component={User} /> //this one is a route for the api
<Route path="/counter" component={Counter} />
<Route path="/fetch-data" component={FetchData} />
</Layout>
Startup.cs
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<MafiaContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
I want to grab data that is shown in the picture and put it in the react table.

NSwag: Generate C# Client from multiple Versions of an API

We are versioning our API and generating the Swagger specification using Swashbuckle in ASP.NET Core 1.1. We can generate two API docs based on those JSON specification files:
<!-- language: c# -->
services.AddSwaggerGen(setupAction =>
{
setupAction.SwaggerDoc("0.1", new Info { Title = "Api", Version = "0.1", Description = "API v0.1" });
setupAction.SwaggerDoc("0.2", new Info { Title = "Api", Version = "0.2", Description = "API v0.2" });
// more configuration omitted
}
We are including all actions in both spec files, unless it is mapped to a specific version using the [MapToApiVersion] and ApiExplorerSettings(GroupName ="<version>")] attributes. Methods belonging to an older version only are also decorated with the [Obsolete] attribute:
<!-- language: c# -->
[MapToApiVersion("0.1")]
[ApiExplorerSettings(GroupName = "0.1")]
[Obsolete]
However, we want to have only one C# Client generated from the Union of both spec files, where all methods are included in the Client, 0.1 as well as 0.2, but all obsolete methods marked, in fact, as obsolete.
I have looked into both NSwag (which we are using for quite some time now) as well as AutoRest. AutoRest seems to support a merging scenario, but I could not get it to work because of schema validation errors (and I am more than unsure whether our specific scenario would be actually supported).
My last idea as of now to get this sorted is to somehow JSON-merge the specs into one and then feed it to NSwag.
Do we miss anything here? Is this somehow possible to realize with NSwag?
I wrote an article about similar problem https://medium.com/dev-genius/nswag-charp-client-from-multiple-api-versions-7c79a3de4622
First of all, create a schema. As I see, there are two approaches:
one schema where multiple versions are living
own schema for each version
Next, create clients for each supported version and wrap them under the wrapper client:
public class AppApiClient
{
public IV1Client V1 { get; }
public IV2Client V2 { get; }
public AppApiClient(HttpClient httpClient)
{
V1 = new V1Client(httpClient);
V2 = new V2Client(httpClient);
}
}
Here is my idea, expanding from the comments:
With swashbuckle you can generate as many SwaggerDoc as you like, the idea on this case is to generate 3 keep the same 2 versions that you have and add one more that will have everything.
c.MultipleApiVersions(
(apiDesc, targetApiVersion) =>
targetApiVersion.Equals("default") || // Include everything by default
apiDesc.Route.RouteTemplate.StartsWith(targetApiVersion), // Only include matching routes for other versions
(vc) =>
{
vc.Version("default", "Swagger_Test");
vc.Version("v1_0", "Swagger_Test V1_0");
vc.Version("v2_0", "Swagger_Test V2_0");
});
Here is a working sample:
http://swagger-net-test-multiapiversions.azurewebsites.net/swagger/ui/index?filter=Api
And the entire code for that project is on GitHub:
https://github.com/heldersepu/Swagger-Net-Test/tree/MultiApiVersions
Packages:
Install-Package Swashbuckle.AspNetCore
Install-Package Microsoft.AspNetCore.Mvc.Versioning
ValueV1Controller.cs
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/Values")]
public class ValuesV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
ValueV2Controller.cs
[ApiVersion("2")]
[Route("api/v{version:apiVersion}/Values")]
public class ValuesV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1.2", "value2.2" };
}
}
Startup.cs
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.AddMvc();
services.AddApiVersioning();
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API - V1", Version = "v1" });
c.SwaggerDoc("v2", new Info { Title = "My API - V2", Version = "v2" });
c.DocInclusionPredicate((docName, apiDesc) =>
{
var versions = apiDesc.ControllerAttributes()
.OfType<ApiVersionAttribute>()
.SelectMany(attr => attr.Versions);
return versions.Any(v => $"v{v.ToString()}" == docName);
});
c.OperationFilter<RemoveVersionParameters>();
c.DocumentFilter<SetVersionInPaths>();
});
}
// 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();
}
// 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/v2/swagger.json", "My API V2");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseMvc();
}
}
public class RemoveVersionParameters : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters?.SingleOrDefault(p => p.Name == "version");
if (versionParameter != null)
operation.Parameters.Remove(versionParameter);
}
}
public class SetVersionInPaths : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Paths = swaggerDoc.Paths
.ToDictionary(
path => path.Key.Replace("v{version}", swaggerDoc.Info.Version),
path => path.Value
);
}
}

How to redirect root to swagger in Asp.Net Core 2.x?

I'm building Asp.Net Core 2.x web api integrated with Swagger. To access the swagger, I had to append /swagger to the url, eg. https://mywebapi.azurewebsites.net/swagger/
How can I redirect https://mywebapi.azurewebsites.net/ to https://mywebapi.azurewebsites.net/swagger/ ?
Install Microsoft.AspNetCore.Rewrite from Nuget
In Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
before
app.UseMvc();
add
var option = new RewriteOptions();
option.AddRedirect("^$", "swagger");
app.UseRewriter(option);
In Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
You should have section where you set Swagger UI options. Add and set the RoutePrefix option to an empty string.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My service");
c.RoutePrefix = string.Empty; // Set Swagger UI at apps root
});
Create a default controller like this:
using Microsoft.AspNetCore.Mvc;
namespace Api
{
[ApiExplorerSettings(IgnoreApi = true)]
public class DefaultController : Controller
{
[Route("/")]
[Route("/docs")]
[Route("/swagger")]
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
}
}
Any url "/", "/docs" or "/swagger" is redirect to "/swagger".
On Startup.cs, after:
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
add :
options.RoutePrefix = string.Empty;
this will make your root url the main api url.
Open the launchSettings.json file.
Under the "profiles" node depending on your setup you should
have one or more profiles. In may case I had "IIS Express" and
another with named with my project name (e.g WebApplication1 ),
now changing the launchUrl entry to "launchUrl": "swagger"
solved my problem.
If this does not work and you have other profiles do the same and
test.
If you want to keep the path /swagger/index.html and you want to redirect to it from the / (root), in aspnetcore 6 you can use the following:
app.UseEndpoints(endpoints =>
{
...
endpoints.Map("/", context => Task.Run((() =>
context.Response.Redirect("/swagger/index.html"))));
...
});
I have modified the launchSettings.json with
"launchUrl": "swagger" instead of "launchUrl": "api/values"
work for me, if that doesnt work for you remove the
c.RoutePrefix = string.Empty; from your app.UseSwaggerUI configuration.
One straitforward approach is to redirect directly via IApplicationBuilder.Use():
app.Use(async (context, next) =>
{
if (!context.Request.Path.Value.Contains("/swagger", StringComparison.OrdinalIgnoreCase))
{
context.Response.Redirect("swagger");
return;
}
await next();
});

Categories

Resources