Consider controllers below:
namespace Web.Controllers
{
[ApiVersioning("1.0")
[Route("api/v{version:apiVersion}/[controller]")]
public class Product : ApiController
{
[HttpGet("id")]
public IHttpActionResult<bool> GetProduct(Guid id)
{ /* rest of the code */ }
}
}
namespace Web.Controllers
{
[ApiVersioning("2.0")
[Route("api/v{version:apiVersion}/[controller]")]
public class Product2 : ApiController
{
[HttpGet("id")]
public IHttpActionResult<bool> GetProduct(Guid id)
{ /* rest of the code */ }
}
}
And Swagger documents in Startup class:
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.PostProcess = document =>
{
document.Info.Version = "v1.0";
};
});
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.PostProcess = document =>
{
document.Info.Version = "v2.0";
};
});
Now after testing the API browser with NSwag it ignores the versions and shows all the APIs in both v1 and v2 document.
How to tell NSwag to separate them?
I think you are missing ApiGroupNames property which is used to select Api version. Please add ApiGroupNames property like below code and let us know.
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.PostProcess = document =>
{
document.Info.Version = "v1.0";
};
config.ApiGroupNames = new[] { "1.0" };
});
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.PostProcess = document =>
{
document.Info.Version = "v2.0";
};
config.ApiGroupNames = new[] { "2.0" };
});
Related
I am using RavenTestDriver for my unit tests .
Here is my configuration of my test :
ConfigureServer(new TestServerOptions
{
CommandLineArgs = new System.Collections.Generic.List<string> { "--RunInMemory=true", },
FrameworkVersion = null,
}) ;
IDocumentStore store = GetDocumentStore();
try
{
store.Maintenance.ForDatabase(ravenTestDatabaseName).Send(new GetStatisticsOperation());
}
catch (DatabaseDoesNotExistException)
{
store.Maintenance.Server.Send(new CreateDatabaseOperation(new DatabaseRecord(ravenTestDatabaseName)));
}
session = store.OpenAsyncSession(new SessionOptions()
{
Database=ravenTestDatabaseName,
});
var hostBuilder = easy.api.Program.CreateHostBuilder(new string[0])
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseTestServer();
})
.ConfigureServices(services =>
{
services.AddScoped<ICurrentUserService, InitRequest>();
services.AddScoped<ICacheStorage>(provider=>
{
return new Mock<ICacheStorage>().Object;
});
services.AddRavenDbAsyncSession(GetDocumentStore(new GetDocumentStoreOptions()));
services.AddScoped<IAsyncDocumentSession>((c) =>
{
return store.OpenAsyncSession(new SessionOptions()
{
Database=ravenTestDatabaseName,
});
});
});
So I have several test in my solution:
When I run each test individual the test passed .But When I run all tests together I get this error :
Raven.Client.Exceptions.Database.DatabaseDisabledException : Raven.Client.Exceptions.Database.DatabaseDisabledException: The database Test-Server is currently locked because Checking if we need to recreate indexes
at Raven.Server.Documents.DatabasesLandlord.UnloadAndLockDatabase(String dbName, String reason) in C:\Builds\RavenDB-Stable-5.2\52000\src\Raven.Server\Documents\DatabasesLandlord.cs:line 907
at Raven.Server.Web.System.AdminDatabasesHandler.Put() in C:\Builds\RavenDB-Stable-5.2\52000\src\Raven.Server\Web\System\AdminDatabasesHandler.cs:line 327
at Raven.Server.Routing.RequestRouter.HandlePath(RequestHandlerContext reqCtx) in C:\Builds\RavenDB-Stable-5.2\52000\src\Raven.Server\Routing\RequestRouter.cs:line 349
at Raven.Server.RavenServerStartup.RequestHandler(HttpContext context) in C:\Builds\RavenDB-Stable-5.2\52000\src\Raven.Server\RavenServerStartup.cs:line 174
The server at /admin/databases?name=Test-Server&replicationFactor=1&raft-request-id=d9a39f56-a1ad-44b7-b6e5-b195c1143c4b responded with status code: ServiceUnavailable.
I just leave the database name empty .
public class TestHostBuilder : RavenTestDriver, IAsyncLifetime
{
public HttpClient httpClient = null;
public IDocumentStore documentStore = null;
public async Task InitializeAsync()
{
documentStore = GetDocumentStore();
var hostBuilder = easy.api.Program.CreateHostBuilder(new string[0])
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseTestServer();
})
.ConfigureServices(services =>
{
services.AddScoped<ICurrentUserService, InitRequest>();
services.AddScoped<ICacheStorage>(provider =>
{
return new Mock<ICacheStorage>().Object;
});
services.AddRavenDbAsyncSession(GetDocumentStore(new GetDocumentStoreOptions()));
services.AddTransient<IAsyncDocumentSession>((c) =>
{
return documentStore.OpenAsyncSession();
});
});
var host = hostBuilder.Start();
httpClient = host.GetTestClient();
}
public Task DisposeAsync()=> Task.CompletedTask;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(); //this replaces these services.AddMvcCore().AddApiExplorer();
...
services.AddSwaggerGen(options =>
{
// make this automatic ???
options.SwaggerDoc("v1", new Info { Version = "v1", Title = "v1 API", Description = "v1 API Description" });
options.SwaggerDoc("v2", new Info { Version = "v2", Title = "v2 API", Description = "v2 API Description" });
...
options.DocInclusionPredicate((version, desc) =>
{
var versions = desc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
var maps = desc.CustomAttributes().OfType<MapToApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
return versions.Any(v => $"v{v.ToString()}" == version) && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
});
});
}
This code works as expected. But can the calls to SwaggerDoc be automated, in order to make the code more generic? In DocInclusionPredicate from the desc parameter the versions can be gathered.
As you are using the ApiVersionAttribute, I assume you are using the Microsoft.AspNetCore.Mvc.Versioning nuget package. The package provides a service named IApiVersionDescriptionProvider. This service provides an enumeration of all detected API-Versions. You can then automatically add them as a swagger-doc.
services.AddSwaggerGen(options =>
{
// you can use the IApiVersionDescriptionProvider
var provider = services.BuildServiceProvider()
.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
var info = new Info
{
Title = $"My API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Contact = new Contact
{
Email = "info#mydomain.com",
Name = "Foo Bar",
Url = "https://thecatapi.com/"
}
};
options.SwaggerDoc(description.GroupName, info);
}
// instead of manually adding your versions
//options.SwaggerDoc("v1", new Info { Version = "v1", Title = "v1 API", Description = "v1 API Description" });
//options.SwaggerDoc("v2", new Info { Version = "v2", Title = "v2 API", Description = "v2 API Description" });
options.DocInclusionPredicate((version, desc) =>
{
var versions = desc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
var maps = desc.CustomAttributes().OfType<MapToApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
return versions.Any(v => $"v{v.ToString()}" == version) && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
});
});
I'm trying to bind the data from api which is written in .net core with angular api using ng for i getting the value properly but when i use the check input field my console is full on unstoppable errors
I have tried many examples from stackoverflow non them worked for me
export class UsermanagementComponent {
userDetailsList: any = [];
public userList: any= [];
departmentuser: any = {};
public searchTxt:any;
isActive: boolean = false;
checkuserstatus: boolean;
constructor(private router: Router, private http: HttpClient, private
toastr: ToastrService, private appComponent: AppComponent) {
this.userList
}
private jwtHelper: JwtHelperService = new JwtHelperService();
ngOnInit() {
this.appComponent.startSpinner();
this.getuser();
;
}
edituser(userList: any) {
localStorage.setItem("userList", JSON.stringify(userList));
console.log(userList);
this.router.navigate(["/landingpage/edituser"], userList);
}
lockUnlockUser(userList: any) {
console.log(userList);
this.http.post(environment.apiUrl + "Account/LockUserAccount", userList,
{
}).subscribe(data => {
this.appComponent.stopSpinner();
this.router.navigate(["/landingpage/usermanagement"]);
this.userList = data;
this.checkuserstatus = this.userList.lockoutEnabled;
console.log(this.checkuserstatus);
if (this.checkuserstatus == true) {
let toast = this.toastr.success(MessageVariable.UserLocked);
alert(toast);
} else if (this.checkuserstatus == false) {
let toast = this.toastr.info(MessageVariable.UserUnLocked);
alert(toast);
}
}, (err) => {
this.toastr.error(MessageVariable.ErrorMsg);
});
}
getuser() {
this.appComponent.startSpinner();
var userId = localStorage.getItem('userid');
console.log(userId);
this.http.get(environment.apiUrl + "Account/GetUser", {
}).subscribe(data => {
this.appComponent.stopSpinner();
this.userList = data;
console.log(this.userList);
}, (err) => {
this.toastr.error(MessageVariable.ErrorMsg);
});
}
}
UsermanagementComponent.html:22 ERROR Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed
at
I have been looking around the Telerik forums & Stackoverflow for an answer for this and I am completely stuck and unable to figure out the issue.
I am using the Kendo UI for Asp.Net Core Scheduler Control. I have it reading the data from my controller fine. However, I cannot get it call the HttpPut handler correctly.
When checking the traffic I get the following response, and therefor my breakpoint inside my HttpPut handler will never be hit.
400 - Bad Request
{"":["The input was not valid."]}
My code in my view is:
#(Html.Kendo().Scheduler<MeetingViewModel>()
.Name("SchedulerView")
.Height(500)
.Date(DateTime.Now.ToUniversalTime())
.StartTime(new DateTime(2018, 11, 28, 0, 00, 00).ToUniversalTime())
.MajorTick(30)
.ShowWorkHours(false)
.Footer(false)
.Editable(edit =>
{
//edit.Resize(false);
edit.Create(false);
})
.Views(views =>
{
views.TimelineView(timeline => timeline.EventHeight(50));
//views.TimelineWeekView(timeline => timeline.EventHeight(50));
//views.TimelineWorkWeekView(timeline => timeline.EventHeight(50));
//views.TimelineMonthView(timeline =>
//{
// timeline.StartTime(DateTime.Now);
// timeline.EndTime(DateTime.Now.AddMonths(1));
// timeline.MajorTick(1440);
// timeline.EventHeight(50);
//});
})
.Timezone("Etc/UTC")
.Group(group => group.Resources("WorkCenters" /*,"Attendees"*/).Orientation(SchedulerGroupOrientation.Vertical))
.Resources(resource =>
{
resource.Add(m => m.ScheduleRowID)
.Title("Work Center")
.Name("WorkCenters")
.DataTextField("Text")
.DataValueField("Value")
.DataColorField("Color")
.BindTo(#Model.AvailableWorkCenters);
})
.DataSource(d => d
.ServerOperation(true)
.WebApi()
.Model(m =>
{
m.Id(f => f.ActivityID);
m.Field(f => f.Title).DefaultValue("No title");
//m.RecurrenceId(f => f.RecurrenceID);
m.Field(f => f.Description).DefaultValue("No Description");
})
.Events(events => events.Error("error_handler"))
.Read(read => read.Action("GetActivities", "Scheduler").Data("setRequestDateTimes"))
//.Create(create => create.Action("Post", "Scheduler"))
.Update(update => update.Action("PutActivity", "Scheduler", new { id = "{0}" }).Type(HttpVerbs.Put))
//.Destroy(destroy => destroy.Action("Delete", "Scheduler", new { id = "{0}" }))
)))
And my API Controller is as follows:
[Route("Api/[controller]")]
[ApiController]
public class SchedulerController : DawnController
{
public SchedulerController(DatabaseContext context) : base(context)
{
}
[HttpGet]
public DataSourceResult GetActivities([DataSourceRequest] DataSourceRequest request, DateTime requestStartDateTime, DateTime requestEndDateTime)
{
//Kendo doesnt seem to send the full date range. so + 1 day to end
requestEndDateTime = requestEndDateTime.AddDays(1);
List<MeetingViewModel> test = new List<MeetingViewModel>();
foreach (JobTask jobTask in Context.JobTask)
{
if (JobTask.HasActivityInDateRange(jobTask, requestStartDateTime, requestEndDateTime))
{
foreach (Activites jobTaskAct in jobTask.Activites)
{
test.Add(new MeetingViewModel()
{
JobTaskID = jobTask.JobTaskId,
ActivityID = jobTaskAct.ActivityId,
Title = jobTaskAct.Name,
Description = jobTaskAct.Description,
Start = jobTaskAct.StartTime.ToUniversalTime(),
End = jobTaskAct.EndTime.ToUniversalTime(),
IsAllDay = false,
ScheduleRowID = jobTaskAct.Workcenter.WorkCenterId,
});
}
}
}
return test.ToDataSourceResult(request);
}
[HttpPut("{id}")]
public IActionResult PutActivity(int id, MeetingViewModel task)
{
if (ModelState.IsValid && id == task.ActivityID)
{
try
{
//breakpoint here
bool a = true;
//update the db here
}
catch (DbUpdateConcurrencyException)
{
return new NotFoundResult();
}
return new StatusCodeResult(200);
}
else
{
return BadRequest(ModelState.Values.SelectMany(v => v.Errors).Select(error => error.ErrorMessage));
}
}
}
Thanks
The URL exposing your controller method PutActivity in your controller example is PUT api/scheduler/{id}
To access that URL use this Update method.
.Update(update => update.Action("Put", "Scheduler", new { id = "{0}" }))
See this demo as example
Alternatively
If you want to implment the URL api/Scheduler/PutActivity/{id} (similar pattern to your GET) then you will need to modify the attribute over the put method as follows.
[HttpPut("PutActivity/{id}")]
public IActionResult PutActivity(int id, MeetingViewModel task)
Then you can call api/Scheduler/PutActivity/{id} with this asp.net action call.
.Update(update => update.Action("PutActivity", "Scheduler", new { id = "{0}" }).Type(HttpVerbs.Put))
I am trying to setup a Web API with Swagger and an IdentityServer and can't figure out how to make Swagger works correctly.
My React app is working with the IdentityServer and I managed to get the ui working but when I try to activate authentication, I always get a "insufficient_scope" error.
Here's my config :
Client
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = "ipassportimplicit",
ClientName = "iPassport (Implicit)",
Flow = Flows.Implicit,
AllowAccessToAllScopes = true,
//redirect = URI of the React application callback page
RedirectUris = new List<string>
{
Constants.iPassportReact + "callback.html"
}
},
new Client
{
ClientId = "swaggerui",
ClientName = "Swagger (Implicit)",
Flow = Flows.Implicit,
AllowAccessTokensViaBrowser = true,
PostLogoutRedirectUris = new List<string>
{
"http://localhost:53633/swagger/"
},
AllowAccessToAllScopes = true,
RedirectUris = new List<string>
{
"http://localhost:53633/swagger/ui/o2c-html"
}
}
};
}
Scope
public static IEnumerable<Scope> Get()
{
return new List<Scope>
{
new Scope
{
Name = "passportmanagement",
DisplayName = "Passport Management",
Description = "Allow the application to manage passports on your behalf.",
Type = ScopeType.Resource
},
new Scope
{
Name = "swagger",
DisplayName = "Swagger UI",
Description = "Display Swagger UI",
Type = ScopeType.Resource
}
};
}
SwaggerConfig
public static void Register(HttpConfiguration config)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
config
.EnableSwagger(c =>
{
c.SingleApiVersion("v2", "api_iPassport");
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(Constants.iPassportSTSAuthorizationEndpoint)
.TokenUrl(Constants.iPassportSTSTokenEndpoint)
.Scopes(scopes =>
{
scopes.Add("swagger", "Swagger UI");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support("swaggerui", "swaggerrealm", "Swagger UI");
});
}
Operation Filter
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var actFilters = apiDescription.ActionDescriptor.GetFilterPipeline();
var allowsAnonymous = actFilters.Select(f => f.Instance).OfType<OverrideAuthorizationAttribute>().Any();
if (allowsAnonymous)
return; // must be an anonymous method
//var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
// .Select(filterInfo => filterInfo.Instance)
// .OfType<AllowAnonymousAttribute>()
// .SelectMany(attr => attr.Roles.Split(','))
// .Distinct();
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{"oauth2", new List<string> {"swagger"}}
};
operation.security.Add(oAuthRequirements);
}
}
Response Headers
{
"date": "Fri, 12 May 2017 03:37:08 GMT",
"www-authenticate": "Bearer error=\"insufficient_scope\"",
"x-sourcefiles": "=?UTF-8?B?TzpcTG9jYWwgV29ya3NwYWNlXFZTVFMgSUJNXFJlcG9zXFdlYkFQSVxhcGlfaVBhc3Nwb3J0XGFwaV9pUGFzc3BvcnRcYXBpXFVzZXJcR2V0?=",
"server": "Microsoft-IIS/10.0",
"x-powered-by": "ASP.NET",
"content-length": "0",
"content-type": null
}
Anything I can't see? All help appreciated!
Thanks
My problem was in my Startup.cs class of the Web API in which I didn't add the required scope to the
public void ConfigureAuth(IAppBuilder app)
{
var options = new IdentityServerBearerTokenAuthenticationOptions()
{
Authority = Constants.iPassportSTS,
RequiredScopes = new[] { "passportmanagement", "swagger" }
};
app.UseIdentityServerBearerTokenAuthentication(options);
}