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!!");
}
...
Related
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.
I have an ASP.NET Core Webservice and a Blazor client. When my method is triggered in a class (not a controller) in the webservice, I want to send my data to my clients.
When I use a controller, it works but in a regular class, I see the clients.All are empty even OnConnectedAsync is triggered (means connected)
As I understand, HubContext is not a singleton but transient. I tried many things but the connected clients list is empty.
In my Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.InstallServicesInAssembly(Configuration);
services.AddTransient<IGeneralStockMarketHandler, GeneralStockMarketHandler>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<NotificationHub>("/notificationhub");
endpoints.MapControllers();
});
}
My hub:
public class NotificationHub : Hub
{
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.WriteLine("Client has disconnected");
return base.OnDisconnectedAsync(exception);
}
}
My GeneralStockMarketHandler class which I injected IHubContext:
public GeneralStockMarketHandler(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
private async void MyStockMarketHandler_OnDataReceived(StreamSnapshotResponseModel response)
{
await HandleStreamSnapshotResponse(response);
await _hubContext.Clients.All.SendAsync("notification", response.Data);
}
As you see above _hubContext.Clients.All is empty even a client has connected, so it does not send any client.
In my Blazor app, Index.razor:
#code {
string url = "http://localhost:1580/notificationhub";
HubConnection _connection = null;
bool isConnected = false;
string connectionStatus = "Ready";
private async Task ConnectToServer()
{
_connection = new HubConnectionBuilder()
.WithUrl(url)
.Build();
await _connection.StartAsync();
isConnected = true;
connectionStatus = "Connected";
_connection.Closed += async (s) =>
{
isConnected = false;
connectionStatus = "Disconnected";
await _connection.StartAsync();
isConnected = true;
};
_connection.On<string>("notification", m =>
{
connectionStatus = "I have data!";
StateHasChanged();
});
}
}
When my HubContext is injected into the controller, it works but when it is in a regular class, it does not work because the clients.All is empty. How can I solve this problem?
UPDATE:
I think, I found out the problem. In my installer, I create an instance from GeneralStockMarketHandler class and call a method which is StartToSubscribeAllStocksFromDb. I have to call this method once in the beginning
services.AddScoped<IGeneralStockMarketHandler, GeneralStockMarketHandler>();
var serviceProvider = services.BuildServiceProvider();
var stockMarketHandler serviceProvider.GetRequiredService<IGeneralStockMarketHandler>();
stockMarketHandler.StartToSubscribeAllStocksFromDb();
If I do not create an instance from GeneralStockMarketHandler class then I can get the connected clients.
I dont understand why it effects the logic. It is not singleton and I use it only one time
I am trying to send a message to a client in the server using SignalR
I am trying to do that in a class that is not a Controller. I have made the Startup like so:
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ConfigurationModel>(Configuration.GetSection("configurationModel"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
}
// 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");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseSignalR(routes => { routes.MapHub<MoveViewHub>("/movehub"); });
}
}
In my Program, this one:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
This is in my Hub:
public class MoveViewHub : Hub
{
private async void ReceiveTagNumber(object sender, EventArgs e)
{
await Clients.All.SendAsync("ReceivedFromServer", sender.ToString());
}
public async Task MoveViewFromServer(float newX, float newY)
{
Console.WriteLine(#"Receive position from Server app: " + newX + "/" + newY);
await Clients.Others.SendAsync("ReceivedNewPosition", newX, newY);
//await Clients.All.SendAsync("ReceivedNewPosition", newX, newY);
}
public async Task WriteThisMessage(string message)
{
Console.WriteLine(message);
await Clients.Others.SendAsync("ReceivedStatus", "Message was received. Thank you.");
}
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
RfidClass rfidClass = new RfidClass("THE HUB CONTEXT SHOULD BE HERE"); ====>> I NEED TO PASS MY HUBCONTEXT
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.Write("Client has disconnected");
return base.OnDisconnectedAsync(exception);
}
}
This is the RfidClass:
private IHubContext<MoveViewHub> hubContext;
public RfidClass(IHubContext<MoveViewHub> hubContext)
{
this.hubContext = hubContext;
}
public void sas()
{
Start();
}
private void Start()
{
try
{
hubContext.Clients.Others.SendAsync("ReceivedFromServer", "You are connected");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
How can I make it right?
You need to inject IServiceProvider into your Hub by .NET Core DI (like into standard Controller, injecting by constructor):
public class MoveViewHub : Hub
{
private readonly IServiceProvider provider
public MovieViewHub(IServiceProvider provider)
{
this.provider = provider
}
}
Then you can do something like this:
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
// you need to inject service provider to your hub, then get hub context from
// registered services
using (var scope = this.provider.CreateScope())
{
// get instance of hub from service provider
var scopedServices = scope.ServiceProvider;
var hub = scopedServices.GetRequiredService<IHubContext<MoveViewHub>>
// pass hub to class constructor
RfidClass rfidClass = new RfidClass(hub)
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
}
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
EDIT:
If you just want to SignalR work, you dont need to work on Hub. Instead make service. In this service inject HubContext<> of your Hub:
// you need to make your own class and interface and inject hub context
public interface ISignalRService
{
Task SendMessageToAll(string message);
}
public class SignalRService : ISignalRService
{
private readonly IHubContext<YourHub> hubContext;
public SignalRService (IHubContext<NotificationHub> hubContext)
{
this.hubContext = hubContext;
}
public async Task SendMessageToAll(string message)
{
await this.hubContext.Clients.All.SendAsync("ReciveMessage", message);
}
}
Then register that service in your Startup class:
services.AddScoped<ISignalRService, SignalRService>();
After that you can call SignalRService wherever you want to like normal service from .NetCore DI container:
private readonly ISignalRService notificationService;
public SomeController(ISignalRService notificationService)
{
this.notificationService = notificationService;
}
[HttpGet]
public async Task<IActionResult> Send()
{
await this.notificationService.SendMessageToAll("message");
return Ok();
}
You dont need to make some work around like RfidClass.
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