ASP.Net Core - Dynamically generate index.html - c#

The standard way to provide an index.html to the client with ASP.Net core is to use the middleware app.UseStaticFiles(); on IApplicationBuilder instance in the Configure method of the StartUp class. This provides a static index.html file from the wwwroot folder. Is there a way to provide an index.html to the client, that is dynamically generated in code on request?

There is a way to change the SPA files content served from the static folder.
You should do the following:
update your usage of UseStaticFiles, it should have a lambda provided for the OnPrepareResponse property; See this MS DOC
It is called every time the static file is returned to the client;
You can analyze the file name to ensure it is index.html;
Your code can completely change the content that will be returned. Just generate a file dynamically to replace index.html, and write it as a response body by writing to a stream. You might need to clear or save original body content before modifying and returning it if your writing has failed.
See code example to get you started:
var staticFileOptions = new StaticFileOptions
{
OnPrepareResponse = context =>
{
// Only for Index.HTML file
if (context.File.Name == "index.html") {
var response = context.Context.Response;
var str = "This is your new content";
var jsonString = JsonConvert.SerializeObject(str);
// modified stream
var responseData = Encoding.UTF8.GetBytes(jsonString);
var stream = new MemoryStream(responseData);
// set the response body
response.Body = stream;
}
}
};
app.UseStaticFiles(staticFileOptions);

Related

How can I solve FileSystemException: Cannot retrieve length of file, path ='...' (OS Error: No such file or directory, errno = 2)

I am trying to send a user model from dart to the api where my file is set as "IFormFile" data type in my c# backend.
I tried using the multipart request but all i get is the error stated , i can't understand why it cannot retrieve the length of file.
This is my code:
updateUser() async {
var uri = Uri.parse('http://myIP:8070/api/Users');
var request = http.MultipartRequest('Put', uri);
request.fields['id']="07bb2a17-7cd5-471b-973a-4b77d239b6c3";
request.fields['username']="beeso";
request.fields['email']="jake-username2#gmail.com";
request.fields['password']="Jake123-";
request.fields["oldPassword"]="Jake124-";
request.fields["gender"]="Male";
request.fields["dateOfBirth"]=DateTime.now().toString();
request.fields["name"]="dsjnss";
request.fields["languages"]=["Language1","Language2"].toString();
request.fields["nationalities"]=["Nationality1","Nationality2"].toString();
request.fields["phoneNumber"]="70502030";
request.fields["biography"]="jdnknkdas";
request.fields["info"]="asndasnkdas";
request.fields["religion"]="Christian";
request.fields["location"]="LA";
File imageFile = new File('Anatomy_of_a_Sunset-2.jpg');
var stream = new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
var length = await imageFile.length();
var multipartFile = new http.MultipartFile('file', stream, length,
filename: basename(imageFile.path));
request.files.add(multipartFile);
Map<String, String> headers = {
"content-type": "application/json"
};
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
print(response.statusCode);
}
Any help would be appreciated.
This picture shows where my file is located
In Flutter, you can't access files directly from the project. You need to add them to an assets folder (typically assets) and also to pubspec.yaml. Then, instead of using File to read them, you use rootBundle (or one of the Asset classes).
var multipartFile = http.MultipartFile.fromBytes(
'file',
(await rootBundle.load('assets/anatomy.jpg')).buffer.asUint8List(),
filename: 'anatomy.jpg', // use the real name if available, or omit
contentType: MediaType('image', 'jpg'),
);
request.files.add(multipartFile);
While testing your API, you may find it easier to just create a Dart command line project, where you do have access to Files in the project folders.
my case was image uploading problem and I solved it by using
xfile.path that image picker returned
XFile? image = await _picker.pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxHeight: 500,
maxWidth: 500);
#flutter
#dio

Trouble accessing images uploaded in back-end project

I am trying to access images stored in an /uploads/ folder in my front-end app.
I get a 404 for both http://localhost:4200/uploads/a.jpg and http://localhost:4200/StaticFiles/a.jpg. I need the image's path to display it in a gallery.
I tried using the full path of the image ("D:/blabla/uploads/a.jpg"), but it seems that is restricted. Then I used the code below to provide from the back-end a path to my uploads folder, but I still get 404.
My back-end and front-end projects are in different folders, and my front-end sends requests to back-end's port.
Back-end running latest .net core, front-end is Angular 8.
This is how I serve the static files:
var aux = Path.Combine(Directory.GetCurrentDirectory(), "uploads");
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(aux),
RequestPath = "/StaticFiles"
}
);
This is how I upload the image. The code works and uploads images in the /uploads folder, inside the project.
var uploadFilesPath = Path.Combine(_host.ContentRootPath, "uploads");
if (!Directory.Exists(uploadFilesPath))
Directory.CreateDirectory(uploadFilesPath);
foreach (var file in fileData)
{
var fileName = Guid.NewGuid() + Path.GetExtension(file.FileName);
var filePath = Path.Combine(uploadFilesPath, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
var propToAddImageTo = _propManager.GetById(id);
_propManager.AddImage(propToAddImageTo, filePath);
}
When I try to set a simple <img src="localhost:4200/uploads/a.jpg"> or <img src="localhost:4200/StaticFiles/a.jpg">, the request on the network tab from Mozilla's Developer Tools says 404.
The default value for _host.ContentRootPath is equal to Directory.GetCurrentDirectory(), but only if you are using WebHost.CreateDefaultBuilder. From the documentation:
CreateDefaultBuilder performs the following tasks:
...
Sets the content root to the path returned by Directory.GetCurrentDirectory.
Your code assumes ContentRootPath is always the same as Directory.GetCurrentDirectory, but that won't always be true. If you are not using CreateDefaultBuilder, or your code is split across multiple projects (as you mentioned in your comment), they won't point to the same place.
Below codes works for me :
app.UseCors("CorsPolicy");
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), #"uploads")),
RequestPath = new PathString("/StaticFiles")
});
app.UseMvc();
It needs the uploads folder should exist in root directory , otherwise it will throw error . Suppose the structure is ApplicationName/uploads/a.jpg , you can use like :
<img src="http://localhost:xxxx/StaticFiles/a.jpg">
To save images on server side , you could find the path by :
var pathToSave = Path.Combine(Directory.GetCurrentDirectory(), "uploads");

WebAPI + OWIN: zipping files directly to the output stream does not work as expected

I'm developing a website with ASP.NET MVC 5 + Web API. One of the requirements is that users must be able to download a large zip file, which is created on the fly.
Because I immediately want to show progress of the user, my idea was to use a PushStreamContent with a callback in the resonse. The callback creates the zipfile and streams it to the response.
When I implement this as follows, starting from an empty ASP.NET MVC + Web API project, it works as expected. As soon as the result is returned to the client, the callback gets invoked and
the zipfile is streamed to the client. So the user can see progress as soon as the callback creates the zip archive and add files to it.
[RoutePrefix("api/download")]
public class DownloadController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
var files = new DirectoryInfo(#"c:\tempinput").GetFiles();
var pushStreamContent = new PushStreamContent(async (outputStream, httpContext, transportContext) =>
{
using (var zipOutputStream = new ZipOutputStream(outputStream))
{
zipOutputStream.CompressionLevel = CompressionLevel.BestCompression;
foreach (var file in files)
{
zipOutputStream.PutNextEntry(file.Name);
using (var stream = File.OpenRead(file.FullName))
{
await stream.CopyToAsync(zipOutputStream);
}
}
}
});
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = pushStreamContent
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment") {FileName = "MyZipfile.zip"};
return response;
}
}
Now, I have to integrate this in an existing website, which is configured to use Microsoft.Owin.OwinMiddleware. I used the same code as pasted above, but now the behavior is different: during the creation of the zipfile, it 's not streamed to the response, but only downloaded when the creation of the zip has finished. So the user doesn't see any progress during the creation of the file.
I also tried a different approach in my Web API + Owin project, as described here: (generate a Zip file from azure blob storage files).
In an empty Asp.NET MVC project (without OWIN middleware), this works exactly as expected, but when OWIN is involved, I get this HTTPException and stacktrace:
System.Web.HttpException: 'Server cannot set status after HTTP headers have been sent.'
System.Web.dll!System.Web.HttpResponse.StatusCode.set(int value) Unknown
System.Web.dll!System.Web.HttpResponseWrapper.StatusCode.set(int value) Unknown
Microsoft.Owin.Host.SystemWeb.dll!Microsoft.Owin.Host.SystemWeb.OwinCallContext.Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.IPropertySource.SetResponseStatusCode(int value) Unknown
It seems that OWIN wants to set a response status, although that was already done in my Get() method (HttpResponseMessage(HttpStatusCode.OK)).
Any suggestions how to fix this or ideas for a different approach?
Thanks a lot!

How to handle paths setup for multi tenant ASP MVC site

I am working on a site that need to support host based Multi Tenancy, and I got this whole part figured out. The issue I have is that I have in the CSS folder a subfolder for every tenant (1,2,3).
CSS
|_ tenant_1
|_ tenant_2
|_ tenant_3
|_ tenant (virtual)
in the tenant_X folder there are custom css files used for stypling every specific tenant.
My idea was to somehow create a virtual location (tenant) that would be mapped to the tenant's folder and only one additional line of coude would be needed in the _Layout. I am not profound in MVC and so far I know, I think I can get this to work with a custom route.
One other reason for this approach is that the tenants user is not allowed to see that there are other tenants. I have to exclude the possibility to have the user loaded the wrong files.
Is this the right approach? can you suggest any better way?
A possible implementation to achieve this just by adding 1 line to the _Layout page, could be to get a css file from a controller as text/css.
So assuming that the current tenant ID is available on front-end you could call a method on controller with that id
For example something like this:
#Styles.Render(string.Format("/CustomizationController/GetCssForTenant?tenantId={0}", loggedTeanant == null ? (int?) null : loggedTenant.Id))
And now create a customization controller with the method as follows
public class CustomizationController : Controller
{
//this will cache cliente side the css file but if the duration expires
// or the tenantId changes it will be ask for the new file
[OutputCache(Duration = 43200, VaryByParam = "tenantId")]
public FileResult GetCssForTenant(int? tenantId)
{
var contentType = "text/css";
//if teanant id is null return empty css file
if(!tenantID.HasValue)
return new FileContentResult(new byte[0], contentType);
//load the real css file here <-
var result = ...
//---
//if having problems with the encoding use this ...
//System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
//var content = encoding.GetBytes(result);
//---
Response.ContentType = contentType;
return new FileContentResult(result, contentType);
//return new FileContentResult(content, contentType);
}
}
Hope that this help achieve what you need. Remember that this is a sketch of an possible implementation.
Edit
If you want to make a quick try of my suggested implementation use this
public class CustomizationController : Controller
{
//this will cache cliente side the css file but if the duration expires
// or the tenantId changes it will be ask for the new file
[OutputCache(Duration = 43200, VaryByParam = "tenantId")]
public FileResult GetCssForTenant(int? tenantId)
{
var contentType = "text/css";
//if teanant id is null return empty css file
if(!tenantID.HasValue)
return new FileContentResult(new byte[0], contentType);
//load the real css file here <-
var result = Environment.NewLine;
if(tenantID = 1)
result "body{ background-color: black !important;}"
else
result "body{ background-color: pink !important;}"
result += Environment.NewLine;
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
var content = encoding.GetBytes(result);
Response.ContentType = contentType;
return new FileContentResult(result, contentType);
}
}
And change the _Layout
#Styles.Render(string.Format("/CustomizationController/GetCssForTenant?tenantId={0}", 1))
Now the background-color of the page should change to black if you send 1 and to pink if you send 2.
You also can see in the network that if you ask 2 time with the same id the status will be 304 this means that the file comes from cache.
If you change the id the status will be 200 that is a not cached response.
If you pass null the css file will come empty so it will fallback to your default css.

Add css file to an OWIN SelfHost

For my internship in C#, I've to create an embedded monitoring for existing applications, I wrote the whole "application" in an Owin SelfHost service to make it available and non dependant of the current architecture of these application, my server is launch with this snippet:
public void Configuration(IAppBuilder appBuilder)
{
var configuration = new HttpConfiguration();
configuration.Routes.MapHttpRoute(
name: "DefaultRoute",
routeTemplate: "{controller}/{action}",
defaults: new { controller = "Monitoring", action = "Get" }
);
appBuilder.UseWebApi(configuration);
}
WebApp.Start<Startup>("http://localhost:9000");
I'm also providing a graphic interface for this monitoring, I'm using HttpResponseMessage to do this and simpmly write the HTML content with this code.
public HttpResponseMessage GetGraphic()
{
var response = new HttpResponseMessage()
{
Content = new StringContent("...")
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
Now the problem is that I want to add style to my current interface, I've put them in the same directory as the rest of the project (everything is stored in a subfolder of these others applications, called Monitoring) problem is that these file are not on the new hosted service, I can still access them with projetUrl/Monitoring/file but I would like to get them on http://localhost:9000/file because actually, this cause me CORS error when trying to load font file.
Is it possible, and if yes, how?
Would something like this work...?
public HttpResponseMessage GetStyle(string name)
{
var response = new HttpResponseMessage()
{
Content = GetFileContent(name)
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
return response;
}
private StringContent GetFileContent(string name)
{
//TODO: fetch the file, read its contents
return new StringContent(content);
}
Notice, that you can open a stream to read the files content in the GetFileContents method. You could even add some caching approach to that action method. Also, you can get creative and instead of taking one single string parameter, you can take an array of them and bundle the response
I finally used UseStaticFiles() to handle this situation, thanks Callumn Linington for the idea, I did not knew that something like this was existing!
Here's the code I used for potential future seeker:
appBuilder.UseStaticFiles(new StaticFileOptions()
{
RequestPath = new PathString("/assets"),
FileSystem = new PhysicalFileSystem(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Monitoring/static"))
});

Categories

Resources