How to create custom StaticFileMiddleware and redirect request to needed folder - middleware

My site with Asp.Net Core 6 hes custom root folder for static files, different from folders with configuration, folders with Program.cs/vb, folders with View, folders with Area, folders with Controllers.
Parts of static files from this custom root working fine, but other static files don't working.
I try to intercept request to static files and manually redirect all request to my root folder with static files.
I write this code for this interception.
App.Use(Async Function(context, [next])
Dim CurrentEndpoint = context.GetEndpoint()
If (CurrentEndpoint Is Nothing) Then
Debug.WriteLine($"RequestPath {context.Request.Path} endpoint nothing.")
Dim StaticOptions As StaticFileOptions = New StaticFileOptions With {.FileProvider = New PhysicalFileProvider(Builder.Configuration("StaticFilesRoot"))}
Dim Opt As IOptions(Of StaticFileOptions) = Options.Create(StaticOptions)
Dim StaticMiddleware = New StaticFileMiddleware(
Async Function()
Await [next](context)
End Function,
Environment,
Opt.Value,
LoggerFactory)
Else
Await [next](context)
End If
End Function)
But my attempt to intercept "wrong" request and manually redirect it to needed folder is failed. Because I maybe made mistake to create StaticFileMiddleware.
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2 GET https://localhost:7168/scripts/script.js - -
RequestPath /scripts/script.js endpoint nothing.
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.
System.InvalidCastException: Unable to cast object of type 'Microsoft.AspNetCore.Builder.StaticFileOptions' to type 'Microsoft.Extensions.Options.IOptions`1[Microsoft.AspNetCore.Builder.StaticFileOptions]'.
at FrontEnd.Program._Closure$__12-0._Lambda$__10(HttpContext context, RequestDelegate next) in G:\Projects\CryptoChestMax\FrontEnd\FrontEndCode\Program.vb:line 256
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Unfortunately, I can not understand what I doing wrong and what cast is not valid. If I try to create custom middleWare in debugger than there are not error, but attempt to processing request with my new StaticMiddleware get me error.
What wrong in my StaticMiddleware?

In current question this is my mistake with Root for StaticHtmlFiles, but my way to detection error can be helpful to any programmer.
I have create this Route Analyzer and finally understand that I have wrong variables loaded as Root for static files.
'Route anylizer
App.Use(Async Function(context As HttpContext, NextRequestDelegate As RequestDelegate)
Dim CurrentEndpoint = context.GetEndpoint()
If (CurrentEndpoint Is Nothing) Then
Debug.WriteLine($"RequestPath {context.Request.Path} endpoint nothing.")
Dim StaticOptions As StaticFileOptions = New StaticFileOptions With {.FileProvider = New PhysicalFileProvider(Builder.Configuration("StaticFilesRoot"))}
Dim Opt As IOptions(Of StaticFileOptions) = Options.Create(StaticOptions)
Dim NewEnvironment As IWebHostEnvironment = Environment
NewEnvironment.ContentRootPath = Builder.Configuration("StaticFilesRoot")
NewEnvironment.WebRootPath = Builder.Configuration("StaticFilesRoot")
Dim StaticMiddleware = New StaticFileMiddleware(
Async Function()
'Await NextRequestDelegate(context)
Dim StaticFile As IFileInfo = Opt.Value.FileProvider.GetFileInfo(context.Request.Path)
If Not StaticFile.Exists Then
Await context.Response.WriteAsync("File is nothing")
Else
Await context.Response.SendFileAsync(StaticFile)
End If
End Function,
NewEnvironment,
Opt,
LoggerFactory)
Await StaticMiddleware.Invoke(context)
Else
Debug.WriteLine($"Endpoint: {CurrentEndpoint.DisplayName}")
Dim Endpoint As RouteEndpoint = TryCast(CurrentEndpoint, RouteEndpoint)
Debug.WriteLine($"RoutePattern: {Endpoint?.RoutePattern.RawText}")
For j As Integer = 0 To CurrentEndpoint.Metadata.Count - 1
Debug.WriteLine($"Endpoint Metadata {j}: {CurrentEndpoint.Metadata(j)}")
Next
Await NextRequestDelegate(context)
End If
End Function)
And anybody can create custom message, custom code or custom header for missing files by this way

Related

How to create GoogleCredential object referencing the service account json file in Dataflow?

I have written a pipeline to extract G suite activity logs by referring the G suite java-quickstart where the code reads client_secret.json file as below,
InputStream in = new FileInputStream("D://mypath/client_secret.json");
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
Pipeline runs as expected in local(runner=DirectRunner) but the same code fails with java.io.FileNotFoundException expection when executed on cloud(runner=DataflowRunner)
I understand local path is invalid when executed on cloud. Any suggestion here?
Updated:
I have modified the code as below and I am able to read the client_secrets.json file
InputStream in =
Activities.class.getResourceAsStream("client_secret.json");
Actual problem is in creating the credential object
private static java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"),
".credentials/admin-reports_v1-java-quickstart");
private static final List<String> SCOPES = Arrays.asList(ReportsScopes.ADMIN_REPORTS_AUDIT_READONLY);
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
}
public static Credential authorize() throws IOException {
// Load client secrets.
InputStream in =
Activities.class.getResourceAsStream("client_secret.json");
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
clientSecrets, SCOPES).setDataStoreFactory(DATA_STORE_FACTORY).setAccessType("offline").build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
return credential;
}
Observations:
Local execution:
On initial execution, program attempts to open browser to authorize the request and stores the authenticated object in a file - "StoredCredential".
On further executions, the stored file is used to make API calls.
Running on cloud(DataFlowRunner):
When I check logs, dataflow tries to open a browser to authenticate the request and stops there.
What I need?
How to modify GoogleAuthorizationCodeFlow.Builder such that the credential object can be created while running as dataflow pipeline?
I have found a solution to create GoogleCredential object using the service account. Below is the code for it.
public static Credential authorize() throws IOException, GeneralSecurityException {
String emailAddress = "service_account.iam.gserviceaccount.com";
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(emailAddress)
.setServiceAccountPrivateKeyFromP12File(Activities.class.getResourceAsStream("MYFILE.p12"))
.setServiceAccountScopes(Collections.singleton(ReportsScopes.ADMIN_REPORTS_AUDIT_READONLY))
.setServiceAccountUser("USER_NAME")
.build();
return credential;
}
Can you try running the program multiple times locally. What I am wondering is, if the "StoredCredential" file is available, will it just work? Or will it try to load up the browser again?
If so, can you determine the proper place to store that file, and download a copy of it from GCS onto the Dataflow worker? There should be APIs to download GCS files bundled with the dataflow SDK jar. So you should be able to use those to download the credential file.

Not enough storage is available for `Console.ReadLine`.`

I am using a dual service/console model to test a service of mine. The code in the spotlight is:
static void Main(string[] args)
{
// Seems important to use the same service instance, regardless of debug or runtime.
var service = new HostService();
service.EventLog.EntryWritten += EventLogEntryWritten;
if (Environment.UserInteractive)
{
service.OnStart(args);
Console.WriteLine("Host Service is running. Press any key to terminate.");
Console.ReadLine();
service.OnStop();
}
else
{
var servicesToRun = new ServiceBase[] { service };
Run(servicesToRun);
}
}
When I run the app under the debugger, using F5, on the line Console.ReadLine(); I get a System.IO.IOException, with "Not enough storage is available to process this command."
The only purpose of the ReadLine is to wait until someone presses a key to end the app, so I can't imagine where the data is coming from that needs so much storage.
This is a service, and its output is likely set to Windows Application, change the output to Console Application and this should go away.
I having the same problem, I found the setting under project properties but I am creating a windows application so I can not change the application type.
This is the code I use.
Dim t As Task = New Task(AddressOf DownloadPageAsync)
t.Start()
Console.WriteLine("Downloading page...")
Console.ReadLine()
Async Sub DownloadPageAsync()
Using client As HttpClient = New HttpClient()
Using response As HttpResponseMessage = Await client.GetAsync(page)
Using content As HttpContent = response.Content
' Get contents of page as a String.
Dim result As String = Await content.ReadAsStringAsync()
' If data exists, print a substring.
If result IsNot Nothing And result.Length > 50 Then
Console.WriteLine(result.Substring(0, 50) + "...")
End If
End Using
End Using
End Using
End Sub

Getting 401 Unauthorized from Google API Resumable Update

I am trying to integrate upload of arbitrary files to Google Docs into an existing application. This used to work before using resumable upload became mandatory. I am using Java client libraries.
The application is doing the upload in 2 steps:
- get the resourceId of the file
- upload the data
To get the resourceId I am uploading a 0-size file (i.e. Content-Length=0). I am passing ?convert=false in the resumable URL (i.e. https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false).
I am passing "application/octet-stream" as content-type. This seems to work, though I do get different resourcesIds - "file:..." resourceIds for things like images, but "pdf:...." resourceIds for PDFs.
The second step constructs a URL based on the resourceId obtained previously and performs a search (getEntry). The URL is in the form of https://docs.google.com/feeds/default/private/full/file%3A.....
Once the entry is found the ResumableGDataFileUploader is used to update the content (0-byte file) with the actual data from the file being uploaded. This operation fails with 401 Unauthorized response when building ResumableGDataFileUploader instance.
I've tried with ?convert=false as well as ?new-revision=true and both of these at the same time. The result is the same.
The relevant piece of code:
MediaFileSource mediaFile = new MediaFileSource(
tempFile, "application/octet-stream");
final ResumableGDataFileUploader.Builder builder =
new ResumableGDataFileUploader.Builder(client, mediaFile, documentListEntry);
builder.executor(MoreExecutors.sameThreadExecutor());
builder.requestType(ResumableGDataFileUploader.RequestType.UPDATE);
// This is where it fails
final ResumableGDataFileUploader resumableGDataFileUploader = builder.build();
resumableGDataFileUploader.start();
return tempFile.length();
The "client" is an instance of DocsService, configured to use OAuth. It is used to find "documentListEntry" immediately before the given piece of code.
I had to explicitly specify request type, since it seems the client library code contains a bug causing NullPointerException for "update existing entry" case.
I have a suspicion that the issue is specifically in the sequence of actions (upload 0-byte file to get the resourceId, then update with actual file) but I can't figure out why it doesn't work.
Please help?
This code snippet works for me using OAuth 1.0 and OAuth 2.0:
static void uploadDocument(DocsService client) throws IOException, ServiceException,
InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
File file = new File("<PATH/TO/FILE>");
String mimeType = DocumentListEntry.MediaType.fromFileName(file.getName()).getMimeType();
DocumentListEntry documentEntry = new DocumentListEntry();
documentEntry.setTitle(new PlainTextConstruct("<DOCUMENT TITLE>"));
int DEFAULT_CHUNK_SIZE = 2 * 512 * 1024;
ResumableGDataFileUploader.Builder builder =
new ResumableGDataFileUploader.Builder(
client,
new URL(
"https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false"),
new MediaFileSource(file, mimeType), documentEntry).title(file.getName())
.requestType(RequestType.INSERT).chunkSize(DEFAULT_CHUNK_SIZE).executor(executor);
ResumableGDataFileUploader uploader = builder.build();
Future<ResponseMessage> msg = uploader.start();
while (!uploader.isDone()) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
throw ie; // rethrow
}
}
DocumentListEntry uploadedEntry = uploader.getResponse(DocumentListEntry.class);
// Print the document's ID.
System.out.println(uploadedEntry.getId());
System.out.println("Upload is done!");
}

How to get an instance of the current HttpRequestBase outside of a Controller or View's context

What I'm doing:
I am creating an email mailing engine that takes html templates, replaces tokens with data and then sends off the mail to an SMTP server. Eventually we'll want a CMS UI on top of this to allow them to add/remove file templates or update the content in them via a CMS UI. Anyway I am putting the .htm files for the email templates inside my MVC web project and need to read their contents and get the string of HTML back to work with. If I move all this code out of my MVC project into like a utility C# project layer, the problem is then I have to worry about maintaining physical paths ("C:...\theMVCWebSiteFolder...\Email\Templates\SomeTemplate.htm") and that to me would have to be hard coded I think to keep track if you were to move the site to a different server, different hard drive partition, etc. So I'd like to be able to work with the files using the current application's context unless there's a way to do this agnostic to my MVC app and still not have to worry about having to ever change the location of the physical root every time we move folders.
I've got the following utility method I created in a utility class that sits in a folder somewhere in my ASP.NET MVC web project in just a folder (a folder outside of any view folders:
public static string GetFileData(string filePath)
{
if (!File.Exists(HttpContext.Current.Request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
I'm trying to figure out why the context is turning up null here. I originally tried HttpContext.Current but current is coming up null so it can't find the current context in my Utility.cs method that sits in my MVC web project.
UPDATE:
Ok so the consensus is to use HttpRequestBase object and not the HttpContext.Current object. I still find it weird that the HttpContext.Current is null. But moving on, if my Utility.cs is outside any context of a controller or view, then how the heck do I get an instance of the current request (HttpRequestBase) to work with and send an instance that implements HttpRequestBase (I do not know what object that would be if I want to do this in the "MVC way" outside a controller or view) to this utility method?
No idea why this returns null but since it is a bad idea to tie your business layers with ASP.NET specifics I'd recommend you the following change to your method:
public static string GetFileData(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
return File.ReadAllText(filePath);
}
and then when you need to consume it from a web application:
public ActionResult Foo()
{
var result = MyClass.GetFileData(Server.MapPath("~/foo/bar.txt"));
...
}
and when you need to consume it in a WinForms application:
protected void Button1_Click(object sender, EventArgs e)
{
var filePath = Path.Combine(Environment.CurrentDirectory, "bar.txt");
var result = MyClass.GetFileData(filePath);
...
}
In a utility class I would remove the dependency on any web-related stuff.
For a path relative to the application root I would use:
Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName)
which will give you what you probably want (a path relative to the web root directory in a web application; relative to the path containing the executable in a WinForms app, ...)
If want to rewrite that method in asp.net mvc way, you could rewrite it this way and remove coupling with HttpContext class
public static string GetFileData(HttpRequestBase request, string filePath)
{
if (!File.Exists(request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
And generally in MVC HttpContext, HttpRequest and HttpResponse are abstracted into HttpContextBase, HttpRequestBase and HttpResponseBase accordingly

Getting the Id of an error in Elmah after calling .Raise()

I'm working on an MVC3 application and I'm using Elmah to handle my error logging. What I want in my application is to carry the Elmah Id onto the custom error page as I will provide a link which allows a user to specifically report it in the event that it is a repeat error (in their opinion).
Now, I've read similar questions on here and they suggest adding the following code (or similar) to the Global.asax.cs file:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string sessionId = Session.SessionID;
Session["ElmahId_" + sessionId] = args.Entry.Id;
}
This is what I'm using at the moment, with the SessionID allowing for added flexibility in making the Session stored object unique. However, this may still cause issues if more than one error occurs at (virtually) the same time.
Instead, I decided to work on my own HandleErrorAttribute that looks something like this:
public class ElmahHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.IsChildAction && (!filterContext.ExceptionHandled
&& filterContext.HttpContext.IsCustomErrorEnabled))
{
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception);
// get error id here
string errorId = null;
string areaName = (String)filterContext.RouteData.Values["area"];
string controllerName = (String)filterContext.RouteData.Values["controller"];
string actionName = (String)filterContext.RouteData.Values["action"];
var model = new ErrorDetail
{
Area = areaName,
Controller = controllerName,
Action = actionName,
ErrorId = errorId,
Exception = filterContext.Exception
};
ViewResult result = new ViewResult
{
ViewName = "Error",,
ViewData = new ViewDataDictionary<ErrorDetail>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = result;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
where ErrorDetail is a custom model which just has the public properties that are being set here as strings. This data can then be shown in the model for admin's at a quick glance and the errorId can be used to create the 'Report Error' link.
So my question is does anyone know of a way of getting the Id after the line
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception)
without using the Logged event in the global.asax.cs?
Any thoughts are much appreciated.
After reading Dupin's comments it seems logical that it isn't quite possible. I tried digging around the Elmah source code and came up with a couple of alternatives that might be worth sharing.
The obvious alternative is stick with my original option of using the Logged event:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string sessionId = Session.SessionID;
Session["ElmahId_" + sessionId] = args.Entry.Id;
}
For a more direct solution it is possible to manually log the error with the following:
string errorId = Elmah.ErrorLog.GetDefault(HttpContext.Current)
.Log(new Elmah.Error(filterContext.Exception));
However, using this approach won't hit your filters or mail module and so on.
After doing a bit of thinking and a little more searching, I came up with a new compromise. Still using the logged event but I've found a way to create a new unique key that can be passed to the view, by adding my own data to the exception.
string loggingKey = "ElmahId_" + Guid.NewGuid().ToString();
filterContext.Exception.Data.Add("LoggingKey", loggingKey);
This way I can pass the exception in my view model, which has this key value in the Data collection. The logged event would be changed to something like this:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
string key = args.Entry.Error.Exception.Data["LoggingKey"].ToString();
Session[key] = args.Entry.Id;
}
Then in the view I get the key from the model to then pull the Id from the Session collection.
Maybe not very helpful but I suspect you can't get the error id at that point and you will need to use the logged event.
When you call
Elmah.ErrorSignal.FromCurrentContext().Raise(filterContext.Exception)
You're just raising the error. Depending on how you've configured ELMAH you might be logging the error or you might just send an email or a tweet.
There's no direct link between a raised error and an Id. That will only come with logging which, if you're feeling funny, you could be doing in multiple places and so creating multiple ids.
http://code.google.com/p/elmah/issues/detail?id=148#c3 is an identical request and a proposed patch on the Elmah project site
The solution above only works only if there is a Session object (website scenario). We needed it to work in an Azure WorkerRole, or a console / desktop app type setup. This solution will also work for web and save some session memory. There isn't a perfect solution, but one that worked for us to be able to log the error and retrieve the stored ID AND fire off an email is to:
Store the error using ErrorLog.Log(error) (see: Using ELMAH in a console application)
Raise the error skipping the logging (SQL or otherwise)
For the second part, we used the implementation of ElmahExtension given here: https://stackoverflow.com/a/2473580/476400
and REMOVED the following lines adding the logging:
(ErrorLog as IHttpModule).Init(httpApplication);
errorFilter.HookFiltering(ErrorLog); //removed!
The entire call from our client code looks like this:
ErrorLog errorLog = ErrorLog.GetDefault(null);
errorLog.ApplicationName = "YourAppName";
Error error = new Error(ex);
string errorResult = errorLog.Log(error);
Guid errorId = new Guid(errorResult);
ex.LogToElmah(); //this is just going to send the email
You might want to call that extention method something else, like RaiseToElmahNoStorage(), or something to indicate it is skipping the storage component.

Resources