Why IHttpContextAccessor always get null IP in Blazor server-side? - asp.net-core

The SDK I am using is 5.0.100-rc.2.20479.15.
The target framework also is .Net 5.
Here is my code in startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Sample
{
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.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
}
// 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("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
}
And here is my code in index.razor:
#page "/"
#inject IHttpContextAccessor httpContextAccessor
<h1>#IP</h1>
#code{
public string IP{get;set;}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
IP=httpContextAccessor.HttpContext.Connection?.RemoteIpAddress.ToString();
}
}
}
I published it to the server computer and the IP always returns null.
My program needs to submit a form. And the form is only allowed to submit once per day by the same people(without an account).
So I need to get the IP and set an IMemoryCache with a one-day DateTimeOffset.
In spite, storing the IP address is not a perfect way to solve my feature but it seems I can only do it like this.

The server-side of Blazor Server App communicates with the front-end (Browser) via SignalR, not HTTP. Thus the HttpContext object is not available in Blazor Server App, except on the initial request to the app, which is always an HTTP request. This is the only opportunity when you can access the HttpContext. Note that the _Host.cshtml file is a Razor Page file, so you can put some code in it that access the HttpContext directly, and get whatever data you want to read from the HttpContext, such as the Remote IP, etc. You can then pass the data retrieved to your Blazor SPA as parameters of the component tag helper present in the _Host.cshtml. Add code in the OnInitialized life-cycle method of the App component to get the data, as for instance define a property that gets the remote IP address of the current user. You can then store this data in the local storage for a later use

WARNING: AS #enet comment suggests this is not the recommended way to go about this in Blazor
In case it helps anyone... Here is my interpretation of #Melon NG's suggestion
In _Host.cshtml.cs
using AutoCheck.BlazorApp.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AutoCheck.BlazorApp.Pages
{
public class Host : PageModel
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRememberMe _rememberMe;
public Host(IHttpContextAccessor httpContextAccessor, IRememberMe rememberMe)
{
_rememberMe = rememberMe;
_httpContextAccessor = httpContextAccessor;
}
public void OnGet()
{
_rememberMe.RemoteIpAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress;
}
}
}
RememberMe
using System.Net;
namespace AutoCheck.BlazorApp.Components
{
public interface IRememberMe
{
public IPAddress RemoteIpAddress { get; set; }
}
public class RememberMe : IRememberMe
{
public IPAddress RemoteIpAddress { get; set; }
}
}
About.razor
#page "/About"
#inject IRememberMe RememberMe
<h3>About</h3>
<table class="table table-striped">
<thead>
<td>Item</td>
<td>Value</td>
</thead>
<tr>
<td>Remote IP</td>
<td>#RememberMe.RemoteIpAddress</td>
</tr>
</table>
In ConfigureServices in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
//services.AddSingleton<IRememberMe, RememberMe>();
services.AddScoped<IRememberMe, RememberMe>();
...
}

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.

Unable to resolve service for type Microsoft.AspNetCore.Identity.RoleManager

I am working on project using microservice architecture, I use ASP.NET Core Identity as a separate microservice to create users and roles. I extend users and roles with custom fields and configure Identity in my API project's startup.cs. But while I run my application I got an error as following,
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microservice.IdentityMS.Application.Interfaces.IIdentityMSService Lifetime: Transient ImplementationType: Microservice.IdentityMS.Application.Services.IdentityMSService': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.RoleManager`1[Microservice.IdentityMS.Domain.Models.MembershipRole]' while attempting to activate 'Alexa.IdentityMS.Data.Repository.IdentityMSRepository'.)
Here's my Identity Microservice startup
Startup.cs:
using Microservice.IdentityMS.Data.Context;
using Microservice.IdentityMS.Domain.Models;
using Microservice.Infra.IoC;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microservice.Identity.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.AddControllers();
var userConnectionString = Configuration["DbContextSettings:UserConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var userBuilder = new NpgsqlConnectionStringBuilder(userConnectionString)
{
Password = dbPassword
};
services.AddDbContext<MembershipDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddDbContext<UserDBContext>(opts => opts.UseNpgsql(userBuilder.ConnectionString));
services.AddIdentity<MembershipUser, MembershipRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
options.SignIn.RequireConfirmedEmail = false;
}).AddRoles<MembershipRole>().AddEntityFrameworkStores<MembershipDBContext>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Identity Microservice", Version = "v1" });
});
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
DependencyContainer.RegisterServices(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Identity Microservice V1");
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
DependencyContainer.cs:
using MediatR;
using Microservices.Domain.Core.Bus;
using Microservices.Infra.Bus;
using Microsoft.Extensions.DependencyInjection;
using Microservices.ProductMS.Domain.Interfaces;
using Microservices.ProductMS.Data.Repository;
using Microservices.ProductMS.Data.Context;
using Microservices.ProductMS.Application.Interfaces;
using Microservices.ProductMS.Application.Services;
using Microservices.PartyMS.Application.Interfaces;
using Microservices.PartyMS.Application.Services;
using Microservices.PartyMS.Domain.Interfaces;
using Microservices.PartyMS.Data.Repository;
using Microservices.PartyMS.Data.Context;
using Microservices.MasterMS.Application.Interfaces;
using Microservices.MasterMS.Application.Services;
using Microservices.MasterMS.Domain.Interfaces;
using Microservices.MasterMS.Data.Repository;
using Microservices.MasterMS.Data.Context;
using Microservices.MasterMS.Domain.Commands;
using Microservices.MasterMS.Domain.CommandHandler;
using Microservices.ProductMS.Domain.Commands;
using Microservices.ProductMS.Domain.CommandHandler;
using Microservices.PartyMS.Domain.CommandHandler;
using Microservices.AccountMS.Application.Interfaces;
using Microservices.AccountMS.Application.Services;
using Microservices.AccountMS.Domain.Interfaces;
using Microservices.AccountMS.Data.Repository;
using Microservices.AccountMS.Data.Context;
using Microservices.SalesPurchaseMS.Domain.Interfaces;
using Microservices.SalesPurchaseMS.Data.Repository;
using Microservices.SalesPurchaseMS.Data.Context;
using Microservices.SalesPurchaseMS.Application.Interfaces;
using Microservices.SalesPurchaseMS.Application.Services;
using Microservices.IdentityMS.Application.Interfaces;
using Microservices.IdentityMS.Application.Services;
using Microservices.IdentityMS.Domain.Interfaces;
using Microservices.IdentityMS.Data.Repository;
using Microservices.IdentityMS.Data.Context;
using Microsoft.AspNetCore.Identity;
using Microservices.IdentityMS.Domain.Models;
namespace Microservices.Infra.IoC
{
public class DependencyContainer
{
public static void RegisterServices(IServiceCollection services)
{
//Domain Bus
services.AddSingleton<IEventBus, RabbitMQBus>(sp =>
{
var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
return new RabbitMQBus(sp.GetService<IMediator>(), scopeFactory);
});
//Subscriptions
services.AddTransient<ProductMS.Domain.EventHandler.CompanyEventHandler>();
services.AddTransient<PartyMS.Domain.EventHandler.CompanyEventHandler>();
//Domain Events
services.AddTransient<IEventHandler<ProductMS.Domain.Events.CompanyEvent>, ProductMS.Domain.EventHandler.CompanyEventHandler>();
services.AddTransient<IEventHandler<PartyMS.Domain.Events.CompanyEvent>, PartyMS.Domain.EventHandler.CompanyEventHandler>();
//services.AddTransient<IEventHandler<SalesMS.Domain.Events.CompanyEvent>, SalesMS.Domain.EventHandler.CompanyEventHandler>();
//services.AddTransient<IEventHandler<SalesMS.Domain.Events.PartyEvent>, SalesMS.Domain.EventHandler.PartyEventHandler>();
//Domain Commands
services.AddTransient<IRequestHandler<CompanySyncCommand, bool>, CompanySyncCommandHandler>();
services.AddTransient<IRequestHandler<ProductSyncCommand, bool>, ProductSyncCommandHandler>();
services.AddTransient<IRequestHandler<ProductCategorySyncCommand, bool>, ProductCategorySyncCommandHandler>();
services.AddTransient<IRequestHandler<PartySyncCommand, bool>, PartySyncCommandHandler>();
//Application Services
services.AddTransient<IMasterService, MasterService>();
services.AddTransient<IProductService, ProductService>();
services.AddTransient<IPartyMSService, PartyMSService>();
//services.AddTransient<IPurchaseService, PurchaseService>();
services.AddTransient<IPurchaseService, PurchaseService>();
services.AddTransient<ISaleService, SaleService>();//SaleMS
services.AddTransient<IAccountMSService, AccountMSService>();
services.AddTransient<IIdentityMSService, IdentityMSService>();
services.AddTransient<IAdministrationService, AdministrationService>();
//Data
services.AddTransient<IMasterRepository, MasterRepository>();
services.AddTransient<MasterDbContext>();
services.AddTransient<IProductRepository, ProductRepository>();
services.AddTransient<ProductsDBContext>();
services.AddTransient<IPartyMSRepository, PartyMSRepository>();
services.AddTransient<PartyMSDBContext>();
services.AddTransient<IPurchaseRepository, PurchaseRepository>();
services.AddTransient<ISaleRepository, SaleRepository>();
services.AddTransient<SPDBContext>();
services.AddTransient<IAccountMSRepository, AccountMSRepository>();
services.AddTransient<AccountDbContext>();
services.AddTransient<IIdentityMSRepository, IdentityMSRepository>();
services.AddTransient<IAdministrationRepository, AdministrationRepository>();//IdentityMS
services.AddTransient<UserDBContext>();
}
}
}
IdentityMSRepository.cs:
public class IdentityMSRepository : IIdentityMSRepository
{
private readonly UserDBContext _userContext;
private readonly RoleManager<MembershipRole> _roleManager;
private readonly UserManager<MembershipUser> _userManager;
public IdentityMSRepository(UserDBContext userContext, RoleManager<MembershipRole> roleManager, UserManager<MembershipUser> userManager)
{
_userContext = userContext;
_roleManager = roleManager;
_userManager = userManager;
}
}
What am I missing ?
Finally I made change as the comment suggested I moved it from DI container to startup file and it's working. #Rena Thank you so much.

Controller's action not invoked in ASPNETCORE console app running Kestrel

I'd like to have a console application running a standalone webserver accepting REST calls. My application is a .NET Core app with ASP .NET Core inside. I am completely new in this area. I found some examples and now I am struggling with controller route configuration. With the code below I always get "404 Not Found" error when using http://localhost:3354/api/Demo/Hello. Does anyone have an idea what am I doing wrong? Thank you for any suggestion!
I use VS2019 and ASPNETCORE 2.2.8.
class Program
{
static void Main(string[] args)
{
var builder = WebHost.CreateDefaultBuilder()
.ConfigureKestrel(options => options.ListenAnyIP(3354))
.UseStartup<Startup>();
builder.Build().Run();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder builder, IHostingEnvironment env)
{
builder.UseMvc(delegate(IRouteBuilder routeBuilder)
{
routeBuilder.MapRoute("default", "api/{controller}/{action}");
});
}
}
Here comes the DemoController class.
public class DemoController : Controller
{
public IActionResult Hello()
{
return Ok("Hello world");
}
}
Your example is working fine for me on .net core 2.2
You could try explicitly declare routes like
[ApiController]
[Route("api/[controller]")]
public class DemoController : Controller
{
[HttpGet("hello")]
public IActionResult Hello()
{
return Ok("Hello world");
}
}
Also you could consider using Visual studio built-in templates of api web applications
After some investigation and comparison of my project with the sample project of Roman Kalinchuk I found out that the problem is that mvc controller provider doesn't look for controller types in my application assembly. It is enought to add my application assembly to the application parts collection.
See the .AddApplicationPart(typeof(DemoController).Assembly); line.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddApplicationPart(typeof(DemoController).Assembly);
}
public void Configure(IApplicationBuilder builder, IHostingEnvironment env)
{
env.EnvironmentName = "Development";
builder.UseMvc(delegate(IRouteBuilder routeBuilder)
{
routeBuilder.MapRoute("test", "api/{controller}/{action}");
});
}
}

Does OData actually work in AspNetCore on Linux?

I work in an environment where all new work is done in AspNetCore, one of the primary reasons being so we can run it on Linux servers. We have an API to access one of our databases that I've been asked to add OData to. No problem.
The Problem
I've got a lovely example working in a test project and I'm moving it over to the real API in a branch of the code annnnnnd.....what's that? It's a reference to Microsoft.AspNet.
My test project is .NetCore 2.1, and the only NuGet packages installed are:
Microsoft.AspNetCore.App v2.1.1
Microsoft.AspNetCore.OData v7.0.1 (tried v7.1.0 too)
Microsoft.AspNetCore.Razor.Design v2.1.2
Microsoft.NETCore.App v2.1.0
This (truncated) code works great on my Windows development machine, but I foresee problems when we try to build it for Linux deployment.
Startup.cs - Notice the first 2 usings
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OData.Edm;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ODataTest.Models;
namespace ODataTest
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddOData();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMvc(b =>
{
b.Filter().Expand();
b.MapODataServiceRoute("odata", "odata", GetEdmModel());
b.EnableDependencyInjection();
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<ThingDto>(nameof(ThingDto));
return builder.GetEdmModel();
}
}
}
ThingController.cs - Notice using #3
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.OData;
using Microsoft.AspNetCore.Mvc;
using ODataTest.Models;
namespace ODataTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ODataController
{
private readonly Db _db;
public ValuesController(Db db)
{
this._db = db;
}
[HttpGet]
[EnableQuery]
public ActionResult<IEnumerable<ProductPricePointMarkdownDto>> Index()
{
var things =
from thing in _db.Things
select new ThingDto
{
ThingID = thing.ID,
StyleID = thing.StyleID,
ColourID = thing.ColourID
};
return Ok(things);
}
}
}
ThingDto.cs - Notice the last using
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.OData.Query;
namespace ODataTest.Models
{
[Filter("ColourID", Disabled = true)]
[Filter]
public class ThingDto
{
[Key]
public int ThingID { get; set; }
public int StyleID { get; set; }
public int ColourID { get; set; }
}
}
Can anyone steer me away from my current thinking that OData "works with Core" is marketing, and in reality it doesn't?
So the answer is "Yes, it does work". I have not tracked down whether it's a bad namespace, or actually referring to .NET Standard. The motivation to find out went once I proved this ran on a Linux docker container.

Set dummy IP address in integration test with Asp.Net Core TestServer

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā€ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}