.NET CORE web api Changes not detected - vue.js

I'm currently working on a VueJS app using .NET Core 2.2 for the back-end part.
I was working on it for a few months but it suddenly stopped working like a charm when I updated from 2.0 to 2.2.
My web API changes are not detected and I don't know why.
For instance, I have a few controllers and whenever I change them, and then use the web API, the changes are not made. I can even delete the whole file and the web API using this file will still be working!
Another problem I get is that when I create new controller files, it's not detected; I'm stuck with my old controllers, which I'm not able to update.
Others files updates are detected (at least if I change the VueJS front-end)
I can also change the providers, delete whatever file used for the web API, changes are not detected. It may be a configuration issue?
Is there anything I could try to make things update again?
Update: I can change whatever I want in the back-end and it will do nothing. Compilations errors are the only problem I have to care about, it's like the app doesn't use the code anymore.
Here is an example I can provide:
I have a controller InterventionController which retrieve data about operations (I am french in a french context so variables names, etc will be in french) :
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Vue2Spa.Models;
using Vue2Spa.Providers;
namespace Vue2Spa.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class InterventionController : Controller
{
private readonly IInterventionProvider interventionProvider;
public InterventionController(IInterventionProvider interventionProvider)
{
this.interventionProvider = interventionProvider;
}
[HttpGet("[action]")]
public IActionResult Interventions([FromQuery(Name = "from")] int from = 0, [FromQuery(Name = "to")] int to = 5000)
{
var quantity = to - from;
if (quantity <= 0)
{
return BadRequest("La quantité doit être positive !");
}
else if (from < 0)
{
return BadRequest("Vous devez spécifier un indice de départ non nul !");
}
var allInterventions = interventionProvider.GetInterventions();
var result = new
{
TotalInterventions = allInterventions.Count,
Interventions = allInterventions.Skip(from).Take(quantity).ToArray()
};
return Ok(result);
}
}
// Others methods not useful for my example
}
It calls a provider which has the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Vue2Spa.Models;
namespace Vue2Spa.Providers
{
public class DBInterventionProvider : IInterventionProvider
{
private List<Intervention> interventions { get; set; }
DbContextOptionsBuilder<DepouillementTestContext> optionsBuilder = new DbContextOptionsBuilder<DepouillementTestContext>();
public DBInterventionProvider()
{
optionsBuilder.UseSqlServer(credentials); // Credentials are correct but not including it there for obvious reasons
using (var context = new LECESDepouillementTestContext(optionsBuilder.Options))
{
interventions = context.Intervention.ToList();
}
}
public List<Intervention> GetInterventions()
{
using (var context = new LECESDepouillementTestContext(optionsBuilder.Options))
{
interventions = context.Intervention.ToList();
}
return interventions;
}
// Others methods not useful for this example
}
}
I can delete these files, and I'm still able to access my operations web API
If needed, here is my startup.cs file:
using System;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Vue2Spa.Models;
namespace Vue2Spa
{
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)
{
// Add framework services.
services.AddMvc();
// Additional code for SQL connection
services.AddDbContext<DepouillementTestContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.
MigrationsAssembly(
typeof(Startup).
GetTypeInfo().
Assembly.
GetName().Name);
//Configuring Connection Resiliency:
sqlOptions.
EnableRetryOnFailure(maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw.
// Default in EFCore would be to log warning when client evaluation is done.
options.ConfigureWarnings(warnings => warnings.Throw(
RelationalEventId.QueryClientEvaluationWarning));
});
// Provider pour les interventions
services.AddSingleton<Providers.IInterventionProvider, Providers.DBInterventionProvider>();
}
// 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();
// Webpack initialization with hot-reload.
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true,
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
}
Thanks in advance,

Well I've found why I had this problem, and I feel kinda dumb for that but well, it's working now.
I didn't change my launch.json when I upgraded from .NETCORE 2.0 to 2.2, all I had to do was changing
"program": "${workspaceFolder}/content/bin/Debug/netcoreapp2.0/Vue2Spa.dll",
by
"program": "${workspaceFolder}/content/bin/Debug/netcoreapp2.2/Vue2Spa.dll",
For more informations, see : https://learn.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio

Related

How can I use an API controller as a service in an ASP.NET Core application?

I'm new to entity framework and have built a working API with a controller that I'll call APIController. My aim is to use the API as a 'service' in my ASP.NET Core MVC app which is in the same project but a different solution by having an instance of the API controller in my MVC controller and using it for the View requests.
The issue is that doing it this way means I have two contexts: one for the database in the API 'TTContext', which is the main one used to actually edit the files and another in the MVC 'TraineeTrackerContext'which is required for userManager authentication because it needs to inherent IdentityDbContext (which can't be done in the API).
I've tried to introduce these contexts in the program.cs as below:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SpartaTraineeTrackerAPI.Models;
using SpartaTraineeTrackerAPI.Service;
using TraineeTrackerMVC.Data;
using SpartaTraineeTrackerAPI.Models;
using SpartaTraineeTrackerAPI.Service;
using TraineeTrackerMVC.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<TTContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDbContext<TraineeTrackerContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<TTContext>();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<ITrackerService, TrackerService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
With the idea being that the TraineeTrackerContext has an instance of the TTContext attached like this:
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using TraineeTrackerMVC.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using SpartaTraineeTrackerAPI.Models;
namespace TraineeTrackerMVC.Data;
public partial class TraineeTrackerContext : IdentityDbContext
{
public TraineeTrackerContext()
{
ttContext = new TTContext();
}
public TraineeTrackerContext(TTContext context)
{
ttContext = context;
}
/*
public TraineeTrackerContext(DbContextOptions<TraineeTrackerContext> options)
: base(options)
{
ttContext = new TTContext(options);
}
*/
public TTContext ttContext;
public virtual DbSet<Profile> Profiles { get; set; }
public virtual DbSet<Tracker> Trackers { get; set; }
public virtual DbSet<User> Users { get; set; }
public TraineeTrackerContext(DbSet<Tracker> trackers, DbSet<User> users, DbSet<Profile> profiles)
{
Trackers = trackers;
Users = users;
Profiles = profiles;
}
}
But running it throws two exceptions when doing builder.Build():
InvalidOperationException: Error while validating the service descriptor ServiceType
System.AggregateException: Unable to activate type Trainee TrackerContext
I've been trying to fix these myself but I'm very new to Entity Framework and this is one of the first applications I've made so I was hoping someone could help explain where I'm going wrong and perhaps how to do this correctly. I haven't found many resources online using the API as a service in this way, as others tend to use the HttpClient class. Any help would be appreciated.

Serve both REST and GraphQL APIs from .NET Core application

I have a .NET Core REST API server that is already serving customers.
Can I configure the API server to also support GraphQL by adding the HotChocolate library and defining the queries? Is it OK to serve both GraphQL and REST APIs from my .NET Core server?
Yes, supporting both REST APIs (controllers) and GraphQL is totally OK.
If you take libraries out of the picture, handling a GraphQL request just means handling an incoming POST to /graphql.
You can write a typical ASP.NET Core controller that handles those POSTs, if you want. Frameworks like Hot Chocolate provide middleware like .UseGraphQl() that make it more convenient to configure, but conceptually you can think of .UseGraphQl() as adding a controller that just handles the /graphql route. All of your other controllers will continue to work just fine!
There is a way you can automate having both APIs up and running at the same time using hotchocolate and schema stitching.
Basically I followed this tutorial offered by Ian webbington.
https://ian.bebbs.co.uk/posts/LessReSTMoreHotChocolate
Ian uses swagger schema from its API to create a graphql schema which saves us time if we think about it. It's easy to implement, however you still need to code to expose graphql endpoints.
This is what I implemented to connect all my graphql and rest APIs in just one API gateway. I'm sharing my custom implementation to have swagger schema (REST) running under hotchocolate (Graphql):
using System;
using HotChocolate;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Playground;
using HotChocolate.AspNetCore.Voyager;
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.Stitching;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using SmartGateway.Api.Filters;
using SmartGateway.Api.RestServices.SmartConfig;
namespace SmartGateway.Api.Extensions
{
public static class GraphQlExtensions
{
public static IServiceCollection AddGraphQlApi(this IServiceCollection services)
{
services.AddHttpClient("smartauth", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartAuth.Endpoint);
});
services.AddHttpClient("smartlog", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartLog.Endpoint);
});
services.AddHttpClient("smartway", (sp, client) =>
{
sp.SetUpContext(client); //GRAPHQL API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartWay.Endpoint);
});
services.AddHttpClient<ISmartConfigSession, SmartConfigSession>((sp, client) =>
{
sp.SetUpContext(client); //REST API
client.BaseAddress = new Uri(AppSettings.SmartServices.SmartConfig.Endpoint);
}
);
services.AddDataLoaderRegistry();
services.AddGraphQLSubscriptions();
services.AddStitchedSchema(builder => builder
.AddSchemaFromHttp("smartauth")
.AddSchemaFromHttp("smartlog")
.AddSchemaFromHttp("smartway")
.AddSchema(new NameString("smartconfig"), SmartConfigSchema.Build())
.AddSchemaConfiguration(c =>
{
c.RegisterExtendedScalarTypes();
}));
services.AddErrorFilter<GraphQLErrorFilter>();
return services;
}
public static IApplicationBuilder UseGraphQlApi(this IApplicationBuilder app)
{
app.UseWebSockets();
app.UseGraphQL("/graphql");
app.UsePlayground(new PlaygroundOptions
{
Path = "/ui/playground",
QueryPath = "/graphql"
});
app.UseVoyager(new PathString("/graphql"), new PathString("/ui/voyager"));
return app;
}
}
}
Set up HttpContext extension:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace SmartGateway.Api.Extensions
{
public static class HttpContextExtensions
{
public static void SetUpContext(this IServiceProvider servicesProvider, HttpClient httpClient)
{
HttpContext context = servicesProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (context?.Request?.Headers?.ContainsKey("Authorization") ?? false)
{
httpClient.DefaultRequestHeaders.Authorization =
AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"].ToString());
}
}
}
}
You need this to handle and pass the HTTPClient to your swagger Sdk.
using System.Net.Http;
namespace SmartGateway.Api.RestServices.SmartConfig
{
public interface ISmartConfigSession
{
HttpClient GetHttpClient();
}
public class SmartConfigSession : ISmartConfigSession
{
private readonly HttpClient _httpClient;
public SmartConfigSession(HttpClient httpClient)
{
_httpClient = httpClient;
}
public HttpClient GetHttpClient()
{
return _httpClient;
}
}
}
This is my graphql Schema:
namespace SmartGateway.Api.RestServices.SmartConfig
{
public static class SmartConfigSchema
{
public static ISchema Build()
{
return SchemaBuilder.New()
.AddQueryType<SmartConfigQueries>()
.AddMutationType<SmartConfigMutations>()
.ModifyOptions(o => o.RemoveUnreachableTypes = true)
.Create();
}
}
public class SmartConfigMutations
{
private readonly ISmartConfigClient _client;
public SmartConfigMutations(ISmartConfigSession session)
{
_client = new SmartConfigClient(AppSettings.SmartServices.SmartConfig.Endpoint, session.GetHttpClient());
}
public UserConfigMutations UserConfig => new UserConfigMutations(_client);
}
}
Finally, this is how you publish endpoints:
using System.Threading.Tasks;
using SmartConfig.Sdk;
namespace SmartGateway.Api.RestServices.SmartConfig.UserConfigOps
{
public class UserConfigMutations
{
private readonly ISmartConfigClient _client;
public UserConfigMutations(ISmartConfigClient session)
{
_client = session;
}
public async Task<UserConfig> CreateUserConfig(CreateUserConfigCommand createUserConfigInput)
{
var result = await _client.CreateUserConfigAsync(createUserConfigInput);
return result.Response;
}
public async Task<UserConfig> UpdateUserConfig(UpdateUserConfigCommand updateUserConfigInput)
{
var result = await _client.UpdateUserConfigAsync(updateUserConfigInput);
return result.Response;
}
}
}
More documentation about hotchocolate and schema stitching here:
https://hotchocolate.io/docs/stitching

Unable to create swagger.json file when using aspnet-api-versioning

I have .NET Core 2.2 application. I am trying to set up API with different versions using Microsoft.AspnetCore.Mvc.Versioning nugetpackage. I followed the samples provided in the repository.
I want to use an API version based on the name of the defining controller's namespace.
Project Structure
Controllers
namespace NetCoreApiVersioning.V1.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
namespace NetCoreApiVersioning.V2.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
Note the controllers does not have [ApiVersion] attribute becuase i want the versioning to be defined by the namespace
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddApiVersioning(
options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
// automatically applies an api version based on the name of the defining controller's namespace
options.Conventions.Add(new VersionByNamespaceConvention());
});
services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "API v1 ", Version = "v1" });
c.SwaggerDoc("v2", new Info { Title = "API v2", Version = "v2" });
});
// commented code below is from
// https://github.com/microsoft/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerSample
//services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
//services.AddSwaggerGen(
// options =>
// {
// // add a custom operation filter which sets default values
// //options.OperationFilter<SwaggerDefaultValues>();
// // integrate xml comments
// //options.IncludeXmlComments(XmlCommentsFilePath);
// });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
// remaining configuration omitted for brevity
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
app.UseMvc();
}
}
Issue
It is not able to generate swagger.json file. When i browse url /swaggger i see error undefined /swagger/v1/swagger.json
found..
i am missing [HttpGet] attribute in ActionMethods

Error when calling UseSwagger in Azure Web API

I have VS2015 and .Net Core Web API project created.
I'm following example in http://www.technicalblogs.sentientmindz.com/2017/04/09/enabling-swagger-support-to-the-web-api/
I have installed Swashbuckle.AspNetCore and next trying to code, but getting errors when using UseSwagger. Please advise me.
/* Startup.cs */
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Owin;
using Owin;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.Extensions.DependencyInjection;
[assembly: OwinStartup(typeof(TestApi.Startup))]
namespace TestApi
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
/*use swagger added by me*/
app.UseSwagger(); /*ERROR:iAppBuilder does not contain definition for UseSwagger…*/
app.UseSwaggerUI(c =>. /*ERROR :iAppBuilder does not contain definition for UseSwaggerUI…*/
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Accounts API V1");
});
}
//Add framework services by me
public void ConfigureServices(IServiceCollection services) {
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "AcccountsAPI", Version = "v1" });
});
}
}
}
I'm assuming from your code that you're using .Net Core v1.1, this is how I've done it:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
config.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "WebAPI");
c.IncludeXmlComments(GetXmlCommentsPath());
c.ResolveConflictingActions(x => x.First());
}).EnableSwaggerUi();
}
protected static string GetXmlCommentsPath()
{
return System.String.Format($#"{0}\bin\MyApi.XML",
System.AppDomain.CurrentDomain.BaseDirectory);
}

How to add web API to an existing MVC Hottowel project

I have one Hottowel project created using it's template from Visual Studio. I want to add the Web API feature in that project. I have created a Web Api controller to the controller folder and tries to access like "http://localhost:53397/api/Values" But I get an error saying The resource cannot be found error.
My controller code looks like below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MvcApplication8.Controllers
{
public class ValuesController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
I have the cs file in APP_start folder called BreezeWebApiConfig.cs which contains the logic to map the route like below.
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "api/{controller}/{action}"
);
Let me know If I am missing any configuration setting for Web APi.
Try to decorate your ApiController like bellow :
[BreezeController]
public class NorthwindIBModelController : System.Web.Http.ApiController {
readonly EFContextProvider<NorthwindIBContext> ContextProvider =
new EFContextProvider<NorthwindIBContext>();
[HttpGet]
public String Metadata() {
return ContextProvider.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
return ContextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<Customer> Customers() {
return ContextProvider.Context.Customers;
}
For more information have a look to breeze documentation here.
Its seems like you are making a wrong Url Request. Look at your breeze route configuration for WebApi. You need to Pass like that http://localhost:53397/api/Values/Get as breeze is using Controller action based routing.
Hope this will help.