ASP.NET Core 2.2 - accessing the StaticFileOption RequestPath later in code - asp.net-core

In Startup.cs Configure function I do something like this:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(#"\\server\somepath\someimages"),
RequestPath = "/images"
});
Later, say in the controller, I'd like to not hard-code:
string ImageImLookingFor = "/images" + foo.jpg;
Instead I'd like to do something like:
string ImageImLookingFor = SomeObjectThatGivesMe.RequestPath + foo.jpg;
Is this possible?

Not entirely sure if it is possible but a workaround can be an appsettings key and read it from both locations.
ex:
in your appsettings
{
"ImagesPath" : '/images"
}
in Starup.cs
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(#"\\server\somepath\someimages"),
RequestPath = Configuration["ImagesPath"]
});
In your contorller
string ImageImLookingFor = configuration.RequestPath + foo.jpg;
You can make the configuration file a strong type and replace it with IOptions<ImageConfiguration> where ImageConfiguration is a class that has ImagesPath property

You could try to configure StaticFileOptions with services.Configure like
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<StaticFileOptions>(options => {
options.FileProvider = new PhysicalFileProvider(#"xxx");
options.RequestPath = "/images";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles(app.ApplicationServices.GetRequiredService<IOptions<StaticFileOptions>>().Value);
}
}
And then access it by IOptions<StaticFileOptions> like
public class HomeController : Controller
{
private readonly StaticFileOptions _options;
public HomeController(IOptions<StaticFileOptions> options)
{
this.configuration = configuration;
_serviceProvider = serviceProvider;
_options = options.Value;
}
public IActionResult Index()
{
return Ok(_options.RequestPath);
}
}

Related

How to inject custom service on startup in .NET Core 5

I want to read my data from database and control it, and I need to do this in the request pipeline at startup.
So I have to do dependency injection at startup.
This is my (DI)
public Startup(IConfiguration configuration,IAuthHelper authHelper)
{
Configuration = configuration;
AuthHelper = authHelper;
}
public IConfiguration Configuration { get; }
public IAuthHelper AuthHelper;
I encounter this error
An error occurred while starting the application.
InvalidOperationException: Unable to resolve service for type 'Laboratory.Core.Services.Interfaces.IAuthHelper' while attempting to activate 'Laboratory.Startup'.
I used service like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var siteDirectory = AuthHelper.GetSiteSetting().MediaPath;
var fileServerOptions = new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine
(env.WebRootPath, $#"{siteDirectory}User Picture\")),
RequestPath = "/ServerFiles"
};
app.UseFileServer(fileServerOptions);
}
This is my service
public class AuthHelper : IAuthHelper
{
private readonly LaboratoryContext _context;
private readonly IRazorPartialToStringRenderer _renderer;
private readonly IHttpContextAccessor _httpContext;
private readonly IHttpClientFactory _clientFactory;
public AuthHelper(LaboratoryContext context, IRazorPartialToStringRenderer renderer, IHttpContextAccessor httpContext, IHttpClientFactory clientFactory)
{
_context = context;
_renderer = renderer;
_httpContext = httpContext;
_clientFactory = clientFactory;
}
public TableSiteSetting GetSiteSetting()
{
try
{
return _context.TableSiteSettings.AsNoTracking().FirstOrDefault();
}
catch (SqlException)
{
return new TableSiteSetting() { StaticIp = "ServerConnectionError" };
}
catch (Exception)
{
return new TableSiteSetting() { StaticIp = "ServerError" };
}
}
}
Thanks for any help.
Your service can't be injected in Startup constructor because it has not been added yet to the dependency injection container. But you can inject it to the Configure method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAuthHelper authHelper)
{
...
}
I assume you have already registered the service in ConfigureServices
services.AddSingleton<IAuthHelper, AuthHelper>(); // Or scoped/transient depends what your service does.
You can get dbcontext service in program.cs and do what ever you like to your database data.
for example I use this approach to seed my database:
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
await ApplicationDbContextSeed.SeedSampleDataAsync(context)
}
host.Run();

Difference between instance of HttpClient and IHttpClientFactory in .NET Core5

Below is my Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
And controller:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
private readonly HttpClient _httpClient;
private readonly IHttpClientFactory _httpClientFactory;
public TestController(HttpClient httpClient, IHttpClientFactory httpClientFactory)
{
_httpClient = httpClient;
_httpClientFactory = httpClientFactory;
}
[HttpGet]
[Route("getdata")]
public async Task GetData()
{
var baseAddress = "http://youtube.com";
var response = await _httpClient.GetAsync(baseAddress);
var client = _httpClientFactory.CreateClient();
response = await client.GetAsync(baseAddress);
}
}
As you can see I can get an instance of HttpClient in two ways:
By Injecting HttpClient
By Injecting IHttpClientFactory and then _httpClientFactory.CreateClient();
Though I am getting responses using both instances, my question is what is the difference between them? And when to use which one?
If you just post signal request.there's even no difference between injecting an instance of httpclient and creating a instance of httpclient with httpclientfactory .
If you use several instances of httpclient or reuse httpclient to post multiple requests, some problems may occur, using httpclient could handle these problems.
You could create httpclients with different settings as follows:
In startup.cs:
services.AddHttpClient("client_1", config =>
{
config.BaseAddress = new Uri("http://client_1.com");
config.DefaultRequestHeaders.Add("header_1", "header_1");
});
services.AddHttpClient("client_2", config =>
{
config.BaseAddress = new Uri("http://client_2.com");
config.DefaultRequestHeaders.Add("header_2", "header_2");
});
In your controller:
var client1 = _httpClientFactory.CreateClient("client_1");
var client2 = _httpClientFactory.CreateClient("client_2");

error 404 showing for each controller after user authorization via ldap

Scenario:
I'm implementing asp.net core 3.1 MVC project. I authorize my user via ldap Active Directory service. The user authenticates successfully and enter into my website. but after clicking on each menu item in order to see the related controller index it shows white page. I wrote on top of all my controller class [Authorize] keyword in order to let any authorized user to see all controllers.
My Problem is:
when user clicks on each menu item in home in order to see the related controller's index, it shows white page and when I publish my project on ldap server, it shows me 404 error. I appreciate if any one can suggest me a solution. It seems to the routing has problem but I'm not sure. I even wrote on top of my controller class the keyword [AllowAnonymous] but still I see white pages for index pages for each controller. Should I add anything to startup.cs for AutheticationHelper or CustomAuthenticationMiddleware as a service?
Here is my sign in method in account controller
namespace CSDDashboard.Controllers
{
[Route("[controller]/[action]")]
[AllowAnonymous]
public class AccountController : Controller
{
private readonly LdapUserManager _userManager;
private readonly LdapSignInManager _signInManager;
private readonly ILogger _logger;
public AccountController(
LdapUserManager userManager,
LdapSignInManager signInManager,
ILogger<AccountController> logger)
{
this._userManager = userManager;
this._signInManager = signInManager;
this._logger = logger;
}
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> Signin(string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
await this.HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
this.ViewData["ReturnUrl"] = returnUrl;
return this.View();
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Signin(SigninViewModel model, string returnUrl = null)
{
this.ViewData["ReturnUrl"] = returnUrl;
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "tehran.iri"))
{
// validate the user's credentials
//var result = ctx.ValidateCredentials(model.UserName, model.Password);
// try {
if (ctx.ValidateCredentials(model.UserName, model.Password))
{
// credentials are OK --> allow user in
HttpContext.Session.MarkAsAuthenticated(model.UserName);
//Added recently
Debug.Writeline(string.Format("Redirection to {0}", returnUrl);
return RedirectToLocal(returnUrl);
}
else
{
this.TempData["ErrorMessage"] = "The username and/or password are incorrect!";
return this.View(model);
// credentials aren't OK --> send back error message
}
}
}}}
Here is my middleware class and AuthenticationHelper class
public static class AuthenticationHelper
{
private const string SessionKey = "AuthenticationHelper.UserName";
public static void MarkAsAuthenticated(this Microsoft.AspNetCore.Http.ISession session, string authenticatedUserName)
{
session.SetString(SessionKey, authenticatedUserName);
}
public static ClaimsPrincipal GetAuthenticatedUser(this Microsoft.AspNetCore.Http.ISession session)
{
string authenticatedUserName = session.GetString(SessionKey);
if (string.IsNullOrEmpty(authenticatedUserName)) return null;
return new GenericPrincipal(new GenericIdentity(authenticatedUserName), Array.Empty<string>());
}
}
public class CustomAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public CustomAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
ClaimsPrincipal user = context.Session.GetAuthenticatedUser();
if (user != null) context.User = user;
await _next(context);
}
}
public static class CustomAuthenticationMiddlewareExtensions
{
public static IApplicationBuilder UseCustomAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomAuthenticationMiddleware>();
}
}
Here is my code in statrup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<LdapSettings>(Configuration.GetSection("LdapSettings"));
services.AddDbContext<LdapDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("CSDDashboardContext")));
//-------------------------------------------------
services.AddIdentity<LdapUser, IdentityRole>()
.AddEntityFrameworkStores<LdapDbContext>()
.AddUserManager<LdapUserManager>()
.AddSignInManager<LdapSignInManager>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "CSDDashboard";
options.LoginPath = "/Account/Signin"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Signout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
services.AddRazorPages();
services.AddTransient<ILdapService, LdapService>();
//-------------------------------------------------
services.AddControllersWithViews();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);//We set Time here
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddDistributedMemoryCache();
//Notice this is NOT the same class... Assuming this is a valid DBContext. You need to add this class as well.
services.AddDbContext<CSSDDashboardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CSDDashboardContext")));
services.AddDbContext<CSDDashboardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CSDDashboardContext")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.UseDeveloperExceptionPage(options);
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.UseSession();
app.UseRouting();
app.UseCustomAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
//Here are all of my controllers, but as it seems just I can uncomment one controller pattern here, I commented all the others
// pattern: "{controller=Applications}/{action=Index}/{id?}");
//pattern: "{controller=Home}/{action=Index}/{id?}");
// pattern: "{controller=ApiApplications}/{action=Index}/{id?}");
pattern: "{controller=Gates}/{action=Index}/{id?}");

How to return indented json content from an OData controller in asp core web api?

I can retrieve intended json result from normal WebApi using following way.
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x=>
{
x.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
});
But I cannot find a way to output json like this when using ODataController as opposed to ControllerBase when web api is used. ODataController always sends a minified json.
public class EmployeeController : ODataController
{
[EnableQuery()]
public IActionResult Get()
{
return Ok(new BOContext().Employees.ToList());
}
}
Also, startup.cs
public class Startup
{
private static IEdmModel GetModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employee");
return builder.GetEdmModel();
}
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.AddOData();
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.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x=>
{
x.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
});
}
// 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("/Home/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapODataServiceRoute("odata", "odata", GetModel());
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The route is working and I am receiving correct data.
Is there a way to control and output indented json from an OData controller?
I suggest you to make the transfer with minified jsonm, but use json beutifier to show formatted json. dont do this on the data flow phase.
If you are using javascript on the front-end side. You can simple use
JSON.stringify(jsObj, null, "\t"); // stringify with tabs inserted at each level
JSON.stringify(jsObj, null, 2); // stringify with 2 spaces at each level
Not sure if this is still actual, but you can specify formatter when returning the data
// [...]
public IActionResult Get()
{
var res = Ok(_db.Employees);
res.Formatters.Add(new Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter(
new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.Indented },
System.Buffers.ArrayPool<char>.Create()));
return res;
}
And of course, if you want more generalized solution (or you just have a lot of code that is already written), you can create interim abstract class and inherit from that class instead of just ODataController:
public abstract class AbstractFormattedOdataController : ODataController
{
public override OkObjectResult Ok(object value)
{
var res = base.Ok(value);
res.Formatters.Add(new Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter(
new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.Indented },
System.Buffers.ArrayPool<char>.Create()));
return res;
}
}
// [...]
public class EmployeesController : AbstractFormattedOdataController
{
[EnableQuery()]
public IActionResult Get()
{
return Ok(new BOContext().Employees.ToList());
}
}

asp.net core custom IRouter Dependency Injection

I'm creating a custom routing in asp.net core 2, where I check the path in a DB and update action and controller to the desired one.
I have this custom IRouter defined like this
public interface IRouteCustom : IRouter
{
}
public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
public RouteCustom(IRouter innerRouter, IMemoryCache memoryCache, IUnitOfWork unitOfWork)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
_memoryCache = memoryCache;
_unitOfWork = unitOfWork;
}
public async Task RouteAsync(RouteContext context)
{
I check the routes in the DB using the _unitOfWork
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
Also I do the same here...
}
}
I have no problem with those functions and I'm able to select controller and action.
My problem is how to access the database, since I can't inject the IUnitOfWork dependency into de custom router.
I'm getting this error message:
'Cannot resolve 'IUnitOfWork' from root provider because it requires scoped service 'DbContext'.'
I have my ConfigureServices like this
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(options => options.UseMySQL(configuration.GetConnectionString("DefaultClient")));
services.AddIdentity<Domain.EntitiesClient.Entities.ApplicationUser, Domain.EntitiesClient.Entities.ApplicationRole>().AddEntityFrameworkStores<DbContext>().AddDefaultTokenProviders();
services.AddScoped<IDbContext, DbContext>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMemoryCache();
services.AddMvc();
/*route custom*/
var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("es-ES"), new CultureInfo("it-IT") };
var optionsCulture = new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("en-US", "en-US"), SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures };
optionsCulture.RequestCultureProviders = new IRequestCultureProvider[] { new RouteDataRequestCultureProvider() { RouteDataStringKey = "culture", Options = optionsCulture } };
services.AddSingleton(optionsCulture);
}
And the Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.Routes.Add(new RouteCustom(routes.DefaultHandler
, routes.ServiceProvider.GetRequiredService<IMemoryCache>()
, app.ApplicationServices.GetService<IUnitOfWork>()
));
routes.MapRoute(name: "areas", template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});
}
The problem is here
app.ApplicationServices.GetService<IUnitOfWork>()
I need to inject the IUnitOfWork in order to check the database, but I don't know how to do it. In other Middlewares I could inject the IUnitOfWork directly in the function, but in this case I can't do it in the
public async Task RouteAsync(RouteContext context)
How can I achieve this? I'm sure I'm doing something wrong here, but I have been reading a lot of articles and can't figure out the way.
Thanks.
UPDATE: POSSIBLE SOLUTION
The only solution I can think is remove the injection into the IRouter and get the service "manually" inside the RouteAsync method.
public async Task RouteAsync(RouteContext context)
{
var unitOfWork = context.HttpContext.RequestServices.GetRequiredService<IUnitOfWork>()
var routes = unitOfWork.Router.GetAll();
...
}
This way we have access to the database and it works good.
Is it a good approach?