Lamar with ASP.NET Core 6.0 and controller registration - asp.net-web-api

I am trying to properly structure my ASP.NET Core 6.0 service registrations and therefore would like to move the registration into its own class.
Unfortunately, the code below doesn't work. It seems the controllers are not registered as swagger complains about No operations defined in spec!. If I move the call to registry.AddControllers() back to my ConfigureRegistry method, it works.
What am I doing wrong here?
var builder = WebApplication.CreateBuilder(args);
ConfigureConfiguration(builder.Configuration);
ConfigureRegistry(builder.Host, builder.Configuration);
var app = builder.Build();
ConfigureMiddleware(app, app.Services, app.Environment);
ConfigureEndpoints(app, app.Services, app.Environment);
app.Run();
void ConfigureConfiguration(ConfigurationManager configuration)
{
}
void ConfigureRegistry(ConfigureHostBuilder host, ConfigurationManager config)
{
host.UseLamar((context, registry) =>
{
// registry.AddControllers();
// registry.AddEndpointsApiExplorer();
// registry.AddSwaggerGen();
// registry.AddAutoMapper(typeof(Program));
registry.IncludeRegistry<WebRegistry>();
// ...
registry.IncludeRegistry<ApplicationRegistry>();
registry.IncludeRegistry<InfrastructureRegistry>();
});
}
void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services, IWebHostEnvironment environment)
{
if (environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseRouting();
}
void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services, IWebHostEnvironment environment)
{
app.MapControllers();
}
public class WebRegistry : ServiceRegistry
{
public WebRegistry()
{
this.AddControllers();
this.AddEndpointsApiExplorer();
this.AddSwaggerGen();
this.AddAutoMapper(typeof(Program));
For<ICategoryViewService>().Use<CategoryViewService>();
}
}

Related

self hosted ASP.NET Core Web Api capable of starting and stopping

I'm attempting to convert an old WCF service to an ASP.NET Core Web API, making use of the CoreWCF package. A key feature of this existing service is that it's being self hosted by an other application and is able to gracefully start & stop, without creating memory leaks.
I have been able to figure out how to start and stop a prototype service. However, after performing some stress testing, it does seem like I've left a memory leak somewhere and I'm sadly out of ideas or available documentation at this point. I'm also considering that an ASP.NET Core Web API just isn't supposed to be used like this and I misunderstood this, if so, be sure to let me know. Also my apologies for the truckload of code, but I'm not sure what's relevant or not to the question.
The code for my prototype service looks like this:
Configuring the webhost:
private void CreateWebHostBuilder(){
host = WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.ListenLocalhost(Startup.PORT_NR);
options.ConfigureHttpsDefaults(
options => options.ClientCertificateMode = ClientCertificateMode.RequireCertificate
);
})
.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); })
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseShutdownTimeout(TimeSpan.FromSeconds(1))
.UseStartup<Startup>()
.Build();
}
Inside the Startup class:
Configuring the IApplicationBuilder:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
app.UseMiddleware<SwaggerMiddleware>();
app.UseSwaggerUI();
app.UseAuthentication();
}
Configuring the services:
public void ConfigureServices(IServiceCollection services){
services.AddServiceModelWebServices()
.AddHostedService<EchoService>()
.AddSingleton(new SwaggerOptions())
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
}
The service interface:
[ServiceContract]
[OpenApiBasePath($"/{Startup.WEB_API_PATH}")]
public interface IEchoService : IHostedService {
[OperationContract]
[WebGet(UriTemplate = "/hello")]
[OpenApiOperation(Description = "Method used to receive a friendly \"Hello world\"",
Summary = "Hello world")]
[OpenApiResponse(Description = "OK Response", StatusCode = HttpStatusCode.OK)]
string HelloWorld();
}
The implemented service:
public class EchoService : IEchoService {
public EchoService() { }
public string HelloWorld() {
return "Hello world!";
}
public Task StartAsync(CancellationToken cancellationToken) {
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Creating and starting the host + services:
public void StartWebService(object obj){
CreateWebHostBuilder();
host.StartAsync();
}
Stopping and disposing the services and host:
public void StopWebService(object obj) {
host.StopAsync().Wait();
host.Dispose();
}
So if anyone has any suggestions or tutorial reference, be sure to let me know, any help is welcome.

OData Controller withing Asp.Net Core MVC application

I'm working on a project in ASP .NET Core 3.1 MVC now I want to add some API controllers to return list of objects.
For this I want to use OData Controller version 8.0.0 so I can get quarriable data to improve performance on large data tables
I'm new in ASP .NET Core and OData. can anybody explain how to configure my project's Startup file so I can run both MVC and OData controllers same time.
Kindly share some example code
Firstly, you have a MVC project, since MVC project can also expose API, so OData should also work for MVC project. Firstly, assuming you've integrate ef core and in my workaround, I followed this document to create database and data management view for a model.
Then let's add OData. Install this nuget package: Microsoft.AspNetCore.OData, modify your startup.cs file, please see the Configuration and GetEdmModel method.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using WebMvcNet5.Data;
using WebMvcNet5.Models;
using Microsoft.OData.ModelBuilder;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.Edm;
namespace WebMvcNet5
{
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.AddControllersWithViews();
services.AddControllers().AddOData(opt => opt.EnableQueryFeatures().AddRouteComponents("odata", GetEdmModel()));
services.AddDbContext<WebMvcNet5Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("WebMvcNet5Context")));
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/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.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
//My model is Movie, and what I set "GetMovie" here means I need to create a controller named "GetMovieController"
builder.EntitySet<Movie>("GetMovie");
return builder.GetEdmModel();
}
}
}
Then this is my controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using WebMvcNet5.Data;
namespace WebMvcNet5.Controllers
{
[Route("odata/[Controller]")]
public class GetMovieController : Controller
{
private readonly WebMvcNet5Context _context;
public GetMovieController(WebMvcNet5Context context)
{
_context = context;
}
[EnableQuery]
public IActionResult Get()
{
return Ok(_context.Movie);
}
}
}
My test result:
I have managed to fix my issue to run Web Application which exposes OData APIs
Issue was in Startup.cs file
I'm using Asp.Net Core 3.1 and Microsoft.AspNetCore.OData v7.3.0
my Startup.cs file code is:
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<AppUser, AppRole>(opt =>
{
opt.User.RequireUniqueEmail = true;
})
//.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//add services
services.RegisterServices();
services.AddScoped<ViewRendererService>();
services.AddMvc()
.AddMvcOptions(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddOData();
services.AddRouting();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext dataContext)
{
if (env.EnvironmentName == "Development")
{
dataContext.Database.Migrate();
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRequestLocalization();
app.UseMvc(routes =>
{
routes.Select().Filter().OrderBy().Expand().Count().SkipToken().MaxTop(null);
routes.MapODataServiceRoute("odata", "api", GetEdmModel());
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "Finance",
template: "{area:exists}/{controller=Account}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("ProductApi");
builder.EntitySet<ProductUOM>("ProductUomApi");
ActionConfiguration action = builder.EntityType<Product>().Action("GetUOM");
action.Parameter<long>("id");
action.ReturnsCollectionFromEntitySet<Product>("Product");
return builder.GetEdmModel();
}
}
Hope this will help others

asp.net core web api work on iis express but not working in iis on windows

i am new in asp.net core. i use asp.net core 2.1. i have two controller. a default valuecontroller that doesn't have db connection and customerController that have sqlserver db connection. when i run my project on iis express everything is good but when i publish my project and use windows iis value api work nice but my customer api that have a sqlserver connection doesn't work.
appsettings calss:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=AHAD;Initial Catalog=mydb;Integrated Security=True"
}
}
Startup class:
namespace SalesApp
{
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);
services.AddDbContext<MyDbContext>(Options =>
{
Options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
//,
//ILoggerFactory loggerFactory,
//MyDbContext db)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
//loggerFactory.AddConsole(Configuration.GetSection("Logging"));
//loggerFactory.AddDebug();
//db.Database.Migrate();
//app.UseMvc();
}
}
}
MyDbContext class:
public class MyDbContext : DbContext
{
public MyDbContext()
{
}
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
//public DbSet<CUSTOMER> customers { get; set; }
public DbSet<CUSTOMER> Customer { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CUSTOMER>(entity =>
{
entity.Property(e => e.C_Code).HasMaxLength(5);
entity.Property(e => e.C_Name).HasMaxLength(60);
entity.Property(e => e.C_Code_C).HasMaxLength(12);
});
}
}
CustomerController Class:
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
MyDbContext _context;
public CustomerController(MyDbContext context)
{
_context = context;
}
[HttpGet]
public IActionResult GetCustomers()
{
return new ObjectResult(_context.Customer);
}
}
values api:
enter image description here
customer api:
enter image description here
it confused me 2 weeks.
You will need to have IIS set up in order to get it to work correctly.
You also need to ensure you are using the .NET Core Windows Server Hosting Bundle
Then:
Restart the system or execute net stop was /y, followed by net start w3svc from a command shell. Restarting IIS picks up a change to the system PATH, which is an environment variable, made by the installer.
After that open the command prompt as Administrator and type:
C:\Windows\System32> iisreset
Then publish the app to a folder and open the command prompt there. Run the application by typing
C:\Temp\publish> dotnet YourApplicationName.dll
You can now go to the browser and type in http://localhost:port/ and it will display your .Net Core app.
Using sql server auth worked for me.
I use connection string like this:
Server=.;
Database=myDB;
User Id=sa2;
Password=myPass;

How to bring in ASP.NET Core MVC to an existing ASP.NET blank project?

I have an ASP.NET Core blank project, and it works great to serve static files through https://localhost/filename. Now I want to add MVC functions to it. But referencing https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/adding-controller?view=aspnetcore-2.2&tabs=visual-studio , after adding "Controllers" folder, add a controller class:
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
StartUp.cs is like:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// 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();
}
app.UseMvc();
app.UseStaticFiles();
}
Builder is like:
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
after all this, I still can't access "https://localhost/HelloWorld/Welcome/".
What did I omit?
You have no default route specified, or routing of any sort for that matter. The quickest fix is to change app.UseMvc() to app.UseMvcWithDefaultRoute(). Alternatively, you can add attribute routes:
[Route("[controller]")]
public class HelloWorldController : Controller
{
[HttpGet("Welcome")]
public string Welcome()
{
return "This is the Welcome action method...";
}
}

Adding custom authorize claim based on local database for Azure user .net core

I am trying to recognize database user with Azure AD email address, and then add custom claim to azure AD authenticated user, based on property from local database user. In startup.cs I got:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IClaimsTransformer, ClaimsTransformer>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, KayttajatContext context)
{
...
app.UseClaimsTransformation(async (c) =>
{
IClaimsTransformer transformer = c.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
return await transformer.TransformAsync(c);
});
...
}
Then ClaimsTransformer.cs looks like this:
namespace Authtest
{
public class ClaimsTransformer : IClaimsTransformer
{
private readonly KayttajatContext _context;
public ClaimsTransformer(KayttajatContext dbContext)
{
_context = dbContext;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext ctrans)
{
string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name;
var user = await _context.Henkilöt.FirstOrDefaultAsync(t => t.Sposti == sposti);
if (user.Sposti == sposti)
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "Administrator"));
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
else
{
((ClaimsIdentity)ctrans.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
}
return ctrans.Principal;
}
}
}
But this gives me "NullReferenceException: Object reference not set to an instance of an object." at if (user.Sposti == sposti)
If I instead give string value to either one of the variables, if statement works fine. I don't know what I'm doing wrong? Does it have something to do with async? Please help this is driving me nuts.
I was trying to call string sposti = ((ClaimsIdentity)ctrans.Principal.Identity).Name; before it was set. Works now, thanks :)

Resources