I'm using Nancy 2.0.0 with ASP.Net Core 2.0.0 and I can't get my application to return static content (in this case, a zip file) from a route defined in a Nancy module.
The Nancy convention is to store static content in /Content and the ASP.Net Core convention is to store it in /wwwroot, but I can't get my app to recognize either.
My Startup.Configure method looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseOwin(b => b.UseNancy());
}
And my module route looks like this:
Get("/big_file", _ => {
return Response.AsFile("wwwroot/test.zip");
});
But Nancy always returns a 404 when I hit this route. I've also tried directing ASP.Net Core to the static directory expected by Nancy, like this:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), #"Content")),
RequestPath = new PathString("/Content")
});
But this didn't work either. I've tried putting the file in /Content and in /wwwroot with the same result. And I've tried different casing of Content, but nothing seems to work. What am I missing?
I figured it out. The issue was that I needed to let Nancy know what I wanted to use as the root path for the application. I did this by creating a class that inherits from IRootPathProvider. Nancy will discover any class that inherits from this on Startup, so you can put it wherever you want.
public class DemoRootPathProvider : IRootPathProvider
{
public string GetRootPath()
{
return Directory.GetCurrentDirectory();
}
}
Once this was in place I was able to access static content in /Content. Additionally, I was able to add additional static directories (for example, if I wanted to stick with /wwwroot) by adding a class that inherits from DefaultNancyBootstrapper. Again, Nancy will find this on Startup, so you can put it anywhere.
public class DemoBootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory("wwwroot")
);
}
}
Related
I have bunch of files stored in a directory. Those files have links stored in the database like
https://localhost:44325/Repositories/Aopdio/2019/05/QWNjb3VX0FNT1NUSElSRF9LTA==.pdf
When I log in to my application, I am able to access the files via the application. However, when I copy this link and paste it in another browser, the file still opens.
Is there a way to prevent this from happening? I'd like the content to only be served from the application.
This is my startup.cs
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(RepositoryManager.GetBasePath(Environment.GetEnvironmentVariable("APPONE_REPOSITORY"))),
RequestPath = RepositoryManager.GetRequestPath(Environment.GetEnvironmentVariable("APPONE_REPOSITORY"))
});
app.UseStaticFiles(
new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Resources")),
RequestPath = "/Resources"
}
);
This is the middleware that I have tried to implement:
In Startup.cs
app.UseRequestMiddleware();
The Middleware
public class RequestMiddleware
{
private readonly RequestDelegate next;
public RequestMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context, Func<Task> next)
{
var test = context;
}
}
The extension that calls the middleware and is called in Startup.cs
public static class RequestMiddlewareExtension
{
public static IApplicationBuilder UseRequestMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestMiddleware>();
}
}
}
However, I get this error:
InvalidOperationException: Unable to resolve service for type 'System.Func`1[System.Threading.Tasks.Task]' while attempting to Invoke middleware 'App.ClassLibrary.Middleware.RequestMiddleware'.
In the default configuration of app.UseStaticFiles(), only files under wwwroot are served. If the files outside that directory is served directly, then either you have enabled static files for the whole app from your root folder or you have enabled directory browsing on the IIS. You can check and disable them.
[Update]
When you serve the files using UseStaticFiles(), the request doesn't method does not comes through the normal middleware pipeline. So you can't have the regular middleware features like Authenticate/Authorize. Either you can have a Repositories Controller that returns FileResult or you can have a custom middleware like the one here.
What I have done is created a small API in a class library. This API would be used by other sites. Think of it as a standard endpoint that all of our websites will contain.
[Route("api/[controller]")]
[ApiController]
public class CustomController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
The above is in a class library. Now what i would like to do is be able to add this to the projects in a simple manner.
app.UseCustomAPI("/api/crap");
I am not exactly sure how i should handle routing to the api controllers in the library. I created a CustomAPIMiddleware which is able to catch that i called "/api/crap" however i am not sure how i should forward the request over to CustomController in the library
public async Task Invoke(HttpContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
PathString matched;
PathString remaining;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matched, out remaining))
{
PathString path = context.Request.Path;
PathString pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matched);
context.Request.Path = remaining;
try
{
await this._options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
path = new PathString();
pathBase = new PathString();
}
else
await this._next(context);
}
After having done that i am starting to think i may have approached this in the wrong manner and should actually be trying to add it directly to the routing tables somehow. That being said i would like it if they could customize the endpoint that the custom controller reads from.
Update
The following does work. Loading and registering API Controllers From Class Library in ASP.NET core
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
However i am really looking for a middlewere type solution so that users can simply add it and i can configure the default settings or they can change some of the settings. The above example would not allow for altering the settings.
app.UseCustomAPI("/api/crap");
Update from comment without Assembly
If i dont add the .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
This localhost page can’t be found No webpage was found for the web address:
https://localhost:44368/api/Custom
To customise the routing for a controller at runtime, you can use an Application Model Convention. This can be achieved with a custom implementation of IControllerModelConvention:
public class CustomControllerConvention : IControllerModelConvention
{
private readonly string newEndpoint;
public CustomControllerConvention(string newEndpoint)
{
this.newEndpoint = newEndpoint;
}
public void Apply(ControllerModel controllerModel)
{
if (controllerModel.ControllerType.AsType() != typeof(CustomController))
return;
foreach (var selectorModel in controllerModel.Selectors)
selectorModel.AttributeRouteModel.Template = newEndpoint;
}
}
This example just replaces the existing template (api/[controller]) with whatever is provided in the CustomControllerConvention constructor. The next step is to register this new convention, which can be done via the call to AddMvc. Here's an example of how that works:
services.AddMvc(o =>
{
o.Conventions.Add(new CustomControllerConvention("api/whatever"));
});
That's all that's needed to make things work here, but as you're offering this up from another assembly, I'd suggest an extension method based approach. Here's an example of that:
public static class MvcBuilderExtensions
{
public static IMvcBuilder SetCustomControllerRoute(
this IMvcBuilder mvcBuilder, string newEndpoint)
{
return mvcBuilder.AddMvcOptions(o =>
{
o.Conventions.Add(new CustomControllerConvention(newEndpoint));
});
}
}
Here's how that would be called:
services.AddMvc()
.SetCustomControllerRoute("api/whatever");
This whole approach means that without a call to SetCustomControllerRoute, api/Custom will still be used as a default.
I'm creating ASP.NET Core integration tests (xUnit based) following these docs. I want to start the test web server with its own appsettings.json. My abbreviated folder structure is:
\SampleAspNetWithEfCore
\SampleAspNetWithEfCore\SampleAspNetWithEfCore.csproj
\SampleAspNetWithEfCore\Startup.cs
\SampleAspNetWithEfCore\appsettings.json
\SampleAspNetWithEfCore\Controllers\*
\SampleAspNetWithEfCore.Tests\SampleAspNetWithEfCore.Tests.csproj
\SampleAspNetWithEfCore.Tests\IntegrationTests.cs
\SampleAspNetWithEfCore.Tests\appsettings.json
then I have these utilities:
public static class ServicesExtensions
{
public static T AddOptions<T>(this IServiceCollection services, IConfigurationSection section)
where T : class, new()
{
services.Configure<T>(section);
services.AddSingleton(provider => provider.GetRequiredService<IOptions<T>>().Value);
return section.Get<T>();
}
}
and inside Startup.cs ConfigureServices(...) I do this:
services.AddOptions<SystemOptions>(Configuration.GetSection("System"));
Referring to the appsettings.json section like this:
"System": {
"PingMessageSuffix": " suffix-from-actual-project"
}
So far so good: this is picked up in a strongly typed manner. My controller gets a SystemOptions instance that mirrors the json structure, and the controller uses the suffix correctly.
The problems are with building the Integration Tests WebHost. I want to run the Startup from my real project as is, with its own appsettings.json settings, but as an extra layer of settings I want the appsettings.json from my test csproj to be added, overriding any settings if applicable. This is my appsettings from the test project:
"System": {
"PingMessageSuffix": " suffix-from-test-appsettings"
}
Here's what I've tried:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseStartup<Startup>()
.ConfigureAppConfiguration(config => config
.AddJsonFile("appsettings.json")
);
}
}
However, this doesn't work. If I hit a breakpoint in my controller I see only the settings from the base project. The controller just echo's the config value currently, and logically the return result is also not as expected.
The documentation doesn't mention "appsettings" anywhere on the page.
Bottom line: How can you add a layer of appSettings from a test project's appsettings.json file when running ASP.NET Core integration tests?
Solved it like this:
For appsettings.json in the Test project set the Properties:
Build Action to Content
Copy to Output Directory to Copy if newer
Use a custom WebApplicationFactory like so:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Note: ↓↓↓↓
builder.ConfigureTestServices(services =>
services.AddOptions<SystemOptions>(configuration.GetSection("System"))
);
}
}
And voila: it works!
The first step is needed to make the ConfigurationBuilder find your json file easily. The second step subtly uses a ...TestServices configuration (if you use the regular ConfigureServices method it'll be called before the Startup's service configuration and get overwritten).
Footnote: commenters (on the question) have mentioned it might be better to have a appsettings.ci.json file in the SUT project, and control things by environment (which you'd set via launch settings or via the WebHostBuilder). The documentation links to a few closed GitHub issues that suggest the same thing: 8712, 9060, 7153. Depending on your scenario and taste, that might be a better or more idiomatic solution.
Update Feb 2020 - ASP.NET Core 3.0 and above
The way you do this has changed, you need to use the ConfigureAppConfiguration delegate.
public class HomeControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public HomeControllerTests(WebApplicationFactory<Startup> factory)
{
var projectDir = Directory.GetCurrentDirectory();
var configPath = Path.Combine(projectDir, "appsettings.json");
//New ↓↓↓
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((context,conf) =>
{
conf.AddJsonFile(configPath);
});
});
}
}
Credit to: https://gunnarpeipman.com/aspnet-core-integration-tests-appsettings/
Both Unit test projects and integration test projects will require their own appsettings. We call ours appsettings.Test.json
Then we use the Microsoft.Extensions.Configuration to access them. So typically in a test I will do something like:
private readonly IConfiguration _configuration;
public HomeControllerTests()
{
_configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Test.json")
.Build();
}
then a reference to a value in this file by:
_configuration["user:emailAddress"]
where the file looks like:
"user": { "emailAddress": "myemail.com", …...
For what you are trying to do you will probably need to create an appsettings.test.json file similar to mine that sits alongside your main apsettings file. You'll then want to test the environment and then add the appropritate appsettings file.
I'm currently upgrading a project from ASP.NET WebAPI 5.2.6 (OWIN) to ASP.NET Core 2.1.1 (Kestrel).
Our project is a single page application and we communicate via WebAPI with the client. Therefore I wanted to annotate the controllers wit the new ApiController attribute.
Unfortunately it seems that the binding source parameter inference isn't working as expected (at least for me). I assumed based on the docs, that complex types (e.g. my LoginRequest) are inferred as [FromBody].
Code (Controller & Startup)
// AccountController.cs
[Route("/account"), ApiController]
public class AccountController : ControllerBase
{
[HttpPost("backendLogin"), AllowAnonymous]
public async Task<ActionResult<LoginResponse>> BackendLogin(LoginRequest lr)
{
await Task.CompletedTask.ConfigureAwait(false); // do some business logic
return Ok(new LoginResponse {UserId = "123"});
}
// Models
public class LoginRequest {
public string Email { get; set; }
public string Password { get; set; }
}
public class LoginResponse {
public string UserId { get; set; }
}
}
// Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvcCore()
.AddJsonFormatters(settings => {
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<ApiBehaviorOptions>(options => {
// options.SuppressConsumesConstraintForFormFileParameters = true;
// options.SuppressInferBindingSourcesForParameters = true;
// options.SuppressModelStateInvalidFilter = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
Problem
Calling the controller from the client via Ajax call (Content-Type: application/x-www-form-urlencoded;) results in a 400 (bad request) response, with content {"":["The input was not valid."]}. On the server I get the following trace output:
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor: Information: Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.SerializableError'.
If I change the options.SuppressInferBindingSourcesForParameters in ConfigureServices to true, it seems to work. This is strange, since this setting should disable the binding inference or have I misconceived something? Is this a bug in ASP.NET core or am I missing something?
Btw. it also works if I ommit the ApiController attribute, but I guess this is not the real solution to this problem.
Furthermore I would be happy if I don't need to change anything on the client side (adding headers, change content types, ...), because there are a lot of Ajax calls out there and I just want to upgrade the server side components.
I also asked this question on the official ASP.NET Core MVC repo.
One of the members (pranavkm) came back with an answer, which I will just quote here:
ApiController is designed for REST-client specific scenarios and isn't designed towards browser based (form-urlencoded) requests. FromBody assumes JSON \ XML request bodies and it'll attempt to serialize it, which is not what you want with form url encoded content. Using a vanilla (non-ApiController) would be the way to go here.
So for now I will omit the [ApiController] attribute.
In a later step I may change the client calls to use a JSON body, so I can readd the attribute.
I am migrating a ASP.NET 5 RC1 project to ASP.NET Core, and have come across an interesting issue I've not yet seen, or found a solution for.
In order to use configuration settings within Startup I have previously retrived the configuration the following way
// Works fine for DI both in ASP.NET 5 RC1 and ASP.NET Core
services.Configure<SomeConfigurationClass>(Configuration.GetSection("SomeConfigurationSection"));
// How I previous retrieved the configuration for use in startup.
// No longer available in ASP.NET Core
var someConfigurationToUseLater = Configuration.Get<SomeConfigurationClass>("SomeConfigurationSection");
After updating to ASP.NET Core 1.0 it seems Configuration.Get<T>() is no longer available.
I have tried updating the code to use Configuration.GetValue<T>() however this does not seem to work with objects and will only work when providing a path to a value. This has left me with a workaround for most of my configuration classes like so
var someConfigurationName = "someConfiguration";
var someConfigurationClass = new SomeConfigurationClass()
{
Value1 = Configuration.GetValue<string>($"{someConfigurationName}:value1"),
Foo = Configuration.GetValue<string>($"{someConfigurationName}:foo"),
Bar = Configuration.GetValue<string>($"{someConfigurationName}:bar")
};
However this is an issue when the configuration class contains an array of objects. In my case an array of Client objects
public class ClientConfiguration
{
public Client[] Clients { get; set; }
}
With the following configuration
"configuredClients": {
"clients": [
{
"clientName": "Client1",
"clientId": "Client1"
},
{
"clientName": "Client2",
"clientId": "Client2"
}
]
}
Where this would previously bind to the Clients property of my configuration class no problem, I can no longer find a way of doing so in ASP.NET Core 1.0
Updated Answer
For ASP Core 1.1.0 generic model binding is now done using Get:
var config = Configuration.GetSection("configuredClients").Get<ClientConfiguration>();
Original Answer
How about this:
var config = Configuration.GetSection("configuredClients").Bind<ClientConfiguration>();
With ASP.NET Core 2.0 (basically Core 1.1+), the IConfiguration is injected to Startup, and that can be used within ConfigureServices() and Configure() methods.
As shown in the accepted answer, the configuration can be bound to an object. But if just one value is required, the key based approach works well.
The IConfiguration still works with colon : separated string keys. And for array, use 0-based index. Or use the the generic getValue<T>() method with same keys. See example below:
var clientId2 = Configuration["configuredClients:clients:1:clientId"]?.ToString();
var clientName1 = Configuration.GetValue<string>("configuredClients:clients:0:clientName");
To use the same configuration values in other classes (e.g. Controllers)
Either inject the IConfiguration and use the same key-based approach like above. Or
Register an instance of the strongly-typed configuration object with the DI container, and inject that object directly into client classes.
Sample code below:
//In Startup.ConfigureServices()
var clientConfig = Configuration.GetSection("configuredClients")
.Get<ClientConfiguration>();
services.AddSingleton(clientConfig);
//Controller
public class TestController : Controller
{
IConfiguration _configStore;
ClientConfiguration _clientConfiguration;
public TestController(IConfiguration configuration,
ClientConfiguration clientConfiguration)
{
_configStore = configuration;
_clientConfiguration = clientConfiguration;
}
public IActionResult Get()
{
//with IConfiguration
var clientId1 = _configStore
.GetValue<string>("configuredClients:clients:0:clientId");
//with strongly typed ClientConfiguration
var clientName1 = _clientConfiguration.Clients[0]?.ClientName;
return new OkObjectResult("Configuration test");
}
}
More examples here.
You don't read the configuration manually generally in ASP.NET Core yourself, instead you create an object that matches your definition. You can read more on that in the official documentation here.
E.g.
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
// Setup options with DI
services.AddOptions();
services.Configure<MyOptions>(Configuration);
}
Then you just inject the options IOptions<MyOptions> where you need them.
If you want to get first "clientName"(expected "Client1"), just write:
Configuration.GetSection("configuredClients")["clients:0:clientName"];
Update for comment
Install .NET Core 1.0.1 and go with #TomMakin's way.