I have a REST API like so:
[ApiController]
[Route("abc/events/v{version:ApiVersion}")]
[ApiVersion("1.0")]
public class ABCController : Controller
{
[HttpPut("a")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult A(
[FromBody, BindRequired] AEvent aEvent)
{
StatusCodeResult result = Ok();
// do something with aEvent
return result;
}
[HttpPut("b")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult B(
[FromBody, BindRequired] BEvent bEvent)
{
StatusCodeResult result = Ok();
// do something with event b
return result;
}
[HttpPut("game")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult C(
[FromBody, BindRequired] CEvent cEvent)
{
StatusCodeResult result = Ok();
// do something with event c
return result;
}
}
I call my API from an Integration test project one of whose tests look like this:
[TestMethod]
public async Task Test_Put_A_Event_OK()
{
var logger = _container.Resolve<ILogger>();
var client = _container.Resolve<IMyClientAPI>(
new NamedParameter("logger", logger),
new NamedParameter("schema", "http"),
new NamedParameter("hostname", "localhost"),
new NamedParameter("hostPort", 5000));
var result = await client.PutEvent(_AJsonData);
Assert.IsTrue(result == Result.Success);
}
The calls these tests make to each of the endpoints occurs correctly. the problem then occurs a second or two after the successfully handled call returns to the client. The server appears to receive a request for each endpoint one after the other (even for those endpoints that have not been requested).
The associated Startup code:
public class Startup
{
// Location of xml comments for generating
// additional swagger UI information.
// Type '///' before any endpoint route function
// and it will auto-generate the comment structure.
static string XmlCommentsFilePath
{
get
{
var exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
return Path.Combine(exePath, "docs", "etsapi.xml");
}
}
public void ConfigureServices(IServiceCollection services)
{
var abcService = new ABCService();
services.AddControllers();
services.AddApiVersioning();
services.AddSingleton<IEtsSignageService>(abcService);
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "ABCApi", Version = "v1" });
options.IncludeXmlComments(XmlCommentsFilePath);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
// Cors, Authentication and authorization here if needed
app.UseEndpoints(x => x.MapControllers());
// Enable middleware to serve generated Swagger as a JSON endpoint.
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
}
}
Any thoughts on what I may be doing wrong will be greatly appreciated.
The issue I was having was to do with Autofac Dependancy injecting multiple versions of a backend class generating duplicate messages in my queue that issues the the API Requests.
Related
I need to make a way for app user to listen to server , and get updated HTTPGET request every time database is updated. Client side implementation is not needed, only server side. I have small experience in SignalR and would appreciate any help on Hub side .
My code so far
Startup
{
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.AddDbContext<DataContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
services.AddScoped<IContactRepository, ContactRepository>();
services.AddControllers();
services.AddSignalR();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Projekt", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Projekt v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<UpdatesHub>("hubs/");
});
} ```
here are my ApiCalls user can use
[ApiController]
[Route("api/[controller]")]
public class ContactsController : ControllerBase
{
private readonly IContactRepository _contactRepository;
private readonly IMapper _mapper;
public ContactsController(IContactRepository contactRepository, IMapper mapper)
{
_contactRepository = contactRepository;
_mapper = mapper;
}
#region API CALLS
[HttpGet]
public async Task<ActionResult<IEnumerable<AppUserDto>>> GetUsers([FromQuery]UserParams userParams)
{
//Ego loading phone numbers, gives circular reference problem,
//use DTOs and Mapping instead
var users = await _contactRepository.GetUsersAsync(userParams);
Response.AddpaginationHeader(users.CurrentPage, users.PageSize,
users.TotalCount, users.TotalPages);
var usersToReturn = _mapper.Map<IEnumerable<AppUserDto>>(users);
return Ok(usersToReturn);
}
//~/api/Contacts/1
[HttpGet("{id}")]
public async Task<ActionResult<AppUser>> GetUser(int id)
{
var user = await _contactRepository.GetUserByIdAsync(id);
var userToReturn = _mapper.Map<AppUserDto>(user);
return Ok(userToReturn);
}
[HttpPost("addContact")]
public async Task<ActionResult> AddContact(AppUser appUser)
{
var IsConstrained = await _contactRepository.CheckIfConstrained(appUser);
if (IsConstrained)
{
return BadRequest("user already exists");
}
else
{
_contactRepository.AddContact(appUser);
var result = await _contactRepository.SaveAllAsync();
if (result)
return Ok();
else
return BadRequest("user not saved to database");
}
}
[HttpPut("update")]
public async Task<ActionResult> Update(AppUser user)
{
//Check if user changed his name, if changed ->check if it's unique then update,
//if name not changed just update
var UserBeforeUpdate = _contactRepository.GetUserByIdAsync(user.Id);
var IsNameChangedBool = _contactRepository.CheckIfNameChanged(user , UserBeforeUpdate.Result);
if (IsNameChangedBool)
{
var IsConstrained = await _contactRepository.CheckIfConstrained(user);
if (IsConstrained)
{
return BadRequest("user already exists");
}
else
{
_contactRepository.Update(user);
if (await _contactRepository.SaveAllAsync()) return Ok("Contact updated");
return BadRequest("user not saved to database");
}
}
else
{
_contactRepository.Update(user);
if (await _contactRepository.SaveAllAsync()) return Ok("Contact updated");
return BadRequest("user not saved to database");
}
} ```
This is Hub part i don't know how to send updated httpget request to user
{
public class UpdatesHub : Hub
{
public async Task SendMessageToCaller()
{
await Clients.Caller.SendAsync("RecieveMessage");
}
}
}
Please, be sure that the route to your hub is correct. For example, if your hub is in the hubs folder you need to add endpoints.MapHub<UpdatesHub>("/hubs/hubName");
Clients can connect to SignalR in your javaScript with:
var connection = new signalR.HubConnectionBuilder().withUrl("/hubs/hubName").build();
The clients can subscribe to messages sent from the server. Something like that:
connection.on("ReceiveMessage", function (message) {
alert(message);
});
Create or modify an overloaded constructor of the class in which the database update function is implemented, in order to access the hub:
...
public class DatabaseClass
{
private IHubContext<UpdatesHub> _hub;
public DatabaseClass(IHubContext<UpdatesHub> hub)
{
_hub = hub;
}
...
Now you can use the hub to send a message to the clients, after updating your database:
...
public class DatabaseClass
{
private IHubContext<UpdatesHub> _hub;
public DatabaseClass(IHubContext<UpdatesHub> hub)
{
_hub = hub;
}
public MyUpdateFunction()
{
//Update database
//
//
_hub.Clients.All.SendAsync("ReceiveMessage", "Database updated!!");
}
...
When trying to test my application i get "This localhost page can’t be found" using vs2017.
Trying to reach https://localhost:44347/test/test
This is my 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_1);
}
// 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.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
Based upon the code shared by you, you would need to explicitly provide the routes using attribute routing.
public class ProjectenEnPersoneelCONTROLLER : Controller
{
[Route("Test/Test")]
public IActionResult Index()
{
var webClient = new WebClient();
var json = webClient.DownloadString(#"D:\Users\tijnv\source\repos\API_STA_1\API_STA_1\Json\test-request.json");
var projects = JsonConvert.DeserializeObject<Projects>(json);
return View(projects);
}
}
Alternatively you can rename controller to TestController and Action method to Test
public class TestController : Controller
{
public IActionResult Test()
{
var webClient = new WebClient();
var json = webClient.DownloadString(#"D:\Users\tijnv\source\repos\API_STA_1\API_STA_1\Json\test-request.json");
var projects = JsonConvert.DeserializeObject<Projects>(json);
return View(projects);
}
}
Using xUnit 2.4.1 to test the Api always fails to find Controller
When I create a WebApplicationFactory and pass Startup file as parameter the HTTP Client from WebApplicationFactory.CreatVlient() always returns 404 for Get requests.
Testing a .Net Core Api that uses MVC.
The CommonContext is an internal class that sets the connection.
The Configfile reads correctly
The Connectionstring to DB is correct
The Endpoint is not called correctly and therefore never hits the controller.
Class that inherits WebApplocationFactory
public class WebApiTestFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var configValues = new Dictionary<string, string>
{
{"RunStatus", "Test"},
};
builder.AddInMemoryCollection(configValues);
return WebHost.CreateDefaultBuilder()
.UseConfiguration(builder.Build())
.UseUrls("http://localhost:53976/")
.UseSetting("applicationUrl", "http://localhost:53976/")
.UseStartup<Else.WebApi.Core.Startup>();
}
}
Unit Test
public class TestControllerTest : IClassFixture<WebApiTestFactory<Startup>>
{
private readonly WebApiTestFactory<Startup> _factory;
public TestControllerTest(WebApiTestFactory<Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("api/Test/GetExample")]
public async Task Create(string url)
{
// Arrange
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.BaseAddress = new Uri("http://localhost:53976");
var client = _factory.CreateClient(clientOptions);
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Controller is in the project im testing
[ApiController]
[Route("api/Test")]
public class TestController : Controller
{
[HttpGet("GetExample")]
public ActionResult GetExample()
{
return Ok();
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
HostingEnvironment = env;
Configuration = configuration;
EwBootstrapper.BootstrapElsewareServices();
}
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
if (Configuration["RunStatus"] != "Test")
{
services.AddTransient<AuthenticationTokens>();
services.AddTransient<IPasswordValidator, PasswordValidator>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddMvc();
services.AddScoped(_ =>
new CommonContext(Configuration.GetConnectionString("DbConnection")));
services.AddSwaggerDocumentation();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // Configure authentication (JWT bearer)
.AddJwtBearer(jwtOpt => // Configure JWT bearer
{
jwtOpt.TokenValidationParameters = AuthenticationTokens.GetValidationParameters();
});
}
else
{
//services.AddMvcCore().AddJsonFormatters();
services.AddScoped(_ =>
new CommonContext(Configuration.GetConnectionString("DbTestConnection")));
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
if (Configuration["RunStatus"] != "Test")
{
app.UseSwaggerDocumentation();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseMiddleware<ApiLoggerMiddleware>();
app.UseMvc(builder => builder.MapRoute("Default", "api/{controller}/{action=Get}/{id?}")); // No default values for controller or action
app.UseDefaultFiles(); // Enable default documents ( "/" => "/index.html")
app.UseStaticFiles(); // Static files under wwwroot
app.UseAuthentication();
}
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
}
}
According to the attribute routing in your controller, the action method has the url api/Test/GetExample: [HttpGet("GetExample")], yet in your in test you are testing for CreateExample:
[InlineData("api/Test/CreateExample")]
So I guess, your test is correct in returning a 404. That route simply will not resolve to any existing action method.
I suggest you change your theory to [InlineData("api/Test/GetExample")]
I noticed that after I restart my ASP .NET API and send a POST request the API routes to the GET request method and then my POST request method. This only happens on the first POST request after I restart the API. Each POST request after this one routes directly to my POST method without processing the GET request method. Below is the class methods from my API.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
//public class variable
private readonly fujimiContext _context;
//constructor for ValuesController class
public ValuesController(fujimiContext context)
{
_context = context;
}
// GET api/values
// [Authorize(Policy = "RequireReadRole")]
[HttpGet("bins")]
public async Task<IActionResult> GetValues()
{
var values = await _context.FcbinCnfg.ToListAsync();
return Ok(values);
}
// POST api/values
// [Authorize(Policy = "RequireEditRole")]
[HttpPost("sitepost")]
public async Task<IActionResult> Post([FromBody] FcbinCnfg [] fcbincnfg)
{
if (fcbincnfg == null)
{
throw new ArgumentNullException(nameof(fcbincnfg));
}
string WindUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
string AppName = System.AppDomain.CurrentDomain.FriendlyName;
if (ModelState.IsValid)
{
int i = 0;
foreach (var obj in fcbincnfg){
_context.Update(fcbincnfg[i]);
i++;
}
await _context.SaveChangesAsync();
return StatusCode(201);
}
return BadRequest("this does not work");
}
Startup.cs file
namespace FcBin.API
{
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_1);
var connection = #"Server=XXXXX\XXXX;Database=XXXXX;Trusted_Connection=True;";
services.AddDbContext<fujimiContext>(options => options.UseSqlServer(connection));
services.AddCors();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
});
}
// 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.UseHsts();
}
app.UseCors(x => x.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
app.UseMvc();
}
}
}
Angular Route
import { Routes} from '#angular/router';
import { BinConfigComponent } from './BinConfig/BinConfig.component';
import { BinResolver } from './_resolver/bin.resolver';
export const appRoutes: Routes = [
{path: 'binconfig'
, component: BinConfigComponent
, resolve: {binconfig: BinResolver}, runGuardsAndResolvers: 'always'},
{path: '', redirectTo: 'binconfig', pathMatch: 'full', runGuardsAndResolvers: 'always'}
];
So the issue had nothing to do with my routes or the API. The issue was with my save() function in my Angular front end. I approached the function as a sequential problem when in actuality the browser/client approached my function from and efficiency stand point. Below is what I had that the browser would try to optimize
save() {
if (this.dirtyFlag) {
this.dbService.dbsave(this.binconfig).subscribe( () => {
}, error => {
console.log(error);
});
}
if (this.isFoo && this.valid) {
this.dbService.dbsavebin(this.newbin).subscribe( error => {
console.log(error);
});
} else if (!this.valid && this.isFoo) {
this.toastr.warning('Enter a Bin Id');
}
this.router.navigate(['/binconfig']);
}
Here I was having a route resolver reload the page which I triggered after a save. Tacking the route at the end of this save would result in the browser trying to optimize the POST / GET methods in the save() function and in the route resolver. I solved the issue by instead using the arrow function to execute the router navigation after a successful save.
save() {
if (this.dirtyFlag) {
this.dbService.dbsave(this.binconfig).subscribe( () => {
}, error => {
console.log(error);
}, () => {
this.router.navigate(['/binconfig']);
this.toastr.success('Changes Saved');
this.dirtyFlag = false;
});
}
if (this.isFoo && this.valid) {
this.dbService.dbsavebin(this.newbin).subscribe( () => {
this.router.navigate(['/binconfig']);
this.toastr.success('New Bin Added');
this.isFoo = false;
}, error => {
console.log(error);
});
} else if (!this.valid && this.isFoo) {
this.toastr.warning('Enter a Bin Id');
}
}
I am trying to add swagger+swashbuckle to my ASP.NET Core project. I can get the Swagger UI up and running but it is completely empty. I tried poking around and found a similar problem at https://github.com/domaindrivendev/Swashbuckle/issues/1058. This made me think that maybe the routing was the issue so I tried giving my controller an explicit route using [Route("testroute")] over the method but not the class. This made the endpoints I added a route to show up with no problem.
As adding an explicit route to every endpoint is non-optimal, what am I doing wrong and how do I fix it to get swagger to show all my endpoints?
My startup where swagger is integrated
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true , reloadOnChange: true);
Configuration = builder.Build();
}
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().AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
services.AddDbContext<PromotionContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:Jasmine"]));
services.AddTransient<PromotionDbInitializer>();
services.AddTransient<IComponentHelper, ComponentHelper>();
services.AddTransient<IComponentFileHelper, ComponentFileHelper>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, PromotionDbInitializer promotionSeeder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
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");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Promotion}/{action=Index}/{id?}");
});
//Because there is not a seed method built into the EF migrations pipeline in EFCore this seeding method will interfere with the migrations when attempting to deploy the database
//uncomment if you need to seed
//promotionSeeder.Seed().Wait();
}
}
My controller where the GetAll and Post method show up on the swagger ui page under testroute and testFormRoute, but the Get and Delete methods do not show up
public class PromotionController : Controller
{
private PromotionContext context;
public PromotionController(PromotionContext _context)
{
context = _context;
}
public IActionResult Index()
{
return View();
}
[HttpGet]
[Route("testroute")]
public IActionResult GetAll()
{
try
{
var result = context.Promotions
.Include(promotion => promotion.CombinabilityType)
.Include(promotion => promotion.ValueType)
.Include(promotion => promotion.Currency)
.Include(promotion => promotion.Components)
.ThenInclude(component => component.TargetType)
.ToList();
return Ok(result);
}
catch(Exception ex)
{
return StatusCode(500);
}
}
public IActionResult Get(string promoCode)
{
try
{
var result = context.Promotions
.Include(promotion => promotion.CombinabilityType)
.Include(promotion => promotion.ValueType)
.Include(promotion => promotion.Currency)
.Include(promotion => promotion.Components)
.ThenInclude(component => component.TargetType)
.FirstOrDefault(x => x.PromoCode == promoCode);
return Ok(result);
}
catch(Exception ex)
{
return StatusCode(500);
}
}
[HttpPost]
[Route("testFormRoute")]
public IActionResult Post([FromForm] Promotion newPromotion)
{
try
{
context.Promotions.Add(newPromotion);
context.SaveChanges();
}
catch(DbUpdateException ex)
{
return StatusCode(500);
}
return Ok();
}
[HttpDelete]
public IActionResult Delete(string promoCode)
{
try
{
var promotion = context.Promotions.FirstOrDefault(x => x.PromoCode == promoCode);
if(promotion != null)
{
context.Promotions.Remove(promotion);
context.SaveChanges();
}
}
catch(DbUpdateException ex)
{
return StatusCode(500);
}
return Ok();
}
}
Add a route attribute to your controller:
[Route("[controller]/[action]")]
public class PromotionController : Controller
{
...
And set the HttpGet attribute on your actions:
[HttpGet]
public IActionResult GetAll()
{
...
[HttpGet("{promoCode}")]
public IActionResult Get(string promoCode)
{
...
You have to be careful about how you mix and match static and dynamic routes. Check out this article for more detail about attribute based routing in asp.net core.
Try changing
public class PromotionController : Controller
to
public class PromotionController : ApiController